summaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Makefile.am53
-rw-r--r--src/plugins/Makefile.in810
-rw-r--r--src/plugins/acl/Makefile.am78
-rw-r--r--src/plugins/acl/Makefile.in1035
-rw-r--r--src/plugins/acl/acl-api-private.h135
-rw-r--r--src/plugins/acl/acl-api.c847
-rw-r--r--src/plugins/acl/acl-api.h167
-rw-r--r--src/plugins/acl/acl-attributes.c233
-rw-r--r--src/plugins/acl/acl-backend-vfile-acllist.c424
-rw-r--r--src/plugins/acl/acl-backend-vfile-update.c260
-rw-r--r--src/plugins/acl/acl-backend-vfile.c659
-rw-r--r--src/plugins/acl/acl-backend-vfile.h88
-rw-r--r--src/plugins/acl/acl-backend.c194
-rw-r--r--src/plugins/acl/acl-cache.c395
-rw-r--r--src/plugins/acl/acl-cache.h57
-rw-r--r--src/plugins/acl/acl-global-file.c246
-rw-r--r--src/plugins/acl/acl-global-file.h23
-rw-r--r--src/plugins/acl/acl-lookup-dict.c373
-rw-r--r--src/plugins/acl/acl-lookup-dict.h17
-rw-r--r--src/plugins/acl/acl-mailbox-list.c629
-rw-r--r--src/plugins/acl/acl-mailbox.c714
-rw-r--r--src/plugins/acl/acl-plugin.c27
-rw-r--r--src/plugins/acl/acl-plugin.h73
-rw-r--r--src/plugins/acl/acl-shared-storage.c103
-rw-r--r--src/plugins/acl/acl-shared-storage.h6
-rw-r--r--src/plugins/acl/acl-storage.c62
-rw-r--r--src/plugins/acl/acl-storage.h50
-rw-r--r--src/plugins/acl/doveadm-acl.c629
-rw-r--r--src/plugins/acl/test-acl.c66
-rw-r--r--src/plugins/apparmor/Makefile.am14
-rw-r--r--src/plugins/apparmor/Makefile.in816
-rw-r--r--src/plugins/apparmor/apparmor-plugin.c111
-rw-r--r--src/plugins/charset-alias/Makefile.am19
-rw-r--r--src/plugins/charset-alias/Makefile.in822
-rw-r--r--src/plugins/charset-alias/charset-alias-plugin.c198
-rw-r--r--src/plugins/charset-alias/charset-alias-plugin.h7
-rw-r--r--src/plugins/fs-compress/Makefile.am14
-rw-r--r--src/plugins/fs-compress/Makefile.in811
-rw-r--r--src/plugins/fs-compress/fs-compress.c285
-rw-r--r--src/plugins/fts-lucene/Makefile.am61
-rw-r--r--src/plugins/fts-lucene/Makefile.in990
-rw-r--r--src/plugins/fts-lucene/Snowball.cc151
-rw-r--r--src/plugins/fts-lucene/SnowballAnalyzer.h51
-rw-r--r--src/plugins/fts-lucene/SnowballFilter.h42
-rw-r--r--src/plugins/fts-lucene/doveadm-fts-lucene.c70
-rw-r--r--src/plugins/fts-lucene/fts-backend-lucene.c605
-rw-r--r--src/plugins/fts-lucene/fts-lucene-plugin.c146
-rw-r--r--src/plugins/fts-lucene/fts-lucene-plugin.h36
-rw-r--r--src/plugins/fts-lucene/lucene-wrapper.cc1639
-rw-r--r--src/plugins/fts-lucene/lucene-wrapper.h67
-rw-r--r--src/plugins/fts-lucene/textcat.conf25
-rw-r--r--src/plugins/fts-solr/Makefile.am64
-rw-r--r--src/plugins/fts-solr/Makefile.in965
-rw-r--r--src/plugins/fts-solr/fts-backend-solr-old.c879
-rw-r--r--src/plugins/fts-solr/fts-backend-solr.c984
-rw-r--r--src/plugins/fts-solr/fts-solr-plugin.c131
-rw-r--r--src/plugins/fts-solr/fts-solr-plugin.h35
-rw-r--r--src/plugins/fts-solr/solr-connection.c327
-rw-r--r--src/plugins/fts-solr/solr-connection.h26
-rw-r--r--src/plugins/fts-solr/solr-response.c372
-rw-r--r--src/plugins/fts-solr/solr-response.h23
-rw-r--r--src/plugins/fts-solr/test-solr-response.c295
-rw-r--r--src/plugins/fts-squat/Makefile.am47
-rw-r--r--src/plugins/fts-squat/Makefile.in883
-rw-r--r--src/plugins/fts-squat/fts-backend-squat.c497
-rw-r--r--src/plugins/fts-squat/fts-squat-plugin.c18
-rw-r--r--src/plugins/fts-squat/fts-squat-plugin.h14
-rw-r--r--src/plugins/fts-squat/squat-test.c197
-rw-r--r--src/plugins/fts-squat/squat-trie-private.h192
-rw-r--r--src/plugins/fts-squat/squat-trie.c2096
-rw-r--r--src/plugins/fts-squat/squat-trie.h54
-rw-r--r--src/plugins/fts-squat/squat-uidlist.c1624
-rw-r--r--src/plugins/fts-squat/squat-uidlist.h71
-rw-r--r--src/plugins/fts/Makefile.am74
-rw-r--r--src/plugins/fts/Makefile.in1140
-rwxr-xr-xsrc/plugins/fts/decode2text.sh105
-rw-r--r--src/plugins/fts/doveadm-dump-fts-expunge-log.c116
-rw-r--r--src/plugins/fts/doveadm-fts.c470
-rw-r--r--src/plugins/fts/doveadm-fts.h11
-rw-r--r--src/plugins/fts/fts-api-private.h139
-rw-r--r--src/plugins/fts/fts-api.c554
-rw-r--r--src/plugins/fts/fts-api.h173
-rw-r--r--src/plugins/fts/fts-build-mail.c719
-rw-r--r--src/plugins/fts/fts-build-mail.h9
-rw-r--r--src/plugins/fts/fts-expunge-log.c617
-rw-r--r--src/plugins/fts/fts-expunge-log.h58
-rw-r--r--src/plugins/fts/fts-indexer.c300
-rw-r--r--src/plugins/fts/fts-indexer.h22
-rw-r--r--src/plugins/fts/fts-parser-html.c64
-rw-r--r--src/plugins/fts/fts-parser-script.c277
-rw-r--r--src/plugins/fts/fts-parser-tika.c278
-rw-r--r--src/plugins/fts/fts-parser.c127
-rw-r--r--src/plugins/fts/fts-parser.h48
-rw-r--r--src/plugins/fts/fts-plugin.c33
-rw-r--r--src/plugins/fts/fts-plugin.h7
-rw-r--r--src/plugins/fts/fts-search-args.c258
-rw-r--r--src/plugins/fts/fts-search-args.h7
-rw-r--r--src/plugins/fts/fts-search-serialize.c99
-rw-r--r--src/plugins/fts/fts-search-serialize.h16
-rw-r--r--src/plugins/fts/fts-search.c385
-rw-r--r--src/plugins/fts/fts-storage.c981
-rw-r--r--src/plugins/fts/fts-storage.h70
-rw-r--r--src/plugins/fts/fts-user.c423
-rw-r--r--src/plugins/fts/fts-user.h27
-rw-r--r--src/plugins/fts/xml2text.c44
-rw-r--r--src/plugins/imap-acl/Makefile.am31
-rw-r--r--src/plugins/imap-acl/Makefile.in831
-rw-r--r--src/plugins/imap-acl/imap-acl-plugin.c1128
-rw-r--r--src/plugins/imap-acl/imap-acl-plugin.h12
-rw-r--r--src/plugins/imap-old-stats/Makefile.am28
-rw-r--r--src/plugins/imap-old-stats/Makefile.in827
-rw-r--r--src/plugins/imap-old-stats/imap-stats-plugin.c128
-rw-r--r--src/plugins/imap-old-stats/imap-stats-plugin.h12
-rw-r--r--src/plugins/imap-quota/Makefile.am27
-rw-r--r--src/plugins/imap-quota/Makefile.in827
-rw-r--r--src/plugins/imap-quota/imap-quota-plugin.c266
-rw-r--r--src/plugins/imap-quota/imap-quota-plugin.h12
-rw-r--r--src/plugins/imap-zlib/Makefile.am25
-rw-r--r--src/plugins/imap-zlib/Makefile.in827
-rw-r--r--src/plugins/imap-zlib/imap-zlib-plugin.c184
-rw-r--r--src/plugins/imap-zlib/imap-zlib-plugin.h12
-rw-r--r--src/plugins/last-login/Makefile.am19
-rw-r--r--src/plugins/last-login/Makefile.in822
-rw-r--r--src/plugins/last-login/last-login-plugin.c157
-rw-r--r--src/plugins/last-login/last-login-plugin.h7
-rw-r--r--src/plugins/lazy-expunge/Makefile.am21
-rw-r--r--src/plugins/lazy-expunge/Makefile.in824
-rw-r--r--src/plugins/lazy-expunge/lazy-expunge-plugin.c654
-rw-r--r--src/plugins/lazy-expunge/lazy-expunge-plugin.h7
-rw-r--r--src/plugins/listescape/Makefile.am18
-rw-r--r--src/plugins/listescape/Makefile.in821
-rw-r--r--src/plugins/listescape/listescape-plugin.c36
-rw-r--r--src/plugins/listescape/listescape-plugin.h7
-rw-r--r--src/plugins/mail-crypt/Makefile.am116
-rw-r--r--src/plugins/mail-crypt/Makefile.in1200
-rw-r--r--src/plugins/mail-crypt/doveadm-mail-crypt.c1048
-rw-r--r--src/plugins/mail-crypt/fs-crypt-common.c368
-rw-r--r--src/plugins/mail-crypt/fs-crypt-settings.c35
-rw-r--r--src/plugins/mail-crypt/fs-crypt-settings.h11
-rw-r--r--src/plugins/mail-crypt/fs-crypt.c65
-rw-r--r--src/plugins/mail-crypt/fs-mail-crypt.c72
-rw-r--r--src/plugins/mail-crypt/mail-crypt-acl-plugin.c431
-rw-r--r--src/plugins/mail-crypt/mail-crypt-common.h30
-rw-r--r--src/plugins/mail-crypt/mail-crypt-global-key.c172
-rw-r--r--src/plugins/mail-crypt/mail-crypt-global-key.h38
-rw-r--r--src/plugins/mail-crypt/mail-crypt-key.c1242
-rw-r--r--src/plugins/mail-crypt/mail-crypt-key.h119
-rw-r--r--src/plugins/mail-crypt/mail-crypt-plugin.c486
-rw-r--r--src/plugins/mail-crypt/mail-crypt-plugin.h32
-rw-r--r--src/plugins/mail-crypt/mail-crypt-pluginenv.c106
-rw-r--r--src/plugins/mail-crypt/mail-crypt-userenv.c66
-rw-r--r--src/plugins/mail-crypt/test-mail-global-key.c130
-rw-r--r--src/plugins/mail-crypt/test-mail-key.c424
-rw-r--r--src/plugins/mail-log/Makefile.am24
-rw-r--r--src/plugins/mail-log/Makefile.in826
-rw-r--r--src/plugins/mail-log/mail-log-plugin.c546
-rw-r--r--src/plugins/mail-log/mail-log-plugin.h9
-rw-r--r--src/plugins/mail-lua/Makefile.am32
-rw-r--r--src/plugins/mail-lua/Makefile.in873
-rw-r--r--src/plugins/mail-lua/mail-lua-plugin.c167
-rw-r--r--src/plugins/mail-lua/mail-lua-plugin.h14
-rw-r--r--src/plugins/mailbox-alias/Makefile.am18
-rw-r--r--src/plugins/mailbox-alias/Makefile.in821
-rw-r--r--src/plugins/mailbox-alias/mailbox-alias-plugin.c356
-rw-r--r--src/plugins/mailbox-alias/mailbox-alias-plugin.h7
-rw-r--r--src/plugins/notify-status/Makefile.am17
-rw-r--r--src/plugins/notify-status/Makefile.in817
-rw-r--r--src/plugins/notify-status/notify-status-plugin.c362
-rw-r--r--src/plugins/notify/Makefile.am25
-rw-r--r--src/plugins/notify/Makefile.in852
-rw-r--r--src/plugins/notify/notify-plugin-private.h29
-rw-r--r--src/plugins/notify/notify-plugin.c265
-rw-r--r--src/plugins/notify/notify-plugin.h43
-rw-r--r--src/plugins/notify/notify-storage.c269
-rw-r--r--src/plugins/old-stats/Makefile.am33
-rw-r--r--src/plugins/old-stats/Makefile.in898
-rw-r--r--src/plugins/old-stats/mail-stats-connection.c75
-rw-r--r--src/plugins/old-stats/mail-stats-connection.h19
-rw-r--r--src/plugins/old-stats/mail-stats-fill.c156
-rw-r--r--src/plugins/old-stats/mail-stats.c168
-rw-r--r--src/plugins/old-stats/mail-stats.h41
-rw-r--r--src/plugins/old-stats/stats-plugin.c481
-rw-r--r--src/plugins/old-stats/stats-plugin.h60
-rw-r--r--src/plugins/pop3-migration/Makefile.am41
-rw-r--r--src/plugins/pop3-migration/Makefile.in872
-rw-r--r--src/plugins/pop3-migration/pop3-migration-plugin.c1058
-rw-r--r--src/plugins/pop3-migration/pop3-migration-plugin.h13
-rw-r--r--src/plugins/pop3-migration/test-pop3-migration-plugin.c63
-rw-r--r--src/plugins/push-notification/Makefile.am83
-rw-r--r--src/plugins/push-notification/Makefile.in1037
-rw-r--r--src/plugins/push-notification/push-notification-driver-dlog.c114
-rw-r--r--src/plugins/push-notification/push-notification-driver-lua.c663
-rw-r--r--src/plugins/push-notification/push-notification-driver-ox.c470
-rw-r--r--src/plugins/push-notification/push-notification-drivers.c181
-rw-r--r--src/plugins/push-notification/push-notification-drivers.h123
-rw-r--r--src/plugins/push-notification/push-notification-event-flagsclear.c170
-rw-r--r--src/plugins/push-notification/push-notification-event-flagsclear.h22
-rw-r--r--src/plugins/push-notification/push-notification-event-flagsset.c167
-rw-r--r--src/plugins/push-notification/push-notification-event-flagsset.h22
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxcreate.c56
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxcreate.h12
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxdelete.c46
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxdelete.h12
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxrename.c50
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxrename.h11
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxsubscribe.c48
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxsubscribe.h12
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c48
-rw-r--r--src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h12
-rw-r--r--src/plugins/push-notification/push-notification-event-message-common.c117
-rw-r--r--src/plugins/push-notification/push-notification-event-message-common.h40
-rw-r--r--src/plugins/push-notification/push-notification-event-messageappend.c99
-rw-r--r--src/plugins/push-notification/push-notification-event-messageappend.h30
-rw-r--r--src/plugins/push-notification/push-notification-event-messageexpunge.c53
-rw-r--r--src/plugins/push-notification/push-notification-event-messageexpunge.h12
-rw-r--r--src/plugins/push-notification/push-notification-event-messagenew.c97
-rw-r--r--src/plugins/push-notification/push-notification-event-messagenew.h36
-rw-r--r--src/plugins/push-notification/push-notification-event-messageread.c58
-rw-r--r--src/plugins/push-notification/push-notification-event-messageread.h11
-rw-r--r--src/plugins/push-notification/push-notification-event-messagetrash.c58
-rw-r--r--src/plugins/push-notification/push-notification-event-messagetrash.h12
-rw-r--r--src/plugins/push-notification/push-notification-events-rfc5423.c59
-rw-r--r--src/plugins/push-notification/push-notification-events-rfc5423.h10
-rw-r--r--src/plugins/push-notification/push-notification-events.c100
-rw-r--r--src/plugins/push-notification/push-notification-events.h124
-rw-r--r--src/plugins/push-notification/push-notification-plugin.c390
-rw-r--r--src/plugins/push-notification/push-notification-plugin.h18
-rw-r--r--src/plugins/push-notification/push-notification-triggers.c215
-rw-r--r--src/plugins/push-notification/push-notification-triggers.h64
-rw-r--r--src/plugins/push-notification/push-notification-txn-mbox.c90
-rw-r--r--src/plugins/push-notification/push-notification-txn-mbox.h29
-rw-r--r--src/plugins/push-notification/push-notification-txn-msg.c139
-rw-r--r--src/plugins/push-notification/push-notification-txn-msg.h39
-rw-r--r--src/plugins/quota-clone/Makefile.am19
-rw-r--r--src/plugins/quota-clone/Makefile.in822
-rw-r--r--src/plugins/quota-clone/quota-clone-plugin.c308
-rw-r--r--src/plugins/quota-clone/quota-clone-plugin.h7
-rw-r--r--src/plugins/quota/Makefile.am144
-rw-r--r--src/plugins/quota/Makefile.in1179
-rw-r--r--src/plugins/quota/doveadm-quota.c165
-rw-r--r--src/plugins/quota/quota-count.c400
-rw-r--r--src/plugins/quota/quota-dict.c269
-rw-r--r--src/plugins/quota/quota-dirsize.c232
-rw-r--r--src/plugins/quota/quota-fs.c970
-rw-r--r--src/plugins/quota/quota-fs.h51
-rw-r--r--src/plugins/quota/quota-imapc.c494
-rw-r--r--src/plugins/quota/quota-maildir.c953
-rw-r--r--src/plugins/quota/quota-plugin.c31
-rw-r--r--src/plugins/quota/quota-plugin.h36
-rw-r--r--src/plugins/quota/quota-private.h230
-rw-r--r--src/plugins/quota/quota-status-settings.c37
-rw-r--r--src/plugins/quota/quota-status-settings.h10
-rw-r--r--src/plugins/quota/quota-status.c360
-rw-r--r--src/plugins/quota/quota-storage.c780
-rw-r--r--src/plugins/quota/quota-util.c465
-rw-r--r--src/plugins/quota/quota.c1543
-rw-r--r--src/plugins/quota/quota.h147
-rw-r--r--src/plugins/quota/rquota.x139
-rw-r--r--src/plugins/quota/test-quota-util.c96
-rw-r--r--src/plugins/replication/Makefile.am25
-rw-r--r--src/plugins/replication/Makefile.in827
-rw-r--r--src/plugins/replication/replication-plugin.c404
-rw-r--r--src/plugins/replication/replication-plugin.h9
-rw-r--r--src/plugins/trash/Makefile.am23
-rw-r--r--src/plugins/trash/Makefile.in824
-rw-r--r--src/plugins/trash/trash-plugin.c392
-rw-r--r--src/plugins/trash/trash-plugin.h9
-rw-r--r--src/plugins/var-expand-crypt/Makefile.am39
-rw-r--r--src/plugins/var-expand-crypt/Makefile.in938
-rw-r--r--src/plugins/var-expand-crypt/test-var-expand-crypt.c102
-rw-r--r--src/plugins/var-expand-crypt/var-expand-crypt-plugin.c335
-rw-r--r--src/plugins/virtual/Makefile.am29
-rw-r--r--src/plugins/virtual/Makefile.in859
-rw-r--r--src/plugins/virtual/virtual-config.c567
-rw-r--r--src/plugins/virtual/virtual-mail.c583
-rw-r--r--src/plugins/virtual/virtual-plugin.c25
-rw-r--r--src/plugins/virtual/virtual-plugin.h7
-rw-r--r--src/plugins/virtual/virtual-save.c153
-rw-r--r--src/plugins/virtual/virtual-search.c206
-rw-r--r--src/plugins/virtual/virtual-storage.c950
-rw-r--r--src/plugins/virtual/virtual-storage.h251
-rw-r--r--src/plugins/virtual/virtual-sync.c1956
-rw-r--r--src/plugins/virtual/virtual-transaction.c87
-rw-r--r--src/plugins/virtual/virtual-transaction.h24
-rw-r--r--src/plugins/welcome/Makefile.am14
-rw-r--r--src/plugins/welcome/Makefile.in814
-rw-r--r--src/plugins/welcome/welcome-plugin.c146
-rw-r--r--src/plugins/zlib/Makefile.am24
-rw-r--r--src/plugins/zlib/Makefile.in827
-rw-r--r--src/plugins/zlib/zlib-plugin.c388
-rw-r--r--src/plugins/zlib/zlib-plugin.h7
291 files changed, 84979 insertions, 0 deletions
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
new file mode 100644
index 0000000..ab0b9d1
--- /dev/null
+++ b/src/plugins/Makefile.am
@@ -0,0 +1,53 @@
+if BUILD_ZLIB_PLUGIN
+ZLIB = zlib imap-zlib
+endif
+
+if BUILD_LUCENE
+FTS_LUCENE = fts-lucene
+endif
+
+if BUILD_SOLR
+FTS_SOLR = fts-solr
+endif
+
+if HAVE_APPARMOR
+APPARMOR = apparmor
+endif
+
+if HAVE_LUA
+MAIL_LUA = mail-lua
+endif
+
+SUBDIRS = \
+ acl \
+ imap-acl \
+ fts \
+ fts-squat \
+ last-login \
+ lazy-expunge \
+ listescape \
+ notify \
+ notify-status \
+ push-notification \
+ mail-log \
+ $(MAIL_LUA) \
+ mailbox-alias \
+ quota \
+ quota-clone \
+ imap-quota \
+ pop3-migration \
+ replication \
+ old-stats \
+ imap-old-stats \
+ mail-crypt \
+ trash \
+ virtual \
+ welcome \
+ $(ZLIB) \
+ $(FTS_LUCENE) \
+ $(FTS_SOLR) \
+ $(DICT_LDAP) \
+ $(APPARMOR) \
+ fs-compress \
+ var-expand-crypt \
+ charset-alias
diff --git a/src/plugins/Makefile.in b/src/plugins/Makefile.in
new file mode 100644
index 0000000..727ab4d
--- /dev/null
+++ b/src/plugins/Makefile.in
@@ -0,0 +1,810 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = acl imap-acl fts fts-squat last-login lazy-expunge \
+ listescape notify notify-status push-notification mail-log \
+ mail-lua mailbox-alias quota quota-clone imap-quota \
+ pop3-migration replication old-stats imap-old-stats mail-crypt \
+ trash virtual welcome zlib imap-zlib fts-lucene fts-solr \
+ apparmor fs-compress var-expand-crypt charset-alias
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+@BUILD_ZLIB_PLUGIN_TRUE@ZLIB = zlib imap-zlib
+@BUILD_LUCENE_TRUE@FTS_LUCENE = fts-lucene
+@BUILD_SOLR_TRUE@FTS_SOLR = fts-solr
+@HAVE_APPARMOR_TRUE@APPARMOR = apparmor
+@HAVE_LUA_TRUE@MAIL_LUA = mail-lua
+SUBDIRS = \
+ acl \
+ imap-acl \
+ fts \
+ fts-squat \
+ last-login \
+ lazy-expunge \
+ listescape \
+ notify \
+ notify-status \
+ push-notification \
+ mail-log \
+ $(MAIL_LUA) \
+ mailbox-alias \
+ quota \
+ quota-clone \
+ imap-quota \
+ pop3-migration \
+ replication \
+ old-stats \
+ imap-old-stats \
+ mail-crypt \
+ trash \
+ virtual \
+ welcome \
+ $(ZLIB) \
+ $(FTS_LUCENE) \
+ $(FTS_SOLR) \
+ $(DICT_LDAP) \
+ $(APPARMOR) \
+ fs-compress \
+ var-expand-crypt \
+ charset-alias
+
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/acl/Makefile.am b/src/plugins/acl/Makefile.am
new file mode 100644
index 0000000..8a5323e
--- /dev/null
+++ b/src/plugins/acl/Makefile.am
@@ -0,0 +1,78 @@
+doveadm_moduledir = $(moduledir)/doveadm
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/list \
+ -I$(top_srcdir)/src/doveadm
+
+NOPLUGIN_LDFLAGS =
+lib10_doveadm_acl_plugin_la_LDFLAGS = -module -avoid-version
+lib01_acl_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib01_acl_plugin.la
+
+lib01_acl_plugin_la_SOURCES = \
+ acl-api.c \
+ acl-attributes.c \
+ acl-backend.c \
+ acl-backend-vfile.c \
+ acl-backend-vfile-acllist.c \
+ acl-backend-vfile-update.c \
+ acl-cache.c \
+ acl-global-file.c \
+ acl-lookup-dict.c \
+ acl-mailbox.c \
+ acl-mailbox-list.c \
+ acl-plugin.c \
+ acl-shared-storage.c \
+ acl-storage.c
+
+noinst_HEADERS = \
+ acl-backend-vfile.h \
+ acl-shared-storage.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = \
+ acl-api.h \
+ acl-api-private.h \
+ acl-cache.h \
+ acl-global-file.h \
+ acl-lookup-dict.h \
+ acl-plugin.h \
+ acl-storage.h
+
+doveadm_module_LTLIBRARIES = \
+ lib10_doveadm_acl_plugin.la
+
+lib10_doveadm_acl_plugin_la_SOURCES = \
+ doveadm-acl.c
+
+test_programs = \
+ test-acl
+
+test_libs = \
+ $(module_LTLIBRARIES) \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+test_deps = \
+ $(module_LTLIBRARIES) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+test_acl_SOURCES = test-acl.c
+test_acl_LDADD = $(test_libs)
+test_acl_DEPENDENCIES = $(test_deps)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+noinst_PROGRAMS = $(test_programs)
diff --git a/src/plugins/acl/Makefile.in b/src/plugins/acl/Makefile.in
new file mode 100644
index 0000000..4b133e0
--- /dev/null
+++ b/src/plugins/acl/Makefile.in
@@ -0,0 +1,1035 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/plugins/acl
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-acl$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(doveadm_moduledir)" \
+ "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES)
+lib01_acl_plugin_la_LIBADD =
+am_lib01_acl_plugin_la_OBJECTS = acl-api.lo acl-attributes.lo \
+ acl-backend.lo acl-backend-vfile.lo \
+ acl-backend-vfile-acllist.lo acl-backend-vfile-update.lo \
+ acl-cache.lo acl-global-file.lo acl-lookup-dict.lo \
+ acl-mailbox.lo acl-mailbox-list.lo acl-plugin.lo \
+ acl-shared-storage.lo acl-storage.lo
+lib01_acl_plugin_la_OBJECTS = $(am_lib01_acl_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib01_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib01_acl_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+lib10_doveadm_acl_plugin_la_LIBADD =
+am_lib10_doveadm_acl_plugin_la_OBJECTS = doveadm-acl.lo
+lib10_doveadm_acl_plugin_la_OBJECTS = \
+ $(am_lib10_doveadm_acl_plugin_la_OBJECTS)
+lib10_doveadm_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib10_doveadm_acl_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_test_acl_OBJECTS = test-acl.$(OBJEXT)
+test_acl_OBJECTS = $(am_test_acl_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(module_LTLIBRARIES) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/acl-api.Plo \
+ ./$(DEPDIR)/acl-attributes.Plo \
+ ./$(DEPDIR)/acl-backend-vfile-acllist.Plo \
+ ./$(DEPDIR)/acl-backend-vfile-update.Plo \
+ ./$(DEPDIR)/acl-backend-vfile.Plo ./$(DEPDIR)/acl-backend.Plo \
+ ./$(DEPDIR)/acl-cache.Plo ./$(DEPDIR)/acl-global-file.Plo \
+ ./$(DEPDIR)/acl-lookup-dict.Plo \
+ ./$(DEPDIR)/acl-mailbox-list.Plo ./$(DEPDIR)/acl-mailbox.Plo \
+ ./$(DEPDIR)/acl-plugin.Plo ./$(DEPDIR)/acl-shared-storage.Plo \
+ ./$(DEPDIR)/acl-storage.Plo ./$(DEPDIR)/doveadm-acl.Plo \
+ ./$(DEPDIR)/test-acl.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib01_acl_plugin_la_SOURCES) \
+ $(lib10_doveadm_acl_plugin_la_SOURCES) $(test_acl_SOURCES)
+DIST_SOURCES = $(lib01_acl_plugin_la_SOURCES) \
+ $(lib10_doveadm_acl_plugin_la_SOURCES) $(test_acl_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+doveadm_moduledir = $(moduledir)/doveadm
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/list \
+ -I$(top_srcdir)/src/doveadm
+
+lib10_doveadm_acl_plugin_la_LDFLAGS = -module -avoid-version
+lib01_acl_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib01_acl_plugin.la
+
+lib01_acl_plugin_la_SOURCES = \
+ acl-api.c \
+ acl-attributes.c \
+ acl-backend.c \
+ acl-backend-vfile.c \
+ acl-backend-vfile-acllist.c \
+ acl-backend-vfile-update.c \
+ acl-cache.c \
+ acl-global-file.c \
+ acl-lookup-dict.c \
+ acl-mailbox.c \
+ acl-mailbox-list.c \
+ acl-plugin.c \
+ acl-shared-storage.c \
+ acl-storage.c
+
+noinst_HEADERS = \
+ acl-backend-vfile.h \
+ acl-shared-storage.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = \
+ acl-api.h \
+ acl-api-private.h \
+ acl-cache.h \
+ acl-global-file.h \
+ acl-lookup-dict.h \
+ acl-plugin.h \
+ acl-storage.h
+
+doveadm_module_LTLIBRARIES = \
+ lib10_doveadm_acl_plugin.la
+
+lib10_doveadm_acl_plugin_la_SOURCES = \
+ doveadm-acl.c
+
+test_programs = \
+ test-acl
+
+test_libs = \
+ $(module_LTLIBRARIES) \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+
+test_deps = \
+ $(module_LTLIBRARIES) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+test_acl_SOURCES = test-acl.c
+test_acl_LDADD = $(test_libs)
+test_acl_DEPENDENCIES = $(test_deps)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/acl/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/acl/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \
+ }
+
+uninstall-doveadm_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \
+ done
+
+clean-doveadm_moduleLTLIBRARIES:
+ -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES)
+ @list='$(doveadm_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib01_acl_plugin.la: $(lib01_acl_plugin_la_OBJECTS) $(lib01_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib01_acl_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib01_acl_plugin_la_LINK) -rpath $(moduledir) $(lib01_acl_plugin_la_OBJECTS) $(lib01_acl_plugin_la_LIBADD) $(LIBS)
+
+lib10_doveadm_acl_plugin.la: $(lib10_doveadm_acl_plugin_la_OBJECTS) $(lib10_doveadm_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib10_doveadm_acl_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib10_doveadm_acl_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib10_doveadm_acl_plugin_la_OBJECTS) $(lib10_doveadm_acl_plugin_la_LIBADD) $(LIBS)
+
+test-acl$(EXEEXT): $(test_acl_OBJECTS) $(test_acl_DEPENDENCIES) $(EXTRA_test_acl_DEPENDENCIES)
+ @rm -f test-acl$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_acl_OBJECTS) $(test_acl_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-api.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-attributes.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile-acllist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile-update.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-cache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-global-file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-lookup-dict.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-mailbox-list.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-mailbox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-shared-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-acl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-acl.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/acl-api.Plo
+ -rm -f ./$(DEPDIR)/acl-attributes.Plo
+ -rm -f ./$(DEPDIR)/acl-backend-vfile-acllist.Plo
+ -rm -f ./$(DEPDIR)/acl-backend-vfile-update.Plo
+ -rm -f ./$(DEPDIR)/acl-backend-vfile.Plo
+ -rm -f ./$(DEPDIR)/acl-backend.Plo
+ -rm -f ./$(DEPDIR)/acl-cache.Plo
+ -rm -f ./$(DEPDIR)/acl-global-file.Plo
+ -rm -f ./$(DEPDIR)/acl-lookup-dict.Plo
+ -rm -f ./$(DEPDIR)/acl-mailbox-list.Plo
+ -rm -f ./$(DEPDIR)/acl-mailbox.Plo
+ -rm -f ./$(DEPDIR)/acl-plugin.Plo
+ -rm -f ./$(DEPDIR)/acl-shared-storage.Plo
+ -rm -f ./$(DEPDIR)/acl-storage.Plo
+ -rm -f ./$(DEPDIR)/doveadm-acl.Plo
+ -rm -f ./$(DEPDIR)/test-acl.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-doveadm_moduleLTLIBRARIES \
+ install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/acl-api.Plo
+ -rm -f ./$(DEPDIR)/acl-attributes.Plo
+ -rm -f ./$(DEPDIR)/acl-backend-vfile-acllist.Plo
+ -rm -f ./$(DEPDIR)/acl-backend-vfile-update.Plo
+ -rm -f ./$(DEPDIR)/acl-backend-vfile.Plo
+ -rm -f ./$(DEPDIR)/acl-backend.Plo
+ -rm -f ./$(DEPDIR)/acl-cache.Plo
+ -rm -f ./$(DEPDIR)/acl-global-file.Plo
+ -rm -f ./$(DEPDIR)/acl-lookup-dict.Plo
+ -rm -f ./$(DEPDIR)/acl-mailbox-list.Plo
+ -rm -f ./$(DEPDIR)/acl-mailbox.Plo
+ -rm -f ./$(DEPDIR)/acl-plugin.Plo
+ -rm -f ./$(DEPDIR)/acl-shared-storage.Plo
+ -rm -f ./$(DEPDIR)/acl-storage.Plo
+ -rm -f ./$(DEPDIR)/doveadm-acl.Plo
+ -rm -f ./$(DEPDIR)/test-acl.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-doveadm_moduleLTLIBRARIES \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am \
+ install-doveadm_moduleLTLIBRARIES install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/acl/acl-api-private.h b/src/plugins/acl/acl-api-private.h
new file mode 100644
index 0000000..6859779
--- /dev/null
+++ b/src/plugins/acl/acl-api-private.h
@@ -0,0 +1,135 @@
+#ifndef ACL_API_PRIVATE_H
+#define ACL_API_PRIVATE_H
+
+#include "acl-api.h"
+
+#define ACL_ID_NAME_ANYONE "anyone"
+#define ACL_ID_NAME_AUTHENTICATED "authenticated"
+#define ACL_ID_NAME_OWNER "owner"
+#define ACL_ID_NAME_USER_PREFIX "user="
+#define ACL_ID_NAME_GROUP_PREFIX "group="
+#define ACL_ID_NAME_GROUP_OVERRIDE_PREFIX "group-override="
+
+struct acl_backend_vfuncs {
+ struct acl_backend *(*alloc)(void);
+ int (*init)(struct acl_backend *backend, const char *data);
+ void (*deinit)(struct acl_backend *backend);
+
+ struct acl_mailbox_list_context *
+ (*nonowner_lookups_iter_init)(struct acl_backend *backend);
+ bool (*nonowner_lookups_iter_next)(struct acl_mailbox_list_context *ctx,
+ const char **name_r);
+ int (*nonowner_lookups_iter_deinit)
+ (struct acl_mailbox_list_context *ctx);
+ int (*nonowner_lookups_rebuild)(struct acl_backend *backend);
+
+ struct acl_object *(*object_init)(struct acl_backend *backend,
+ const char *name);
+ struct acl_object *(*object_init_parent)(struct acl_backend *backend,
+ const char *child_name);
+ void (*object_deinit)(struct acl_object *aclobj);
+
+ int (*object_refresh_cache)(struct acl_object *aclobj);
+ int (*object_update)(struct acl_object *aclobj,
+ const struct acl_rights_update *update);
+ int (*last_changed)(struct acl_object *aclobj, time_t *last_changed_r);
+
+ struct acl_object_list_iter *
+ (*object_list_init)(struct acl_object *aclobj);
+ bool (*object_list_next)(struct acl_object_list_iter *iter,
+ struct acl_rights *rights_r);
+ int (*object_list_deinit)(struct acl_object_list_iter *iter);
+};
+
+struct acl_backend {
+ pool_t pool;
+ const char *username;
+ const char **groups;
+ unsigned int group_count;
+
+ struct mailbox_list *list;
+ struct acl_cache *cache;
+ struct acl_global_file *global_file;
+
+ struct acl_object *default_aclobj;
+ struct acl_mask *default_aclmask;
+ const char *const *default_rights;
+
+ struct acl_backend_vfuncs v;
+
+ bool owner:1;
+ bool debug:1;
+ bool globals_only:1;
+};
+
+struct acl_mailbox_list_context {
+ struct acl_backend *backend;
+
+ bool empty:1;
+ bool failed:1;
+ const char *error;
+};
+
+struct acl_object {
+ struct acl_backend *backend;
+ char *name;
+
+ pool_t rights_pool;
+ ARRAY_TYPE(acl_rights) rights;
+};
+
+struct acl_object_list_iter {
+ struct acl_object *aclobj;
+ pool_t pool;
+
+ struct acl_rights *rights;
+ unsigned int idx, count;
+
+ bool empty:1;
+ bool failed:1;
+ const char *error;
+};
+
+extern const char *const all_mailbox_rights[];
+
+struct acl_object_list_iter *
+acl_default_object_list_init(struct acl_object *aclobj);
+bool acl_default_object_list_next(struct acl_object_list_iter *iter,
+ struct acl_rights *rights_r);
+int acl_default_object_list_deinit(struct acl_object_list_iter *iter);
+
+const char *const *
+acl_backend_mask_get_names(struct acl_backend *backend,
+ const struct acl_mask *mask, pool_t pool);
+struct acl_object *acl_backend_get_default_object(struct acl_backend *backend);
+int acl_backend_get_default_rights(struct acl_backend *backend,
+ const struct acl_mask **mask_r);
+void acl_rights_write_id(string_t *dest, const struct acl_rights *right);
+bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights);
+
+int acl_identifier_parse(const char *line, struct acl_rights *rights);
+int acl_rights_update_import(struct acl_rights_update *update,
+ const char *id, const char *const *rights,
+ const char **error_r);
+const char *acl_rights_export(const struct acl_rights *rights);
+int acl_rights_parse_line(const char *line, pool_t pool,
+ struct acl_rights *rights_r, const char **error_r);
+void acl_rights_dup(const struct acl_rights *src,
+ pool_t pool, struct acl_rights *dest_r);
+int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2);
+void acl_rights_sort(struct acl_object *aclobj);
+
+const char *const *
+acl_right_names_parse(pool_t pool, const char *acl, const char **error_r);
+void acl_right_names_write(string_t *dest, const char *const *rights);
+void acl_right_names_merge(pool_t pool, const char *const **destp,
+ const char *const *src, bool dup_strings);
+bool acl_right_names_modify(pool_t pool,
+ const char *const **rightsp,
+ const char *const *modify_rights,
+ enum acl_modify_mode modify_mode);
+void acl_object_rebuild_cache(struct acl_object *aclobj);
+void acl_object_remove_all_access(struct acl_object *aclobj);
+void acl_object_add_global_acls(struct acl_object *aclobj);
+
+#endif
diff --git a/src/plugins/acl/acl-api.c b/src/plugins/acl/acl-api.c
new file mode 100644
index 0000000..2e422ea
--- /dev/null
+++ b/src/plugins/acl/acl-api.c
@@ -0,0 +1,847 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "hash.h"
+#include "mail-user.h"
+#include "mailbox-list.h"
+#include "acl-global-file.h"
+#include "acl-cache.h"
+#include "acl-api-private.h"
+
+struct acl_letter_map {
+ char letter;
+ const char *name;
+};
+
+static const struct acl_letter_map acl_letter_map[] = {
+ { 'l', MAIL_ACL_LOOKUP },
+ { 'r', MAIL_ACL_READ },
+ { 'w', MAIL_ACL_WRITE },
+ { 's', MAIL_ACL_WRITE_SEEN },
+ { 't', MAIL_ACL_WRITE_DELETED },
+ { 'i', MAIL_ACL_INSERT },
+ { 'p', MAIL_ACL_POST },
+ { 'e', MAIL_ACL_EXPUNGE },
+ { 'k', MAIL_ACL_CREATE },
+ { 'x', MAIL_ACL_DELETE },
+ { 'a', MAIL_ACL_ADMIN },
+ { '\0', NULL }
+};
+
+struct acl_object *acl_object_init_from_name(struct acl_backend *backend,
+ const char *name)
+{
+ return backend->v.object_init(backend, name);
+}
+
+struct acl_object *acl_object_init_from_parent(struct acl_backend *backend,
+ const char *child_name)
+{
+ return backend->v.object_init_parent(backend, child_name);
+}
+
+void acl_object_deinit(struct acl_object **_aclobj)
+{
+ struct acl_object *aclobj = *_aclobj;
+
+ *_aclobj = NULL;
+ aclobj->backend->v.object_deinit(aclobj);
+}
+
+int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx)
+{
+ struct acl_backend *backend = aclobj->backend;
+ const struct acl_mask *have_mask;
+ unsigned int read_idx;
+
+ if (backend->v.object_refresh_cache(aclobj) < 0)
+ return -1;
+
+ have_mask = acl_cache_get_my_rights(backend->cache, aclobj->name);
+ if (have_mask == NULL) {
+ if (acl_backend_get_default_rights(backend, &have_mask) < 0)
+ return -1;
+ }
+
+ if (acl_cache_mask_isset(have_mask, right_idx))
+ return 1;
+
+ if (mailbox_list_get_user(aclobj->backend->list)->dsyncing) {
+ /* when dsync is running on a shared mailbox, it must be able
+ to do everything inside it. however, dsync shouldn't touch
+ mailboxes where user doesn't have any read access, because
+ that could make them readable on the replica. */
+ read_idx = acl_backend_lookup_right(aclobj->backend,
+ MAIL_ACL_READ);
+ if (acl_cache_mask_isset(have_mask, read_idx))
+ return 1;
+ }
+ return 0;
+}
+
+const char *const *
+acl_backend_mask_get_names(struct acl_backend *backend,
+ const struct acl_mask *mask, pool_t pool)
+{
+ const char *const *names;
+ const char **buf, **rights;
+ unsigned int names_count, count, i, j, name_idx;
+
+ names = acl_cache_get_names(backend->cache, &names_count);
+ buf = t_new(const char *, (mask->size * CHAR_BIT) + 1);
+ count = 0;
+ for (i = 0, name_idx = 0; i < mask->size; i++) {
+ if (mask->mask[i] == 0)
+ name_idx += CHAR_BIT;
+ else {
+ for (j = 1; j < (1 << CHAR_BIT); j <<= 1, name_idx++) {
+ if ((mask->mask[i] & j) == 0)
+ continue;
+
+ /* @UNSAFE */
+ i_assert(name_idx < names_count);
+ buf[count++] = p_strdup(pool, names[name_idx]);
+ }
+ }
+ }
+
+ /* @UNSAFE */
+ rights = p_new(pool, const char *, count + 1);
+ memcpy(rights, buf, count * sizeof(const char *));
+ return rights;
+}
+
+static int acl_object_get_my_rights_real(struct acl_object *aclobj, pool_t pool,
+ const char *const **rights_r)
+{
+ struct acl_backend *backend = aclobj->backend;
+ const struct acl_mask *mask;
+
+ if (backend->v.object_refresh_cache(aclobj) < 0)
+ return -1;
+
+ mask = acl_cache_get_my_rights(backend->cache, aclobj->name);
+ if (mask == NULL) {
+ if (acl_backend_get_default_rights(backend, &mask) < 0)
+ return -1;
+ }
+
+ *rights_r = acl_backend_mask_get_names(backend, mask, pool);
+ return 0;
+}
+
+int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool,
+ const char *const **rights_r)
+{
+ int ret;
+
+ if (pool->datastack_pool)
+ return acl_object_get_my_rights_real(aclobj, pool, rights_r);
+ T_BEGIN {
+ ret = acl_object_get_my_rights_real(aclobj, pool, rights_r);
+ } T_END;
+ return ret;
+}
+
+const char *const *acl_object_get_default_rights(struct acl_object *aclobj)
+{
+ return acl_backend_mask_get_names(aclobj->backend,
+ aclobj->backend->default_aclmask,
+ pool_datastack_create());
+}
+
+int acl_object_last_changed(struct acl_object *aclobj, time_t *last_changed_r)
+{
+ return aclobj->backend->v.last_changed(aclobj, last_changed_r);
+}
+
+int acl_object_update(struct acl_object *aclobj,
+ const struct acl_rights_update *update)
+{
+ return aclobj->backend->v.object_update(aclobj, update);
+}
+
+struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj)
+{
+ return aclobj->backend->v.object_list_init(aclobj);
+}
+
+bool acl_object_list_next(struct acl_object_list_iter *iter,
+ struct acl_rights *rights_r)
+{
+ if (iter->failed)
+ return FALSE;
+
+ return iter->aclobj->backend->v.object_list_next(iter, rights_r);
+}
+
+int acl_object_list_deinit(struct acl_object_list_iter **_iter)
+{
+ struct acl_object_list_iter *iter = *_iter;
+
+ *_iter = NULL;
+ return iter->aclobj->backend->v.object_list_deinit(iter);
+}
+
+struct acl_object_list_iter *
+acl_default_object_list_init(struct acl_object *aclobj)
+{
+ struct acl_object_list_iter *iter;
+ const struct acl_rights *aclobj_rights;
+ unsigned int i;
+ pool_t pool;
+
+ pool = pool_alloconly_create("acl object list", 512);
+ iter = p_new(pool, struct acl_object_list_iter, 1);
+ iter->pool = pool;
+ iter->aclobj = aclobj;
+
+ if (!array_is_created(&aclobj->rights)) {
+ /* we may have the object cached, but we don't have all the
+ rights read into memory */
+ acl_cache_flush(aclobj->backend->cache, aclobj->name);
+ }
+
+ if (aclobj->backend->v.object_refresh_cache(aclobj) < 0)
+ iter->failed = TRUE;
+
+ aclobj_rights = array_get(&aclobj->rights, &iter->count);
+ if (iter->count > 0) {
+ iter->rights = p_new(pool, struct acl_rights, iter->count);
+ for (i = 0; i < iter->count; i++)
+ acl_rights_dup(&aclobj_rights[i], pool, &iter->rights[i]);
+ } else
+ iter->empty = TRUE;
+ return iter;
+}
+
+bool acl_default_object_list_next(struct acl_object_list_iter *iter,
+ struct acl_rights *rights_r)
+{
+ if (iter->failed)
+ return FALSE;
+
+ if (iter->idx == iter->count)
+ return FALSE;
+ *rights_r = iter->rights[iter->idx++];
+ return TRUE;
+}
+
+int acl_default_object_list_deinit(struct acl_object_list_iter *iter)
+{
+ int ret = 0;
+ if (iter->failed)
+ ret = -1;
+ else if (iter->empty)
+ ret = 0;
+ else
+ ret = 1;
+
+ pool_unref(&iter->pool);
+ return ret;
+}
+
+struct acl_mailbox_list_context *
+acl_backend_nonowner_lookups_iter_init(struct acl_backend *backend)
+{
+ return backend->v.nonowner_lookups_iter_init(backend);
+}
+
+bool acl_backend_nonowner_lookups_iter_next(struct acl_mailbox_list_context *ctx,
+ const char **name_r)
+{
+ return ctx->backend->v.nonowner_lookups_iter_next(ctx, name_r);
+}
+
+int acl_backend_nonowner_lookups_iter_deinit(struct acl_mailbox_list_context **_ctx)
+{
+ struct acl_mailbox_list_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+ return ctx->backend->v.nonowner_lookups_iter_deinit(ctx);
+}
+
+int acl_backend_nonowner_lookups_rebuild(struct acl_backend *backend)
+{
+ return backend->v.nonowner_lookups_rebuild(backend);
+}
+
+void acl_rights_write_id(string_t *dest, const struct acl_rights *right)
+{
+ switch (right->id_type) {
+ case ACL_ID_ANYONE:
+ str_append(dest, ACL_ID_NAME_ANYONE);
+ break;
+ case ACL_ID_AUTHENTICATED:
+ str_append(dest, ACL_ID_NAME_AUTHENTICATED);
+ break;
+ case ACL_ID_OWNER:
+ str_append(dest, ACL_ID_NAME_OWNER);
+ break;
+ case ACL_ID_USER:
+ str_append(dest, ACL_ID_NAME_USER_PREFIX);
+ str_append(dest, right->identifier);
+ break;
+ case ACL_ID_GROUP:
+ str_append(dest, ACL_ID_NAME_GROUP_PREFIX);
+ str_append(dest, right->identifier);
+ break;
+ case ACL_ID_GROUP_OVERRIDE:
+ str_append(dest, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX);
+ str_append(dest, right->identifier);
+ break;
+ case ACL_ID_TYPE_COUNT:
+ i_unreached();
+ }
+}
+
+const char *acl_rights_get_id(const struct acl_rights *right)
+{
+ string_t *str = t_str_new(32);
+
+ acl_rights_write_id(str, right);
+ return str_c(str);
+}
+
+static bool is_standard_right(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; all_mailbox_rights[i] != NULL; i++) {
+ if (strcmp(all_mailbox_rights[i], name) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int acl_rights_update_import(struct acl_rights_update *update,
+ const char *id, const char *const *rights,
+ const char **error_r)
+{
+ ARRAY_TYPE(const_string) dest_rights, dest_neg_rights, *dest;
+ unsigned int i, j;
+
+ if (acl_identifier_parse(id, &update->rights) < 0) {
+ *error_r = t_strdup_printf("Invalid ID: %s", id);
+ return -1;
+ }
+ if (rights == NULL) {
+ update->modify_mode = ACL_MODIFY_MODE_CLEAR;
+ update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR;
+ return 0;
+ }
+
+ t_array_init(&dest_rights, 8);
+ t_array_init(&dest_neg_rights, 8);
+ for (i = 0; rights[i] != NULL; i++) {
+ const char *right = rights[i];
+
+ if (right[0] != '-')
+ dest = &dest_rights;
+ else {
+ right++;
+ dest = &dest_neg_rights;
+ }
+ if (strcmp(right, "all") != 0) {
+ if (*right == ':') {
+ /* non-standard right */
+ right++;
+ array_push_back(dest, &right);
+ } else if (is_standard_right(right)) {
+ array_push_back(dest, &right);
+ } else {
+ *error_r = t_strdup_printf("Invalid right '%s'",
+ right);
+ return -1;
+ }
+ } else {
+ for (j = 0; all_mailbox_rights[j] != NULL; j++)
+ array_push_back(dest, &all_mailbox_rights[j]);
+ }
+ }
+ if (array_count(&dest_rights) > 0) {
+ array_append_zero(&dest_rights);
+ update->rights.rights = array_front(&dest_rights);
+ } else if (update->modify_mode == ACL_MODIFY_MODE_REPLACE) {
+ update->modify_mode = ACL_MODIFY_MODE_CLEAR;
+ }
+ if (array_count(&dest_neg_rights) > 0) {
+ array_append_zero(&dest_neg_rights);
+ update->rights.neg_rights = array_front(&dest_neg_rights);
+ } else if (update->neg_modify_mode == ACL_MODIFY_MODE_REPLACE) {
+ update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR;
+ }
+ return 0;
+}
+
+const char *acl_rights_export(const struct acl_rights *rights)
+{
+ string_t *str = t_str_new(128);
+
+ if (rights->rights != NULL)
+ str_append(str, t_strarray_join(rights->rights, " "));
+ if (rights->neg_rights != NULL && rights->neg_rights[0] != NULL) {
+ if (str_len(str) > 0)
+ str_append_c(str, ' ');
+ str_append_c(str, '-');
+ str_append(str, t_strarray_join(rights->neg_rights, " -"));
+ }
+ return str_c(str);
+}
+
+int acl_rights_parse_line(const char *line, pool_t pool,
+ struct acl_rights *rights_r, const char **error_r)
+{
+ const char *id_str, *const *right_names, *error = NULL;
+
+ /* <id> [<imap acls>] [:<named acls>] */
+ if (*line == '"') {
+ line++;
+ if (str_unescape_next(&line, &id_str) < 0 ||
+ (line[0] != ' ' && line[0] != '\0')) {
+ *error_r = "Invalid quoted ID";
+ return -1;
+ }
+ if (line[0] == ' ')
+ line++;
+ } else {
+ id_str = line;
+ line = strchr(id_str, ' ');
+ if (line == NULL)
+ line = "";
+ else
+ id_str = t_strdup_until(id_str, line++);
+ }
+
+ i_zero(rights_r);
+
+ right_names = acl_right_names_parse(pool, line, &error);
+ if (*id_str != '-')
+ rights_r->rights = right_names;
+ else {
+ id_str++;
+ rights_r->neg_rights = right_names;
+ }
+
+ if (acl_identifier_parse(id_str, rights_r) < 0)
+ error = t_strdup_printf("Unknown ID '%s'", id_str);
+
+ if (error != NULL) {
+ *error_r = error;
+ return -1;
+ }
+
+ rights_r->identifier = p_strdup(pool, rights_r->identifier);
+ return 0;
+}
+
+void acl_rights_dup(const struct acl_rights *src,
+ pool_t pool, struct acl_rights *dest_r)
+{
+ i_zero(dest_r);
+ dest_r->id_type = src->id_type;
+ dest_r->identifier = p_strdup(pool, src->identifier);
+ dest_r->rights = src->rights == NULL ? NULL :
+ p_strarray_dup(pool, src->rights);
+ dest_r->neg_rights = src->neg_rights == NULL ? NULL :
+ p_strarray_dup(pool, src->neg_rights);
+ dest_r->global = src->global;
+}
+
+int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2)
+{
+ int ret;
+
+ if (r1->global != r2->global) {
+ /* globals have higher priority than locals */
+ return r1->global ? 1 : -1;
+ }
+
+ ret = (int)r1->id_type - (int)r2->id_type;
+ if (ret != 0)
+ return ret;
+
+ return null_strcmp(r1->identifier, r2->identifier);
+}
+
+void acl_rights_sort(struct acl_object *aclobj)
+{
+ struct acl_rights *rights;
+ unsigned int i, dest, count;
+
+ if (!array_is_created(&aclobj->rights))
+ return;
+
+ array_sort(&aclobj->rights, acl_rights_cmp);
+
+ /* merge identical identifiers */
+ rights = array_get_modifiable(&aclobj->rights, &count);
+ for (dest = 0, i = 1; i < count; i++) {
+ if (acl_rights_cmp(&rights[i], &rights[dest]) == 0) {
+ /* add i's rights to dest and delete i */
+ acl_right_names_merge(aclobj->rights_pool,
+ &rights[dest].rights,
+ rights[i].rights, FALSE);
+ acl_right_names_merge(aclobj->rights_pool,
+ &rights[dest].neg_rights,
+ rights[i].neg_rights, FALSE);
+ } else {
+ if (++dest != i)
+ rights[dest] = rights[i];
+ }
+ }
+ if (++dest < count)
+ array_delete(&aclobj->rights, dest, count - dest);
+}
+
+bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights)
+{
+ const char *const *p;
+
+ if (rights->id_type == ACL_ID_OWNER) {
+ /* ignore owner rights */
+ return FALSE;
+ }
+
+ if (rights->rights == NULL)
+ return FALSE;
+
+ for (p = rights->rights; *p != NULL; p++) {
+ if (strcmp(*p, MAIL_ACL_LOOKUP) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int acl_identifier_parse(const char *line, struct acl_rights *rights)
+{
+ if (str_begins(line, ACL_ID_NAME_USER_PREFIX)) {
+ rights->id_type = ACL_ID_USER;
+ rights->identifier = line + 5;
+ } else if (strcmp(line, ACL_ID_NAME_OWNER) == 0) {
+ rights->id_type = ACL_ID_OWNER;
+ } else if (str_begins(line, ACL_ID_NAME_GROUP_PREFIX)) {
+ rights->id_type = ACL_ID_GROUP;
+ rights->identifier = line + 6;
+ } else if (str_begins(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX)) {
+ rights->id_type = ACL_ID_GROUP_OVERRIDE;
+ rights->identifier = line + 15;
+ } else if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) {
+ rights->id_type = ACL_ID_AUTHENTICATED;
+ } else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 ||
+ strcmp(line, "anonymous") == 0) {
+ rights->id_type = ACL_ID_ANYONE;
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+static const char *const *
+acl_right_names_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr,
+ bool dup_strings)
+{
+ const char **ret, *const *rights;
+ unsigned int i, dest, count;
+
+ /* sort the rights first so we can easily drop duplicates */
+ array_sort(rights_arr, i_strcmp_p);
+
+ /* @UNSAFE */
+ rights = array_get(rights_arr, &count);
+ ret = p_new(pool, const char *, count + 1);
+ if (count > 0) {
+ ret[0] = rights[0];
+ for (i = dest = 1; i < count; i++) {
+ if (strcmp(rights[i-1], rights[i]) != 0)
+ ret[dest++] = rights[i];
+ }
+ ret[dest] = NULL;
+ if (dup_strings) {
+ for (i = 0; i < dest; i++)
+ ret[i] = p_strdup(pool, ret[i]);
+ }
+ }
+ return ret;
+}
+
+const char *const *
+acl_right_names_parse(pool_t pool, const char *acl, const char **error_r)
+{
+ ARRAY_TYPE(const_string) rights;
+ const char *const *names;
+ unsigned int i;
+
+ /* parse IMAP ACL list */
+ while (*acl == ' ' || *acl == '\t')
+ acl++;
+
+ t_array_init(&rights, 64);
+ while (*acl != '\0' && *acl != ' ' && *acl != '\t' && *acl != ':') {
+ for (i = 0; acl_letter_map[i].letter != '\0'; i++) {
+ if (acl_letter_map[i].letter == *acl)
+ break;
+ }
+
+ if (acl_letter_map[i].letter == '\0') {
+ *error_r = t_strdup_printf("Unknown ACL '%c'", *acl);
+ return NULL;
+ }
+
+ array_push_back(&rights, &acl_letter_map[i].name);
+ acl++;
+ }
+ while (*acl == ' ' || *acl == '\t') acl++;
+
+ if (*acl != '\0') {
+ /* parse our own extended ACLs */
+ if (*acl != ':') {
+ *error_r = "Missing ':' prefix in ACL extensions";
+ return NULL;
+ }
+
+ names = t_strsplit_spaces(acl + 1, ", \t");
+ for (; *names != NULL; names++) {
+ const char *name = p_strdup(pool, *names);
+ array_push_back(&rights, &name);
+ }
+ }
+
+ return acl_right_names_alloc(pool, &rights, FALSE);
+}
+
+void acl_right_names_write(string_t *dest, const char *const *rights)
+{
+ char c2[2];
+ unsigned int i, j, pos;
+
+ c2[1] = '\0';
+ pos = str_len(dest);
+ for (i = 0; rights[i] != NULL; i++) {
+ /* use letters if possible */
+ for (j = 0; acl_letter_map[j].name != NULL; j++) {
+ if (strcmp(rights[i], acl_letter_map[j].name) == 0) {
+ c2[0] = acl_letter_map[j].letter;
+ str_insert(dest, pos, c2);
+ pos++;
+ break;
+ }
+ }
+ if (acl_letter_map[j].name == NULL) {
+ /* fallback to full name */
+ str_append_c(dest, ' ');
+ str_append(dest, rights[i]);
+ }
+ }
+ if (pos + 1 < str_len(dest)) {
+ c2[0] = ':';
+ str_insert(dest, pos + 1, c2);
+ }
+}
+
+void acl_right_names_merge(pool_t pool, const char *const **destp,
+ const char *const *src, bool dup_strings)
+{
+ const char *const *dest = *destp;
+ ARRAY_TYPE(const_string) rights;
+ unsigned int i;
+
+ t_array_init(&rights, 64);
+ if (dest != NULL) {
+ for (i = 0; dest[i] != NULL; i++)
+ array_push_back(&rights, &dest[i]);
+ }
+ if (src != NULL) {
+ for (i = 0; src[i] != NULL; i++)
+ array_push_back(&rights, &src[i]);
+ }
+
+ *destp = acl_right_names_alloc(pool, &rights, dup_strings);
+}
+
+bool acl_right_names_modify(pool_t pool,
+ const char *const **rightsp,
+ const char *const *modify_rights,
+ enum acl_modify_mode modify_mode)
+{
+ const char *const *old_rights = *rightsp;
+ const char *const *new_rights = NULL;
+ const char *null = NULL;
+ ARRAY_TYPE(const_string) rights;
+ unsigned int i, j;
+
+ if (modify_rights == NULL && modify_mode != ACL_MODIFY_MODE_CLEAR) {
+ /* nothing to do here */
+ return FALSE;
+ }
+
+ switch (modify_mode) {
+ case ACL_MODIFY_MODE_REMOVE:
+ if (old_rights == NULL || *old_rights == NULL) {
+ /* nothing to do */
+ return FALSE;
+ }
+ t_array_init(&rights, 64);
+ for (i = 0; old_rights[i] != NULL; i++) {
+ for (j = 0; modify_rights[j] != NULL; j++) {
+ if (strcmp(old_rights[i], modify_rights[j]) == 0)
+ break;
+ }
+ if (modify_rights[j] == NULL)
+ array_push_back(&rights, &old_rights[i]);
+ }
+ new_rights = &null;
+ modify_rights = array_count(&rights) == 0 ? NULL :
+ array_front(&rights);
+ acl_right_names_merge(pool, &new_rights, modify_rights, TRUE);
+ break;
+ case ACL_MODIFY_MODE_ADD:
+ new_rights = old_rights;
+ acl_right_names_merge(pool, &new_rights, modify_rights, TRUE);
+ break;
+ case ACL_MODIFY_MODE_REPLACE:
+ new_rights = &null;
+ acl_right_names_merge(pool, &new_rights, modify_rights, TRUE);
+ break;
+ case ACL_MODIFY_MODE_CLEAR:
+ if (*rightsp == NULL) {
+ /* ACL didn't exist before either */
+ return FALSE;
+ }
+ *rightsp = NULL;
+ return TRUE;
+ }
+ i_assert(new_rights != NULL);
+ *rightsp = new_rights;
+
+ if (old_rights == NULL)
+ return new_rights[0] != NULL;
+
+ /* see if anything changed */
+ for (i = 0; old_rights[i] != NULL && new_rights[i] != NULL; i++) {
+ if (strcmp(old_rights[i], new_rights[i]) != 0)
+ return TRUE;
+ }
+ return old_rights[i] != NULL || new_rights[i] != NULL;
+}
+
+static void apply_owner_default_rights(struct acl_object *aclobj)
+{
+ struct acl_rights_update ru;
+ const char *null = NULL;
+
+ i_zero(&ru);
+ ru.modify_mode = ACL_MODIFY_MODE_REPLACE;
+ ru.neg_modify_mode = ACL_MODIFY_MODE_REPLACE;
+ ru.rights.id_type = ACL_ID_OWNER;
+ ru.rights.rights = aclobj->backend->default_rights;
+ ru.rights.neg_rights = &null;
+ acl_cache_update(aclobj->backend->cache, aclobj->name, &ru);
+}
+
+void acl_object_rebuild_cache(struct acl_object *aclobj)
+{
+ struct acl_rights_update ru;
+ enum acl_modify_mode add_mode;
+ const struct acl_rights *rights, *prev_match = NULL;
+ unsigned int i, count;
+ bool first_global = TRUE;
+
+ acl_cache_flush(aclobj->backend->cache, aclobj->name);
+
+ if (!array_is_created(&aclobj->rights))
+ return;
+
+ /* Rights are sorted by their 1) locals first, globals next,
+ 2) acl_id_type. We'll apply only the rights matching ourself.
+
+ Every time acl_id_type or local/global changes, the new ACLs will
+ replace all of the existing ACLs. Basically this means that if
+ user belongs to multiple matching groups or group-overrides, their
+ ACLs are merged. In all other situations the ACLs are replaced
+ (because there aren't duplicate rights entries and a user can't
+ match multiple usernames). */
+ i_zero(&ru);
+ rights = array_get(&aclobj->rights, &count);
+ if (!acl_backend_user_is_owner(aclobj->backend))
+ i = 0;
+ else {
+ /* we're the owner. skip over all rights entries until we
+ reach ACL_ID_OWNER or higher, or alternatively when we
+ reach a global ACL (even ACL_ID_ANYONE overrides owner's
+ rights if it's global) */
+ for (i = 0; i < count; i++) {
+ if (rights[i].id_type >= ACL_ID_OWNER ||
+ rights[i].global)
+ break;
+ }
+ apply_owner_default_rights(aclobj);
+ /* now continue applying the rest of the rights,
+ if there are any */
+ }
+ for (; i < count; i++) {
+ if (!acl_backend_rights_match_me(aclobj->backend, &rights[i]))
+ continue;
+
+ if (prev_match == NULL ||
+ prev_match->id_type != rights[i].id_type ||
+ prev_match->global != rights[i].global) {
+ /* replace old ACLs */
+ add_mode = ACL_MODIFY_MODE_REPLACE;
+ } else {
+ /* merging to existing ACLs */
+ i_assert(rights[i].id_type == ACL_ID_GROUP ||
+ rights[i].id_type == ACL_ID_GROUP_OVERRIDE);
+ add_mode = ACL_MODIFY_MODE_ADD;
+ }
+ prev_match = &rights[i];
+
+ /* If [neg_]rights is NULL it needs to be ignored.
+ The easiest way to do that is to just mark it with
+ REMOVE mode */
+ ru.modify_mode = rights[i].rights == NULL ?
+ ACL_MODIFY_MODE_REMOVE : add_mode;
+ ru.neg_modify_mode = rights[i].neg_rights == NULL ?
+ ACL_MODIFY_MODE_REMOVE : add_mode;
+ ru.rights = rights[i];
+ if (rights[i].global && first_global) {
+ /* first global: reset negative ACLs so local ACLs
+ can't mess things up via them */
+ first_global = FALSE;
+ ru.neg_modify_mode = ACL_MODIFY_MODE_REPLACE;
+ }
+ acl_cache_update(aclobj->backend->cache, aclobj->name, &ru);
+ }
+}
+
+void acl_object_remove_all_access(struct acl_object *aclobj)
+{
+ static const char *null = NULL;
+ struct acl_rights rights;
+
+ i_zero(&rights);
+ rights.id_type = ACL_ID_ANYONE;
+ rights.rights = &null;
+ array_push_back(&aclobj->rights, &rights);
+
+ rights.id_type = ACL_ID_OWNER;
+ rights.rights = &null;
+ array_push_back(&aclobj->rights, &rights);
+}
+
+void acl_object_add_global_acls(struct acl_object *aclobj)
+{
+ struct acl_backend *backend = aclobj->backend;
+ const char *vname, *error;
+
+ if (mailbox_list_is_valid_name(backend->list, aclobj->name, &error))
+ vname = mailbox_list_get_vname(backend->list, aclobj->name);
+ else
+ vname = "";
+
+ acl_global_file_get(backend->global_file, vname,
+ aclobj->rights_pool, &aclobj->rights);
+}
diff --git a/src/plugins/acl/acl-api.h b/src/plugins/acl/acl-api.h
new file mode 100644
index 0000000..7b19a98
--- /dev/null
+++ b/src/plugins/acl/acl-api.h
@@ -0,0 +1,167 @@
+#ifndef ACL_API_H
+#define ACL_API_H
+
+#include <sys/stat.h>
+
+struct mailbox_list;
+struct mail_storage;
+struct mailbox;
+struct acl_object;
+
+/* Show mailbox in mailbox list. Allow subscribing to it. */
+#define MAIL_ACL_LOOKUP "lookup"
+/* Allow opening mailbox for reading */
+#define MAIL_ACL_READ "read"
+/* Allow permanent flag changes (except for seen/deleted).
+ If not set, doesn't allow save/copy to set any flags either. */
+#define MAIL_ACL_WRITE "write"
+/* Allow permanent seen-flag changes */
+#define MAIL_ACL_WRITE_SEEN "write-seen"
+/* Allow permanent deleted-flag changes */
+#define MAIL_ACL_WRITE_DELETED "write-deleted"
+/* Allow saving and copying mails into the mailbox */
+#define MAIL_ACL_INSERT "insert"
+/* Allow posting mails to the mailbox (e.g. Sieve fileinto) */
+#define MAIL_ACL_POST "post"
+/* Allow expunging mails */
+#define MAIL_ACL_EXPUNGE "expunge"
+/* Allow creating child mailboxes */
+#define MAIL_ACL_CREATE "create"
+/* Allow deleting this mailbox */
+#define MAIL_ACL_DELETE "delete"
+/* Allow changing ACL state in this mailbox */
+#define MAIL_ACL_ADMIN "admin"
+
+#define MAILBOX_ATTRIBUTE_PREFIX_ACL \
+ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"acl/"
+
+/* ACL identifiers in override order */
+enum acl_id_type {
+ /* Anyone's rights, including anonymous's.
+ identifier name is ignored. */
+ ACL_ID_ANYONE,
+ /* Authenticate users' rights. identifier name is ignored. */
+ ACL_ID_AUTHENTICATED,
+ /* Group's rights */
+ ACL_ID_GROUP,
+ /* Owner's rights, used when user is the storage's owner.
+ identifier name is ignored. */
+ ACL_ID_OWNER,
+ /* User's rights */
+ ACL_ID_USER,
+ /* Same as group's rights, but also overrides user's rights */
+ ACL_ID_GROUP_OVERRIDE,
+
+ ACL_ID_TYPE_COUNT
+};
+
+enum acl_modify_mode {
+ /* Remove rights from existing ACL */
+ ACL_MODIFY_MODE_REMOVE = 0,
+ /* Add rights to existing ACL (or create a new one) */
+ ACL_MODIFY_MODE_ADD,
+ /* Replace existing ACL with given rights */
+ ACL_MODIFY_MODE_REPLACE,
+ /* Clear all the rights from an existing ACL */
+ ACL_MODIFY_MODE_CLEAR
+};
+
+struct acl_rights {
+ /* Type of the identifier, user/group */
+ enum acl_id_type id_type;
+ /* Identifier, eg. username / group name */
+ const char *identifier;
+
+ /* Rights assigned. NULL entry can be ignored, but { NULL } means user
+ has no rights. */
+ const char *const *rights;
+ /* Negative rights assigned */
+ const char *const *neg_rights;
+
+ /* These rights are global for all users */
+ bool global:1;
+};
+ARRAY_DEFINE_TYPE(acl_rights, struct acl_rights);
+
+struct acl_rights_update {
+ struct acl_rights rights;
+
+ enum acl_modify_mode modify_mode;
+ enum acl_modify_mode neg_modify_mode;
+ /* These changes' "last changed" timestamp */
+ time_t last_change;
+};
+
+/* data contains the information needed to initialize ACL backend. If username
+ is NULL, it means the user is anonymous. Username and groups are matched
+ case-sensitively. */
+struct acl_backend *
+acl_backend_init(const char *data, struct mailbox_list *list,
+ const char *acl_username, const char *const *groups,
+ bool owner);
+void acl_backend_deinit(struct acl_backend **backend);
+
+/* Returns the acl_username passed to acl_backend_init(). Note that with
+ anonymous users NULL is returned. */
+const char *acl_backend_get_acl_username(struct acl_backend *backend);
+
+/* Returns TRUE if user isn't anonymous. */
+bool acl_backend_user_is_authenticated(struct acl_backend *backend);
+/* Returns TRUE if user owns the storage. */
+bool acl_backend_user_is_owner(struct acl_backend *backend);
+/* Returns TRUE if given name matches the ACL user name. */
+bool acl_backend_user_name_equals(struct acl_backend *backend,
+ const char *username);
+/* Returns TRUE if ACL user is in given group. */
+bool acl_backend_user_is_in_group(struct acl_backend *backend,
+ const char *group_name);
+/* Returns index for the right name. If it doesn't exist, it's created. */
+unsigned int acl_backend_lookup_right(struct acl_backend *backend,
+ const char *right);
+/* Returns TRUE if acl_rights matches backend user. */
+bool acl_backend_rights_match_me(struct acl_backend *backend,
+ const struct acl_rights *rights);
+
+/* List mailboxes that have lookup right to some non-owners. */
+struct acl_mailbox_list_context *
+acl_backend_nonowner_lookups_iter_init(struct acl_backend *backend);
+bool acl_backend_nonowner_lookups_iter_next(struct acl_mailbox_list_context *ctx,
+ const char **name_r);
+int
+acl_backend_nonowner_lookups_iter_deinit(struct acl_mailbox_list_context **ctx);
+
+/* Force a rebuild for nonowner lookups index */
+int acl_backend_nonowner_lookups_rebuild(struct acl_backend *backend);
+
+struct acl_object *acl_object_init_from_name(struct acl_backend *backend,
+ const char *name);
+struct acl_object *acl_object_init_from_parent(struct acl_backend *backend,
+ const char *child_name);
+void acl_object_deinit(struct acl_object **aclobj);
+
+/* Returns 1 if we have the requested rights, 0 if not, or -1 if internal
+ error occurred. */
+int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx);
+/* Returns 0 = ok, -1 = internal error */
+int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool,
+ const char *const **rights_r);
+/* Returns the default rights for the object. */
+const char *const *acl_object_get_default_rights(struct acl_object *aclobj);
+/* Returns timestamp of when the ACLs were last changed for this object,
+ or 0 = never. */
+int acl_object_last_changed(struct acl_object *aclobj, time_t *last_changed_r);
+
+/* Update ACL of given object. */
+int acl_object_update(struct acl_object *aclobj,
+ const struct acl_rights_update *update);
+
+/* List all identifiers. */
+struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj);
+bool acl_object_list_next(struct acl_object_list_iter *iter,
+ struct acl_rights *rights_r);
+int acl_object_list_deinit(struct acl_object_list_iter **iter);
+
+/* Returns the canonical ID for the right. */
+const char *acl_rights_get_id(const struct acl_rights *right);
+
+#endif
diff --git a/src/plugins/acl/acl-attributes.c b/src/plugins/acl/acl-attributes.c
new file mode 100644
index 0000000..515ff42
--- /dev/null
+++ b/src/plugins/acl/acl-attributes.c
@@ -0,0 +1,233 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "mail-storage-private.h"
+#include "acl-api-private.h"
+#include "acl-plugin.h"
+#include "acl-storage.h"
+
+struct acl_mailbox_attribute_iter {
+ struct mailbox_attribute_iter iter;
+ struct mailbox_attribute_iter *super;
+
+ struct acl_object_list_iter *acl_iter;
+ string_t *acl_name;
+
+ bool failed;
+};
+
+static int
+acl_attribute_update_acl(struct mailbox_transaction_context *t, const char *key,
+ const struct mail_attribute_value *value)
+{
+ const char *value_str, *id, *const *rights, *error;
+ struct acl_rights_update update;
+
+ /* for now allow only dsync to update ACLs this way.
+ if this check is removed, it should be replaced by a setting, since
+ some admins may still have configured Dovecot using dovecot-acl
+ files directly that they don't want users to update. and in any case
+ ACL_STORAGE_RIGHT_ADMIN must be checked then. */
+ if (!t->box->storage->user->dsyncing) {
+ mail_storage_set_error(t->box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ return -1;
+ }
+
+ if (mailbox_attribute_value_to_string(t->box->storage, value,
+ &value_str) < 0)
+ return -1;
+
+ i_zero(&update);
+ update.modify_mode = ACL_MODIFY_MODE_REPLACE;
+ update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE;
+ update.last_change = value->last_change;
+ id = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL);
+ rights = value_str == NULL ? NULL : t_strsplit(value_str, " ");
+ if (acl_rights_update_import(&update, id, rights, &error) < 0) {
+ mail_storage_set_error(t->box->storage, MAIL_ERROR_PARAMS, error);
+ return -1;
+ }
+ /* FIXME: this should actually be done only at commit().. */
+ return acl_mailbox_update_acl(t, &update);
+}
+
+static int acl_attribute_get_acl(struct mailbox *box, const char *key,
+ struct mail_attribute_value *value_r)
+{
+ struct acl_object *aclobj = acl_mailbox_get_aclobj(box);
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights, wanted_rights;
+ const char *id;
+ int ret = 0;
+
+ i_zero(value_r);
+
+ if (!box->storage->user->dsyncing) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ return -1;
+ }
+ /* set last_change for all ACL objects, even if they don't exist
+ (because they could have been removed by the last change, and dsync
+ can use this information) */
+ (void)acl_object_last_changed(aclobj, &value_r->last_change);
+
+ i_zero(&wanted_rights);
+ id = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL);
+ if (acl_identifier_parse(id, &wanted_rights) < 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS,
+ t_strdup_printf("Invalid ID: %s", id));
+ return -1;
+ }
+
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ if (!rights.global &&
+ rights.id_type == wanted_rights.id_type &&
+ null_strcmp(rights.identifier, wanted_rights.identifier) == 0) {
+ value_r->value = acl_rights_export(&rights);
+ ret = 1;
+ break;
+ }
+ }
+ /* the return value here cannot be used, because this function
+ needs to return whether it actually matched something
+ or not */
+ if (acl_object_list_deinit(&iter) < 0) {
+ mail_storage_set_internal_error(box->storage);
+ ret = -1;
+ }
+ return ret;
+}
+
+static int acl_have_attribute_rights(struct mailbox *box)
+{
+ int ret;
+
+ if (box->deleting) {
+ /* deleting attributes during mailbox deletion */
+ return 1;
+ }
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP);
+ if (ret <= 0) {
+ if (ret < 0)
+ return -1;
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname));
+ return -1;
+ }
+
+ return acl_mailbox_have_extra_attribute_rights(box) ? 0 : -1;
+}
+
+int acl_attribute_set(struct mailbox_transaction_context *t,
+ enum mail_attribute_type type, const char *key,
+ const struct mail_attribute_value *value)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(t->box);
+
+ if (acl_have_attribute_rights(t->box) < 0)
+ return -1;
+ if (str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_ACL))
+ return acl_attribute_update_acl(t, key, value);
+ return abox->module_ctx.super.attribute_set(t, type, key, value);
+}
+
+int acl_attribute_get(struct mailbox *box,
+ enum mail_attribute_type type, const char *key,
+ struct mail_attribute_value *value_r)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ if (acl_have_attribute_rights(box) < 0)
+ return -1;
+ if (str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_ACL))
+ return acl_attribute_get_acl(box, key, value_r);
+ return abox->module_ctx.super.attribute_get(box, type, key, value_r);
+}
+
+struct mailbox_attribute_iter *
+acl_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type,
+ const char *prefix)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ struct acl_mailbox_attribute_iter *aiter;
+
+ aiter = i_new(struct acl_mailbox_attribute_iter, 1);
+ aiter->iter.box = box;
+ if (acl_have_attribute_rights(box) < 0)
+ aiter->failed = TRUE;
+ else {
+ aiter->super = abox->module_ctx.super.
+ attribute_iter_init(box, type, prefix);
+ if (box->storage->user->dsyncing &&
+ type == MAIL_ATTRIBUTE_TYPE_SHARED &&
+ str_begins(MAILBOX_ATTRIBUTE_PREFIX_ACL, prefix)) {
+ aiter->acl_iter = acl_object_list_init(abox->aclobj);
+ aiter->acl_name = str_new(default_pool, 128);
+ str_append(aiter->acl_name, MAILBOX_ATTRIBUTE_PREFIX_ACL);
+ }
+ }
+ return &aiter->iter;
+}
+
+static const char *
+acl_attribute_iter_next_acl(struct acl_mailbox_attribute_iter *aiter)
+{
+ struct acl_rights rights;
+
+ if (aiter->failed)
+ return NULL;
+
+ while (acl_object_list_next(aiter->acl_iter, &rights)) {
+ if (rights.global)
+ continue;
+ str_truncate(aiter->acl_name, strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL));
+ acl_rights_write_id(aiter->acl_name, &rights);
+ return str_c(aiter->acl_name);
+ }
+ if (acl_object_list_deinit(&aiter->acl_iter) < 0) {
+ mail_storage_set_internal_error(aiter->iter.box->storage);
+ aiter->failed = TRUE;
+ }
+ return NULL;
+}
+
+const char *acl_attribute_iter_next(struct mailbox_attribute_iter *iter)
+{
+ struct acl_mailbox_attribute_iter *aiter =
+ (struct acl_mailbox_attribute_iter *)iter;
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(iter->box);
+ const char *key;
+
+ if (aiter->super == NULL)
+ return NULL;
+ if (aiter->acl_iter != NULL) {
+ if ((key = acl_attribute_iter_next_acl(aiter)) != NULL)
+ return key;
+ }
+ return abox->module_ctx.super.attribute_iter_next(aiter->super);
+}
+
+int acl_attribute_iter_deinit(struct mailbox_attribute_iter *iter)
+{
+ struct acl_mailbox_attribute_iter *aiter =
+ (struct acl_mailbox_attribute_iter *)iter;
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(iter->box);
+ int ret = aiter->failed ? -1 : 0;
+
+ if (aiter->super != NULL) {
+ if (abox->module_ctx.super.attribute_iter_deinit(aiter->super) < 0)
+ ret = -1;
+ }
+ if (aiter->acl_iter != NULL && acl_object_list_deinit(&aiter->acl_iter) < 0) {
+ mail_storage_set_internal_error(aiter->iter.box->storage);
+ ret = -1;
+ }
+ str_free(&aiter->acl_name);
+ i_free(aiter);
+ return ret;
+}
diff --git a/src/plugins/acl/acl-backend-vfile-acllist.c b/src/plugins/acl/acl-backend-vfile-acllist.c
new file mode 100644
index 0000000..c6029a2
--- /dev/null
+++ b/src/plugins/acl/acl-backend-vfile-acllist.c
@@ -0,0 +1,424 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "safe-mkstemp.h"
+#include "istream.h"
+#include "ostream.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "acl-plugin.h"
+#include "acl-cache.h"
+#include "acl-lookup-dict.h"
+#include "acl-backend-vfile.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+struct acl_mailbox_list_context_vfile {
+ struct acl_mailbox_list_context ctx;
+
+ unsigned int idx;
+};
+
+static void
+acllist_clear(struct acl_backend_vfile *backend, uoff_t file_size)
+{
+ if (backend->acllist_pool == NULL) {
+ backend->acllist_pool =
+ pool_alloconly_create("vfile acllist",
+ I_MAX(file_size / 2, 128));
+ i_array_init(&backend->acllist, I_MAX(16, file_size / 60));
+ } else {
+ p_clear(backend->acllist_pool);
+ array_clear(&backend->acllist);
+ }
+}
+
+static bool acl_list_get_root_dir(struct acl_backend_vfile *backend,
+ const char **root_dir_r,
+ enum mailbox_list_path_type *type_r)
+{
+ struct mail_storage *storage;
+ const char *rootdir, *maildir;
+ enum mailbox_list_path_type type;
+
+ if (backend->backend.globals_only)
+ return FALSE;
+
+ storage = mailbox_list_get_namespace(backend->backend.list)->storage;
+ type = mail_storage_get_acl_list_path_type(storage);
+ if (!mailbox_list_get_root_path(backend->backend.list, type, &rootdir))
+ return FALSE;
+ *type_r = type;
+
+ if (type == MAILBOX_LIST_PATH_TYPE_DIR &&
+ mail_storage_is_mailbox_file(storage)) {
+ maildir = mailbox_list_get_root_forced(backend->backend.list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ if (strcmp(maildir, rootdir) == 0) {
+ /* dovecot-acl-list would show up as a mailbox if we
+ created it to root dir. since we don't really have
+ any other good alternatives, place it to control
+ dir */
+ rootdir = mailbox_list_get_root_forced(backend->backend.list,
+ MAILBOX_LIST_PATH_TYPE_CONTROL);
+ *type_r = MAILBOX_LIST_PATH_TYPE_CONTROL;
+ }
+ }
+ *root_dir_r = rootdir;
+ return TRUE;
+}
+
+static bool acl_list_get_path(struct acl_backend_vfile *backend,
+ const char **path_r)
+{
+ enum mailbox_list_path_type type;
+ const char *root_dir;
+
+ if (!acl_list_get_root_dir(backend, &root_dir, &type))
+ return FALSE;
+ *path_r = t_strconcat(root_dir, "/"ACLLIST_FILENAME, NULL);
+ return TRUE;
+}
+
+static int acl_backend_vfile_acllist_read(struct acl_backend_vfile *backend)
+{
+ struct acl_backend_vfile_acllist acllist;
+ struct istream *input;
+ struct stat st;
+ const char *path, *line, *p;
+ int fd, ret = 0;
+
+ backend->acllist_last_check = ioloop_time;
+
+ if (!acl_list_get_path(backend, &path)) {
+ /* we're never going to build acllist for this namespace. */
+ acllist_clear(backend, 0);
+ return 0;
+ }
+
+ if (backend->acllist_mtime != 0) {
+ /* see if the file's mtime has changed */
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT)
+ backend->acllist_mtime = 0;
+ else
+ i_error("stat(%s) failed: %m", path);
+ return -1;
+ }
+ if (st.st_mtime == backend->acllist_mtime)
+ return 0;
+ }
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ if (errno == ENOENT) {
+ backend->acllist_mtime = 0;
+ return -1;
+ }
+ i_error("open(%s) failed: %m", path);
+ return -1;
+ }
+ if (fstat(fd, &st) < 0) {
+ i_error("fstat(%s) failed: %m", path);
+ i_close_fd(&fd);
+ return -1;
+ }
+ backend->acllist_mtime = st.st_mtime;
+ acllist_clear(backend, st.st_size);
+
+ input = i_stream_create_fd(fd, SIZE_MAX);
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ acllist.mtime = 0;
+ for (p = line; *p >= '0' && *p <= '9'; p++)
+ acllist.mtime = acllist.mtime * 10 + (*p - '0');
+
+ if (p == line || *p != ' ' || p[1] == '\0') {
+ i_error("Broken acllist file: %s", path);
+ i_unlink_if_exists(path);
+ i_close_fd(&fd);
+ return -1;
+ }
+ acllist.name = p_strdup(backend->acllist_pool, p + 1);
+ array_push_back(&backend->acllist, &acllist);
+ }
+ if (input->stream_errno != 0)
+ ret = -1;
+ i_stream_destroy(&input);
+
+ if (close(fd) < 0)
+ i_error("close(%s) failed: %m", path);
+ return ret;
+}
+
+void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend)
+{
+ i_assert(!backend->iterating_acllist);
+
+ if (backend->acllist_last_check +
+ (time_t)backend->cache_secs > ioloop_time)
+ return;
+
+ if (acl_backend_vfile_acllist_read(backend) < 0) {
+ acllist_clear(backend, 0);
+ if (!backend->rebuilding_acllist)
+ (void)acl_backend_vfile_acllist_rebuild(backend);
+ }
+}
+
+static int
+acllist_append(struct acl_backend_vfile *backend, struct ostream *output,
+ const char *vname)
+{
+ struct acl_object *aclobj;
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+ struct acl_backend_vfile_acllist acllist;
+ const char *name;
+ int ret;
+
+ name = mailbox_list_get_storage_name(backend->backend.list, vname);
+ acl_cache_flush(backend->backend.cache, name);
+ aclobj = acl_object_init_from_name(&backend->backend, name);
+
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ if (acl_rights_has_nonowner_lookup_changes(&rights))
+ break;
+ }
+ ret = acl_object_list_deinit(&iter);
+
+ if (acl_backend_vfile_object_get_mtime(aclobj, &acllist.mtime) < 0)
+ ret = -1;
+
+ if (ret > 0) {
+ acllist.name = p_strdup(backend->acllist_pool, name);
+ array_push_back(&backend->acllist, &acllist);
+
+ o_stream_nsend_str(output, t_strdup_printf(
+ "%s %s\n", dec2str(acllist.mtime), name));
+ }
+ acl_object_deinit(&aclobj);
+ return ret < 0 ? -1 : 0;
+}
+
+static int
+acl_backend_vfile_acllist_try_rebuild(struct acl_backend_vfile *backend)
+{
+ struct mailbox_list *list = backend->backend.list;
+ struct mail_namespace *ns;
+ struct mailbox_list_iterate_context *iter;
+ enum mailbox_list_path_type type;
+ const struct mailbox_info *info;
+ const char *rootdir, *acllist_path;
+ struct ostream *output;
+ struct stat st;
+ struct mailbox_permissions perm;
+ string_t *path;
+ int fd, ret;
+
+ i_assert(!backend->rebuilding_acllist);
+
+ if (!acl_list_get_root_dir(backend, &rootdir, &type))
+ return 0;
+
+ ns = mailbox_list_get_namespace(list);
+ if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) {
+ /* we can't write anything here */
+ return 0;
+ }
+
+ path = t_str_new(256);
+ str_printfa(path, "%s/%s", rootdir, mailbox_list_get_temp_prefix(list));
+
+ /* Build it into a temporary file and rename() over. There's no need
+ to use locking, because even if multiple processes are rebuilding
+ the file at the same time the result should be the same. */
+ mailbox_list_get_root_permissions(list, &perm);
+ fd = safe_mkstemp_group(path, perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin);
+ if (fd == -1 && errno == ENOENT) {
+ if (mailbox_list_mkdir_root(backend->backend.list,
+ rootdir, type) < 0)
+ return -1;
+ fd = safe_mkstemp_group(path, perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin);
+ }
+ if (fd == -1) {
+ if (errno == EACCES) {
+ /* Ignore silently if we can't create it */
+ return 0;
+ }
+ i_error("dovecot-acl-list creation failed: "
+ "safe_mkstemp(%s) failed: %m", str_c(path));
+ return -1;
+ }
+ output = o_stream_create_fd_file(fd, 0, FALSE);
+ o_stream_cork(output);
+
+ ret = 0;
+ acllist_clear(backend, 0);
+
+ backend->rebuilding_acllist = TRUE;
+ iter = mailbox_list_iter_init(list, "*",
+ MAILBOX_LIST_ITER_RAW_LIST |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ while (ret == 0 && (info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
+ ret = acllist_append(backend, output, info->vname);
+ } T_END;
+
+ if (o_stream_finish(output) < 0) {
+ i_error("write(%s) failed: %s", str_c(path),
+ o_stream_get_error(output));
+ ret = -1;
+ }
+ if (mailbox_list_iter_deinit(&iter) < 0)
+ ret = -1;
+ o_stream_destroy(&output);
+
+ if (ret == 0) {
+ if (fstat(fd, &st) < 0) {
+ i_error("fstat(%s) failed: %m", str_c(path));
+ ret = -1;
+ }
+ }
+ if (close(fd) < 0) {
+ i_error("close(%s) failed: %m", str_c(path));
+ ret = -1;
+ }
+
+ if (ret == 0) {
+ if (!acl_list_get_path(backend, &acllist_path))
+ i_unreached();
+ if (rename(str_c(path), acllist_path) < 0) {
+ i_error("rename(%s, %s) failed: %m",
+ str_c(path), acllist_path);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ struct acl_user *auser = ACL_USER_CONTEXT(ns->user);
+ i_assert(auser != NULL);
+ backend->acllist_mtime = st.st_mtime;
+ backend->acllist_last_check = ioloop_time;
+ /* FIXME: dict rebuild is expensive, try to avoid it */
+ (void)acl_lookup_dict_rebuild(auser->acl_lookup_dict);
+ } else {
+ acllist_clear(backend, 0);
+ i_unlink_if_exists(str_c(path));
+ }
+ backend->rebuilding_acllist = FALSE;
+ return ret;
+}
+
+int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend)
+{
+ const char *acllist_path;
+
+ if (acl_backend_vfile_acllist_try_rebuild(backend) == 0)
+ return 0;
+ else {
+ /* delete it to make sure it gets rebuilt later */
+ if (!acl_list_get_path(backend, &acllist_path))
+ i_unreached();
+ i_unlink_if_exists(acllist_path);
+ return -1;
+ }
+}
+
+static const struct acl_backend_vfile_acllist *
+acl_backend_vfile_acllist_find(struct acl_backend_vfile *backend,
+ const char *name)
+{
+ const struct acl_backend_vfile_acllist *acllist;
+
+ array_foreach(&backend->acllist, acllist) {
+ if (strcmp(acllist->name, name) == 0)
+ return acllist;
+ }
+ return NULL;
+}
+
+void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend,
+ const char *name, time_t mtime)
+{
+ const struct acl_backend_vfile_acllist *acllist;
+
+ if (backend->rebuilding_acllist || backend->iterating_acllist)
+ return;
+
+ acl_backend_vfile_acllist_refresh(backend);
+ acllist = acl_backend_vfile_acllist_find(backend, name);
+ if (acllist != NULL && acllist->mtime != mtime)
+ (void)acl_backend_vfile_acllist_rebuild(backend);
+}
+
+struct acl_mailbox_list_context *
+acl_backend_vfile_nonowner_iter_init(struct acl_backend *_backend)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_backend;
+ struct acl_mailbox_list_context_vfile *ctx;
+
+ acl_backend_vfile_acllist_refresh(backend);
+
+ ctx = i_new(struct acl_mailbox_list_context_vfile, 1);
+ ctx->ctx.backend = _backend;
+ backend->iterating_acllist = TRUE;
+ return &ctx->ctx;
+}
+
+bool acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *_ctx,
+ const char **name_r)
+{
+ struct acl_mailbox_list_context_vfile *ctx =
+ (struct acl_mailbox_list_context_vfile *)_ctx;
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_ctx->backend;
+ const struct acl_backend_vfile_acllist *acllist;
+ unsigned int count;
+
+ if (_ctx->failed)
+ return FALSE;
+
+ acllist = array_get(&backend->acllist, &count);
+ if (count == 0)
+ _ctx->empty = TRUE;
+ if (ctx->idx == count)
+ return FALSE;
+
+ *name_r = acllist[ctx->idx++].name;
+ return TRUE;
+}
+
+int
+acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)ctx->backend;
+ int ret;
+
+ backend->iterating_acllist = FALSE;
+ if (ctx->failed)
+ ret = -1;
+ else if (ctx->empty)
+ ret = 0;
+ else
+ ret = 1;
+ i_free(ctx);
+ return ret;
+}
+
+int acl_backend_vfile_nonowner_lookups_rebuild(struct acl_backend *_backend)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_backend;
+
+ return acl_backend_vfile_acllist_rebuild(backend);
+}
diff --git a/src/plugins/acl/acl-backend-vfile-update.c b/src/plugins/acl/acl-backend-vfile-update.c
new file mode 100644
index 0000000..7c48c4e
--- /dev/null
+++ b/src/plugins/acl/acl-backend-vfile-update.c
@@ -0,0 +1,260 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "bsearch-insert-pos.h"
+#include "ioloop.h"
+#include "str.h"
+#include "strescape.h"
+#include "file-dotlock.h"
+#include "ostream.h"
+#include "mail-storage.h"
+#include "acl-cache.h"
+#include "acl-backend-vfile.h"
+
+#include <utime.h>
+#include <sys/stat.h>
+
+static struct dotlock_settings dotlock_set = {
+ .timeout = 30,
+ .stale_timeout = 120
+};
+
+static int acl_backend_vfile_update_begin(struct acl_object_vfile *aclobj,
+ struct dotlock **dotlock_r)
+{
+ struct acl_object *_aclobj = &aclobj->aclobj;
+ struct mailbox_permissions perm;
+ int fd;
+
+ if (aclobj->local_path == NULL) {
+ i_error("Can't update acl object '%s': No local acl file path",
+ aclobj->aclobj.name);
+ return -1;
+ }
+
+ /* first lock the ACL file */
+ mailbox_list_get_permissions(_aclobj->backend->list,
+ _aclobj->name, &perm);
+ fd = file_dotlock_open_group(&dotlock_set, aclobj->local_path, 0,
+ perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin, dotlock_r);
+ if (fd == -1) {
+ i_error("file_dotlock_open(%s) failed: %m", aclobj->local_path);
+ return -1;
+ }
+
+ /* locked successfully, re-read the existing file to make sure we
+ don't lose any changes. */
+ acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+ if (_aclobj->backend->v.object_refresh_cache(_aclobj) < 0) {
+ file_dotlock_delete(dotlock_r);
+ return -1;
+ }
+ return fd;
+}
+
+static bool
+vfile_object_modify_right(struct acl_object *aclobj, unsigned int idx,
+ const struct acl_rights_update *update)
+{
+ struct acl_rights *right;
+ bool c1, c2;
+
+ right = array_idx_modifiable(&aclobj->rights, idx);
+ c1 = acl_right_names_modify(aclobj->rights_pool, &right->rights,
+ update->rights.rights, update->modify_mode);
+ c2 = acl_right_names_modify(aclobj->rights_pool, &right->neg_rights,
+ update->rights.neg_rights,
+ update->neg_modify_mode);
+
+ if (right->rights == NULL && right->neg_rights == NULL) {
+ /* this identifier no longer exists */
+ array_delete(&aclobj->rights, idx, 1);
+ c1 = TRUE;
+ }
+ return c1 || c2;
+}
+
+static bool
+vfile_object_add_right(struct acl_object *aclobj, unsigned int idx,
+ const struct acl_rights_update *update)
+{
+ struct acl_rights right;
+ bool c1, c2;
+
+ if (update->modify_mode == ACL_MODIFY_MODE_REMOVE &&
+ update->neg_modify_mode == ACL_MODIFY_MODE_REMOVE) {
+ /* nothing to do */
+ return FALSE;
+ }
+
+ i_zero(&right);
+ right.id_type = update->rights.id_type;
+ right.identifier = p_strdup(aclobj->rights_pool,
+ update->rights.identifier);
+
+ c1 = acl_right_names_modify(aclobj->rights_pool, &right.rights,
+ update->rights.rights, update->modify_mode);
+ c2 = acl_right_names_modify(aclobj->rights_pool, &right.neg_rights,
+ update->rights.neg_rights,
+ update->neg_modify_mode);
+ if (c1 || c2) {
+ array_insert(&aclobj->rights, idx, &right, 1);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+vfile_write_right(string_t *dest, const struct acl_rights *right,
+ bool neg)
+{
+ const char *const *rights = neg ? right->neg_rights : right->rights;
+
+ if (neg) str_append_c(dest,'-');
+ acl_rights_write_id(dest, right);
+
+ if (strchr(str_c(dest), ' ') != NULL) T_BEGIN {
+ /* need to escape it */
+ const char *escaped = t_strdup(str_escape(str_c(dest)));
+ str_truncate(dest, 0);
+ str_printfa(dest, "\"%s\"", escaped);
+ } T_END;
+
+ str_append_c(dest, ' ');
+ acl_right_names_write(dest, rights);
+ str_append_c(dest, '\n');
+}
+
+static int
+acl_backend_vfile_update_write(struct acl_object *aclobj,
+ int fd, const char *path)
+{
+ struct ostream *output;
+ string_t *str;
+ const struct acl_rights *rights;
+ unsigned int i, count;
+ int ret = 0;
+
+ output = o_stream_create_fd_file(fd, 0, FALSE);
+ o_stream_cork(output);
+
+ str = str_new(default_pool, 256);
+ /* rights are sorted with globals at the end, so we can stop at the
+ first global */
+ rights = array_get(&aclobj->rights, &count);
+ for (i = 0; i < count && !rights[i].global; i++) {
+ if (rights[i].rights != NULL) {
+ vfile_write_right(str, &rights[i], FALSE);
+ o_stream_nsend(output, str_data(str), str_len(str));
+ str_truncate(str, 0);
+ }
+ if (rights[i].neg_rights != NULL) {
+ vfile_write_right(str, &rights[i], TRUE);
+ o_stream_nsend(output, str_data(str), str_len(str));
+ str_truncate(str, 0);
+ }
+ }
+ str_free(&str);
+ if (o_stream_finish(output) < 0) {
+ i_error("write(%s) failed: %s", path,
+ o_stream_get_error(output));
+ ret = -1;
+ }
+ o_stream_destroy(&output);
+ /* we really don't want to lose ACL files' contents, so fsync() always
+ before renaming */
+ if (fsync(fd) < 0) {
+ i_error("fsync(%s) failed: %m", path);
+ ret = -1;
+ }
+ return ret;
+}
+
+static void acl_backend_vfile_update_cache(struct acl_object *_aclobj, int fd)
+{
+ struct acl_backend_vfile_validity *validity;
+ struct stat st;
+
+ if (fstat(fd, &st) < 0) {
+ /* we'll just recalculate or fail it later */
+ acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+ return;
+ }
+
+ validity = acl_cache_get_validity(_aclobj->backend->cache,
+ _aclobj->name);
+ validity->local_validity.last_read_time = ioloop_time;
+ validity->local_validity.last_mtime = st.st_mtime;
+ validity->local_validity.last_size = st.st_size;
+}
+
+int acl_backend_vfile_object_update(struct acl_object *_aclobj,
+ const struct acl_rights_update *update)
+{
+ struct acl_object_vfile *aclobj =
+ (struct acl_object_vfile *)_aclobj;
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_aclobj->backend;
+ struct acl_backend_vfile_validity *validity;
+ struct dotlock *dotlock;
+ struct utimbuf ut;
+ time_t orig_mtime;
+ const char *path;
+ unsigned int i;
+ int fd;
+ bool changed;
+
+ /* global ACLs can't be updated here */
+ i_assert(!update->rights.global);
+
+ fd = acl_backend_vfile_update_begin(aclobj, &dotlock);
+ if (fd == -1)
+ return -1;
+
+ if (!array_bsearch_insert_pos(&_aclobj->rights, &update->rights,
+ acl_rights_cmp, &i))
+ changed = vfile_object_add_right(_aclobj, i, update);
+ else
+ changed = vfile_object_modify_right(_aclobj, i, update);
+ if (!changed) {
+ file_dotlock_delete(&dotlock);
+ return 0;
+ }
+
+ validity = acl_cache_get_validity(_aclobj->backend->cache,
+ _aclobj->name);
+ orig_mtime = validity->local_validity.last_mtime;
+
+ /* ACLs were really changed, write the new ones */
+ path = file_dotlock_get_lock_path(dotlock);
+ if (acl_backend_vfile_update_write(_aclobj, fd, path) < 0) {
+ file_dotlock_delete(&dotlock);
+ acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+ return -1;
+ }
+ if (orig_mtime < update->last_change && update->last_change != 0) {
+ /* set mtime to last_change, if it's higher than the file's
+ original mtime. if original mtime is higher, then we're
+ merging some changes and it's better for the mtime to get
+ updated. */
+ ut.actime = ioloop_time;
+ ut.modtime = update->last_change;
+ if (utime(path, &ut) < 0)
+ i_error("utime(%s) failed: %m", path);
+ }
+ acl_backend_vfile_update_cache(_aclobj, fd);
+ if (file_dotlock_replace(&dotlock, 0) < 0) {
+ acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+ return -1;
+ }
+ /* make sure dovecot-acl-list gets updated if we changed any
+ lookup rights. */
+ if (acl_rights_has_nonowner_lookup_changes(&update->rights) ||
+ update->modify_mode == ACL_MODIFY_MODE_REPLACE ||
+ update->modify_mode == ACL_MODIFY_MODE_CLEAR)
+ (void)acl_backend_vfile_acllist_rebuild(backend);
+ return 0;
+}
diff --git a/src/plugins/acl/acl-backend-vfile.c b/src/plugins/acl/acl-backend-vfile.c
new file mode 100644
index 0000000..3ee0af9
--- /dev/null
+++ b/src/plugins/acl/acl-backend-vfile.c
@@ -0,0 +1,659 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "istream.h"
+#include "nfs-workarounds.h"
+#include "mail-storage-private.h"
+#include "acl-global-file.h"
+#include "acl-cache.h"
+#include "acl-backend-vfile.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define ACL_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT
+#define ACL_VFILE_DEFAULT_CACHE_SECS 30
+
+static struct acl_backend *acl_backend_vfile_alloc(void)
+{
+ struct acl_backend_vfile *backend;
+ pool_t pool;
+
+ pool = pool_alloconly_create("ACL backend", 512);
+ backend = p_new(pool, struct acl_backend_vfile, 1);
+ backend->backend.pool = pool;
+ return &backend->backend;
+}
+
+static int
+acl_backend_vfile_init(struct acl_backend *_backend, const char *data)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_backend;
+ struct stat st;
+ const char *const *tmp;
+
+ tmp = t_strsplit(data, ":");
+ backend->global_path = p_strdup_empty(_backend->pool, *tmp);
+ backend->cache_secs = ACL_VFILE_DEFAULT_CACHE_SECS;
+
+ if (*tmp != NULL)
+ tmp++;
+ for (; *tmp != NULL; tmp++) {
+ if (str_begins(*tmp, "cache_secs=")) {
+ if (str_to_uint(*tmp + 11, &backend->cache_secs) < 0) {
+ i_error("acl vfile: Invalid cache_secs value: %s",
+ *tmp + 11);
+ return -1;
+ }
+ } else {
+ i_error("acl vfile: Unknown parameter: %s", *tmp);
+ return -1;
+ }
+ }
+ if (backend->global_path != NULL) {
+ if (stat(backend->global_path, &st) < 0) {
+ if (errno != ENOENT) {
+ i_error("acl vfile: stat(%s) failed: %m",
+ backend->global_path);
+ return -1;
+ }
+ } else if (!S_ISDIR(st.st_mode)) {
+ _backend->global_file =
+ acl_global_file_init(backend->global_path, backend->cache_secs,
+ _backend->debug);
+ }
+ }
+ if (_backend->debug) {
+ if (backend->global_path == NULL)
+ i_debug("acl vfile: Global ACLs disabled");
+ else if (_backend->global_file != NULL) {
+ i_debug("acl vfile: Global ACL file: %s",
+ backend->global_path);
+ } else {
+ i_debug("acl vfile: Global ACL legacy directory: %s",
+ backend->global_path);
+ }
+ }
+
+ _backend->cache =
+ acl_cache_init(_backend,
+ sizeof(struct acl_backend_vfile_validity));
+ return 0;
+}
+
+static void acl_backend_vfile_deinit(struct acl_backend *_backend)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_backend;
+
+ if (backend->acllist_pool != NULL) {
+ array_free(&backend->acllist);
+ pool_unref(&backend->acllist_pool);
+ }
+ if (_backend->global_file != NULL)
+ acl_global_file_deinit(&_backend->global_file);
+ pool_unref(&backend->backend.pool);
+}
+
+static const char *
+acl_backend_vfile_get_local_dir(struct acl_backend *backend,
+ const char *name, const char *vname)
+{
+ struct mail_namespace *ns = mailbox_list_get_namespace(backend->list);
+ struct mailbox_list *list = ns->list;
+ struct mail_storage *storage;
+ enum mailbox_list_path_type type;
+ const char *dir, *inbox;
+
+ if (*name == '\0')
+ name = NULL;
+
+ if (backend->globals_only)
+ return NULL;
+
+ /* ACL files are very important. try to keep them among the main
+ mail files. that's not possible though with a) if the mailbox is
+ a file or b) if the mailbox path doesn't point to filesystem. */
+ if (mailbox_list_get_storage(&list, vname, &storage) < 0)
+ return NULL;
+ i_assert(list == ns->list);
+
+ type = mail_storage_get_acl_list_path_type(storage);
+ if (name == NULL) {
+ if (!mailbox_list_get_root_path(list, type, &dir))
+ return NULL;
+ } else {
+ if (mailbox_list_get_path(list, name, type, &dir) <= 0)
+ return NULL;
+ }
+
+ /* verify that the directory isn't same as INBOX's directory.
+ this is mainly for Maildir. */
+ if (name == NULL &&
+ mailbox_list_get_path(list, "INBOX",
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &inbox) > 0 &&
+ strcmp(inbox, dir) == 0) {
+ /* can't have default ACLs with this setup */
+ return NULL;
+ }
+ return dir;
+}
+
+static struct acl_object *
+acl_backend_vfile_object_init(struct acl_backend *_backend,
+ const char *name)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_backend;
+ struct acl_object_vfile *aclobj;
+ const char *dir, *vname, *error;
+
+ aclobj = i_new(struct acl_object_vfile, 1);
+ aclobj->aclobj.backend = _backend;
+ aclobj->aclobj.name = i_strdup(name);
+
+ T_BEGIN {
+ if (*name == '\0' ||
+ mailbox_list_is_valid_name(_backend->list, name, &error)) {
+ vname = *name == '\0' ? "" :
+ mailbox_list_get_vname(_backend->list, name);
+
+ dir = acl_backend_vfile_get_local_dir(_backend, name, vname);
+ aclobj->local_path = dir == NULL ? NULL :
+ i_strconcat(dir, "/"ACL_FILENAME, NULL);
+ if (backend->global_path != NULL &&
+ _backend->global_file == NULL) {
+ aclobj->global_path =
+ i_strconcat(backend->global_path, "/", vname, NULL);
+ }
+ } else {
+ /* Invalid mailbox name, just use the default
+ global ACL files */
+ }
+ } T_END;
+ return &aclobj->aclobj;
+}
+
+static const char *
+get_parent_mailbox(struct acl_backend *backend, const char *name)
+{
+ const char *p;
+
+ p = strrchr(name, mailbox_list_get_hierarchy_sep(backend->list));
+ return p == NULL ? NULL : t_strdup_until(name, p);
+}
+
+static int
+acl_backend_vfile_exists(struct acl_backend_vfile *backend, const char *path,
+ struct acl_vfile_validity *validity)
+{
+ struct stat st;
+
+ if (validity->last_check + (time_t)backend->cache_secs > ioloop_time) {
+ /* use the cached value */
+ return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOTFOUND ? 1 : 0;
+ }
+
+ validity->last_check = ioloop_time;
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR) {
+ validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOTFOUND;
+ return 0;
+ }
+ if (errno == EACCES) {
+ validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOACCESS;
+ return 1;
+ }
+ i_error("stat(%s) failed: %m", path);
+ return -1;
+ }
+ validity->last_mtime = st.st_mtime;
+ validity->last_size = st.st_size;
+ return 1;
+}
+
+static bool
+acl_backend_vfile_has_acl(struct acl_backend *_backend, const char *name)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_backend;
+ struct acl_backend_vfile_validity *old_validity, new_validity;
+ const char *global_path, *vname;
+ int ret;
+
+ old_validity = acl_cache_get_validity(_backend->cache, name);
+ if (old_validity != NULL)
+ new_validity = *old_validity;
+ else
+ i_zero(&new_validity);
+
+ /* The caller wants to stop whenever a parent mailbox exists, even if
+ it has no ACL file. Also, if a mailbox doesn't exist then it can't
+ have a local ACL file. First check if there's a matching global ACL.
+ If not, check if the mailbox exists. */
+ vname = *name == '\0' ? "" :
+ mailbox_list_get_vname(_backend->list, name);
+ struct mailbox *box =
+ mailbox_alloc(_backend->list, vname,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
+ if (backend->global_path == NULL) {
+ /* global ACLs disabled */
+ ret = 0;
+ } else if (_backend->global_file != NULL) {
+ /* check global ACL file */
+ ret = acl_global_file_refresh(_backend->global_file);
+ if (ret == 0 && acl_global_file_have_any(_backend->global_file, box->vname))
+ ret = 1;
+ } else {
+ /* check global ACL directory */
+ global_path = t_strconcat(backend->global_path, "/", name, NULL);
+ ret = acl_backend_vfile_exists(backend, global_path,
+ &new_validity.global_validity);
+ }
+
+ if (ret != 0) {
+ /* error / global ACL found */
+ } else if (mailbox_open(box) == 0) {
+ /* mailbox exists */
+ ret = 1;
+ } else {
+ enum mail_error error;
+ const char *errstr =
+ mailbox_get_last_internal_error(box, &error);
+ if (error == MAIL_ERROR_NOTFOUND)
+ ret = 0;
+ else {
+ e_error(box->event, "acl: Failed to open mailbox: %s",
+ errstr);
+ ret = -1;
+ }
+ }
+
+ acl_cache_set_validity(_backend->cache, name, &new_validity);
+ mailbox_free(&box);
+ return ret > 0;
+}
+
+static struct acl_object *
+acl_backend_vfile_object_init_parent(struct acl_backend *backend,
+ const char *child_name)
+{
+ const char *parent;
+
+ /* stop at the first parent that
+ a) has global ACL file
+ b) has local ACL file
+ c) exists */
+ while ((parent = get_parent_mailbox(backend, child_name)) != NULL) {
+ if (acl_backend_vfile_has_acl(backend, parent))
+ break;
+ child_name = parent;
+ }
+ if (parent == NULL) {
+ /* use the root */
+ parent = acl_backend_get_default_object(backend)->name;
+ }
+ return acl_backend_vfile_object_init(backend, parent);
+}
+
+static void acl_backend_vfile_object_deinit(struct acl_object *_aclobj)
+{
+ struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj;
+
+ i_free(aclobj->local_path);
+ i_free(aclobj->global_path);
+
+ if (array_is_created(&aclobj->aclobj.rights))
+ array_free(&aclobj->aclobj.rights);
+ pool_unref(&aclobj->aclobj.rights_pool);
+ i_free(aclobj->aclobj.name);
+ i_free(aclobj);
+}
+
+static int
+acl_backend_vfile_read(struct acl_object *aclobj, bool global, const char *path,
+ struct acl_vfile_validity *validity, bool try_retry,
+ bool *is_dir_r)
+{
+ struct istream *input;
+ struct stat st;
+ struct acl_rights rights;
+ const char *line, *error;
+ unsigned int linenum;
+ int fd, ret = 0;
+
+ *is_dir_r = FALSE;
+
+ fd = nfs_safe_open(path, O_RDONLY);
+ if (fd == -1) {
+ if (errno == ENOENT || errno == ENOTDIR) {
+ if (aclobj->backend->debug)
+ i_debug("acl vfile: file %s not found", path);
+ validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOTFOUND;
+ } else if (errno == EACCES) {
+ if (aclobj->backend->debug)
+ i_debug("acl vfile: no access to file %s",
+ path);
+
+ acl_object_remove_all_access(aclobj);
+ validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOACCESS;
+ } else {
+ i_error("open(%s) failed: %m", path);
+ return -1;
+ }
+
+ validity->last_size = 0;
+ validity->last_read_time = ioloop_time;
+ return 1;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ if (errno == ESTALE && try_retry) {
+ i_close_fd(&fd);
+ return 0;
+ }
+
+ i_error("fstat(%s) failed: %m", path);
+ i_close_fd(&fd);
+ return -1;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ /* we opened a directory. */
+ *is_dir_r = TRUE;
+ i_close_fd(&fd);
+ return 0;
+ }
+
+ if (aclobj->backend->debug)
+ i_debug("acl vfile: reading file %s", path);
+
+ input = i_stream_create_fd(fd, SIZE_MAX);
+ i_stream_set_return_partial_line(input, TRUE);
+ linenum = 0;
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ linenum++;
+ if (line[0] == '\0' || line[0] == '#')
+ continue;
+ T_BEGIN {
+ ret = acl_rights_parse_line(line, aclobj->rights_pool,
+ &rights, &error);
+ rights.global = global;
+ if (ret < 0) {
+ i_error("ACL file %s line %u: %s",
+ path, linenum, error);
+ } else {
+ array_push_back(&aclobj->rights, &rights);
+ }
+ } T_END;
+ if (ret < 0)
+ break;
+ }
+
+ if (ret < 0) {
+ /* parsing failure */
+ } else if (input->stream_errno != 0) {
+ if (input->stream_errno == ESTALE && try_retry)
+ ret = 0;
+ else {
+ ret = -1;
+ i_error("read(%s) failed: %s", path,
+ i_stream_get_error(input));
+ }
+ } else {
+ if (fstat(fd, &st) < 0) {
+ if (errno == ESTALE && try_retry)
+ ret = 0;
+ else {
+ ret = -1;
+ i_error("fstat(%s) failed: %m", path);
+ }
+ } else {
+ ret = 1;
+ validity->last_read_time = ioloop_time;
+ validity->last_mtime = st.st_mtime;
+ validity->last_size = st.st_size;
+ }
+ }
+
+ i_stream_unref(&input);
+ if (close(fd) < 0) {
+ if (errno == ESTALE && try_retry)
+ return 0;
+
+ i_error("close(%s) failed: %m", path);
+ return -1;
+ }
+ return ret;
+}
+
+static int
+acl_backend_vfile_read_with_retry(struct acl_object *aclobj,
+ bool global, const char *path,
+ struct acl_vfile_validity *validity)
+{
+ unsigned int i;
+ int ret;
+ bool is_dir;
+
+ if (path == NULL)
+ return 0;
+
+ for (i = 0;; i++) {
+ ret = acl_backend_vfile_read(aclobj, global, path, validity,
+ i < ACL_ESTALE_RETRY_COUNT,
+ &is_dir);
+ if (ret != 0)
+ break;
+
+ if (is_dir) {
+ /* opened a directory. use dir/.DEFAULT instead */
+ path = t_strconcat(path, "/.DEFAULT", NULL);
+ } else {
+ /* ESTALE - try again */
+ }
+ }
+
+ return ret <= 0 ? -1 : 0;
+}
+
+static bool
+acl_vfile_validity_has_changed(struct acl_backend_vfile *backend,
+ const struct acl_vfile_validity *validity,
+ const struct stat *st)
+{
+ if (st->st_mtime == validity->last_mtime &&
+ st->st_size == validity->last_size) {
+ /* same timestamp, but if it was modified within the
+ same second we want to refresh it again later (but
+ do it only after a couple of seconds so we don't
+ keep re-reading it all the time within those
+ seconds) */
+ time_t cache_secs = backend->cache_secs;
+
+ if (validity->last_read_time != 0 &&
+ (st->st_mtime < validity->last_read_time - cache_secs ||
+ ioloop_time - validity->last_read_time <= cache_secs))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+acl_backend_vfile_refresh(struct acl_object *aclobj, const char *path,
+ struct acl_vfile_validity *validity)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)aclobj->backend;
+ struct stat st;
+ int ret;
+
+ if (validity == NULL)
+ return 1;
+ if (path == NULL ||
+ validity->last_check + (time_t)backend->cache_secs > ioloop_time)
+ return 0;
+
+ validity->last_check = ioloop_time;
+ ret = stat(path, &st);
+ if (ret == 0 && S_ISDIR(st.st_mode)) {
+ /* it's a directory. use dir/.DEFAULT instead */
+ path = t_strconcat(path, "/.DEFAULT", NULL);
+ ret = stat(path, &st);
+ }
+
+ if (ret < 0) {
+ if (errno == ENOENT || errno == ENOTDIR) {
+ /* if the file used to exist, we have to re-read it */
+ return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOTFOUND ? 1 : 0;
+ }
+ if (errno == EACCES)
+ return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOACCESS ? 1 : 0;
+ i_error("stat(%s) failed: %m", path);
+ return -1;
+ }
+ return acl_vfile_validity_has_changed(backend, validity, &st) ? 1 : 0;
+}
+
+int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj,
+ time_t *mtime_r)
+{
+ struct acl_backend_vfile_validity *validity;
+
+ validity = acl_cache_get_validity(aclobj->backend->cache, aclobj->name);
+ if (validity == NULL)
+ return -1;
+
+ if (validity->local_validity.last_mtime != 0)
+ *mtime_r = validity->local_validity.last_mtime;
+ else if (validity->global_validity.last_mtime != 0)
+ *mtime_r = validity->global_validity.last_mtime;
+ else
+ *mtime_r = 0;
+ return 0;
+}
+
+static int
+acl_backend_global_file_refresh(struct acl_object *_aclobj,
+ struct acl_vfile_validity *validity)
+{
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_aclobj->backend;
+ struct stat st;
+
+ if (acl_global_file_refresh(_aclobj->backend->global_file) < 0)
+ return -1;
+
+ acl_global_file_last_stat(_aclobj->backend->global_file, &st);
+ if (validity == NULL)
+ return 1;
+ return acl_vfile_validity_has_changed(backend, validity, &st) ? 1 : 0;
+}
+
+static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj)
+{
+ struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj;
+ struct acl_backend_vfile *backend =
+ (struct acl_backend_vfile *)_aclobj->backend;
+ struct acl_backend_vfile_validity *old_validity;
+ struct acl_backend_vfile_validity validity;
+ time_t mtime;
+ int ret;
+
+ old_validity = acl_cache_get_validity(_aclobj->backend->cache,
+ _aclobj->name);
+ ret = _aclobj->backend->global_file != NULL ?
+ acl_backend_global_file_refresh(_aclobj, old_validity == NULL ? NULL :
+ &old_validity->global_validity) :
+ acl_backend_vfile_refresh(_aclobj, aclobj->global_path,
+ old_validity == NULL ? NULL :
+ &old_validity->global_validity);
+ if (ret == 0) {
+ ret = acl_backend_vfile_refresh(_aclobj, aclobj->local_path,
+ old_validity == NULL ? NULL :
+ &old_validity->local_validity);
+ }
+ if (ret <= 0)
+ return ret;
+
+ /* either global or local ACLs changed, need to re-read both */
+ if (!array_is_created(&_aclobj->rights)) {
+ _aclobj->rights_pool =
+ pool_alloconly_create("acl rights", 256);
+ i_array_init(&_aclobj->rights, 16);
+ } else {
+ array_clear(&_aclobj->rights);
+ p_clear(_aclobj->rights_pool);
+ }
+
+ i_zero(&validity);
+ if (_aclobj->backend->global_file != NULL) {
+ struct stat st;
+
+ acl_object_add_global_acls(_aclobj);
+ acl_global_file_last_stat(_aclobj->backend->global_file, &st);
+ validity.global_validity.last_read_time = ioloop_time;
+ validity.global_validity.last_mtime = st.st_mtime;
+ validity.global_validity.last_size = st.st_size;
+ } else {
+ if (acl_backend_vfile_read_with_retry(_aclobj, TRUE, aclobj->global_path,
+ &validity.global_validity) < 0)
+ return -1;
+ }
+ if (acl_backend_vfile_read_with_retry(_aclobj, FALSE, aclobj->local_path,
+ &validity.local_validity) < 0)
+ return -1;
+
+ acl_rights_sort(_aclobj);
+ /* update cache only after we've successfully read everything */
+ acl_object_rebuild_cache(_aclobj);
+ acl_cache_set_validity(_aclobj->backend->cache,
+ _aclobj->name, &validity);
+
+ if (acl_backend_vfile_object_get_mtime(_aclobj, &mtime) == 0)
+ acl_backend_vfile_acllist_verify(backend, _aclobj->name, mtime);
+ return 0;
+}
+
+static int acl_backend_vfile_object_last_changed(struct acl_object *_aclobj,
+ time_t *last_changed_r)
+{
+ struct acl_backend_vfile_validity *old_validity;
+
+ *last_changed_r = 0;
+
+ old_validity = acl_cache_get_validity(_aclobj->backend->cache,
+ _aclobj->name);
+ if (old_validity == NULL) {
+ if (acl_backend_vfile_object_refresh_cache(_aclobj) < 0)
+ return -1;
+ old_validity = acl_cache_get_validity(_aclobj->backend->cache,
+ _aclobj->name);
+ if (old_validity == NULL)
+ return 0;
+ }
+ *last_changed_r = old_validity->local_validity.last_mtime;
+ return 0;
+}
+
+struct acl_backend_vfuncs acl_backend_vfile = {
+ acl_backend_vfile_alloc,
+ acl_backend_vfile_init,
+ acl_backend_vfile_deinit,
+ acl_backend_vfile_nonowner_iter_init,
+ acl_backend_vfile_nonowner_iter_next,
+ acl_backend_vfile_nonowner_iter_deinit,
+ acl_backend_vfile_nonowner_lookups_rebuild,
+ acl_backend_vfile_object_init,
+ acl_backend_vfile_object_init_parent,
+ acl_backend_vfile_object_deinit,
+ acl_backend_vfile_object_refresh_cache,
+ acl_backend_vfile_object_update,
+ acl_backend_vfile_object_last_changed,
+ acl_default_object_list_init,
+ acl_default_object_list_next,
+ acl_default_object_list_deinit
+};
diff --git a/src/plugins/acl/acl-backend-vfile.h b/src/plugins/acl/acl-backend-vfile.h
new file mode 100644
index 0000000..c5aaa25
--- /dev/null
+++ b/src/plugins/acl/acl-backend-vfile.h
@@ -0,0 +1,88 @@
+#ifndef ACL_BACKEND_VFILE_H
+#define ACL_BACKEND_VFILE_H
+
+#include "acl-api-private.h"
+#include "mail-storage-private.h"
+
+#define ACL_FILENAME "dovecot-acl"
+#define ACLLIST_FILENAME "dovecot-acl-list"
+
+#define ACL_VFILE_VALIDITY_MTIME_NOTFOUND 0
+#define ACL_VFILE_VALIDITY_MTIME_NOACCESS -1
+
+struct acl_vfile_validity {
+ time_t last_check;
+
+ time_t last_read_time;
+ time_t last_mtime;
+ off_t last_size;
+};
+
+struct acl_backend_vfile_validity {
+ struct acl_vfile_validity global_validity, local_validity;
+};
+
+struct acl_object_vfile {
+ struct acl_object aclobj;
+
+ /* if backend->global_file is NULL, assume legacy separate global
+ ACL file per mailbox */
+ char *global_path, *local_path;
+};
+
+struct acl_backend_vfile_acllist {
+ time_t mtime;
+ const char *name;
+};
+
+struct acl_backend_vfile {
+ struct acl_backend backend;
+ const char *global_path;
+
+ pool_t acllist_pool;
+ ARRAY(struct acl_backend_vfile_acllist) acllist;
+
+ time_t acllist_last_check;
+ time_t acllist_mtime;
+ unsigned int acllist_change_counter;
+
+ unsigned int cache_secs;
+ bool rebuilding_acllist:1;
+ bool iterating_acllist:1;
+};
+
+void acl_vfile_write_rights_list(string_t *dest, const char *const *rights);
+int acl_backend_vfile_object_update(struct acl_object *aclobj,
+ const struct acl_rights_update *update);
+
+void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend);
+int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend);
+void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend,
+ const char *name, time_t mtime);
+
+struct acl_mailbox_list_context *
+acl_backend_vfile_nonowner_iter_init(struct acl_backend *backend);
+bool acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *ctx,
+ const char **name_r);
+int
+acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx);
+int acl_backend_vfile_nonowner_lookups_rebuild(struct acl_backend *backend);
+
+int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj,
+ time_t *mtime_r);
+
+static inline enum mailbox_list_path_type
+mail_storage_get_acl_list_path_type(struct mail_storage *storage)
+{
+ if (mail_storage_is_mailbox_file(storage)) {
+ /* mailbox is a directory (e.g. mbox) */
+ return MAILBOX_LIST_PATH_TYPE_CONTROL;
+ }
+ if ((storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_ROOT) != 0) {
+ /* there is no local mailbox directory */
+ return MAILBOX_LIST_PATH_TYPE_CONTROL;
+ }
+ return MAILBOX_LIST_PATH_TYPE_MAILBOX;
+}
+
+#endif
diff --git a/src/plugins/acl/acl-backend.c b/src/plugins/acl/acl-backend.c
new file mode 100644
index 0000000..0514dc7
--- /dev/null
+++ b/src/plugins/acl/acl-backend.c
@@ -0,0 +1,194 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "sort.h"
+#include "mail-storage-settings.h"
+#include "mailbox-list.h"
+#include "mail-namespace.h"
+#include "mail-user.h"
+#include "acl-cache.h"
+#include "acl-api-private.h"
+
+
+extern struct acl_backend_vfuncs acl_backend_vfile;
+
+const char *const all_mailbox_rights[] = {
+ MAIL_ACL_LOOKUP,
+ MAIL_ACL_READ,
+ MAIL_ACL_WRITE,
+ MAIL_ACL_WRITE_SEEN,
+ MAIL_ACL_WRITE_DELETED,
+ MAIL_ACL_INSERT,
+ MAIL_ACL_POST,
+ MAIL_ACL_EXPUNGE,
+ MAIL_ACL_CREATE,
+ MAIL_ACL_DELETE,
+ MAIL_ACL_ADMIN,
+ NULL
+};
+
+static const char *const *owner_mailbox_rights = all_mailbox_rights;
+static const char *const non_owner_mailbox_rights[] = { NULL };
+
+struct acl_backend *
+acl_backend_init(const char *data, struct mailbox_list *list,
+ const char *acl_username, const char *const *groups,
+ bool owner)
+{
+ struct mail_user *user = mailbox_list_get_user(list);
+ struct acl_backend *backend;
+ unsigned int i, group_count;
+
+ e_debug(user->event, "acl: initializing backend with data: %s", data);
+ e_debug(user->event, "acl: acl username = %s", acl_username);
+ e_debug(user->event, "acl: owner = %d", owner ? 1 : 0);
+
+ group_count = str_array_length(groups);
+
+ if (str_begins(data, "vfile:"))
+ data += 6;
+ else if (strcmp(data, "vfile") == 0)
+ data = "";
+ else
+ i_fatal("Unknown ACL backend: %s", t_strcut(data, ':'));
+
+ backend = acl_backend_vfile.alloc();
+ backend->debug = user->mail_debug;
+ backend->v = acl_backend_vfile;
+ backend->list = list;
+ backend->username = p_strdup(backend->pool, acl_username);
+ backend->owner = owner;
+ backend->globals_only =
+ mail_user_plugin_getenv_bool(user, "acl_globals_only");
+
+ if (group_count > 0) {
+ backend->group_count = group_count;
+ backend->groups =
+ p_new(backend->pool, const char *, group_count);
+ for (i = 0; i < group_count; i++) {
+ backend->groups[i] = p_strdup(backend->pool, groups[i]);
+ e_debug(user->event, "acl: group added: %s", groups[i]);
+ }
+ i_qsort(backend->groups, group_count, sizeof(const char *),
+ i_strcmp_p);
+ }
+
+ T_BEGIN {
+ if (acl_backend_vfile.init(backend, data) < 0)
+ i_fatal("acl: backend vfile init failed with data: %s",
+ data);
+ } T_END;
+
+ backend->default_rights = owner ? owner_mailbox_rights :
+ non_owner_mailbox_rights;
+ backend->default_aclmask =
+ acl_cache_mask_init(backend->cache, backend->pool,
+ backend->default_rights);
+ return backend;
+}
+
+void acl_backend_deinit(struct acl_backend **_backend)
+{
+ struct acl_backend *backend = *_backend;
+
+ *_backend = NULL;
+
+ if (backend->default_aclobj != NULL)
+ acl_object_deinit(&backend->default_aclobj);
+ acl_cache_deinit(&backend->cache);
+ backend->v.deinit(backend);
+}
+
+const char *acl_backend_get_acl_username(struct acl_backend *backend)
+{
+ return backend->username;
+}
+
+bool acl_backend_user_is_authenticated(struct acl_backend *backend)
+{
+ return backend->username != NULL;
+}
+
+bool acl_backend_user_is_owner(struct acl_backend *backend)
+{
+ return backend->owner;
+}
+
+bool acl_backend_user_name_equals(struct acl_backend *backend,
+ const char *username)
+{
+ if (backend->username == NULL) {
+ /* anonymous user never matches */
+ return FALSE;
+ }
+
+ return strcmp(backend->username, username) == 0;
+}
+
+bool acl_backend_user_is_in_group(struct acl_backend *backend,
+ const char *group_name)
+{
+ return i_bsearch(group_name, backend->groups, backend->group_count,
+ sizeof(const char *), bsearch_strcmp) != NULL;
+}
+
+bool acl_backend_rights_match_me(struct acl_backend *backend,
+ const struct acl_rights *rights)
+{
+ switch (rights->id_type) {
+ case ACL_ID_ANYONE:
+ return TRUE;
+ case ACL_ID_AUTHENTICATED:
+ return acl_backend_user_is_authenticated(backend);
+ case ACL_ID_GROUP:
+ case ACL_ID_GROUP_OVERRIDE:
+ return acl_backend_user_is_in_group(backend, rights->identifier);
+ case ACL_ID_USER:
+ return acl_backend_user_name_equals(backend, rights->identifier);
+ case ACL_ID_OWNER:
+ return acl_backend_user_is_owner(backend);
+ case ACL_ID_TYPE_COUNT:
+ break;
+ }
+ i_unreached();
+}
+
+unsigned int acl_backend_lookup_right(struct acl_backend *backend,
+ const char *right)
+{
+ return acl_cache_right_lookup(backend->cache, right);
+}
+
+struct acl_object *acl_backend_get_default_object(struct acl_backend *backend)
+{
+ struct mail_user *user = mailbox_list_get_user(backend->list);
+ struct mail_namespace *ns = mailbox_list_get_namespace(backend->list);
+ const char *default_name = "";
+
+ if (backend->default_aclobj != NULL)
+ return backend->default_aclobj;
+
+ if (mail_user_plugin_getenv_bool(user, "acl_defaults_from_inbox")) {
+ if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE ||
+ ns->type == MAIL_NAMESPACE_TYPE_SHARED)
+ default_name = "INBOX";
+ }
+ backend->default_aclobj =
+ acl_object_init_from_name(backend, default_name);
+ return backend->default_aclobj;
+}
+
+int acl_backend_get_default_rights(struct acl_backend *backend,
+ const struct acl_mask **mask_r)
+{
+ struct acl_object *aclobj = acl_backend_get_default_object(backend);
+
+ if (backend->v.object_refresh_cache(aclobj) < 0)
+ return -1;
+
+ *mask_r = acl_cache_get_my_rights(backend->cache, aclobj->name);
+ if (*mask_r == NULL)
+ *mask_r = backend->default_aclmask;
+ return 0;
+}
diff --git a/src/plugins/acl/acl-cache.c b/src/plugins/acl/acl-cache.c
new file mode 100644
index 0000000..8f07d56
--- /dev/null
+++ b/src/plugins/acl/acl-cache.c
@@ -0,0 +1,395 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "acl-cache.h"
+#include "acl-api-private.h"
+
+/* Give more than enough so that the arrays should never have to be grown.
+ IMAP ACLs define only 10 standard rights and 10 user-defined rights. */
+#define DEFAULT_ACL_RIGHTS_COUNT 64
+
+#define ACL_GLOBAL_COUNT 2
+
+struct acl_object_cache {
+ char *name;
+
+ struct acl_mask *my_rights, *my_neg_rights;
+ struct acl_mask *my_current_rights;
+};
+
+struct acl_cache {
+ struct acl_backend *backend;
+ /* name => object */
+ HASH_TABLE(char *, struct acl_object_cache *) objects;
+
+ size_t validity_rec_size;
+
+ /* Right names mapping is used for faster rights checking. Note that
+ acl_mask bitmask relies on the order to never change, so only new
+ rights can be added to the mapping. */
+ pool_t right_names_pool;
+ /* idx => right name. */
+ ARRAY(const char *) right_idx_name_map;
+ /* name => idx+1 */
+ HASH_TABLE(char *, void *) right_name_idx_map;
+};
+
+static struct acl_mask negative_cache_entry;
+
+struct acl_cache *acl_cache_init(struct acl_backend *backend,
+ size_t validity_rec_size)
+{
+ struct acl_cache *cache;
+
+ cache = i_new(struct acl_cache, 1);
+ cache->backend = backend;
+ cache->validity_rec_size = validity_rec_size;
+ cache->right_names_pool =
+ pool_alloconly_create("ACL right names", 1024);
+ hash_table_create(&cache->objects, default_pool, 0, str_hash, strcmp);
+ hash_table_create(&cache->right_name_idx_map,
+ cache->right_names_pool, 0, str_hash, strcmp);
+ i_array_init(&cache->right_idx_name_map, DEFAULT_ACL_RIGHTS_COUNT);
+ return cache;
+}
+
+void acl_cache_deinit(struct acl_cache **_cache)
+{
+ struct acl_cache *cache = *_cache;
+
+ *_cache = NULL;
+
+ acl_cache_flush_all(cache);
+ array_free(&cache->right_idx_name_map);
+ hash_table_destroy(&cache->right_name_idx_map);
+ hash_table_destroy(&cache->objects);
+ pool_unref(&cache->right_names_pool);
+ i_free(cache);
+}
+
+static void acl_cache_free_object_cache(struct acl_object_cache *obj_cache)
+{
+ if (obj_cache->my_current_rights != NULL &&
+ obj_cache->my_current_rights != &negative_cache_entry)
+ acl_cache_mask_deinit(&obj_cache->my_current_rights);
+ if (obj_cache->my_rights != NULL)
+ acl_cache_mask_deinit(&obj_cache->my_rights);
+ if (obj_cache->my_neg_rights != NULL)
+ acl_cache_mask_deinit(&obj_cache->my_neg_rights);
+ i_free(obj_cache->name);
+ i_free(obj_cache);
+}
+
+static struct acl_mask *
+acl_cache_mask_init_real(struct acl_cache *cache, pool_t pool,
+ const char *const *rights)
+{
+ struct acl_mask *mask;
+ unsigned int rights_count, i, idx;
+ unsigned char *p;
+ buffer_t *bitmask;
+
+ rights_count = str_array_length(rights);
+ bitmask = t_buffer_create(DEFAULT_ACL_RIGHTS_COUNT / CHAR_BIT);
+ for (i = 0; i < rights_count; i++) {
+ idx = acl_cache_right_lookup(cache, rights[i]);
+ p = buffer_get_space_unsafe(bitmask, idx / CHAR_BIT, 1);
+ *p |= 1 << (idx % CHAR_BIT);
+ }
+
+ /* @UNSAFE */
+ mask = p_malloc(pool, SIZEOF_ACL_MASK(bitmask->used));
+ memcpy(mask->mask, bitmask->data, bitmask->used);
+ mask->pool = pool;
+ mask->size = bitmask->used;
+ return mask;
+}
+
+struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool,
+ const char *const *rights)
+{
+ struct acl_mask *mask;
+
+ T_BEGIN {
+ mask = acl_cache_mask_init_real(cache, pool, rights);
+ } T_END;
+ return mask;
+}
+
+static struct acl_mask *
+acl_cache_mask_dup(pool_t pool, const struct acl_mask *src)
+{
+ struct acl_mask *mask;
+
+ mask = p_malloc(pool, SIZEOF_ACL_MASK(src->size));
+ memcpy(mask->mask, src->mask, src->size);
+ mask->pool = pool;
+ mask->size = src->size;
+ return mask;
+}
+
+void acl_cache_mask_deinit(struct acl_mask **_mask)
+{
+ struct acl_mask *mask = *_mask;
+
+ *_mask = NULL;
+ p_free(mask->pool, mask);
+}
+
+unsigned int acl_cache_right_lookup(struct acl_cache *cache, const char *right)
+{
+ unsigned int idx;
+ void *idx_p;
+ char *name;
+ const char *const_name;
+
+ /* use +1 for right_name_idx_map values because we can't add NULL
+ values. */
+ idx_p = hash_table_lookup(cache->right_name_idx_map, right);
+ if (idx_p == NULL) {
+ /* new right name, add it */
+ const_name = name = p_strdup(cache->right_names_pool, right);
+
+ idx = array_count(&cache->right_idx_name_map);
+ array_push_back(&cache->right_idx_name_map, &const_name);
+ hash_table_insert(cache->right_name_idx_map, name,
+ POINTER_CAST(idx + 1));
+ } else {
+ idx = POINTER_CAST_TO(idx_p, unsigned int)-1;
+ }
+ return idx;
+}
+
+void acl_cache_flush(struct acl_cache *cache, const char *objname)
+{
+ struct acl_object_cache *obj_cache;
+
+ obj_cache = hash_table_lookup(cache->objects, objname);
+ if (obj_cache != NULL) {
+ hash_table_remove(cache->objects, objname);
+ acl_cache_free_object_cache(obj_cache);
+ }
+}
+
+void acl_cache_flush_all(struct acl_cache *cache)
+{
+ struct hash_iterate_context *iter;
+ char *key;
+ struct acl_object_cache *obj_cache;
+
+ iter = hash_table_iterate_init(cache->objects);
+ while (hash_table_iterate(iter, cache->objects, &key, &obj_cache))
+ acl_cache_free_object_cache(obj_cache);
+ hash_table_iterate_deinit(&iter);
+
+ hash_table_clear(cache->objects, FALSE);
+}
+
+static void
+acl_cache_update_rights_mask(struct acl_cache *cache,
+ struct acl_object_cache *obj_cache,
+ enum acl_modify_mode modify_mode,
+ const char *const *rights,
+ struct acl_mask **mask_p)
+{
+ struct acl_mask *change_mask, *old_mask, *new_mask;
+ unsigned int i, size;
+ bool changed = TRUE;
+
+ change_mask = rights == NULL ? NULL :
+ acl_cache_mask_init(cache, default_pool, rights);
+ old_mask = *mask_p;
+ new_mask = old_mask;
+
+ switch (modify_mode) {
+ case ACL_MODIFY_MODE_ADD:
+ if (old_mask == NULL) {
+ new_mask = change_mask;
+ break;
+ }
+
+ if (change_mask == NULL) {
+ /* no changes */
+ changed = FALSE;
+ break;
+ }
+
+ /* merge the masks */
+ if (old_mask->size >= change_mask->size) {
+ /* keep using the old mask */
+ for (i = 0; i < change_mask->size; i++)
+ old_mask->mask[i] |= change_mask->mask[i];
+ } else {
+ /* use the new mask, put old changes into it */
+ for (i = 0; i < old_mask->size; i++)
+ change_mask->mask[i] |= old_mask->mask[i];
+ new_mask = change_mask;
+ }
+ break;
+ case ACL_MODIFY_MODE_REMOVE:
+ if (old_mask == NULL || change_mask == NULL) {
+ changed = FALSE;
+ break;
+ }
+
+ /* remove changed bits from old mask */
+ size = I_MIN(old_mask->size, change_mask->size);
+ for (i = 0; i < size; i++)
+ old_mask->mask[i] &= ENUM_NEGATE(change_mask->mask[i]);
+ break;
+ case ACL_MODIFY_MODE_REPLACE:
+ if (old_mask == NULL && change_mask == NULL)
+ changed = FALSE;
+ new_mask = change_mask;
+ break;
+ case ACL_MODIFY_MODE_CLEAR:
+ i_unreached();
+ }
+
+ if (new_mask != old_mask) {
+ *mask_p = new_mask;
+ if (old_mask != NULL)
+ acl_cache_mask_deinit(&old_mask);
+ }
+ if (new_mask != change_mask && change_mask != NULL)
+ acl_cache_mask_deinit(&change_mask);
+
+ if (changed && obj_cache->my_current_rights != NULL) {
+ /* current rights need to be recalculated */
+ if (obj_cache->my_current_rights == &negative_cache_entry)
+ obj_cache->my_current_rights = NULL;
+ else
+ acl_cache_mask_deinit(&obj_cache->my_current_rights);
+ }
+}
+
+static struct acl_object_cache *
+acl_cache_object_get(struct acl_cache *cache, const char *objname,
+ bool *created_r)
+{
+ struct acl_object_cache *obj_cache;
+
+ obj_cache = hash_table_lookup(cache->objects, objname);
+ if (obj_cache == NULL) {
+ obj_cache = i_malloc(MALLOC_ADD(sizeof(struct acl_object_cache),
+ cache->validity_rec_size));
+ obj_cache->name = i_strdup(objname);
+ hash_table_insert(cache->objects, obj_cache->name, obj_cache);
+ *created_r = TRUE;
+ } else {
+ *created_r = FALSE;
+ }
+ return obj_cache;
+}
+
+void acl_cache_update(struct acl_cache *cache, const char *objname,
+ const struct acl_rights_update *update)
+{
+ struct acl_object_cache *obj_cache;
+ bool created;
+
+ obj_cache = acl_cache_object_get(cache, objname, &created);
+ i_assert(obj_cache->my_current_rights != &negative_cache_entry);
+
+ if (created && update->modify_mode != ACL_MODIFY_MODE_REPLACE) {
+ /* since the rights aren't being replaced, start with our
+ default rights */
+ obj_cache->my_rights =
+ acl_cache_mask_dup(default_pool,
+ cache->backend->default_aclmask);
+ }
+
+ acl_cache_update_rights_mask(cache, obj_cache, update->modify_mode,
+ update->rights.rights,
+ &obj_cache->my_rights);
+ acl_cache_update_rights_mask(cache, obj_cache, update->neg_modify_mode,
+ update->rights.neg_rights,
+ &obj_cache->my_neg_rights);
+}
+
+void acl_cache_set_validity(struct acl_cache *cache, const char *objname,
+ const void *validity)
+{
+ struct acl_object_cache *obj_cache;
+ bool created;
+
+ obj_cache = acl_cache_object_get(cache, objname, &created);
+
+ /* @UNSAFE: validity is stored after the cache record */
+ memcpy(obj_cache + 1, validity, cache->validity_rec_size);
+
+ if (created) {
+ /* negative cache entry */
+ obj_cache->my_current_rights = &negative_cache_entry;
+ }
+}
+
+void *acl_cache_get_validity(struct acl_cache *cache, const char *objname)
+{
+ struct acl_object_cache *obj_cache;
+
+ obj_cache = hash_table_lookup(cache->objects, objname);
+ return obj_cache == NULL ? NULL : (obj_cache + 1);
+}
+
+const char *const *acl_cache_get_names(struct acl_cache *cache,
+ unsigned int *count_r)
+{
+ *count_r = array_count(&cache->right_idx_name_map);
+ return array_front(&cache->right_idx_name_map);
+}
+
+static void
+acl_cache_my_current_rights_recalculate(struct acl_object_cache *obj_cache)
+{
+ struct acl_mask *mask;
+ unsigned int i, size;
+
+ /* @UNSAFE */
+ size = obj_cache->my_rights == NULL ? 0 :
+ obj_cache->my_rights->size;
+ mask = i_malloc(SIZEOF_ACL_MASK(size));
+ mask->pool = default_pool;
+ mask->size = size;
+
+ /* apply the positive rights */
+ if (obj_cache->my_rights != NULL)
+ memcpy(mask->mask, obj_cache->my_rights->mask, mask->size);
+ if (obj_cache->my_neg_rights != NULL) {
+ /* apply the negative rights. they override positive rights. */
+ size = I_MIN(mask->size, obj_cache->my_neg_rights->size);
+ for (i = 0; i < size; i++)
+ mask->mask[i] &= ENUM_NEGATE(obj_cache->my_neg_rights->mask[i]);
+ }
+
+ obj_cache->my_current_rights = mask;
+}
+
+const struct acl_mask *
+acl_cache_get_my_rights(struct acl_cache *cache, const char *objname)
+{
+ struct acl_object_cache *obj_cache;
+
+ obj_cache = hash_table_lookup(cache->objects, objname);
+ if (obj_cache == NULL ||
+ obj_cache->my_current_rights == &negative_cache_entry)
+ return NULL;
+
+ if (obj_cache->my_current_rights == NULL) {
+ T_BEGIN {
+ acl_cache_my_current_rights_recalculate(obj_cache);
+ } T_END;
+ }
+ return obj_cache->my_current_rights;
+}
+
+bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx)
+{
+ unsigned int mask_idx;
+
+ mask_idx = right_idx / CHAR_BIT;
+ return mask_idx < mask->size &&
+ (mask->mask[mask_idx] & (1 << (right_idx % CHAR_BIT))) != 0;
+}
diff --git a/src/plugins/acl/acl-cache.h b/src/plugins/acl/acl-cache.h
new file mode 100644
index 0000000..b7c2065
--- /dev/null
+++ b/src/plugins/acl/acl-cache.h
@@ -0,0 +1,57 @@
+#ifndef ACL_CACHE_H
+#define ACL_CACHE_H
+
+struct acl_backend;
+struct acl_rights_update;
+
+struct acl_mask {
+ pool_t pool;
+
+ /* mask[] size as bytes */
+ unsigned int size;
+
+ /* variable length bitmask */
+ unsigned char mask[1];
+};
+#define SIZEOF_ACL_MASK(bitmask_size) \
+ (MALLOC_ADD((bitmask_size), sizeof(pool_t) + sizeof(unsigned int)))
+
+struct acl_cache *acl_cache_init(struct acl_backend *backend,
+ size_t validity_rec_size);
+void acl_cache_deinit(struct acl_cache **cache);
+
+struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool,
+ const char *const *rights);
+void acl_cache_mask_deinit(struct acl_mask **mask);
+unsigned int acl_cache_right_lookup(struct acl_cache *cache,
+ const char *right);
+
+/* Flush cache for given object name */
+void acl_cache_flush(struct acl_cache *cache, const char *objname);
+/* Flush cache for all objects */
+void acl_cache_flush_all(struct acl_cache *cache);
+
+/* Update object ACLs. The new rights are always applied on top of the
+ existing rights. The ordering by acl_id_type must be done by the caller. */
+void acl_cache_update(struct acl_cache *cache, const char *objname,
+ const struct acl_rights_update *update);
+/* Return ACL object validity, or NULL if object doesn't exit. */
+void *acl_cache_get_validity(struct acl_cache *cache, const char *objname);
+/* Update ACL object validity, creating the object if needed. */
+void acl_cache_set_validity(struct acl_cache *cache, const char *objname,
+ const void *validity);
+
+/* Returns all the right names currently created. The returned pointer may
+ change after calling acl_cache_update(). */
+const char *const *acl_cache_get_names(struct acl_cache *cache,
+ unsigned int *count_r);
+
+/* Returns user's current rights, or NULL if no rights have been specified
+ for this object. */
+const struct acl_mask *
+acl_cache_get_my_rights(struct acl_cache *cache, const char *objname);
+
+/* Returns TRUE if given right index is set in mask. */
+bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx);
+
+#endif
diff --git a/src/plugins/acl/acl-global-file.c b/src/plugins/acl/acl-global-file.c
new file mode 100644
index 0000000..02f6643
--- /dev/null
+++ b/src/plugins/acl/acl-global-file.c
@@ -0,0 +1,246 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "strescape.h"
+#include "wildcard-match.h"
+#include "acl-api-private.h"
+#include "acl-global-file.h"
+
+#include <sys/stat.h>
+
+struct acl_global_rights {
+ const char *vpattern;
+ ARRAY_TYPE(acl_rights) rights;
+};
+
+struct acl_global_parse_rights {
+ const char *vpattern;
+ struct acl_rights rights;
+};
+
+struct acl_global_file {
+ char *path;
+ struct stat prev_st;
+ time_t last_refresh_time;
+
+ pool_t rights_pool;
+ ARRAY(struct acl_global_rights) rights;
+
+ unsigned int refresh_interval_secs;
+ bool debug;
+};
+
+struct acl_global_file *
+acl_global_file_init(const char *path, unsigned int refresh_interval_secs,
+ bool debug)
+{
+ struct acl_global_file *file;
+
+ file = i_new(struct acl_global_file, 1);
+ file->path = i_strdup(path);
+ file->refresh_interval_secs = refresh_interval_secs;
+ file->debug = debug;
+ i_array_init(&file->rights, 32);
+ file->rights_pool = pool_alloconly_create("acl global file rights", 1024);
+ return file;
+}
+
+void acl_global_file_deinit(struct acl_global_file **_file)
+{
+ struct acl_global_file *file = *_file;
+
+ *_file = NULL;
+
+ array_free(&file->rights);
+ pool_unref(&file->rights_pool);
+ i_free(file->path);
+ i_free(file);
+}
+
+static int acl_global_parse_rights_cmp(const struct acl_global_parse_rights *r1,
+ const struct acl_global_parse_rights *r2)
+{
+ return strcmp(r1->vpattern, r2->vpattern);
+}
+
+struct acl_global_file_parse_ctx {
+ struct acl_global_file *file;
+ ARRAY(struct acl_global_parse_rights) parse_rights;
+};
+
+static int
+acl_global_file_parse_line(struct acl_global_file_parse_ctx *ctx,
+ const char *line, const char **error_r)
+{
+ struct acl_global_parse_rights *pright;
+ const char *p, *vpattern;
+
+ if (*line == '"') {
+ line++;
+ if (str_unescape_next(&line, &vpattern) < 0) {
+ *error_r = "Missing '\"'";
+ return -1;
+ }
+ if (line[0] != ' ') {
+ *error_r = "Expecting space after '\"'";
+ return -1;
+ }
+ line++;
+ } else {
+ p = strchr(line, ' ');
+ if (p == NULL) {
+ *error_r = "Missing ACL rights";
+ return -1;
+ }
+ if (p == line) {
+ *error_r = "Empty ACL pattern";
+ return -1;
+ }
+ vpattern = t_strdup_until(line, p);
+ line = p + 1;
+ }
+
+ pright = array_append_space(&ctx->parse_rights);
+ pright->vpattern = p_strdup(ctx->file->rights_pool, vpattern);
+ if (acl_rights_parse_line(line, ctx->file->rights_pool,
+ &pright->rights, error_r) < 0)
+ return -1;
+ pright->rights.global = TRUE;
+ return 0;
+}
+
+static int acl_global_file_read(struct acl_global_file *file)
+{
+ struct acl_global_file_parse_ctx ctx;
+ struct acl_global_parse_rights *pright;
+ struct acl_global_rights *right;
+ struct istream *input;
+ const char *line, *error, *prev_vpattern;
+ unsigned int linenum = 0;
+ int ret = 0;
+
+ array_clear(&file->rights);
+ p_clear(file->rights_pool);
+
+ i_zero(&ctx);
+ ctx.file = file;
+ i_array_init(&ctx.parse_rights, 32);
+
+ input = i_stream_create_file(file->path, SIZE_MAX);
+ i_stream_set_return_partial_line(input, TRUE);
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ linenum++;
+ if (line[0] == '\0' || line[0] == '#')
+ continue;
+ T_BEGIN {
+ ret = acl_global_file_parse_line(&ctx, line, &error);
+ if (ret < 0) {
+ i_error("Global ACL file %s line %u: %s",
+ file->path, linenum, error);
+ }
+ } T_END;
+ if (ret < 0)
+ break;
+ }
+ if (ret == 0 && input->stream_errno != 0) {
+ i_error("Couldn't read global ACL file %s: %s",
+ file->path, i_stream_get_error(input));
+ ret = -1;
+ }
+ if (ret == 0) {
+ const struct stat *st;
+
+ if (i_stream_stat(input, TRUE, &st) < 0) {
+ i_error("Couldn't stat global ACL file %s: %s",
+ file->path, i_stream_get_error(input));
+ ret = -1;
+ } else {
+ file->prev_st = *st;
+ }
+ }
+ i_stream_destroy(&input);
+
+ /* sort all parsed rights */
+ array_sort(&ctx.parse_rights, acl_global_parse_rights_cmp);
+ /* combine identical patterns into same structs */
+ prev_vpattern = ""; right = NULL;
+ array_foreach_modifiable(&ctx.parse_rights, pright) {
+ if (right == NULL ||
+ strcmp(prev_vpattern, pright->vpattern) != 0) {
+ right = array_append_space(&file->rights);
+ right->vpattern = pright->vpattern;
+ p_array_init(&right->rights, file->rights_pool, 4);
+ }
+ array_push_back(&right->rights, &pright->rights);
+ }
+
+ array_free(&ctx.parse_rights);
+ return ret;
+}
+
+int acl_global_file_refresh(struct acl_global_file *file)
+{
+ struct stat st;
+
+ if (file->last_refresh_time + (time_t)file->refresh_interval_secs > ioloop_time)
+ return 0;
+ if (file->last_refresh_time != 0) {
+ if (stat(file->path, &st) < 0) {
+ i_error("stat(%s) failed: %m", file->path);
+ return -1;
+ }
+ if (st.st_ino == file->prev_st.st_ino &&
+ st.st_size == file->prev_st.st_size &&
+ CMP_ST_MTIME(&st, &file->prev_st)) {
+ /* no change to the file */
+ file->last_refresh_time = ioloop_time;
+ return 0;
+ }
+ }
+ if (acl_global_file_read(file) < 0)
+ return -1;
+ file->last_refresh_time = ioloop_time;
+ return 0;
+}
+
+void acl_global_file_last_stat(struct acl_global_file *file, struct stat *st_r)
+{
+ *st_r = file->prev_st;
+}
+
+void acl_global_file_get(struct acl_global_file *file, const char *vname,
+ pool_t pool, ARRAY_TYPE(acl_rights) *rights_r)
+{
+ struct acl_global_rights *global_rights;
+ const struct acl_rights *rights;
+ struct acl_rights *new_rights;
+
+ array_foreach_modifiable(&file->rights, global_rights) {
+ if (!wildcard_match(vname, global_rights->vpattern))
+ continue;
+ if (file->debug) {
+ i_debug("Mailbox '%s' matches global ACL pattern '%s'",
+ vname, global_rights->vpattern);
+ }
+ array_foreach(&global_rights->rights, rights) {
+ new_rights = array_append_space(rights_r);
+ acl_rights_dup(rights, pool, new_rights);
+ }
+ }
+}
+
+bool acl_global_file_have_any(struct acl_global_file *file, const char *vname)
+{
+ struct acl_global_rights *rights;
+
+ i_assert(file->last_refresh_time != 0);
+
+ array_foreach_modifiable(&file->rights, rights) {
+ if (wildcard_match(vname, rights->vpattern))
+ return TRUE;
+ }
+ return FALSE;
+}
diff --git a/src/plugins/acl/acl-global-file.h b/src/plugins/acl/acl-global-file.h
new file mode 100644
index 0000000..d393dec
--- /dev/null
+++ b/src/plugins/acl/acl-global-file.h
@@ -0,0 +1,23 @@
+#ifndef ACL_GLOBAL_FILE_H
+#define ACL_GLOBAL_FILE_H
+
+#include "acl-api.h"
+
+struct acl_global_file *
+acl_global_file_init(const char *path, unsigned int refresh_interval_secs,
+ bool debug);
+void acl_global_file_deinit(struct acl_global_file **file);
+
+/* Read the global ACLs into memory. */
+int acl_global_file_refresh(struct acl_global_file *file);
+/* Return stat data for the last refresh. */
+void acl_global_file_last_stat(struct acl_global_file *file, struct stat *st_r);
+
+/* Return global ACL rights matching the mailbox name. The file must already
+ have been refreshed at least once. */
+void acl_global_file_get(struct acl_global_file *file, const char *vname,
+ pool_t pool, ARRAY_TYPE(acl_rights) *rights_r);
+/* Returns TRUE if there are any global ACLs matching the mailbox name. */
+bool acl_global_file_have_any(struct acl_global_file *file, const char *vname);
+
+#endif
diff --git a/src/plugins/acl/acl-lookup-dict.c b/src/plugins/acl/acl-lookup-dict.c
new file mode 100644
index 0000000..638a767
--- /dev/null
+++ b/src/plugins/acl/acl-lookup-dict.c
@@ -0,0 +1,373 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "dict.h"
+#include "mail-user.h"
+#include "mail-namespace.h"
+#include "acl-api-private.h"
+#include "acl-storage.h"
+#include "acl-plugin.h"
+#include "acl-lookup-dict.h"
+
+
+#define DICT_SHARED_BOXES_PATH "shared-boxes/"
+
+struct acl_lookup_dict {
+ struct mail_user *user;
+ struct dict *dict;
+};
+
+struct acl_lookup_dict_iter {
+ pool_t pool;
+ struct acl_lookup_dict *dict;
+
+ pool_t iter_value_pool;
+ ARRAY_TYPE(const_string) iter_ids;
+ ARRAY_TYPE(const_string) iter_values;
+ unsigned int iter_idx, iter_value_idx;
+
+ bool failed:1;
+};
+
+struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user)
+{
+ struct acl_lookup_dict *dict;
+ const char *uri, *error;
+
+ dict = i_new(struct acl_lookup_dict, 1);
+ dict->user = user;
+
+ uri = mail_user_plugin_getenv(user, "acl_shared_dict");
+ if (uri != NULL) {
+ struct dict_settings dict_set;
+
+ i_zero(&dict_set);
+ dict_set.base_dir = user->set->base_dir;
+ dict_set.event_parent = user->event;
+ if (dict_init(uri, &dict_set, &dict->dict, &error) < 0)
+ i_error("acl: dict_init(%s) failed: %s", uri, error);
+ } else {
+ e_debug(user->event, "acl: No acl_shared_dict setting - "
+ "shared mailbox listing is disabled");
+ }
+ return dict;
+}
+
+void acl_lookup_dict_deinit(struct acl_lookup_dict **_dict)
+{
+ struct acl_lookup_dict *dict = *_dict;
+
+ *_dict = NULL;
+ if (dict->dict != NULL)
+ dict_deinit(&dict->dict);
+ i_free(dict);
+}
+
+bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict)
+{
+ return dict->dict != NULL;
+}
+
+static void
+acl_lookup_dict_write_rights_id(string_t *dest, const struct acl_rights *right)
+{
+ switch (right->id_type) {
+ case ACL_ID_ANYONE:
+ case ACL_ID_AUTHENTICATED:
+ /* don't bother separating these */
+ str_append(dest, "anyone");
+ break;
+ case ACL_ID_USER:
+ str_append(dest, "user/");
+ str_append(dest, right->identifier);
+ break;
+ case ACL_ID_GROUP:
+ case ACL_ID_GROUP_OVERRIDE:
+ str_append(dest, "group/");
+ str_append(dest, right->identifier);
+ break;
+ case ACL_ID_OWNER:
+ case ACL_ID_TYPE_COUNT:
+ i_unreached();
+ }
+}
+
+static bool
+acl_rights_is_same_user(const struct acl_rights *right, struct mail_user *user)
+{
+ return right->id_type == ACL_ID_USER &&
+ strcmp(right->identifier, user->username) == 0;
+}
+
+static int acl_lookup_dict_rebuild_add_backend(struct mail_namespace *ns,
+ ARRAY_TYPE(const_string) *ids)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list);
+ struct acl_backend *backend;
+ struct acl_mailbox_list_context *ctx;
+ struct acl_object *aclobj;
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+ const char *name, *id_dup;
+ string_t *id;
+ int ret = 0;
+
+ if ((ns->flags & NAMESPACE_FLAG_NOACL) != 0 || ns->owner == NULL ||
+ alist == NULL || alist->ignore_acls)
+ return 0;
+
+ id = t_str_new(128);
+ backend = acl_mailbox_list_get_backend(ns->list);
+ ctx = acl_backend_nonowner_lookups_iter_init(backend);
+ while (acl_backend_nonowner_lookups_iter_next(ctx, &name)) {
+ aclobj = acl_object_init_from_name(backend, name);
+
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ /* avoid pointless user -> user entries,
+ which some clients do */
+ if (acl_rights_has_nonowner_lookup_changes(&rights) &&
+ !acl_rights_is_same_user(&rights, ns->owner)) {
+ str_truncate(id, 0);
+ acl_lookup_dict_write_rights_id(id, &rights);
+ str_append_c(id, '/');
+ str_append(id, ns->owner->username);
+ id_dup = t_strdup(str_c(id));
+ array_push_back(ids, &id_dup);
+ }
+ }
+ if (acl_object_list_deinit(&iter) < 0) ret = -1;
+ acl_object_deinit(&aclobj);
+ }
+ if (acl_backend_nonowner_lookups_iter_deinit(&ctx) < 0) ret = -1;
+ return ret;
+}
+
+static int
+acl_lookup_dict_rebuild_update(struct acl_lookup_dict *dict,
+ const ARRAY_TYPE(const_string) *new_ids_arr,
+ bool no_removes)
+{
+ const char *username = dict->user->username;
+ struct dict_iterate_context *iter;
+ struct dict_transaction_context *dt = NULL;
+ const char *prefix, *key, *value, *const *old_ids, *const *new_ids, *p;
+ const char *error;
+ ARRAY_TYPE(const_string) old_ids_arr;
+ unsigned int newi, oldi, old_count, new_count;
+ string_t *path;
+ size_t prefix_len;
+ int ret;
+ const struct dict_op_settings *set = mail_user_get_dict_op_settings(dict->user);
+
+ /* get all existing identifiers for the user. we might be able to
+ sync identifiers also for other users whose shared namespaces we
+ have, but it's possible that the other users have other namespaces
+ that aren't visible to us, so we don't want to remove anything
+ that could break them. */
+ t_array_init(&old_ids_arr, 128);
+ prefix = DICT_PATH_SHARED DICT_SHARED_BOXES_PATH;
+ prefix_len = strlen(prefix);
+ iter = dict_iterate_init(dict->dict, set, prefix, DICT_ITERATE_FLAG_RECURSE);
+ while (dict_iterate(iter, &key, &value)) {
+ /* prefix/$type/$dest/$source */
+ key += prefix_len;
+ p = strrchr(key, '/');
+ if (p != NULL && strcmp(p + 1, username) == 0) {
+ key = t_strdup_until(key, p);
+ array_push_back(&old_ids_arr, &key);
+ }
+ }
+ if (dict_iterate_deinit(&iter, &error) < 0) {
+ i_error("acl: dict iteration failed: %s - can't update dict", error);
+ return -1;
+ }
+
+ /* sort the existing identifiers */
+ array_sort(&old_ids_arr, i_strcmp_p);
+
+ /* sync the identifiers */
+ path = t_str_new(256);
+ str_append(path, prefix);
+
+ old_ids = array_get(&old_ids_arr, &old_count);
+ new_ids = array_get(new_ids_arr, &new_count);
+ for (newi = oldi = 0; newi < new_count || oldi < old_count; ) {
+ ret = newi == new_count ? 1 :
+ oldi == old_count ? -1 :
+ strcmp(new_ids[newi], old_ids[oldi]);
+ if (ret == 0) {
+ newi++; oldi++;
+ } else if (ret < 0) {
+ /* new identifier, add it */
+ str_truncate(path, prefix_len);
+ str_append(path, new_ids[newi]);
+ dt = dict_transaction_begin(dict->dict, set);
+ dict_set(dt, str_c(path), "1");
+ newi++;
+ } else if (!no_removes) {
+ /* old identifier removed */
+ str_truncate(path, prefix_len);
+ str_append(path, old_ids[oldi]);
+ str_append_c(path, '/');
+ str_append(path, username);
+ dt = dict_transaction_begin(dict->dict, set);
+ dict_unset(dt, str_c(path));
+ oldi++;
+ }
+ if (dt != NULL && dict_transaction_commit(&dt, &error) < 0) {
+ i_error("acl: dict commit failed: %s", error);
+ return -1;
+ }
+ i_assert(dt == NULL);
+ }
+ return 0;
+}
+
+int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict)
+{
+ struct mail_namespace *ns;
+ ARRAY_TYPE(const_string) ids_arr;
+ const char **ids;
+ unsigned int i, dest, count;
+ int ret = 0;
+
+ if (dict->dict == NULL)
+ return 0;
+
+ /* get all ACL identifiers with a positive lookup right */
+ t_array_init(&ids_arr, 128);
+ for (ns = dict->user->namespaces; ns != NULL; ns = ns->next) {
+ if (acl_lookup_dict_rebuild_add_backend(ns, &ids_arr) < 0)
+ ret = -1;
+ }
+
+ /* sort identifiers and remove duplicates */
+ array_sort(&ids_arr, i_strcmp_p);
+
+ ids = array_get_modifiable(&ids_arr, &count);
+ for (i = 1, dest = 0; i < count; i++) {
+ if (strcmp(ids[dest], ids[i]) != 0) {
+ if (++dest != i)
+ ids[dest] = ids[i];
+ }
+ }
+ if (++dest < count)
+ array_delete(&ids_arr, dest, count-dest);
+
+ /* if lookup failed at some point we can still add new ids,
+ but we can't remove any existing ones */
+ if (acl_lookup_dict_rebuild_update(dict, &ids_arr, ret < 0) < 0)
+ ret = -1;
+ return ret;
+}
+
+static void acl_lookup_dict_iterate_read(struct acl_lookup_dict_iter *iter)
+{
+ struct dict_iterate_context *dict_iter;
+ const char *id, *prefix, *key, *value, *error;
+ size_t prefix_len;
+
+ id = array_idx_elem(&iter->iter_ids, iter->iter_idx);
+ iter->iter_idx++;
+ iter->iter_value_idx = 0;
+
+ prefix = t_strconcat(DICT_PATH_SHARED DICT_SHARED_BOXES_PATH,
+ id, "/", NULL);
+ prefix_len = strlen(prefix);
+
+ /* read all of it to memory. at least currently dict-proxy can support
+ only one iteration at a time, but the acl code can end up rebuilding
+ the dict, which opens another iteration. */
+ p_clear(iter->iter_value_pool);
+ array_clear(&iter->iter_values);
+ const struct dict_op_settings *set = mail_user_get_dict_op_settings(iter->dict->user);
+ dict_iter = dict_iterate_init(iter->dict->dict, set, prefix,
+ DICT_ITERATE_FLAG_RECURSE);
+ while (dict_iterate(dict_iter, &key, &value)) {
+ i_assert(prefix_len < strlen(key));
+
+ key = p_strdup(iter->iter_value_pool, key + prefix_len);
+ array_push_back(&iter->iter_values, &key);
+ }
+ if (dict_iterate_deinit(&dict_iter, &error) < 0) {
+ i_error("%s", error);
+ iter->failed = TRUE;
+ }
+}
+
+struct acl_lookup_dict_iter *
+acl_lookup_dict_iterate_visible_init(struct acl_lookup_dict *dict)
+{
+ struct acl_user *auser = ACL_USER_CONTEXT(dict->user);
+ struct acl_lookup_dict_iter *iter;
+ const char *id;
+ unsigned int i;
+ pool_t pool;
+
+ i_assert(auser != NULL);
+
+ pool = pool_alloconly_create("acl lookup dict iter", 1024);
+ iter = p_new(pool, struct acl_lookup_dict_iter, 1);
+ iter->pool = pool;
+ iter->dict = dict;
+
+ p_array_init(&iter->iter_ids, pool, 16);
+ id = "anyone";
+ array_push_back(&iter->iter_ids, &id);
+ id = p_strconcat(pool, "user/", dict->user->username, NULL);
+ array_push_back(&iter->iter_ids, &id);
+
+ i_array_init(&iter->iter_values, 64);
+ iter->iter_value_pool =
+ pool_alloconly_create("acl lookup dict iter values", 1024);
+
+ /* get all groups we belong to */
+ if (auser->groups != NULL) {
+ for (i = 0; auser->groups[i] != NULL; i++) {
+ id = p_strconcat(pool, "group/", auser->groups[i],
+ NULL);
+ array_push_back(&iter->iter_ids, &id);
+ }
+ }
+
+ /* iterate through all identifiers that match us, start with the
+ first one */
+ if (dict->dict != NULL)
+ acl_lookup_dict_iterate_read(iter);
+ else
+ array_clear(&iter->iter_ids);
+ return iter;
+}
+
+const char *
+acl_lookup_dict_iterate_visible_next(struct acl_lookup_dict_iter *iter)
+{
+ const char *const *keys;
+ unsigned int count;
+
+ keys = array_get(&iter->iter_values, &count);
+ if (iter->iter_value_idx < count)
+ return keys[iter->iter_value_idx++];
+
+ if (iter->iter_idx < array_count(&iter->iter_ids)) {
+ /* get to the next iterator */
+ acl_lookup_dict_iterate_read(iter);
+ return acl_lookup_dict_iterate_visible_next(iter);
+ }
+ return NULL;
+}
+
+int acl_lookup_dict_iterate_visible_deinit(struct acl_lookup_dict_iter **_iter)
+{
+ struct acl_lookup_dict_iter *iter = *_iter;
+ int ret = iter->failed ? -1 : 0;
+
+ *_iter = NULL;
+ array_free(&iter->iter_values);
+ pool_unref(&iter->iter_value_pool);
+ pool_unref(&iter->pool);
+ return ret;
+}
diff --git a/src/plugins/acl/acl-lookup-dict.h b/src/plugins/acl/acl-lookup-dict.h
new file mode 100644
index 0000000..856aaf4
--- /dev/null
+++ b/src/plugins/acl/acl-lookup-dict.h
@@ -0,0 +1,17 @@
+#ifndef ACL_LOOKUP_DICT_H
+#define ACL_LOOKUP_DICT_H
+
+struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user);
+void acl_lookup_dict_deinit(struct acl_lookup_dict **dict);
+
+bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict);
+
+int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict);
+
+struct acl_lookup_dict_iter *
+acl_lookup_dict_iterate_visible_init(struct acl_lookup_dict *dict);
+const char *
+acl_lookup_dict_iterate_visible_next(struct acl_lookup_dict_iter *iter);
+int acl_lookup_dict_iterate_visible_deinit(struct acl_lookup_dict_iter **iter);
+
+#endif
diff --git a/src/plugins/acl/acl-mailbox-list.c b/src/plugins/acl/acl-mailbox-list.c
new file mode 100644
index 0000000..18e04ad
--- /dev/null
+++ b/src/plugins/acl/acl-mailbox-list.c
@@ -0,0 +1,629 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "imap-match.h"
+#include "wildcard-match.h"
+#include "mailbox-tree.h"
+#include "mail-namespace.h"
+#include "mailbox-list-iter-private.h"
+#include "acl-api-private.h"
+#include "acl-cache.h"
+#include "acl-shared-storage.h"
+#include "acl-plugin.h"
+
+#define MAILBOX_FLAG_MATCHED 0x40000000
+
+struct acl_mailbox_list_iterate_context {
+ union mailbox_list_iterate_module_context module_ctx;
+
+ struct mailbox_tree_context *lookup_boxes;
+ struct mailbox_info info;
+
+ char sep;
+ bool hide_nonlistable_subscriptions:1;
+ bool simple_star_glob:1;
+ bool autocreate_acls_checked:1;
+};
+
+static const char *acl_storage_right_names[ACL_STORAGE_RIGHT_COUNT] = {
+ MAIL_ACL_LOOKUP,
+ MAIL_ACL_READ,
+ MAIL_ACL_WRITE,
+ MAIL_ACL_WRITE_SEEN,
+ MAIL_ACL_WRITE_DELETED,
+ MAIL_ACL_INSERT,
+ MAIL_ACL_POST,
+ MAIL_ACL_EXPUNGE,
+ MAIL_ACL_CREATE,
+ MAIL_ACL_DELETE,
+ MAIL_ACL_ADMIN
+};
+
+#define ACL_LIST_ITERATE_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, acl_mailbox_list_module)
+
+struct acl_mailbox_list_module acl_mailbox_list_module =
+ MODULE_CONTEXT_INIT(&mailbox_list_module_register);
+
+struct acl_backend *acl_mailbox_list_get_backend(struct mailbox_list *list)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list);
+
+ return alist->rights.backend;
+}
+
+int acl_mailbox_list_have_right(struct mailbox_list *list, const char *name,
+ bool parent, unsigned int acl_storage_right_idx,
+ bool *can_see_r)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list);
+ struct acl_backend *backend = alist->rights.backend;
+ const unsigned int *idx_arr = alist->rights.acl_storage_right_idx;
+ struct acl_object *aclobj;
+ int ret, ret2;
+
+ if (alist->ignore_acls)
+ return 1;
+
+ aclobj = !parent ?
+ acl_object_init_from_name(backend, name) :
+ acl_object_init_from_parent(backend, name);
+ ret = acl_object_have_right(aclobj, idx_arr[acl_storage_right_idx]);
+
+ if (can_see_r != NULL) {
+ ret2 = acl_object_have_right(aclobj,
+ idx_arr[ACL_STORAGE_RIGHT_LOOKUP]);
+ if (ret2 < 0)
+ ret = -1;
+ *can_see_r = ret2 > 0;
+ }
+ acl_object_deinit(&aclobj);
+
+ if (ret < 0)
+ mailbox_list_set_internal_error(list);
+ return ret;
+}
+
+static void
+acl_mailbox_try_list_fast(struct mailbox_list_iterate_context *_ctx)
+{
+ struct acl_mailbox_list_iterate_context *ctx =
+ ACL_LIST_ITERATE_CONTEXT(_ctx);
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list);
+ struct acl_backend *backend = alist->rights.backend;
+ const unsigned int *idxp;
+ const struct acl_mask *acl_mask;
+ struct acl_mailbox_list_context *nonowner_list_ctx;
+ struct mail_namespace *ns = _ctx->list->ns;
+ struct mailbox_list_iter_update_context update_ctx;
+ const char *name;
+
+ if ((_ctx->flags & (MAILBOX_LIST_ITER_RAW_LIST |
+ MAILBOX_LIST_ITER_SELECT_SUBSCRIBED)) != 0)
+ return;
+
+ if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC) {
+ /* mailboxes in public namespace should all be listable to
+ someone. we don't benefit from fast listing. */
+ return;
+ }
+
+ /* If ACLs are ignored for this namespace don't try fast listing. */
+ if (alist->ignore_acls)
+ return;
+
+ /* if this namespace's default rights contain LOOKUP, we'll need to
+ go through all mailboxes in any case. */
+ idxp = alist->rights.acl_storage_right_idx + ACL_STORAGE_RIGHT_LOOKUP;
+ if (acl_backend_get_default_rights(backend, &acl_mask) < 0 ||
+ acl_cache_mask_isset(acl_mask, *idxp))
+ return;
+
+ /* no LOOKUP right by default, we can optimize this */
+ i_zero(&update_ctx);
+ update_ctx.iter_ctx = _ctx;
+ update_ctx.glob =
+ imap_match_init(pool_datastack_create(), "*",
+ (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0,
+ ctx->sep);
+ update_ctx.match_parents = TRUE;
+ update_ctx.tree_ctx = mailbox_tree_init(ctx->sep);
+
+ nonowner_list_ctx = acl_backend_nonowner_lookups_iter_init(backend);
+ while (acl_backend_nonowner_lookups_iter_next(nonowner_list_ctx,
+ &name)) {
+ T_BEGIN {
+ const char *vname =
+ mailbox_list_get_vname(ns->list, name);
+ mailbox_list_iter_update(&update_ctx, vname);
+ } T_END;
+ }
+
+ if (acl_backend_nonowner_lookups_iter_deinit(&nonowner_list_ctx) >= 0)
+ ctx->lookup_boxes = update_ctx.tree_ctx;
+ else
+ mailbox_tree_deinit(&update_ctx.tree_ctx);
+}
+
+static struct mailbox_list_iterate_context *
+acl_mailbox_list_iter_init_shared(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list);
+ struct mailbox_list_iterate_context *ctx;
+ int ret;
+
+ /* before listing anything add namespaces for all users
+ who may have visible mailboxes */
+ ret = acl_shared_namespaces_add(list->ns);
+
+ ctx = alist->module_ctx.super.iter_init(list, patterns, flags);
+ if (ret < 0)
+ ctx->failed = TRUE;
+ return ctx;
+}
+
+static struct mailbox_list_iterate_context *
+acl_mailbox_list_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list);
+ struct mailbox_list_iterate_context *_ctx;
+ struct acl_mailbox_list_iterate_context *ctx;
+ const char *p;
+ unsigned int i;
+
+ _ctx = alist->module_ctx.super.iter_init(list, patterns, flags);
+
+ ctx = p_new(_ctx->pool, struct acl_mailbox_list_iterate_context, 1);
+
+ if (list->ns->type != MAIL_NAMESPACE_TYPE_PRIVATE &&
+ (list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0) {
+ /* non-private namespace with subscriptions=yes. this could be
+ a site-global subscriptions file, so hide subscriptions for
+ mailboxes the user doesn't see. */
+ ctx->hide_nonlistable_subscriptions = TRUE;
+ }
+
+ ctx->sep = mail_namespace_get_sep(list->ns);
+ /* see if all patterns have only a single '*' and it's at the end.
+ we can use it to do some optimizations. */
+ ctx->simple_star_glob = TRUE;
+ for (i = 0; patterns[i] != NULL; i++) {
+ p = strchr(patterns[i], '*');
+ if (p == NULL || p[1] != '\0') {
+ ctx->simple_star_glob = FALSE;
+ break;
+ }
+ }
+
+ MODULE_CONTEXT_SET(_ctx, acl_mailbox_list_module, ctx);
+
+ /* Try to avoid reading ACLs from all mailboxes by getting a smaller
+ list of mailboxes that have even potential to be visible. If we
+ couldn't get such a list, we'll go through all mailboxes. */
+ T_BEGIN {
+ acl_mailbox_try_list_fast(_ctx);
+ } T_END;
+
+ return _ctx;
+}
+
+static const struct mailbox_info *
+acl_mailbox_list_iter_next_info(struct mailbox_list_iterate_context *_ctx)
+{
+ struct acl_mailbox_list_iterate_context *ctx =
+ ACL_LIST_ITERATE_CONTEXT(_ctx);
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list);
+ const struct mailbox_info *info;
+
+ for (;;) {
+ /* Normally the data stack frame is in mailbox_list_iter_next(),
+ but we're bypassing it here by calling super.iter_next()
+ directly. */
+ T_BEGIN {
+ info = alist->module_ctx.super.iter_next(_ctx);
+ } T_END;
+ if (info == NULL)
+ break;
+
+ /* if we've a list of mailboxes with LOOKUP rights, skip the
+ mailboxes not in the list (since we know they can't be
+ visible to us). */
+ if (ctx->lookup_boxes == NULL ||
+ mailbox_tree_lookup(ctx->lookup_boxes, info->vname) != NULL)
+ break;
+ e_debug(_ctx->list->ns->user->event,
+ "acl: Mailbox not in dovecot-acl-list: %s", info->vname);
+ }
+
+ return info;
+}
+
+static const char *
+acl_mailbox_list_iter_get_name(struct mailbox_list_iterate_context *ctx,
+ const char *vname)
+{
+ struct mail_namespace *ns = ctx->list->ns;
+ const char *name;
+ size_t len;
+
+ name = mailbox_list_get_storage_name(ns->list, vname);
+ len = strlen(name);
+ if (len > 0 && name[len-1] == mailbox_list_get_hierarchy_sep(ns->list)) {
+ /* name ends with separator. this can happen if doing e.g.
+ LIST "" foo/% and it lists "foo/". */
+ name = t_strndup(name, len-1);
+ }
+ return name;
+}
+
+static bool
+iter_is_listing_all_children(struct mailbox_list_iterate_context *_ctx)
+{
+ struct acl_mailbox_list_iterate_context *ctx =
+ ACL_LIST_ITERATE_CONTEXT(_ctx);
+ const char *child;
+
+ /* If all patterns (with '.' separator) are in "name*", "name.*" or
+ "%.*" style format, simple_star_glob=TRUE and we can easily test
+ this by simply checking if name/child mailbox matches. */
+ child = t_strdup_printf("%s%cx", ctx->info.vname, ctx->sep);
+ return ctx->simple_star_glob &&
+ imap_match(_ctx->glob, child) == IMAP_MATCH_YES;
+}
+
+static bool
+iter_mailbox_has_visible_children(struct mailbox_list_iterate_context *_ctx,
+ bool only_nonpatterns, bool subscribed)
+{
+ struct acl_mailbox_list_iterate_context *ctx =
+ ACL_LIST_ITERATE_CONTEXT(_ctx);
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ string_t *pattern;
+ const char *prefix;
+ size_t i, prefix_len;
+ bool stars = FALSE, ret = FALSE;
+
+ /* do we have child mailboxes with LOOKUP right that don't match
+ the list pattern? */
+ if (ctx->lookup_boxes != NULL) {
+ /* we have a list of mailboxes with LOOKUP rights. before
+ starting the slow list iteration, check check first
+ if there even are any children with LOOKUP rights. */
+ struct mailbox_node *node;
+
+ node = mailbox_tree_lookup(ctx->lookup_boxes, ctx->info.vname);
+ i_assert(node != NULL);
+ if (node->children == NULL)
+ return FALSE;
+ }
+
+ /* if mailbox name has '*' characters in it, they'll conflict with the
+ LIST wildcard. replace then with '%' and verify later that all
+ results have the correct prefix. */
+ pattern = t_str_new(128);
+ for (i = 0; ctx->info.vname[i] != '\0'; i++) {
+ if (ctx->info.vname[i] != '*')
+ str_append_c(pattern, ctx->info.vname[i]);
+ else {
+ stars = TRUE;
+ str_append_c(pattern, '%');
+ }
+ }
+ if (i > 0 && ctx->info.vname[i-1] != ctx->sep)
+ str_append_c(pattern, ctx->sep);
+ str_append_c(pattern, '*');
+ prefix = str_c(pattern);
+ prefix_len = str_len(pattern) - 1;
+
+ iter = mailbox_list_iter_init(_ctx->list, str_c(pattern),
+ (!subscribed ? 0 :
+ MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ if (only_nonpatterns &&
+ imap_match(_ctx->glob, info->vname) == IMAP_MATCH_YES) {
+ /* at least one child matches also the original list
+ patterns. we don't need to show this mailbox. */
+ ret = FALSE;
+ break;
+ }
+ if (!stars || strncmp(info->vname, prefix, prefix_len) == 0)
+ ret = TRUE;
+ }
+ (void)mailbox_list_iter_deinit(&iter);
+ return ret;
+}
+
+static int
+acl_mailbox_list_info_is_visible(struct mailbox_list_iterate_context *_ctx)
+{
+ struct acl_mailbox_list_iterate_context *ctx =
+ ACL_LIST_ITERATE_CONTEXT(_ctx);
+#define PRESERVE_MAILBOX_FLAGS (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED)
+ struct mailbox_info *info = &ctx->info;
+ const char *acl_name;
+ int ret;
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) {
+ /* skip ACL checks. */
+ return 1;
+ }
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0 &&
+ (_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 &&
+ !ctx->hide_nonlistable_subscriptions) {
+ /* don't waste time doing an ACL check. we're going to list
+ all subscriptions anyway. */
+ info->flags &= MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED;
+ return 1;
+ }
+
+ acl_name = acl_mailbox_list_iter_get_name(_ctx, info->vname);
+ ret = acl_mailbox_list_have_right(_ctx->list, acl_name, FALSE,
+ ACL_STORAGE_RIGHT_LOOKUP,
+ NULL);
+ if (ret != 0) {
+ if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0) {
+ /* don't waste time checking if there are visible
+ children, but also don't return incorrect flags */
+ info->flags &= ENUM_NEGATE(MAILBOX_CHILDREN);
+ } else if ((info->flags & MAILBOX_CHILDREN) != 0 &&
+ !iter_mailbox_has_visible_children(_ctx, FALSE, FALSE)) {
+ info->flags &= ENUM_NEGATE(MAILBOX_CHILDREN);
+ info->flags |= MAILBOX_NOCHILDREN;
+ }
+ return ret;
+ }
+
+ /* no permission to see this mailbox */
+ if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ /* we're listing subscribed mailboxes. this one or its child
+ is subscribed, so we'll need to list it. but since we don't
+ have LOOKUP right, we'll need to show it as nonexistent. */
+ i_assert((info->flags & PRESERVE_MAILBOX_FLAGS) != 0);
+ info->flags = MAILBOX_NONEXISTENT |
+ (info->flags & PRESERVE_MAILBOX_FLAGS);
+ if (ctx->hide_nonlistable_subscriptions) {
+ /* global subscriptions file. hide this entry if there
+ are no visible subscribed children or if we're going
+ to list the subscribed children anyway. */
+ if ((info->flags & MAILBOX_CHILD_SUBSCRIBED) == 0)
+ return 0;
+ if (iter_is_listing_all_children(_ctx) ||
+ !iter_mailbox_has_visible_children(_ctx, TRUE, TRUE))
+ return 0;
+ /* e.g. LSUB "" % with visible subscribed children */
+ }
+ return 1;
+ }
+
+ if (!iter_is_listing_all_children(_ctx) &&
+ iter_mailbox_has_visible_children(_ctx, TRUE, FALSE)) {
+ /* no child mailboxes match the list pattern(s), but mailbox
+ has visible children. we'll need to show this as
+ non-existent. */
+ info->flags = MAILBOX_NONEXISTENT | MAILBOX_CHILDREN |
+ (info->flags & PRESERVE_MAILBOX_FLAGS);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+acl_mailbox_list_iter_check_autocreate_acls(struct mailbox_list_iterate_context *_ctx)
+{
+ struct acl_mailbox_list_iterate_context *ctx =
+ ACL_LIST_ITERATE_CONTEXT(_ctx);
+ struct mailbox_settings *const *box_sets;
+ unsigned int i, count;
+ int ret;
+
+ ctx->autocreate_acls_checked = TRUE;
+ if (_ctx->autocreate_ctx == NULL)
+ return 0;
+ if ((_ctx->flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) {
+ /* skip ACL checks. */
+ return 0;
+ }
+
+ box_sets = array_get(&_ctx->autocreate_ctx->box_sets, &count);
+ i_assert(array_count(&_ctx->autocreate_ctx->boxes) == count);
+
+ for (i = 0; i < count; ) {
+ const char *acl_name =
+ acl_mailbox_list_iter_get_name(_ctx, box_sets[i]->name);
+ ret = acl_mailbox_list_have_right(_ctx->list, acl_name, FALSE,
+ ACL_STORAGE_RIGHT_LOOKUP,
+ NULL);
+ if (ret < 0)
+ return -1;
+ if (ret > 0)
+ i++;
+ else {
+ /* no list right - remove the whole autobox */
+ array_delete(&_ctx->autocreate_ctx->box_sets, i, 1);
+ array_delete(&_ctx->autocreate_ctx->boxes, i, 1);
+ box_sets = array_get(&_ctx->autocreate_ctx->box_sets, &count);
+ }
+ }
+ return 0;
+}
+
+static const struct mailbox_info *
+acl_mailbox_list_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct acl_mailbox_list_iterate_context *ctx =
+ ACL_LIST_ITERATE_CONTEXT(_ctx);
+ const struct mailbox_info *info;
+ int ret;
+
+ if (!ctx->autocreate_acls_checked) {
+ if (acl_mailbox_list_iter_check_autocreate_acls(_ctx) < 0) {
+ _ctx->failed = TRUE;
+ return NULL;
+ }
+ }
+
+ while ((info = acl_mailbox_list_iter_next_info(_ctx)) != NULL) {
+ ctx->info = *info;
+ T_BEGIN {
+ ret = acl_mailbox_list_info_is_visible(_ctx);
+ } T_END;
+ if (ret > 0)
+ break;
+ if (ret < 0) {
+ _ctx->failed = TRUE;
+ return NULL;
+ }
+ /* skip to next one */
+ e_debug(_ctx->list->ns->user->event,
+ "acl: No lookup right to mailbox: %s", info->vname);
+ }
+ return info == NULL ? NULL : &ctx->info;
+}
+
+static int
+acl_mailbox_list_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct acl_mailbox_list_iterate_context *ctx =
+ ACL_LIST_ITERATE_CONTEXT(_ctx);
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list);
+ int ret = _ctx->failed ? -1 : 0;
+
+ if (ctx->lookup_boxes != NULL)
+ mailbox_tree_deinit(&ctx->lookup_boxes);
+ if (alist->module_ctx.super.iter_deinit(_ctx) < 0)
+ ret = -1;
+ return ret;
+}
+
+static void acl_mailbox_list_deinit(struct mailbox_list *list)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list);
+
+ if (alist->rights.backend != NULL)
+ acl_backend_deinit(&alist->rights.backend);
+ alist->module_ctx.super.deinit(list);
+}
+
+static void acl_mailbox_list_init_shared(struct mailbox_list *list)
+{
+ struct acl_mailbox_list *alist;
+ struct mailbox_list_vfuncs *v = list->vlast;
+
+ alist = p_new(list->pool, struct acl_mailbox_list, 1);
+ alist->module_ctx.super = *v;
+ list->vlast = &alist->module_ctx.super;
+ v->deinit = acl_mailbox_list_deinit;
+ v->iter_init = acl_mailbox_list_iter_init_shared;
+
+ MODULE_CONTEXT_SET(list, acl_mailbox_list_module, alist);
+}
+
+static void acl_storage_rights_ctx_init(struct acl_storage_rights_context *ctx,
+ struct acl_backend *backend)
+{
+ unsigned int i;
+
+ ctx->backend = backend;
+ for (i = 0; i < ACL_STORAGE_RIGHT_COUNT; i++) {
+ ctx->acl_storage_right_idx[i] =
+ acl_backend_lookup_right(backend,
+ acl_storage_right_names[i]);
+ }
+}
+
+static bool acl_namespace_is_ignored(struct mailbox_list *list)
+{
+ const char *value =
+ mail_user_plugin_getenv(list->ns->user, "acl_ignore_namespace");
+ for (unsigned int i = 2; value != NULL; i++) {
+ if (wildcard_match(list->ns->prefix, value))
+ return TRUE;
+ value = mail_user_plugin_getenv(list->ns->user,
+ t_strdup_printf("acl_ignore_namespace%u", i));
+ }
+ return FALSE;
+}
+
+static void acl_mailbox_list_init_default(struct mailbox_list *list)
+{
+ struct mailbox_list_vfuncs *v = list->vlast;
+ struct acl_mailbox_list *alist;
+
+ if (list->mail_set->mail_full_filesystem_access) {
+ /* not necessarily, but safer to do this for now. */
+ i_fatal("mail_full_filesystem_access=yes is "
+ "incompatible with ACLs");
+ }
+
+ alist = p_new(list->pool, struct acl_mailbox_list, 1);
+ alist->module_ctx.super = *v;
+ list->vlast = &alist->module_ctx.super;
+ v->deinit = acl_mailbox_list_deinit;
+ v->iter_init = acl_mailbox_list_iter_init;
+ v->iter_next = acl_mailbox_list_iter_next;
+ v->iter_deinit = acl_mailbox_list_iter_deinit;
+ if (acl_namespace_is_ignored(list))
+ alist->ignore_acls = TRUE;
+
+ MODULE_CONTEXT_SET(list, acl_mailbox_list_module, alist);
+}
+
+void acl_mail_namespace_storage_added(struct mail_namespace *ns)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list);
+ struct acl_backend *backend;
+ const char *current_username, *owner_username;
+ bool owner = TRUE;
+
+ if (alist == NULL)
+ return;
+ struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(ns->user);
+
+ owner_username = ns->user->username;
+ current_username = auser->acl_user;
+ if (current_username == NULL)
+ current_username = owner_username;
+ else
+ owner = strcmp(current_username, owner_username) == 0;
+
+ /* We don't care about the username for non-private mailboxes.
+ It's used only when checking if we're the mailbox owner. We never
+ are for shared/public mailboxes. */
+ if (ns->type != MAIL_NAMESPACE_TYPE_PRIVATE)
+ owner = FALSE;
+
+ /* we need to know the storage when initializing backend */
+ backend = acl_backend_init(auser->acl_env, ns->list, current_username,
+ auser->groups, owner);
+ if (backend == NULL)
+ i_fatal("ACL backend initialization failed");
+ acl_storage_rights_ctx_init(&alist->rights, backend);
+}
+
+void acl_mailbox_list_created(struct mailbox_list *list)
+{
+ struct acl_user *auser = ACL_USER_CONTEXT(list->ns->user);
+
+ if (auser == NULL) {
+ /* ACLs disabled for this user */
+ } else if ((list->ns->flags & NAMESPACE_FLAG_NOACL) != 0) {
+ /* no ACL checks for internal namespaces (lda, shared) */
+ if (list->ns->type == MAIL_NAMESPACE_TYPE_SHARED)
+ acl_mailbox_list_init_shared(list);
+ } else if ((list->ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) {
+ /* this namespace is empty. don't attempt to lookup ACLs,
+ because they're not going to work anyway and we could
+ crash doing it. */
+ } else {
+ acl_mailbox_list_init_default(list);
+ }
+}
diff --git a/src/plugins/acl/acl-mailbox.c b/src/plugins/acl/acl-mailbox.c
new file mode 100644
index 0000000..134be7f
--- /dev/null
+++ b/src/plugins/acl/acl-mailbox.c
@@ -0,0 +1,714 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+/* FIXME: If we don't have permission to change flags/keywords, the changes
+ should still be stored temporarily for this session. However most clients
+ don't care and it's a huge job, so I currently this isn't done. The same
+ problem actually exists when opening read-only mailboxes. */
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "mailbox-list-private.h"
+#include "acl-api-private.h"
+#include "acl-plugin.h"
+
+#include <sys/stat.h>
+
+#define ACL_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, acl_mail_module)
+
+struct acl_transaction_context {
+ union mailbox_transaction_module_context module_ctx;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(acl_mail_module, &mail_module_register);
+static struct acl_transaction_context acl_transaction_failure;
+
+struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ return abox->aclobj;
+}
+
+int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ int ret;
+
+ if (abox->skip_acl_checks)
+ return 1;
+
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list);
+
+ /* If acls are ignored for this namespace do not check if
+ there are rights. */
+ if (alist->ignore_acls)
+ return 1;
+
+ ret = acl_object_have_right(abox->aclobj,
+ alist->rights.acl_storage_right_idx[right_idx]);
+ if (ret > 0)
+ return 1;
+ if (ret < 0) {
+ mail_storage_set_internal_error(box->storage);
+ return -1;
+ }
+
+ mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ return 0;
+}
+
+static bool acl_is_readonly(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ enum acl_storage_rights save_right;
+
+ if (abox->module_ctx.super.is_readonly(box))
+ return TRUE;
+
+ save_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ?
+ ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT;
+ if (acl_mailbox_right_lookup(box, save_right) > 0)
+ return FALSE;
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_EXPUNGE) > 0)
+ return FALSE;
+
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0)
+ return FALSE;
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0)
+ return FALSE;
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void acl_mailbox_free(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ if (abox->aclobj != NULL)
+ acl_object_deinit(&abox->aclobj);
+ abox->module_ctx.super.free(box);
+}
+
+static void acl_mailbox_copy_acls_from_parent(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list);
+ struct acl_object *parent_aclobj;
+ struct acl_object_list_iter *iter;
+ struct acl_rights_update update;
+
+ i_zero(&update);
+ update.modify_mode = ACL_MODIFY_MODE_REPLACE;
+ update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE;
+
+ parent_aclobj = acl_object_init_from_parent(alist->rights.backend,
+ box->name);
+ iter = acl_object_list_init(parent_aclobj);
+ while (acl_object_list_next(iter, &update.rights)) {
+ /* don't copy global ACL rights. */
+ if (!update.rights.global)
+ (void)acl_object_update(abox->aclobj, &update);
+ }
+ /* FIXME: Add error handling */
+ (void)acl_object_list_deinit(&iter);
+ acl_object_deinit(&parent_aclobj);
+}
+
+static int
+acl_mailbox_create(struct mailbox *box, const struct mailbox_update *update,
+ bool directory)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ int ret;
+
+ if (!mailbox_is_autocreated(box)) {
+ /* we're looking up CREATE permission from our parent's rights */
+ ret = acl_mailbox_list_have_right(box->list, box->name, TRUE,
+ ACL_STORAGE_RIGHT_CREATE, NULL);
+ } else {
+ /* mailbox is autocreated, so we need to treat it as if it
+ already exists. ignore the "create" ACL here. */
+ ret = 1;
+ }
+ if (ret <= 0) {
+ if (ret < 0) {
+ mail_storage_set_internal_error(box->storage);
+ return -1;
+ }
+ /* Note that if user didn't have LOOKUP permission to parent
+ mailbox, this may reveal the mailbox's existence to user.
+ Can't help it. */
+ mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ return -1;
+ }
+
+ /* ignore ACLs in this mailbox until creation is complete, because
+ super.create() may call e.g. mailbox_open() which will fail since
+ we haven't yet copied ACLs to this mailbox. */
+ abox->skip_acl_checks = TRUE;
+ ret = abox->module_ctx.super.create_box(box, update, directory);
+ abox->skip_acl_checks = FALSE;
+ /* update local acl object, otherwise with LAYOUT=INDEX, we end up
+ without local path to acl file, and copying fails. */
+ struct acl_backend *acl_be = abox->aclobj->backend;
+ acl_object_deinit(&abox->aclobj);
+ abox->aclobj = acl_object_init_from_name(acl_be, box->name);
+
+ if (ret == 0)
+ acl_mailbox_copy_acls_from_parent(box);
+ return ret;
+}
+
+static int
+acl_mailbox_update(struct mailbox *box, const struct mailbox_update *update)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ int ret;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN);
+ if (ret <= 0)
+ return -1;
+ return abox->module_ctx.super.update_box(box, update);
+}
+
+static void acl_mailbox_fail_not_found(struct mailbox *box)
+{
+ int ret;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP);
+ if (ret > 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ } else if (ret == 0) {
+ box->acl_no_lookup_right = TRUE;
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname));
+ }
+}
+
+static int
+acl_mailbox_delete(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ int ret;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_DELETE);
+ if (ret <= 0) {
+ if (ret == 0)
+ acl_mailbox_fail_not_found(box);
+ return -1;
+ }
+
+ return abox->module_ctx.super.delete_box(box);
+}
+
+static int
+acl_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(src);
+ int ret;
+
+ /* renaming requires rights to delete the old mailbox */
+ ret = acl_mailbox_right_lookup(src, ACL_STORAGE_RIGHT_DELETE);
+ if (ret <= 0) {
+ if (ret == 0)
+ acl_mailbox_fail_not_found(src);
+ return -1;
+ }
+
+ /* and create the new one under the parent mailbox */
+ T_BEGIN {
+ ret = acl_mailbox_list_have_right(dest->list, dest->name, TRUE,
+ ACL_STORAGE_RIGHT_CREATE, NULL);
+ } T_END;
+
+ if (ret <= 0) {
+ if (ret == 0) {
+ /* Note that if the mailbox didn't have LOOKUP
+ permission, this now reveals to user the mailbox's
+ existence. Can't help it. */
+ mail_storage_set_error(src->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ } else {
+ mail_storage_set_internal_error(src->storage);
+ }
+ return -1;
+ }
+
+ return abox->module_ctx.super.rename_box(src, dest);
+}
+
+static int
+acl_get_write_rights(struct mailbox *box,
+ bool *flags_r, bool *flag_seen_r, bool *flag_del_r)
+{
+ int ret;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
+ if (ret < 0)
+ return -1;
+ *flags_r = ret > 0;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN);
+ if (ret < 0)
+ return -1;
+ *flag_seen_r = ret > 0;
+
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED);
+ if (ret < 0)
+ return -1;
+ *flag_del_r = ret > 0;
+ return 0;
+}
+
+static void acl_transaction_set_failure(struct mailbox_transaction_context *t)
+{
+ MODULE_CONTEXT_SET(t, acl_storage_module,
+ &acl_transaction_failure);
+}
+
+static void
+acl_mail_update_flags(struct mail *_mail, enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
+ bool acl_flags, acl_flag_seen, acl_flag_del;
+
+ if (acl_get_write_rights(_mail->box, &acl_flags, &acl_flag_seen,
+ &acl_flag_del) < 0) {
+ acl_transaction_set_failure(_mail->transaction);
+ return;
+ }
+
+ if (modify_type != MODIFY_REPLACE) {
+ /* adding/removing flags. just remove the disallowed
+ flags from the mask. */
+ if (!acl_flags)
+ flags &= MAIL_SEEN | MAIL_DELETED;
+ if (!acl_flag_seen)
+ flags &= ENUM_NEGATE(MAIL_SEEN);
+ if (!acl_flag_del)
+ flags &= ENUM_NEGATE(MAIL_DELETED);
+ } else if (!acl_flags || !acl_flag_seen || !acl_flag_del) {
+ /* we don't have permission to replace all the flags. */
+ if (!acl_flags && !acl_flag_seen && !acl_flag_del) {
+ /* no flag changes allowed. ignore silently. */
+ return;
+ }
+
+ /* handle this by first removing the allowed flags and
+ then adding the allowed flags */
+ acl_mail_update_flags(_mail, MODIFY_REMOVE,
+ ENUM_NEGATE(flags));
+ if (flags != 0)
+ acl_mail_update_flags(_mail, MODIFY_ADD, flags);
+ return;
+ }
+
+ amail->super.update_flags(_mail, modify_type, flags);
+}
+
+static void
+acl_mail_update_keywords(struct mail *_mail, enum modify_type modify_type,
+ struct mail_keywords *keywords)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
+ int ret;
+
+ ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_WRITE);
+ if (ret <= 0) {
+ /* if we don't have permission, just silently return success. */
+ if (ret < 0)
+ acl_transaction_set_failure(_mail->transaction);
+ return;
+ }
+
+ amail->super.update_keywords(_mail, modify_type, keywords);
+}
+
+static void acl_mail_expunge(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
+ int ret;
+
+ ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_EXPUNGE);
+ if (ret <= 0) {
+ /* if we don't have permission, silently return success so
+ users won't see annoying error messages in case their
+ clients try automatic expunging. */
+ acl_transaction_set_failure(_mail->transaction);
+ return;
+ }
+
+ amail->super.expunge(_mail);
+}
+
+void acl_mail_allocated(struct mail *_mail)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT(_mail->box);
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ union mail_module_context *amail;
+
+ if (abox == NULL || !abox->acl_enabled)
+ return;
+
+ amail = p_new(mail->pool, union mail_module_context, 1);
+ amail->super = *v;
+ mail->vlast = &amail->super;
+
+ v->update_flags = acl_mail_update_flags;
+ v->update_keywords = acl_mail_update_keywords;
+ v->expunge = acl_mail_expunge;
+ MODULE_CONTEXT_SET_SELF(mail, acl_mail_module, amail);
+}
+
+static int
+acl_save_get_flags(struct mailbox *box, enum mail_flags *flags,
+ enum mail_flags *pvt_flags, struct mail_keywords **keywords)
+{
+ bool acl_flags, acl_flag_seen, acl_flag_del;
+
+ if (acl_get_write_rights(box, &acl_flags, &acl_flag_seen,
+ &acl_flag_del) < 0)
+ return -1;
+
+ if (!acl_flag_seen) {
+ *flags &= ENUM_NEGATE(MAIL_SEEN);
+ *pvt_flags &= ENUM_NEGATE(MAIL_SEEN);
+ }
+ if (!acl_flag_del) {
+ *flags &= ENUM_NEGATE(MAIL_DELETED);
+ *pvt_flags &= ENUM_NEGATE(MAIL_DELETED);
+ }
+ if (!acl_flags) {
+ *flags &= MAIL_SEEN | MAIL_DELETED;
+ *pvt_flags &= MAIL_SEEN | MAIL_DELETED;
+ *keywords = NULL;
+ }
+ return 0;
+}
+
+static int
+acl_save_begin(struct mail_save_context *ctx, struct istream *input)
+{
+ struct mailbox *box = ctx->transaction->box;
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ enum acl_storage_rights save_right;
+
+ save_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ?
+ ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT;
+ if (acl_mailbox_right_lookup(box, save_right) <= 0)
+ return -1;
+ if (acl_save_get_flags(box, &ctx->data.flags,
+ &ctx->data.pvt_flags, &ctx->data.keywords) < 0)
+ return -1;
+
+ return abox->module_ctx.super.save_begin(ctx, input);
+}
+
+static bool
+acl_copy_has_rights(struct mail_save_context *ctx, struct mail *mail)
+{
+ struct mailbox *destbox = ctx->transaction->box;
+ enum acl_storage_rights save_right;
+
+ if (ctx->moving) {
+ if (acl_mailbox_right_lookup(mail->box,
+ ACL_STORAGE_RIGHT_EXPUNGE) <= 0)
+ return FALSE;
+ }
+
+ save_right = (destbox->flags & MAILBOX_FLAG_POST_SESSION) != 0 ?
+ ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT;
+ if (acl_mailbox_right_lookup(destbox, save_right) <= 0)
+ return FALSE;
+ if (acl_save_get_flags(destbox, &ctx->data.flags,
+ &ctx->data.pvt_flags, &ctx->data.keywords) < 0)
+ return FALSE;
+ return TRUE;
+}
+
+static int
+acl_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+ struct mailbox_transaction_context *t = ctx->transaction;
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(t->box);
+
+ if (!acl_copy_has_rights(ctx, mail)) {
+ mailbox_save_cancel(&ctx);
+ return -1;
+ }
+
+ return abox->module_ctx.super.copy(ctx, mail);
+}
+
+static int
+acl_transaction_commit(struct mailbox_transaction_context *ctx,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(ctx->box);
+ void *at = ACL_CONTEXT(ctx);
+ int ret;
+
+ if (at != NULL) {
+ abox->module_ctx.super.transaction_rollback(ctx);
+ return -1;
+ }
+
+ ret = abox->module_ctx.super.transaction_commit(ctx, changes_r);
+ if (abox->no_read_right) {
+ /* don't allow IMAP client to see what UIDs the messages got */
+ changes_r->no_read_perm = TRUE;
+ }
+ return ret;
+}
+
+static int acl_mailbox_exists(struct mailbox *box, bool auto_boxes,
+ enum mailbox_existence *existence_r)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ const char *const *rights;
+ unsigned int i;
+
+ if (acl_object_get_my_rights(abox->aclobj, pool_datastack_create(), &rights) < 0) {
+ mail_storage_set_internal_error(box->storage);
+ return -1;
+ }
+
+ /* for now this is used only by IMAP SUBSCRIBE. we'll intentionally
+ violate RFC 4314 here, because it says SUBSCRIBE should succeed only
+ when mailbox has 'l' right. But there's no point in not allowing
+ a subscribe for a mailbox that can be selected anyway. Just the
+ opposite: subscribing to such mailboxes is a very useful feature. */
+ for (i = 0; rights[i] != NULL; i++) {
+ if (strcmp(rights[i], MAIL_ACL_LOOKUP) == 0 ||
+ strcmp(rights[i], MAIL_ACL_READ) == 0 ||
+ strcmp(rights[i], MAIL_ACL_INSERT) == 0)
+ return abox->module_ctx.super.exists(box, auto_boxes,
+ existence_r);
+ }
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+ return 0;
+}
+
+bool acl_mailbox_have_extra_attribute_rights(struct mailbox *box)
+{
+ /* RFC 5464:
+
+ When the ACL extension [RFC4314] is present, users can only set and
+ retrieve private or shared mailbox annotations on a mailbox on which
+ they have the "l" right and any one of the "r", "s", "w", "i", or "p"
+ rights.
+ */
+ const enum acl_storage_rights check_rights[] = {
+ ACL_STORAGE_RIGHT_READ,
+ ACL_STORAGE_RIGHT_WRITE_SEEN,
+ ACL_STORAGE_RIGHT_WRITE,
+ ACL_STORAGE_RIGHT_INSERT,
+ ACL_STORAGE_RIGHT_POST,
+ };
+ for (unsigned int i = 0; i < N_ELEMENTS(check_rights); i++) {
+ int ret = acl_mailbox_right_lookup(box, check_rights[i]);
+ if (ret > 0)
+ return TRUE;
+ if (ret < 0) {
+ /* unexpected error - stop checking further */
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+static int acl_mailbox_open_check_acl(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list);
+ const unsigned int *idx_arr = alist->rights.acl_storage_right_idx;
+ enum acl_storage_rights open_right;
+ int ret;
+
+ /* mailbox can be opened either for reading or appending new messages */
+ if ((box->flags & MAILBOX_FLAG_IGNORE_ACLS) != 0 ||
+ (box->list->ns->flags & NAMESPACE_FLAG_NOACL) != 0 ||
+ abox->skip_acl_checks)
+ return 0;
+
+ if ((box->flags & MAILBOX_FLAG_SAVEONLY) != 0) {
+ open_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ?
+ ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT;
+ } else if (box->deleting) {
+ open_right = ACL_STORAGE_RIGHT_DELETE;
+ } else if ((box->flags & MAILBOX_FLAG_ATTRIBUTE_SESSION) != 0) {
+ /* GETMETADATA/SETMETADATA requires "l" right and another one
+ which is checked afterwards. */
+ open_right = ACL_STORAGE_RIGHT_LOOKUP;
+ } else {
+ open_right = ACL_STORAGE_RIGHT_READ;
+ }
+
+ ret = acl_object_have_right(abox->aclobj, idx_arr[open_right]);
+ if (ret <= 0) {
+ if (ret == 0) {
+ /* no access. */
+ acl_mailbox_fail_not_found(box);
+ }
+ return -1;
+ }
+ if (open_right != ACL_STORAGE_RIGHT_READ) {
+ ret = acl_object_have_right(abox->aclobj,
+ idx_arr[ACL_STORAGE_RIGHT_READ]);
+ if (ret < 0)
+ return -1;
+ if (ret == 0)
+ abox->no_read_right = TRUE;
+ }
+ if ((box->flags & MAILBOX_FLAG_ATTRIBUTE_SESSION) != 0) {
+ if (!acl_mailbox_have_extra_attribute_rights(box))
+ return -1;
+ }
+ return 0;
+}
+
+static int acl_mailbox_open(struct mailbox *box)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ if (acl_mailbox_open_check_acl(box) < 0)
+ return -1;
+
+ return abox->module_ctx.super.open(box);
+}
+
+static int acl_mailbox_get_status(struct mailbox *box,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
+
+ if (abox->module_ctx.super.get_status(box, items, status_r) < 0)
+ return -1;
+
+ if ((items & STATUS_PERMANENT_FLAGS) != 0) {
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) <= 0) {
+ status_r->permanent_flags &= MAIL_DELETED|MAIL_SEEN;
+ status_r->permanent_keywords = FALSE;
+ status_r->allow_new_keywords = FALSE;
+ }
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) <= 0)
+ status_r->permanent_flags &= ENUM_NEGATE(MAIL_DELETED);
+ if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) <= 0)
+ status_r->permanent_flags &= ENUM_NEGATE(MAIL_SEEN);
+ }
+ return 0;
+}
+
+void acl_mailbox_allocated(struct mailbox *box)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(box->list);
+ struct mailbox_vfuncs *v = box->vlast;
+ struct acl_mailbox *abox;
+ bool ignore_acls = (box->flags & MAILBOX_FLAG_IGNORE_ACLS) != 0;
+
+ if (alist == NULL) {
+ /* ACLs disabled */
+ return;
+ }
+
+ if (mail_namespace_is_shared_user_root(box->list->ns) || alist->ignore_acls) {
+ /* this is the root shared namespace, which itself doesn't
+ have any existing mailboxes. */
+ ignore_acls = TRUE;
+ }
+
+ abox = p_new(box->pool, struct acl_mailbox, 1);
+ abox->module_ctx.super = *v;
+ box->vlast = &abox->module_ctx.super;
+ /* aclobj can be used for setting ACLs, even when mailbox is opened
+ with IGNORE_ACLS flag */
+ if (alist->rights.backend != NULL)
+ abox->aclobj = acl_object_init_from_name(alist->rights.backend,
+ mailbox_get_name(box));
+ else
+ i_assert(ignore_acls);
+
+ v->free = acl_mailbox_free;
+ if (!ignore_acls) {
+ abox->acl_enabled = TRUE;
+ v->is_readonly = acl_is_readonly;
+ v->exists = acl_mailbox_exists;
+ v->open = acl_mailbox_open;
+ v->get_status = acl_mailbox_get_status;
+ v->create_box = acl_mailbox_create;
+ v->update_box = acl_mailbox_update;
+ v->delete_box = acl_mailbox_delete;
+ v->rename_box = acl_mailbox_rename;
+ v->save_begin = acl_save_begin;
+ v->copy = acl_copy;
+ v->transaction_commit = acl_transaction_commit;
+ v->attribute_set = acl_attribute_set;
+ v->attribute_get = acl_attribute_get;
+ v->attribute_iter_init = acl_attribute_iter_init;
+ v->attribute_iter_next = acl_attribute_iter_next;
+ v->attribute_iter_deinit = acl_attribute_iter_deinit;
+ }
+ MODULE_CONTEXT_SET(box, acl_storage_module, abox);
+}
+
+static bool
+acl_mailbox_update_removed_id(struct acl_object *aclobj,
+ const struct acl_rights_update *update)
+{
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+
+ if (update->modify_mode != ACL_MODIFY_MODE_CLEAR &&
+ update->neg_modify_mode != ACL_MODIFY_MODE_CLEAR)
+ return FALSE;
+ if (update->modify_mode == ACL_MODIFY_MODE_CLEAR &&
+ update->neg_modify_mode == ACL_MODIFY_MODE_CLEAR)
+ return TRUE;
+
+ /* mixed clear/non-clear. see if the identifier exists anymore */
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ if (rights.id_type == update->rights.id_type &&
+ null_strcmp(rights.identifier, update->rights.identifier) == 0)
+ break;
+ }
+ return acl_object_list_deinit(&iter) >= 0;
+}
+
+int acl_mailbox_update_acl(struct mailbox_transaction_context *t,
+ const struct acl_rights_update *update)
+{
+ struct acl_object *aclobj;
+ const char *key;
+ time_t ts = update->last_change != 0 ?
+ update->last_change : ioloop_time;
+
+ key = t_strdup_printf(MAILBOX_ATTRIBUTE_PREFIX_ACL"%s",
+ acl_rights_get_id(&update->rights));
+ aclobj = acl_mailbox_get_aclobj(t->box);
+ if (acl_object_update(aclobj, update) < 0) {
+ mailbox_set_critical(t->box, "Failed to set ACL");
+ return -1;
+ }
+
+ /* FIXME: figure out some value lengths, so maybe some day
+ quota could apply to ACLs as well. */
+ if (acl_mailbox_update_removed_id(aclobj, update))
+ mail_index_attribute_unset(t->itrans, FALSE, key, ts);
+ else
+ mail_index_attribute_set(t->itrans, FALSE, key, ts, 0);
+ return 0;
+}
diff --git a/src/plugins/acl/acl-plugin.c b/src/plugins/acl/acl-plugin.c
new file mode 100644
index 0000000..b446dcd
--- /dev/null
+++ b/src/plugins/acl/acl-plugin.c
@@ -0,0 +1,27 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mailbox-list-private.h"
+#include "acl-api.h"
+#include "acl-plugin.h"
+
+
+const char *acl_plugin_version = DOVECOT_ABI_VERSION;
+
+static struct mail_storage_hooks acl_mail_storage_hooks = {
+ .mail_user_created = acl_mail_user_created,
+ .mailbox_list_created = acl_mailbox_list_created,
+ .mail_namespace_storage_added = acl_mail_namespace_storage_added,
+ .mailbox_allocated = acl_mailbox_allocated,
+ .mail_allocated = acl_mail_allocated
+};
+
+void acl_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &acl_mail_storage_hooks);
+}
+
+void acl_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&acl_mail_storage_hooks);
+}
diff --git a/src/plugins/acl/acl-plugin.h b/src/plugins/acl/acl-plugin.h
new file mode 100644
index 0000000..6acfe89
--- /dev/null
+++ b/src/plugins/acl/acl-plugin.h
@@ -0,0 +1,73 @@
+#ifndef ACL_PLUGIN_H
+#define ACL_PLUGIN_H
+
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "acl-storage.h"
+
+#define ACL_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, acl_storage_module)
+#define ACL_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, acl_storage_module)
+#define ACL_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, acl_mailbox_list_module)
+#define ACL_LIST_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, acl_mailbox_list_module)
+#define ACL_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, acl_user_module)
+#define ACL_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, acl_user_module)
+
+struct acl_user {
+ union mail_user_module_context module_ctx;
+
+ const char *acl_user;
+ const char *acl_env;
+ const char *const *groups;
+
+ struct acl_lookup_dict *acl_lookup_dict;
+};
+
+struct acl_storage_rights_context {
+ struct acl_backend *backend;
+ unsigned int acl_storage_right_idx[ACL_STORAGE_RIGHT_COUNT];
+};
+
+struct acl_mailbox_list {
+ union mailbox_list_module_context module_ctx;
+ struct acl_storage_rights_context rights;
+
+ time_t last_shared_add_check;
+ bool ignore_acls;
+};
+
+struct acl_mailbox {
+ union mailbox_module_context module_ctx;
+ struct acl_object *aclobj;
+ bool skip_acl_checks;
+ bool acl_enabled;
+ bool no_read_right;
+};
+
+extern MODULE_CONTEXT_DEFINE(acl_storage_module, &mail_storage_module_register);
+extern MODULE_CONTEXT_DEFINE(acl_user_module, &mail_user_module_register);
+extern MODULE_CONTEXT_DEFINE(acl_mailbox_list_module,
+ &mailbox_list_module_register);
+
+void acl_mailbox_list_created(struct mailbox_list *list);
+void acl_mail_namespace_storage_added(struct mail_namespace *ns);
+void acl_mail_user_created(struct mail_user *list);
+
+void acl_mailbox_allocated(struct mailbox *box);
+void acl_mail_allocated(struct mail *mail);
+
+struct acl_backend *acl_mailbox_list_get_backend(struct mailbox_list *list);
+int acl_mailbox_list_have_right(struct mailbox_list *list, const char *name,
+ bool parent, unsigned int acl_storage_right_idx,
+ bool *can_see_r) ATTR_NULL(5);
+
+void acl_plugin_init(struct module *module);
+void acl_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/acl/acl-shared-storage.c b/src/plugins/acl/acl-shared-storage.c
new file mode 100644
index 0000000..1329cd3
--- /dev/null
+++ b/src/plugins/acl/acl-shared-storage.c
@@ -0,0 +1,103 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "str.h"
+#include "var-expand.h"
+#include "acl-plugin.h"
+#include "acl-lookup-dict.h"
+#include "acl-shared-storage.h"
+#include "index/shared/shared-storage.h"
+
+#define SHARED_NS_RETRY_SECS (60*60)
+
+static bool acl_ns_prefix_exists(struct mail_namespace *ns)
+{
+ struct mailbox *box;
+ const char *vname;
+ enum mailbox_existence existence;
+ bool ret;
+
+ if (ns->list->mail_set->mail_shared_explicit_inbox)
+ return FALSE;
+
+ vname = t_strndup(ns->prefix, ns->prefix_len-1);
+ box = mailbox_alloc(ns->list, vname, 0);
+ ret = mailbox_exists(box, FALSE, &existence) == 0 &&
+ existence == MAILBOX_EXISTENCE_SELECT;
+ mailbox_free(&box);
+ return ret;
+}
+
+static void
+acl_shared_namespace_add(struct mail_namespace *ns,
+ struct mail_storage *storage, const char *userdomain)
+{
+ struct shared_storage *sstorage = (struct shared_storage *)storage;
+ struct mail_namespace *new_ns = ns;
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ const char *mailbox, *error;
+ string_t *str;
+
+ if (strcmp(ns->user->username, userdomain) == 0) {
+ /* skip ourself */
+ return;
+ }
+
+ const struct var_expand_table tab[] = {
+ { 'u', userdomain, "user" },
+ { 'n', t_strcut(userdomain, '@'), "username" },
+ { 'd', i_strchr_to_next(userdomain, '@'), "domain" },
+ { '\0', NULL, NULL }
+ };
+
+ str = t_str_new(128);
+ if (var_expand(str, sstorage->ns_prefix_pattern, tab, &error) <= 0) {
+ i_error("Failed to expand namespace prefix %s: %s",
+ sstorage->ns_prefix_pattern, error);
+ return;
+ }
+ mailbox = str_c(str);
+ if (shared_storage_get_namespace(&new_ns, &mailbox) < 0)
+ return;
+
+ /* check if there are any mailboxes really visible to us */
+ iter = mailbox_list_iter_init(new_ns->list, "*",
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ info = mailbox_list_iter_next(iter);
+ (void)mailbox_list_iter_deinit(&iter);
+
+ if (info == NULL && !acl_ns_prefix_exists(new_ns)) {
+ /* no visible mailboxes, remove the namespace */
+ mail_namespace_destroy(new_ns);
+ }
+}
+
+int acl_shared_namespaces_add(struct mail_namespace *ns)
+{
+ struct acl_user *auser = ACL_USER_CONTEXT(ns->user);
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list);
+ struct mail_storage *storage = mail_namespace_get_default_storage(ns);
+ struct acl_lookup_dict_iter *iter;
+ const char *name;
+
+ i_assert(auser != NULL && alist != NULL);
+ i_assert(ns->type == MAIL_NAMESPACE_TYPE_SHARED);
+ i_assert(strcmp(storage->name, MAIL_SHARED_STORAGE_NAME) == 0);
+
+ if (ioloop_time < alist->last_shared_add_check + SHARED_NS_RETRY_SECS) {
+ /* already added, don't bother rechecking */
+ return 0;
+ }
+ alist->last_shared_add_check = ioloop_time;
+
+ iter = acl_lookup_dict_iterate_visible_init(auser->acl_lookup_dict);
+ while ((name = acl_lookup_dict_iterate_visible_next(iter)) != NULL) {
+ T_BEGIN {
+ acl_shared_namespace_add(ns, storage, name);
+ } T_END;
+ }
+ return acl_lookup_dict_iterate_visible_deinit(&iter);
+}
diff --git a/src/plugins/acl/acl-shared-storage.h b/src/plugins/acl/acl-shared-storage.h
new file mode 100644
index 0000000..6e7736b
--- /dev/null
+++ b/src/plugins/acl/acl-shared-storage.h
@@ -0,0 +1,6 @@
+#ifndef ACL_SHARED_STORAGE_H
+#define ACL_SHARED_STORAGE_H
+
+int acl_shared_namespaces_add(struct mail_namespace *ns);
+
+#endif
diff --git a/src/plugins/acl/acl-storage.c b/src/plugins/acl/acl-storage.c
new file mode 100644
index 0000000..988c0b8
--- /dev/null
+++ b/src/plugins/acl/acl-storage.c
@@ -0,0 +1,62 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "mail-namespace.h"
+#include "mailbox-list-private.h"
+#include "acl-api-private.h"
+#include "acl-lookup-dict.h"
+#include "acl-plugin.h"
+
+
+struct acl_storage_module acl_storage_module =
+ MODULE_CONTEXT_INIT(&mail_storage_module_register);
+struct acl_user_module acl_user_module =
+ MODULE_CONTEXT_INIT(&mail_user_module_register);
+
+static void acl_user_deinit(struct mail_user *user)
+{
+ struct acl_user *auser = ACL_USER_CONTEXT(user);
+
+ i_assert(auser != NULL);
+ acl_lookup_dict_deinit(&auser->acl_lookup_dict);
+ auser->module_ctx.super.deinit(user);
+}
+
+static void acl_mail_user_create(struct mail_user *user, const char *env)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct acl_user *auser;
+
+ auser = p_new(user->pool, struct acl_user, 1);
+ auser->module_ctx.super = *v;
+ user->vlast = &auser->module_ctx.super;
+ v->deinit = acl_user_deinit;
+ auser->acl_lookup_dict = acl_lookup_dict_init(user);
+
+ auser->acl_env = env;
+ auser->acl_user = mail_user_plugin_getenv(user, "acl_user");
+ if (auser->acl_user == NULL)
+ auser->acl_user = mail_user_plugin_getenv(user, "master_user");
+
+ env = mail_user_plugin_getenv(user, "acl_groups");
+ if (env != NULL) {
+ auser->groups =
+ (const char *const *)p_strsplit(user->pool, env, ",");
+ }
+
+ MODULE_CONTEXT_SET(user, acl_user_module, auser);
+}
+
+void acl_mail_user_created(struct mail_user *user)
+{
+ const char *env;
+
+ env = mail_user_plugin_getenv(user, "acl");
+ if (env != NULL && *env != '\0')
+ acl_mail_user_create(user, env);
+ else {
+ e_debug(user->event, "acl: No acl setting - ACLs are disabled");
+ }
+}
diff --git a/src/plugins/acl/acl-storage.h b/src/plugins/acl/acl-storage.h
new file mode 100644
index 0000000..8be4c26
--- /dev/null
+++ b/src/plugins/acl/acl-storage.h
@@ -0,0 +1,50 @@
+#ifndef ACL_STORAGE_H
+#define ACL_STORAGE_H
+
+#include "mail-storage.h"
+
+struct acl_rights_update;
+
+enum acl_storage_rights {
+ ACL_STORAGE_RIGHT_LOOKUP,
+ ACL_STORAGE_RIGHT_READ,
+ ACL_STORAGE_RIGHT_WRITE,
+ ACL_STORAGE_RIGHT_WRITE_SEEN,
+ ACL_STORAGE_RIGHT_WRITE_DELETED,
+ ACL_STORAGE_RIGHT_INSERT,
+ ACL_STORAGE_RIGHT_POST,
+ ACL_STORAGE_RIGHT_EXPUNGE,
+ ACL_STORAGE_RIGHT_CREATE,
+ ACL_STORAGE_RIGHT_DELETE,
+ ACL_STORAGE_RIGHT_ADMIN,
+
+ ACL_STORAGE_RIGHT_COUNT
+};
+
+/* Returns acl_object for the given mailbox. */
+struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box);
+/* Returns 1 if we have the requested right. If not, returns 0 and sets storage
+ error to MAIL_ERROR_PERM. Returns -1 if internal error occurred and also
+ sets storage error. */
+int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx);
+
+/* Returns TRUE if mailbox has the necessary extra ACL for accessing
+ attributes. The caller must have checked the LOOKUP right already. */
+bool acl_mailbox_have_extra_attribute_rights(struct mailbox *box);
+
+int acl_mailbox_update_acl(struct mailbox_transaction_context *t,
+ const struct acl_rights_update *update);
+
+int acl_attribute_set(struct mailbox_transaction_context *t,
+ enum mail_attribute_type type, const char *key,
+ const struct mail_attribute_value *value);
+int acl_attribute_get(struct mailbox *box,
+ enum mail_attribute_type type, const char *key,
+ struct mail_attribute_value *value_r);
+struct mailbox_attribute_iter *
+acl_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type,
+ const char *prefix);
+const char *acl_attribute_iter_next(struct mailbox_attribute_iter *iter);
+int acl_attribute_iter_deinit(struct mailbox_attribute_iter *iter);
+
+#endif
diff --git a/src/plugins/acl/doveadm-acl.c b/src/plugins/acl/doveadm-acl.c
new file mode 100644
index 0000000..0ab416a
--- /dev/null
+++ b/src/plugins/acl/doveadm-acl.c
@@ -0,0 +1,629 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "module-dir.h"
+#include "imap-util.h"
+#include "acl-plugin.h"
+#include "acl-api-private.h"
+#include "acl-lookup-dict.h"
+#include "doveadm-print.h"
+#include "doveadm-mail.h"
+
+struct doveadm_acl_cmd_context {
+ struct doveadm_mail_cmd_context ctx;
+ bool get_match_me;
+ enum acl_modify_mode modify_mode;
+};
+
+const char *doveadm_acl_plugin_version = DOVECOT_ABI_VERSION;
+
+void doveadm_acl_plugin_init(struct module *module);
+void doveadm_acl_plugin_deinit(void);
+
+static int
+cmd_acl_mailbox_open(struct doveadm_mail_cmd_context *ctx,
+ struct mail_user *user, const char *mailbox,
+ struct mailbox **box_r)
+{
+ struct acl_user *auser = ACL_USER_CONTEXT(user);
+ struct mail_namespace *ns;
+ struct mailbox *box;
+
+ if (auser == NULL) {
+ i_error("ACL not enabled for %s", user->username);
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ return -1;
+ }
+
+ ns = mail_namespace_find(user->namespaces, mailbox);
+ box = mailbox_alloc(ns->list, mailbox,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
+ if (mailbox_open(box) < 0) {
+ i_error("Can't open mailbox %s: %s", mailbox,
+ mailbox_get_last_internal_error(box, NULL));
+ doveadm_mail_failed_mailbox(ctx, box);
+ mailbox_free(&box);
+ return -1;
+ }
+ *box_r = box;
+ return 0;
+}
+
+static void cmd_acl_get_right(const struct acl_rights *rights)
+{
+ string_t *str;
+
+ doveadm_print(acl_rights_get_id(rights));
+
+ if (rights->global)
+ doveadm_print("global");
+ else
+ doveadm_print("");
+
+ str = t_str_new(256);
+ if (rights->rights != NULL)
+ str_append(str, t_strarray_join(rights->rights, " "));
+ if (rights->neg_rights != NULL) {
+ if (str_len(str) > 0)
+ str_append_c(str, ' ');
+ str_append_c(str, '-');
+ str_append(str, t_strarray_join(rights->neg_rights, " -"));
+ }
+ doveadm_print(str_c(str));
+}
+
+static int cmd_acl_get_mailbox(struct doveadm_acl_cmd_context *ctx,
+ struct mailbox *box)
+{
+ struct acl_object *aclobj = acl_mailbox_get_aclobj(box);
+ struct acl_backend *backend;
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+ int ret;
+
+ backend = acl_mailbox_list_get_backend(box->list);
+
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) T_BEGIN {
+ if (!ctx->get_match_me ||
+ acl_backend_rights_match_me(backend, &rights))
+ cmd_acl_get_right(&rights);
+ } T_END;
+
+ if ((ret = acl_object_list_deinit(&iter))<0) {
+ i_error("ACL iteration failed");
+ doveadm_mail_failed_error(&ctx->ctx, MAIL_ERROR_TEMP);
+ }
+ return ret;
+}
+
+static int
+cmd_acl_get_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct doveadm_acl_cmd_context *ctx =
+ (struct doveadm_acl_cmd_context *)_ctx;
+ const char *mailbox = _ctx->args[0];
+ struct mailbox *box;
+ int ret;
+
+ if (cmd_acl_mailbox_open(_ctx, user, mailbox, &box) < 0)
+ return -1;
+
+ ret = cmd_acl_get_mailbox(ctx, box);
+ mailbox_free(&box);
+ return ret;
+}
+
+static bool cmd_acl_get_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+ struct doveadm_acl_cmd_context *ctx =
+ (struct doveadm_acl_cmd_context *)_ctx;
+
+ switch (c) {
+ case 'm':
+ ctx->get_match_me = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void cmd_acl_get_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[])
+{
+ if (args[0] == NULL)
+ doveadm_mail_help_name("acl get");
+ doveadm_print_header("id", "ID", 0);
+ doveadm_print_header("global", "Global", 0);
+ doveadm_print_header("rights", "Rights", 0);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_acl_get_alloc(void)
+{
+ struct doveadm_acl_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_acl_cmd_context);
+ ctx->ctx.getopt_args = "m";
+ ctx->ctx.v.parse_arg = cmd_acl_get_parse_arg;
+ ctx->ctx.v.run = cmd_acl_get_run;
+ ctx->ctx.v.init = cmd_acl_get_init;
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ return &ctx->ctx;
+}
+
+static int
+cmd_acl_rights_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user)
+{
+ const char *mailbox = ctx->args[0];
+ struct mailbox *box;
+ struct acl_object *aclobj;
+ const char *const *rights;
+ int ret = 0;
+
+ if (cmd_acl_mailbox_open(ctx, user, mailbox, &box) < 0)
+ return -1;
+
+ aclobj = acl_mailbox_get_aclobj(box);
+ if (acl_object_get_my_rights(aclobj, pool_datastack_create(),
+ &rights) < 0) {
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+ i_error("Failed to get rights");
+ ret = -1;
+ } else {
+ doveadm_print(t_strarray_join(rights, " "));
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static void cmd_acl_rights_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[])
+{
+ if (args[0] == NULL)
+ doveadm_mail_help_name("acl rights");
+ doveadm_print_header("rights", "Rights", 0);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_acl_rights_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_acl_rights_run;
+ ctx->v.init = cmd_acl_rights_init;
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ return ctx;
+}
+
+static int
+cmd_acl_mailbox_update(struct doveadm_mail_cmd_context *ctx, struct mailbox *box,
+ const struct acl_rights_update *update)
+{
+ struct mailbox_transaction_context *t;
+ int ret;
+
+ t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL |
+ ctx->transaction_flags, __func__);
+ ret = acl_mailbox_update_acl(t, update);
+ if (mailbox_transaction_commit(&t) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int
+cmd_acl_set_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user)
+{
+ struct doveadm_acl_cmd_context *ctx =
+ (struct doveadm_acl_cmd_context *)_ctx;
+ const char *mailbox = _ctx->args[0], *id = _ctx->args[1];
+ const char *const *rights = _ctx->args + 2;
+ struct mailbox *box;
+ struct acl_rights_update update;
+ const char *error;
+ int ret;
+
+ if (cmd_acl_mailbox_open(_ctx, user, mailbox, &box) < 0)
+ return -1;
+
+ i_zero(&update);
+ update.modify_mode = ctx->modify_mode;
+ update.neg_modify_mode = ctx->modify_mode;
+ if (acl_rights_update_import(&update, id, rights, &error) < 0)
+ i_fatal_status(EX_USAGE, "%s", error);
+ if ((ret = cmd_acl_mailbox_update(&ctx->ctx, box, &update)) < 0) {
+ i_error("Failed to set ACL: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ doveadm_mail_failed_error(_ctx, MAIL_ERROR_TEMP);
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static void cmd_acl_set_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[])
+{
+ if (str_array_length(args) < 3)
+ doveadm_mail_help_name("acl set");
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_acl_change_alloc(enum acl_modify_mode modify_mode)
+{
+ struct doveadm_acl_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_acl_cmd_context);
+ ctx->ctx.v.run = cmd_acl_set_run;
+ ctx->ctx.v.init = cmd_acl_set_init;
+ ctx->modify_mode = modify_mode;
+ return &ctx->ctx;
+}
+
+static struct doveadm_mail_cmd_context *cmd_acl_set_alloc(void)
+{
+ return cmd_acl_change_alloc(ACL_MODIFY_MODE_REPLACE);
+}
+
+static struct doveadm_mail_cmd_context *cmd_acl_add_alloc(void)
+{
+ return cmd_acl_change_alloc(ACL_MODIFY_MODE_ADD);
+}
+
+static struct doveadm_mail_cmd_context *cmd_acl_remove_alloc(void)
+{
+ return cmd_acl_change_alloc(ACL_MODIFY_MODE_REMOVE);
+}
+
+static int
+cmd_acl_delete_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user)
+{
+ const char *mailbox = ctx->args[0], *id = ctx->args[1];
+ struct mailbox *box;
+ struct acl_rights_update update;
+ const char *error;
+ int ret = 0;
+
+ if (cmd_acl_mailbox_open(ctx, user, mailbox, &box) < 0)
+ return -1;
+
+ i_zero(&update);
+ if (acl_rights_update_import(&update, id, NULL, &error) < 0)
+ i_fatal_status(EX_USAGE, "%s", error);
+ if ((ret = cmd_acl_mailbox_update(ctx, box, &update)) < 0) {
+ i_error("Failed to delete ACL: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static void cmd_acl_delete_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[])
+{
+ if (str_array_length(args) < 2)
+ doveadm_mail_help_name("acl delete");
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_acl_delete_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_acl_delete_run;
+ ctx->v.init = cmd_acl_delete_init;
+ return ctx;
+}
+
+static int
+cmd_acl_recalc_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user)
+{
+ struct acl_user *auser = ACL_USER_CONTEXT(user);
+
+ if (auser == NULL) {
+ i_error("ACL not enabled for %s", user->username);
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ return -1;
+ }
+ if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0) {
+ i_error("Failed to recalculate ACL dicts");
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+ return -1;
+ }
+ return 0;
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_acl_recalc_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_acl_recalc_run;
+ return ctx;
+}
+
+static int
+cmd_acl_debug_mailbox_open(struct doveadm_mail_cmd_context *ctx,
+ struct mail_user *user, const char *mailbox,
+ struct mailbox **box_r)
+{
+ struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(user);
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ const char *path, *errstr;
+ enum mail_error error;
+
+ ns = mail_namespace_find(user->namespaces, mailbox);
+ box = mailbox_alloc(ns->list, mailbox,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
+ if (mailbox_open(box) < 0) {
+ errstr = mail_storage_get_last_internal_error(box->storage, &error);
+ errstr = t_strdup(errstr);
+ doveadm_mail_failed_error(ctx, error);
+
+ if (error != MAIL_ERROR_NOTFOUND ||
+ mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path) <= 0)
+ i_error("Can't open mailbox %s: %s", mailbox, errstr);
+ else {
+ i_error("Mailbox '%s' in namespace '%s' doesn't exist in %s",
+ box->name, ns->prefix, path);
+ }
+ mailbox_free(&box);
+ return -1;
+ }
+
+ if (auser == NULL) {
+ i_info("ACL not enabled for user %s, mailbox can be accessed",
+ user->username);
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ mailbox_free(&box);
+ return -1;
+ }
+
+ *box_r = box;
+ return 0;
+}
+
+static bool cmd_acl_debug_mailbox(struct mailbox *box, bool *retry_r)
+{
+ struct mail_namespace *ns = mailbox_get_namespace(box);
+ struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(ns->user);
+ struct acl_object *aclobj = acl_mailbox_get_aclobj(box);
+ struct acl_backend *backend = acl_mailbox_list_get_backend(box->list);
+ struct acl_mailbox_list_context *iter;
+ struct acl_lookup_dict_iter *diter;
+ const char *const *rights, *name, *path;
+ enum mail_flags private_flags_mask;
+ string_t *str;
+ int ret;
+ bool all_ok = TRUE;
+
+ *retry_r = FALSE;
+
+ i_info("Mailbox '%s' is in namespace '%s'",
+ box->name, box->list->ns->prefix);
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) > 0)
+ i_info("Mailbox path: %s", path);
+
+ private_flags_mask = mailbox_get_private_flags_mask(box);
+ if (private_flags_mask == 0)
+ i_info("All message flags are shared across users in mailbox");
+ else {
+ str = t_str_new(64);
+ imap_write_flags(str, private_flags_mask, NULL);
+ i_info("Per-user private flags in mailbox: %s", str_c(str));
+ }
+
+ /* check if user has lookup right */
+ if (acl_object_get_my_rights(aclobj, pool_datastack_create(),
+ &rights) < 0)
+ i_fatal("Failed to get rights");
+
+ if (rights[0] == NULL)
+ i_info("User %s has no rights for mailbox", ns->user->username);
+ else {
+ i_info("User %s has rights: %s",
+ ns->user->username, t_strarray_join(rights, " "));
+ }
+ if (!str_array_find(rights, MAIL_ACL_LOOKUP)) {
+ i_error("User %s is missing 'lookup' right",
+ ns->user->username);
+ return FALSE;
+ }
+
+ /* check if mailbox is listable */
+ if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) {
+ i_info("Mailbox in user's private namespace");
+ return TRUE;
+ }
+
+ iter = acl_backend_nonowner_lookups_iter_init(backend);
+ while (acl_backend_nonowner_lookups_iter_next(iter, &name)) {
+ if (strcmp(name, box->name) == 0)
+ break;
+ }
+ if ((ret = acl_backend_nonowner_lookups_iter_deinit(&iter))<0)
+ i_fatal("ACL non-owner iteration failed");
+ if (ret == 0) {
+ i_error("Mailbox not found from dovecot-acl-list, rebuilding");
+ if (acl_backend_nonowner_lookups_rebuild(backend) < 0)
+ i_fatal("dovecot-acl-list rebuilding failed");
+ all_ok = FALSE;
+ *retry_r = TRUE;
+ } else {
+ i_info("Mailbox found from dovecot-acl-list");
+ }
+
+ if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC) {
+ i_info("Mailbox is in public namespace");
+ return TRUE;
+ }
+
+ if (!acl_lookup_dict_is_enabled(auser->acl_lookup_dict)) {
+ i_error("acl_lookup_dict not enabled");
+ return FALSE;
+ }
+
+ /* shared namespace. see if it's in acl lookup dict */
+ diter = acl_lookup_dict_iterate_visible_init(auser->acl_lookup_dict);
+ while ((name = acl_lookup_dict_iterate_visible_next(diter)) != NULL) {
+ if (strcmp(name, ns->owner->username) == 0)
+ break;
+ }
+ if (acl_lookup_dict_iterate_visible_deinit(&diter) < 0)
+ i_fatal("ACL shared dict iteration failed");
+ if (name == NULL) {
+ i_error("User %s not found from ACL shared dict, rebuilding",
+ ns->owner->username);
+ if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0)
+ i_fatal("ACL lookup dict rebuild failed");
+ all_ok = FALSE;
+ *retry_r = TRUE;
+ } else {
+ i_info("User %s found from ACL shared dict",
+ ns->owner->username);
+ }
+ return all_ok;
+}
+
+static int
+cmd_acl_debug_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user)
+{
+ const char *mailbox = ctx->args[0];
+ struct mailbox *box;
+ bool ret, retry;
+
+ if (cmd_acl_debug_mailbox_open(ctx, user, mailbox, &box) < 0)
+ return -1;
+
+ ret = cmd_acl_debug_mailbox(box, &retry);
+ if (!ret && retry) {
+ i_info("Retrying after rebuilds:");
+ ret = cmd_acl_debug_mailbox(box, &retry);
+ }
+ if (ret)
+ i_info("Mailbox %s is visible in LIST", box->vname);
+ else
+ i_info("Mailbox %s is NOT visible in LIST", box->vname);
+ mailbox_free(&box);
+ return 0;
+}
+
+static void cmd_acl_debug_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[])
+{
+ if (args[0] == NULL)
+ doveadm_mail_help_name("acl debug");
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_acl_debug_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_acl_debug_run;
+ ctx->v.init = cmd_acl_debug_init;
+ return ctx;
+}
+
+static struct doveadm_cmd_ver2 acl_commands[] = {
+{
+ .name = "acl get",
+ .mail_cmd = cmd_acl_get_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-m] <mailbox>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('m', "match-me", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "acl rights",
+ .mail_cmd = cmd_acl_rights_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "acl set",
+ .mail_cmd = cmd_acl_set_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "acl add",
+ .mail_cmd = cmd_acl_add_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "acl remove",
+ .mail_cmd = cmd_acl_remove_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "acl delete",
+ .mail_cmd = cmd_acl_delete_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "acl recalc",
+ .mail_cmd = cmd_acl_recalc_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "acl debug",
+ .mail_cmd = cmd_acl_debug_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+}
+};
+
+void doveadm_acl_plugin_init(struct module *module ATTR_UNUSED)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(acl_commands); i++)
+ doveadm_cmd_register_ver2(&acl_commands[i]);
+}
+
+void doveadm_acl_plugin_deinit(void)
+{
+}
diff --git a/src/plugins/acl/test-acl.c b/src/plugins/acl/test-acl.c
new file mode 100644
index 0000000..1fafd7a
--- /dev/null
+++ b/src/plugins/acl/test-acl.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "test-common.h"
+#include "acl-api-private.h"
+
+static void test_acl_rights_sort(void)
+{
+ struct acl_rights rights1 = {
+ .rights = t_strsplit("a b a c d b", " "),
+ .neg_rights = t_strsplit("e d c a a d b e", " "),
+ };
+ struct acl_rights rights2 = {
+ .rights = t_strsplit("a c x", " "),
+ .neg_rights = t_strsplit("b c y", " "),
+ };
+ struct acl_object obj = {
+ .rights_pool = pool_alloconly_create("acl rights", 256)
+ };
+ const struct acl_rights *rights;
+
+ test_begin("acl_rights_sort");
+ t_array_init(&obj.rights, 8);
+
+ /* try with zero rights */
+ acl_rights_sort(&obj);
+ test_assert(array_count(&obj.rights) == 0);
+
+ /* try with just one right */
+ array_push_back(&obj.rights, &rights1);
+ acl_rights_sort(&obj);
+ test_assert(array_count(&obj.rights) == 1);
+ rights = array_idx(&obj.rights, 0);
+ test_assert(acl_rights_cmp(rights, &rights1) == 0);
+
+ /* try with two rights that don't have equal ID */
+ struct acl_rights rights1_id2 = rights1;
+ rights1_id2.identifier = "id2";
+ array_push_back(&obj.rights, &rights1_id2);
+ acl_rights_sort(&obj);
+ test_assert(array_count(&obj.rights) == 2);
+ rights = array_idx(&obj.rights, 0);
+ test_assert(acl_rights_cmp(&rights[0], &rights1) == 0);
+ test_assert(acl_rights_cmp(&rights[1], &rights1_id2) == 0);
+
+ /* try with 3 rights where first has equal ID */
+ array_push_back(&obj.rights, &rights2);
+ acl_rights_sort(&obj);
+ test_assert(array_count(&obj.rights) == 2);
+ rights = array_idx(&obj.rights, 0);
+ test_assert_strcmp(t_strarray_join(rights[0].rights, " "), "a b c d x");
+ test_assert_strcmp(t_strarray_join(rights[0].neg_rights, " "), "a b c d e y");
+
+ pool_unref(&obj.rights_pool);
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_acl_rights_sort,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/plugins/apparmor/Makefile.am b/src/plugins/apparmor/Makefile.am
new file mode 100644
index 0000000..510053b
--- /dev/null
+++ b/src/plugins/apparmor/Makefile.am
@@ -0,0 +1,14 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+NOPLUGIN_LDFLAGS =
+lib01_apparmor_plugin_la_LDFLAGS = -module -avoid-version
+lib01_apparmor_plugin_la_LIBADD = $(APPARMOR_LIBS)
+lib01_apparmor_plugin_la_SOURCES = \
+ apparmor-plugin.c
+
+module_LTLIBRARIES = \
+ lib01_apparmor_plugin.la
diff --git a/src/plugins/apparmor/Makefile.in b/src/plugins/apparmor/Makefile.in
new file mode 100644
index 0000000..72c10cf
--- /dev/null
+++ b/src/plugins/apparmor/Makefile.in
@@ -0,0 +1,816 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/apparmor
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+lib01_apparmor_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_lib01_apparmor_plugin_la_OBJECTS = apparmor-plugin.lo
+lib01_apparmor_plugin_la_OBJECTS = \
+ $(am_lib01_apparmor_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib01_apparmor_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib01_apparmor_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/apparmor-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib01_apparmor_plugin_la_SOURCES)
+DIST_SOURCES = $(lib01_apparmor_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+lib01_apparmor_plugin_la_LDFLAGS = -module -avoid-version
+lib01_apparmor_plugin_la_LIBADD = $(APPARMOR_LIBS)
+lib01_apparmor_plugin_la_SOURCES = \
+ apparmor-plugin.c
+
+module_LTLIBRARIES = \
+ lib01_apparmor_plugin.la
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/apparmor/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/apparmor/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib01_apparmor_plugin.la: $(lib01_apparmor_plugin_la_OBJECTS) $(lib01_apparmor_plugin_la_DEPENDENCIES) $(EXTRA_lib01_apparmor_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib01_apparmor_plugin_la_LINK) -rpath $(moduledir) $(lib01_apparmor_plugin_la_OBJECTS) $(lib01_apparmor_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/apparmor-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/apparmor-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/apparmor-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/apparmor/apparmor-plugin.c b/src/plugins/apparmor/apparmor-plugin.c
new file mode 100644
index 0000000..410c652
--- /dev/null
+++ b/src/plugins/apparmor/apparmor-plugin.c
@@ -0,0 +1,111 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "module-dir.h"
+#include "randgen.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "mail-storage-hooks.h"
+#include <sys/apparmor.h>
+
+#define APPARMOR_PLUGIN_SETTING_HAT_PREFIX "apparmor_hat"
+
+const char *apparmor_plugin_version = DOVECOT_ABI_VERSION;
+
+/* hooks into user creation and deinit, will try to use
+ hats provided by apparmor_hat, apparmor_hat1... etc */
+
+#define APPARMOR_USER_CONTEXT(obj) \
+ (struct apparmor_mail_user*)MODULE_CONTEXT(obj, apparmor_mail_user_module)
+
+static MODULE_CONTEXT_DEFINE_INIT(apparmor_mail_user_module,
+ &mail_user_module_register);
+
+struct apparmor_mail_user {
+ union mail_user_module_context module_ctx;
+ unsigned long token;
+};
+
+void apparmor_plugin_init(struct module*);
+void apparmor_plugin_deinit(void);
+
+static void apparmor_log_current_context(struct mail_user *user)
+{
+ char *con, *mode;
+
+ if (aa_getcon(&con, &mode) < 0) {
+ e_debug(user->event, "aa_getcon() failed: %m");
+ } else {
+ e_debug(user->event, "apparmor: Current context=%s, mode=%s",
+ con, mode);
+ free(con);
+ }
+}
+
+static void apparmor_mail_user_deinit(struct mail_user *user)
+{
+ struct apparmor_mail_user *auser = APPARMOR_USER_CONTEXT(user);
+
+ i_assert(auser != NULL);
+ auser->module_ctx.super.deinit(user);
+
+ if (aa_change_hat(NULL, auser->token)<0)
+ i_fatal("aa_change_hat(NULL) failed: %m");
+
+ apparmor_log_current_context(user);
+}
+
+static void apparmor_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct apparmor_mail_user *auser;
+ ARRAY_TYPE(const_string) hats;
+ /* see if we can find any hats */
+ const char *hat =
+ mail_user_plugin_getenv(user, APPARMOR_PLUGIN_SETTING_HAT_PREFIX);
+ if (hat == NULL)
+ return;
+
+ t_array_init(&hats, 8);
+ array_push_back(&hats, &hat);
+ for(unsigned int i = 2;; i++) {
+ hat = mail_user_plugin_getenv(user, t_strdup_printf("%s%u",
+ APPARMOR_PLUGIN_SETTING_HAT_PREFIX, i));
+ if (hat == NULL) break;
+ array_push_back(&hats, &hat);
+ }
+ array_append_zero(&hats);
+
+ /* we got hat(s) to try */
+ auser = p_new(user->pool, struct apparmor_mail_user, 1);
+ auser->module_ctx.super = *v;
+ user->vlast = &auser->module_ctx.super;
+ v->deinit = apparmor_mail_user_deinit;
+ MODULE_CONTEXT_SET(user, apparmor_mail_user_module, auser);
+
+ /* generate a magic token */
+ random_fill(&auser->token, sizeof(auser->token));
+
+ /* try change hat */
+ if (aa_change_hatv(array_front_modifiable(&hats), auser->token) < 0) {
+ i_fatal("aa_change_hatv(%s) failed: %m",
+ t_array_const_string_join(&hats, ","));
+ }
+
+ apparmor_log_current_context(user);
+}
+
+static const struct mail_storage_hooks apparmor_hooks = {
+ .mail_user_created = apparmor_mail_user_created
+};
+
+void apparmor_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &apparmor_hooks);
+}
+
+void apparmor_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&apparmor_hooks);
+}
diff --git a/src/plugins/charset-alias/Makefile.am b/src/plugins/charset-alias/Makefile.am
new file mode 100644
index 0000000..da99095
--- /dev/null
+++ b/src/plugins/charset-alias/Makefile.am
@@ -0,0 +1,19 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-charset \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+NOPLUGIN_LDFLAGS =
+lib20_charset_alias_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_charset_alias_plugin.la
+
+lib20_charset_alias_plugin_la_SOURCES = \
+ charset-alias-plugin.c
+
+noinst_HEADERS = \
+ charset-alias-plugin.h
diff --git a/src/plugins/charset-alias/Makefile.in b/src/plugins/charset-alias/Makefile.in
new file mode 100644
index 0000000..ffb29dc
--- /dev/null
+++ b/src/plugins/charset-alias/Makefile.in
@@ -0,0 +1,822 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/charset-alias
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib20_charset_alias_plugin_la_LIBADD =
+am_lib20_charset_alias_plugin_la_OBJECTS = charset-alias-plugin.lo
+lib20_charset_alias_plugin_la_OBJECTS = \
+ $(am_lib20_charset_alias_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_charset_alias_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib20_charset_alias_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/charset-alias-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_charset_alias_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_charset_alias_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-charset \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+lib20_charset_alias_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_charset_alias_plugin.la
+
+lib20_charset_alias_plugin_la_SOURCES = \
+ charset-alias-plugin.c
+
+noinst_HEADERS = \
+ charset-alias-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/charset-alias/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/charset-alias/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_charset_alias_plugin.la: $(lib20_charset_alias_plugin_la_OBJECTS) $(lib20_charset_alias_plugin_la_DEPENDENCIES) $(EXTRA_lib20_charset_alias_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_charset_alias_plugin_la_LINK) -rpath $(moduledir) $(lib20_charset_alias_plugin_la_OBJECTS) $(lib20_charset_alias_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/charset-alias-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/charset-alias-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/charset-alias-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/charset-alias/charset-alias-plugin.c b/src/plugins/charset-alias/charset-alias-plugin.c
new file mode 100644
index 0000000..612223e
--- /dev/null
+++ b/src/plugins/charset-alias/charset-alias-plugin.c
@@ -0,0 +1,198 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "mail-storage-hooks.h"
+#include "charset-utf8-private.h"
+#include "charset-alias-plugin.h"
+
+
+#define CHARSET_ALIAS_USER_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, charset_alias_user_module)
+
+static MODULE_CONTEXT_DEFINE_INIT(charset_alias_user_module,
+ &mail_user_module_register);
+
+const char *charset_alias_plugin_version = DOVECOT_ABI_VERSION;
+
+static int charset_alias_to_utf8_begin(const char *charset,
+ normalizer_func_t *normalizer,
+ struct charset_translation **t_r);
+
+static void charset_alias_to_utf8_end(struct charset_translation *t);
+
+static void charset_alias_to_utf8_reset(struct charset_translation *t);
+
+static enum charset_result charset_alias_to_utf8(struct charset_translation *t,
+ const unsigned char *src,
+ size_t *src_size, buffer_t *dest);
+
+/* charset_utf8_vfuncs is defined in lib-charset/charset-utf8.c */
+extern const struct charset_utf8_vfuncs *charset_utf8_vfuncs;
+
+static const struct charset_utf8_vfuncs *original_charset_utf8_vfuncs;
+
+static const struct charset_utf8_vfuncs charset_alias_utf8_vfuncs = {
+ charset_alias_to_utf8_begin,
+ charset_alias_to_utf8_end,
+ charset_alias_to_utf8_reset,
+ charset_alias_to_utf8
+};
+
+struct charset_alias {
+ const char *charset;
+ const char *alias;
+};
+
+static ARRAY(struct charset_alias) charset_aliases;
+static pool_t charset_alias_pool;
+static int charset_alias_user_refcount = 0;
+
+struct charset_alias_user {
+ union mail_user_module_context module_ctx;
+};
+
+
+static const char *charset_alias_get_alias(const char *charset)
+{
+ const struct charset_alias* elem;
+ const char *key;
+
+ if (array_is_created(&charset_aliases)) {
+ key = t_str_lcase(charset);
+ array_foreach(&charset_aliases, elem) {
+ if (strcmp(key, elem->charset) == 0) {
+ return elem->alias;
+ }
+ }
+ }
+ return charset;
+}
+
+static int charset_alias_to_utf8_begin(const char *charset,
+ normalizer_func_t *normalizer,
+ struct charset_translation **t_r)
+{
+ i_assert(original_charset_utf8_vfuncs != NULL);
+ charset = charset_alias_get_alias(charset);
+ return original_charset_utf8_vfuncs->to_utf8_begin(charset, normalizer, t_r);
+}
+static void charset_alias_to_utf8_end(struct charset_translation *t)
+{
+ i_assert(original_charset_utf8_vfuncs != NULL);
+ original_charset_utf8_vfuncs->to_utf8_end(t);
+}
+
+static void charset_alias_to_utf8_reset(struct charset_translation *t)
+{
+ i_assert(original_charset_utf8_vfuncs != NULL);
+ original_charset_utf8_vfuncs->to_utf8_reset(t);
+}
+
+static enum charset_result charset_alias_to_utf8(struct charset_translation *t,
+ const unsigned char *src,
+ size_t *src_size, buffer_t *dest)
+{
+ i_assert(original_charset_utf8_vfuncs != NULL);
+ return original_charset_utf8_vfuncs->to_utf8(t, src, src_size, dest);
+}
+
+static unsigned int charset_aliases_init(struct mail_user *user, pool_t pool, const char *str)
+{
+ const char *key, *value, *const *keyvalues;
+ struct charset_alias alias;
+ int i;
+
+ i_assert(!array_is_created(&charset_aliases));
+
+ p_array_init(&charset_aliases, pool, 1);
+ keyvalues = t_strsplit_spaces(str, " ");
+ for (i = 0; keyvalues[i] != NULL; i++) {
+ value = strchr(keyvalues[i], '=');
+ if (value == NULL) {
+ i_error("charset_alias: Missing '=' in charset_aliases setting");
+ continue;
+ }
+ key = t_strdup_until(keyvalues[i], value++);
+ if (key[0] == '\0' || value[0] == '\0') {
+ i_error("charset_alias: charset or alias missing in charset_aliases setting");
+ continue;
+ }
+ if (strcasecmp(key, value) != 0) {
+ e_debug(user->event, "charset_alias: add charset-alias %s for %s", value, key);
+ alias.charset = p_strdup(pool, t_str_lcase(key));
+ alias.alias = p_strdup(pool, value);
+ array_push_back(&charset_aliases, &alias);
+ }
+ }
+ return array_count(&charset_aliases);
+}
+
+static void charset_alias_utf8_vfuncs_set(void)
+{
+ original_charset_utf8_vfuncs = charset_utf8_vfuncs;
+ charset_utf8_vfuncs = &charset_alias_utf8_vfuncs;
+}
+
+static void charset_alias_utf8_vfuncs_reset(void)
+{
+ if (original_charset_utf8_vfuncs != NULL) {
+ charset_utf8_vfuncs = original_charset_utf8_vfuncs;
+ original_charset_utf8_vfuncs = NULL;
+ }
+}
+
+static void charset_alias_mail_user_deinit(struct mail_user *user)
+{
+ struct charset_alias_user *cuser = CHARSET_ALIAS_USER_CONTEXT(user);
+
+ i_assert(charset_alias_user_refcount > 0);
+ if (--charset_alias_user_refcount == 0) {
+ charset_alias_utf8_vfuncs_reset();
+ array_free(&charset_aliases);
+ pool_unref(&charset_alias_pool);
+ }
+
+ cuser->module_ctx.super.deinit(user);
+}
+
+static void charset_alias_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct charset_alias_user *cuser;
+ const char *str;
+
+ cuser = p_new(user->pool, struct charset_alias_user, 1);
+ cuser->module_ctx.super = *v;
+ user->vlast = &cuser->module_ctx.super;
+ v->deinit = charset_alias_mail_user_deinit;
+
+ if (charset_alias_user_refcount++ == 0) {
+ charset_alias_pool = pool_alloconly_create("charset_alias alias list", 128);
+ str = mail_user_plugin_getenv(user, "charset_aliases");
+ if (str != NULL && str[0] != '\0') {
+ if (charset_aliases_init(user, charset_alias_pool, str) > 0) {
+ charset_alias_utf8_vfuncs_set();
+ }
+ }
+ }
+
+ MODULE_CONTEXT_SET(user, charset_alias_user_module, cuser);
+}
+
+static struct mail_storage_hooks charset_alias_mail_storage_hooks = {
+ .mail_user_created = charset_alias_mail_user_created
+};
+
+void charset_alias_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &charset_alias_mail_storage_hooks);
+}
+
+void charset_alias_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&charset_alias_mail_storage_hooks);
+}
diff --git a/src/plugins/charset-alias/charset-alias-plugin.h b/src/plugins/charset-alias/charset-alias-plugin.h
new file mode 100644
index 0000000..0a1d518
--- /dev/null
+++ b/src/plugins/charset-alias/charset-alias-plugin.h
@@ -0,0 +1,7 @@
+#ifndef CHARSET_ALIAS_PLUGIN_H
+#define CHARSET_ALIAS_PLUGIN_H
+
+void charset_alias_plugin_init(struct module *module);
+void charset_alias_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/fs-compress/Makefile.am b/src/plugins/fs-compress/Makefile.am
new file mode 100644
index 0000000..77ba733
--- /dev/null
+++ b/src/plugins/fs-compress/Makefile.am
@@ -0,0 +1,14 @@
+fs_moduledir = $(moduledir)
+fs_module_LTLIBRARIES = \
+ libfs_compress.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-compression \
+ -I$(top_srcdir)/src/lib-fs
+
+NOPLUGIN_LDFLAGS =
+libfs_compress_la_SOURCES = fs-compress.c
+libfs_compress_la_LIBADD = ../../lib-compression/libdovecot-compression.la
+libfs_compress_la_DEPENDENCIES = ../../lib-compression/libdovecot-compression.la
+libfs_compress_la_LDFLAGS = -module -avoid-version
diff --git a/src/plugins/fs-compress/Makefile.in b/src/plugins/fs-compress/Makefile.in
new file mode 100644
index 0000000..255f03d
--- /dev/null
+++ b/src/plugins/fs-compress/Makefile.in
@@ -0,0 +1,811 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/fs-compress
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(fs_moduledir)"
+LTLIBRARIES = $(fs_module_LTLIBRARIES)
+am_libfs_compress_la_OBJECTS = fs-compress.lo
+libfs_compress_la_OBJECTS = $(am_libfs_compress_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libfs_compress_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libfs_compress_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/fs-compress.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libfs_compress_la_SOURCES)
+DIST_SOURCES = $(libfs_compress_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+fs_moduledir = $(moduledir)
+fs_module_LTLIBRARIES = \
+ libfs_compress.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-compression \
+ -I$(top_srcdir)/src/lib-fs
+
+libfs_compress_la_SOURCES = fs-compress.c
+libfs_compress_la_LIBADD = ../../lib-compression/libdovecot-compression.la
+libfs_compress_la_DEPENDENCIES = ../../lib-compression/libdovecot-compression.la
+libfs_compress_la_LDFLAGS = -module -avoid-version
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fs-compress/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/fs-compress/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-fs_moduleLTLIBRARIES: $(fs_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(fs_module_LTLIBRARIES)'; test -n "$(fs_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(fs_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(fs_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(fs_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(fs_moduledir)"; \
+ }
+
+uninstall-fs_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(fs_module_LTLIBRARIES)'; test -n "$(fs_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(fs_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(fs_moduledir)/$$f"; \
+ done
+
+clean-fs_moduleLTLIBRARIES:
+ -test -z "$(fs_module_LTLIBRARIES)" || rm -f $(fs_module_LTLIBRARIES)
+ @list='$(fs_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libfs_compress.la: $(libfs_compress_la_OBJECTS) $(libfs_compress_la_DEPENDENCIES) $(EXTRA_libfs_compress_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libfs_compress_la_LINK) -rpath $(fs_moduledir) $(libfs_compress_la_OBJECTS) $(libfs_compress_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fs-compress.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(fs_moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-fs_moduleLTLIBRARIES clean-generic clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/fs-compress.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-fs_moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/fs-compress.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-fs_moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-fs_moduleLTLIBRARIES clean-generic clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-fs_moduleLTLIBRARIES \
+ install-html install-html-am install-info install-info-am \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-fs_moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/fs-compress/fs-compress.c b/src/plugins/fs-compress/fs-compress.c
new file mode 100644
index 0000000..ab03076
--- /dev/null
+++ b/src/plugins/fs-compress/fs-compress.c
@@ -0,0 +1,285 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "istream-tee.h"
+#include "istream-try.h"
+#include "ostream.h"
+#include "iostream-temp.h"
+#include "compression.h"
+#include "fs-api-private.h"
+
+struct compress_fs {
+ struct fs fs;
+ const struct compression_handler *compress_handler;
+ int compress_level;
+ bool try_plain;
+};
+
+struct compress_fs_file {
+ struct fs_file file;
+ struct compress_fs *fs;
+ struct fs_file *super_read;
+ enum fs_open_mode open_mode;
+ struct istream *input;
+
+ struct ostream *super_output;
+ struct ostream *temp_output;
+};
+
+#define COMPRESS_FS(ptr) container_of((ptr), struct compress_fs, fs)
+#define COMPRESS_FILE(ptr) container_of((ptr), struct compress_fs_file, file)
+
+extern const struct fs fs_class_compress;
+
+static struct fs *fs_compress_alloc(void)
+{
+ struct compress_fs *fs;
+
+ fs = i_new(struct compress_fs, 1);
+ fs->fs = fs_class_compress;
+ return &fs->fs;
+}
+
+static int
+fs_compress_init(struct fs *_fs, const char *args,
+ const struct fs_settings *set, const char **error_r)
+{
+ struct compress_fs *fs = COMPRESS_FS(_fs);
+ const char *p, *compression_name, *level_str;
+ const char *parent_name, *parent_args;
+ int ret;
+
+ /* get compression handler name */
+ if (str_begins(args, "maybe-")) {
+ fs->try_plain = TRUE;
+ args += 6;
+ }
+
+ p = strchr(args, ':');
+ if (p == NULL) {
+ *error_r = "Compression method not given as parameter";
+ return -1;
+ }
+ compression_name = t_strdup_until(args, p++);
+ args = p;
+
+ /* get compression level */
+ p = strchr(args, ':');
+ if (p == NULL || p[1] == '\0') {
+ *error_r = "Parent filesystem not given as parameter";
+ return -1;
+ }
+
+ level_str = t_strdup_until(args, p++);
+ args = p;
+ ret = compression_lookup_handler(compression_name, &fs->compress_handler);
+ if (ret <= 0) {
+ *error_r = t_strdup_printf("Compression method '%s' %s.",
+ compression_name, ret == 0 ?
+ "not supported" : "unknown");
+ return -1;
+ }
+ if (str_to_int(level_str, &fs->compress_level) < 0 ||
+ fs->compress_level < fs->compress_handler->get_min_level() ||
+ fs->compress_level > fs->compress_handler->get_max_level()) {
+ *error_r = t_strdup_printf(
+ "Invalid compression level parameter '%s': "
+ "Level must be between %d..%d", level_str,
+ fs->compress_handler->get_min_level(),
+ fs->compress_handler->get_max_level());
+ return -1;
+ }
+ parent_args = strchr(args, ':');
+ if (parent_args == NULL) {
+ parent_name = args;
+ parent_args = "";
+ } else {
+ parent_name = t_strdup_until(args, parent_args);
+ parent_args++;
+ }
+ return fs_init(parent_name, parent_args, set, &_fs->parent, error_r);
+}
+
+static void fs_compress_free(struct fs *_fs)
+{
+ struct compress_fs *fs = COMPRESS_FS(_fs);
+
+ i_free(fs);
+}
+
+static struct fs_file *fs_compress_file_alloc(void)
+{
+ struct compress_fs_file *file = i_new(struct compress_fs_file, 1);
+ return &file->file;
+}
+
+static void
+fs_compress_file_init(struct fs_file *_file, const char *path,
+ enum fs_open_mode mode, enum fs_open_flags flags)
+{
+ struct compress_fs *fs = COMPRESS_FS(_file->fs);
+ struct compress_fs_file *file = COMPRESS_FILE(_file);
+
+ file->file.path = i_strdup(path);
+ file->fs = fs;
+ file->open_mode = mode;
+
+ /* avoid unnecessarily creating two seekable streams */
+ flags &= ENUM_NEGATE(FS_OPEN_FLAG_SEEKABLE);
+
+ file->file.parent = fs_file_init_parent(_file, path, mode, flags);
+ if (mode == FS_OPEN_MODE_READONLY &&
+ (flags & FS_OPEN_FLAG_ASYNC) == 0) {
+ /* use async stream for parent, so fs_read_stream() won't create
+ another seekable stream needlessly */
+ file->super_read = fs_file_init_parent(_file, path,
+ mode, flags | FS_OPEN_FLAG_ASYNC |
+ FS_OPEN_FLAG_ASYNC_NOQUEUE);
+ } else {
+ file->super_read = file->file.parent;
+ }
+}
+
+static void fs_compress_file_deinit(struct fs_file *_file)
+{
+ struct compress_fs_file *file = COMPRESS_FILE(_file);
+
+ if (file->super_read != _file->parent)
+ fs_file_deinit(&file->super_read);
+ fs_file_free(_file);
+ i_free(file->file.path);
+ i_free(file);
+}
+
+static void fs_compress_file_close(struct fs_file *_file)
+{
+ struct compress_fs_file *file = COMPRESS_FILE(_file);
+
+ i_stream_unref(&file->input);
+ fs_file_close(file->super_read);
+ fs_file_close(_file->parent);
+}
+
+static struct istream *
+fs_compress_read_stream(struct fs_file *_file, size_t max_buffer_size)
+{
+ struct compress_fs_file *file = COMPRESS_FILE(_file);
+ struct istream *input;
+ enum istream_decompress_flags flags = 0;
+
+ if (file->input != NULL) {
+ i_stream_ref(file->input);
+ i_stream_seek(file->input, 0);
+ return file->input;
+ }
+
+ input = fs_read_stream(file->super_read, max_buffer_size);
+ if (file->fs->try_plain)
+ flags |= ISTREAM_DECOMPRESS_FLAG_TRY;
+ file->input = i_stream_create_decompress(input, flags);
+ i_stream_unref(&input);
+ i_stream_ref(file->input);
+ return file->input;
+}
+
+static void fs_compress_write_stream(struct fs_file *_file)
+{
+ struct compress_fs_file *file = COMPRESS_FILE(_file);
+
+ if (file->fs->compress_level == 0) {
+ fs_wrapper_write_stream(_file);
+ return;
+ }
+
+ i_assert(_file->output == NULL);
+
+ file->temp_output =
+ iostream_temp_create_named(_file->fs->temp_path_prefix,
+ IOSTREAM_TEMP_FLAG_TRY_FD_DUP,
+ fs_file_path(_file));
+ _file->output = file->fs->compress_handler->
+ create_ostream(file->temp_output, file->fs->compress_level);
+}
+
+static int fs_compress_write_stream_finish(struct fs_file *_file, bool success)
+{
+ struct compress_fs_file *file = COMPRESS_FILE(_file);
+ struct istream *input;
+ int ret;
+
+ if (file->fs->compress_level == 0)
+ return fs_wrapper_write_stream_finish(_file, success);
+
+ if (_file->output != NULL) {
+ if (_file->output->closed)
+ success = FALSE;
+ if (_file->output == file->super_output)
+ _file->output = NULL;
+ else
+ o_stream_unref(&_file->output);
+ }
+ if (!success) {
+ o_stream_destroy(&file->temp_output);
+ if (file->super_output != NULL)
+ fs_write_stream_abort_parent(_file, &file->super_output);
+ return -1;
+ }
+
+ if (file->super_output != NULL) {
+ i_assert(file->temp_output == NULL);
+ return fs_write_stream_finish(_file->parent, &file->super_output);
+ }
+ if (file->temp_output == NULL) {
+ /* finishing up */
+ i_assert(file->super_output == NULL);
+ return fs_write_stream_finish(_file->parent, &file->temp_output);
+ }
+ /* finish writing the temporary file */
+ input = iostream_temp_finish(&file->temp_output, IO_BLOCK_SIZE);
+ file->super_output = fs_write_stream(_file->parent);
+ o_stream_nsend_istream(file->super_output, input);
+ ret = fs_write_stream_finish(_file->parent, &file->super_output);
+ i_stream_unref(&input);
+ return ret;
+}
+
+const struct fs fs_class_compress = {
+ .name = "compress",
+ .v = {
+ fs_compress_alloc,
+ fs_compress_init,
+ NULL,
+ fs_compress_free,
+ fs_wrapper_get_properties,
+ fs_compress_file_alloc,
+ fs_compress_file_init,
+ fs_compress_file_deinit,
+ fs_compress_file_close,
+ fs_wrapper_file_get_path,
+ fs_wrapper_set_async_callback,
+ fs_wrapper_wait_async,
+ fs_wrapper_set_metadata,
+ fs_wrapper_get_metadata,
+ fs_wrapper_prefetch,
+ fs_read_via_stream,
+ fs_compress_read_stream,
+ fs_write_via_stream,
+ fs_compress_write_stream,
+ fs_compress_write_stream_finish,
+ fs_wrapper_lock,
+ fs_wrapper_unlock,
+ fs_wrapper_exists,
+ fs_wrapper_stat,
+ fs_wrapper_copy,
+ fs_wrapper_rename,
+ fs_wrapper_delete,
+ fs_wrapper_iter_alloc,
+ fs_wrapper_iter_init,
+ fs_wrapper_iter_next,
+ fs_wrapper_iter_deinit,
+ NULL,
+ fs_wrapper_get_nlinks
+ }
+};
diff --git a/src/plugins/fts-lucene/Makefile.am b/src/plugins/fts-lucene/Makefile.am
new file mode 100644
index 0000000..d68e6ae
--- /dev/null
+++ b/src/plugins/fts-lucene/Makefile.am
@@ -0,0 +1,61 @@
+doveadm_moduledir = $(moduledir)/doveadm
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/fts \
+ -I$(top_srcdir)/src/doveadm
+
+AM_CXXFLAGS = \
+ $(CLUCENE_CFLAGS) \
+ $(LIBEXTTEXTCAT_CFLAGS)
+
+NOPLUGIN_LDFLAGS =
+lib21_fts_lucene_plugin_la_LDFLAGS = -module -avoid-version
+lib20_doveadm_fts_lucene_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib21_fts_lucene_plugin.la
+
+if BUILD_FTS_STEMMER
+STEMMER_LIBS = -lstemmer
+SHOWBALL_SOURCES = Snowball.cc
+endif
+
+if BUILD_FTS_EXTTEXTCAT
+TEXTCAT_LIBS = $(LIBEXTTEXTCAT_LIBS)
+else
+if BUILD_FTS_TEXTCAT
+TEXTCAT_LIBS = -ltextcat
+endif
+endif
+
+lib21_fts_lucene_plugin_la_LIBADD = \
+ $(CLUCENE_LIBS) $(TEXTCAT_LIBS) $(STEMMER_LIBS)
+
+lib21_fts_lucene_plugin_la_SOURCES = \
+ fts-lucene-plugin.c \
+ fts-backend-lucene.c \
+ lucene-wrapper.cc \
+ $(SHOWBALL_SOURCES)
+
+noinst_HEADERS = \
+ fts-lucene-plugin.h \
+ lucene-wrapper.h \
+ SnowballAnalyzer.h \
+ SnowballFilter.h
+
+if BUILD_FTS_TEXTCAT
+exampledir = $(docdir)/example-config
+example_DATA = \
+ textcat.conf
+endif
+EXTRA_DIST = textcat.conf
+
+doveadm_module_LTLIBRARIES = \
+ lib20_doveadm_fts_lucene_plugin.la
+
+lib20_doveadm_fts_lucene_plugin_la_SOURCES = \
+ doveadm-fts-lucene.c
diff --git a/src/plugins/fts-lucene/Makefile.in b/src/plugins/fts-lucene/Makefile.in
new file mode 100644
index 0000000..323982d
--- /dev/null
+++ b/src/plugins/fts-lucene/Makefile.in
@@ -0,0 +1,990 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/fts-lucene
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(doveadm_moduledir)" \
+ "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(exampledir)"
+LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES)
+lib20_doveadm_fts_lucene_plugin_la_LIBADD =
+am_lib20_doveadm_fts_lucene_plugin_la_OBJECTS = doveadm-fts-lucene.lo
+lib20_doveadm_fts_lucene_plugin_la_OBJECTS = \
+ $(am_lib20_doveadm_fts_lucene_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_doveadm_fts_lucene_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) \
+ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \
+ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lib20_doveadm_fts_lucene_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+am__DEPENDENCIES_1 =
+@BUILD_FTS_EXTTEXTCAT_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1)
+lib21_fts_lucene_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1)
+am__lib21_fts_lucene_plugin_la_SOURCES_DIST = fts-lucene-plugin.c \
+ fts-backend-lucene.c lucene-wrapper.cc Snowball.cc
+@BUILD_FTS_STEMMER_TRUE@am__objects_1 = Snowball.lo
+am_lib21_fts_lucene_plugin_la_OBJECTS = fts-lucene-plugin.lo \
+ fts-backend-lucene.lo lucene-wrapper.lo $(am__objects_1)
+lib21_fts_lucene_plugin_la_OBJECTS = \
+ $(am_lib21_fts_lucene_plugin_la_OBJECTS)
+lib21_fts_lucene_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(lib21_fts_lucene_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/Snowball.Plo \
+ ./$(DEPDIR)/doveadm-fts-lucene.Plo \
+ ./$(DEPDIR)/fts-backend-lucene.Plo \
+ ./$(DEPDIR)/fts-lucene-plugin.Plo \
+ ./$(DEPDIR)/lucene-wrapper.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+SOURCES = $(lib20_doveadm_fts_lucene_plugin_la_SOURCES) \
+ $(lib21_fts_lucene_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_doveadm_fts_lucene_plugin_la_SOURCES) \
+ $(am__lib21_fts_lucene_plugin_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(example_DATA)
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+doveadm_moduledir = $(moduledir)/doveadm
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/fts \
+ -I$(top_srcdir)/src/doveadm
+
+AM_CXXFLAGS = \
+ $(CLUCENE_CFLAGS) \
+ $(LIBEXTTEXTCAT_CFLAGS)
+
+lib21_fts_lucene_plugin_la_LDFLAGS = -module -avoid-version
+lib20_doveadm_fts_lucene_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib21_fts_lucene_plugin.la
+
+@BUILD_FTS_STEMMER_TRUE@STEMMER_LIBS = -lstemmer
+@BUILD_FTS_STEMMER_TRUE@SHOWBALL_SOURCES = Snowball.cc
+@BUILD_FTS_EXTTEXTCAT_FALSE@@BUILD_FTS_TEXTCAT_TRUE@TEXTCAT_LIBS = -ltextcat
+@BUILD_FTS_EXTTEXTCAT_TRUE@TEXTCAT_LIBS = $(LIBEXTTEXTCAT_LIBS)
+lib21_fts_lucene_plugin_la_LIBADD = \
+ $(CLUCENE_LIBS) $(TEXTCAT_LIBS) $(STEMMER_LIBS)
+
+lib21_fts_lucene_plugin_la_SOURCES = \
+ fts-lucene-plugin.c \
+ fts-backend-lucene.c \
+ lucene-wrapper.cc \
+ $(SHOWBALL_SOURCES)
+
+noinst_HEADERS = \
+ fts-lucene-plugin.h \
+ lucene-wrapper.h \
+ SnowballAnalyzer.h \
+ SnowballFilter.h
+
+@BUILD_FTS_TEXTCAT_TRUE@exampledir = $(docdir)/example-config
+@BUILD_FTS_TEXTCAT_TRUE@example_DATA = \
+@BUILD_FTS_TEXTCAT_TRUE@ textcat.conf
+
+EXTRA_DIST = textcat.conf
+doveadm_module_LTLIBRARIES = \
+ lib20_doveadm_fts_lucene_plugin.la
+
+lib20_doveadm_fts_lucene_plugin_la_SOURCES = \
+ doveadm-fts-lucene.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .cc .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fts-lucene/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/fts-lucene/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \
+ }
+
+uninstall-doveadm_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \
+ done
+
+clean-doveadm_moduleLTLIBRARIES:
+ -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES)
+ @list='$(doveadm_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_doveadm_fts_lucene_plugin.la: $(lib20_doveadm_fts_lucene_plugin_la_OBJECTS) $(lib20_doveadm_fts_lucene_plugin_la_DEPENDENCIES) $(EXTRA_lib20_doveadm_fts_lucene_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_doveadm_fts_lucene_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib20_doveadm_fts_lucene_plugin_la_OBJECTS) $(lib20_doveadm_fts_lucene_plugin_la_LIBADD) $(LIBS)
+
+lib21_fts_lucene_plugin.la: $(lib21_fts_lucene_plugin_la_OBJECTS) $(lib21_fts_lucene_plugin_la_DEPENDENCIES) $(EXTRA_lib21_fts_lucene_plugin_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(lib21_fts_lucene_plugin_la_LINK) -rpath $(moduledir) $(lib21_fts_lucene_plugin_la_OBJECTS) $(lib21_fts_lucene_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Snowball.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-fts-lucene.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-backend-lucene.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-lucene-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lucene-wrapper.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-exampleDATA: $(example_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(example_DATA)'; test -n "$(exampledir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(exampledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(exampledir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(exampledir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(exampledir)" || exit $$?; \
+ done
+
+uninstall-exampleDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(example_DATA)'; test -n "$(exampledir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(exampledir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(DATA) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(exampledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/Snowball.Plo
+ -rm -f ./$(DEPDIR)/doveadm-fts-lucene.Plo
+ -rm -f ./$(DEPDIR)/fts-backend-lucene.Plo
+ -rm -f ./$(DEPDIR)/fts-lucene-plugin.Plo
+ -rm -f ./$(DEPDIR)/lucene-wrapper.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-doveadm_moduleLTLIBRARIES install-exampleDATA \
+ install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/Snowball.Plo
+ -rm -f ./$(DEPDIR)/doveadm-fts-lucene.Plo
+ -rm -f ./$(DEPDIR)/fts-backend-lucene.Plo
+ -rm -f ./$(DEPDIR)/fts-lucene-plugin.Plo
+ -rm -f ./$(DEPDIR)/lucene-wrapper.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-exampleDATA uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am \
+ install-doveadm_moduleLTLIBRARIES install-dvi install-dvi-am \
+ install-exampleDATA install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-doveadm_moduleLTLIBRARIES uninstall-exampleDATA \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/fts-lucene/Snowball.cc b/src/plugins/fts-lucene/Snowball.cc
new file mode 100644
index 0000000..43b54e3
--- /dev/null
+++ b/src/plugins/fts-lucene/Snowball.cc
@@ -0,0 +1,151 @@
+/*------------------------------------------------------------------------------
+* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team
+*
+* Distributable under the terms of either the Apache License (Version 2.0) or
+* the GNU Lesser General Public License, as specified in the COPYING file.
+------------------------------------------------------------------------------*/
+#include <CLucene.h>
+#include "SnowballAnalyzer.h"
+#include "SnowballFilter.h"
+#include <CLucene/util/CLStreams.h>
+#include <CLucene/analysis/Analyzers.h>
+#include <CLucene/analysis/standard/StandardTokenizer.h>
+#include <CLucene/analysis/standard/StandardFilter.h>
+
+extern "C" {
+#include "lib.h"
+#include "buffer.h"
+#include "unichar.h"
+#include "lucene-wrapper.h"
+};
+
+CL_NS_USE(analysis)
+CL_NS_USE(util)
+CL_NS_USE2(analysis,standard)
+
+CL_NS_DEF2(analysis,snowball)
+
+ /** Builds the named analyzer with no stop words. */
+ SnowballAnalyzer::SnowballAnalyzer(normalizer_func_t *_normalizer, const char* _language)
+ : language(i_strdup(_language)),
+ normalizer(_normalizer),
+ stopSet(NULL),
+ prevstream(NULL)
+ {
+ }
+
+ SnowballAnalyzer::~SnowballAnalyzer()
+ {
+ if (prevstream)
+ _CLDELETE(prevstream);
+ i_free(language);
+ if ( stopSet != NULL )
+ _CLDELETE(stopSet);
+ }
+
+ /** Builds the named analyzer with the given stop words.
+ */
+ SnowballAnalyzer::SnowballAnalyzer(const char* language, const TCHAR** stopWords)
+ : language(i_strdup(language)),
+ normalizer(NULL),
+ stopSet(_CLNEW CLTCSetList(true)),
+ prevstream(NULL)
+ {
+ StopFilter::fillStopTable(stopSet,stopWords);
+ }
+
+ TokenStream* SnowballAnalyzer::tokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader) {
+ return this->tokenStream(fieldName,reader,false);
+ }
+
+ /** Constructs a {@link StandardTokenizer} filtered by a {@link
+ StandardFilter}, a {@link LowerCaseFilter} and a {@link StopFilter}. */
+ TokenStream* SnowballAnalyzer::tokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader, bool deleteReader) {
+ BufferedReader* bufferedReader = reader->__asBufferedReader();
+ TokenStream* result;
+
+ if ( bufferedReader == NULL )
+ result = _CLNEW StandardTokenizer( _CLNEW FilteredBufferedReader(reader, deleteReader), true );
+ else
+ result = _CLNEW StandardTokenizer(bufferedReader, deleteReader);
+
+ result = _CLNEW StandardFilter(result, true);
+ result = _CLNEW CL_NS(analysis)::LowerCaseFilter(result, true);
+ if (stopSet != NULL)
+ result = _CLNEW CL_NS(analysis)::StopFilter(result, true, stopSet);
+ result = _CLNEW SnowballFilter(result, normalizer, language, true);
+ return result;
+ }
+
+ TokenStream* SnowballAnalyzer::reusableTokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader) {
+ if (prevstream) _CLDELETE(prevstream);
+ prevstream = this->tokenStream(fieldName, reader);
+ return prevstream;
+ }
+
+
+
+
+
+
+ /** Construct the named stemming filter.
+ *
+ * @param in the input tokens to stem
+ * @param name the name of a stemmer
+ */
+ SnowballFilter::SnowballFilter(TokenStream* in, normalizer_func_t *normalizer, const char* language, bool deleteTS):
+ TokenFilter(in,deleteTS)
+ {
+ stemmer = sb_stemmer_new(language, NULL); //use utf8 encoding
+ this->normalizer = normalizer;
+
+ if ( stemmer == NULL ){
+ _CLTHROWA(CL_ERR_IllegalArgument, "language not available for stemming\n"); //todo: richer error
+ }
+ }
+
+ SnowballFilter::~SnowballFilter(){
+ sb_stemmer_delete(stemmer);
+ }
+
+ /** Returns the next input Token, after being stemmed */
+ Token* SnowballFilter::next(Token* token){
+ if (input->next(token) == NULL)
+ return NULL;
+
+ unsigned char utf8text[LUCENE_MAX_WORD_LEN*5+1];
+ unsigned int len = I_MIN(LUCENE_MAX_WORD_LEN, token->termLength());
+
+ buffer_t buf = { { 0, 0 } };
+ i_assert(sizeof(wchar_t) == sizeof(unichar_t));
+ buffer_create_from_data(&buf, utf8text, sizeof(utf8text));
+ uni_ucs4_to_utf8((const unichar_t *)token->termBuffer(), len, &buf);
+
+ const sb_symbol* stemmed = sb_stemmer_stem(stemmer, utf8text, buf.used);
+ if ( stemmed == NULL )
+ _CLTHROWA(CL_ERR_Runtime,"Out of memory");
+
+ int stemmedLen=sb_stemmer_length(stemmer);
+
+ if (normalizer == NULL) {
+ unsigned int tchartext_size =
+ uni_utf8_strlen_n(stemmed, stemmedLen) + 1;
+ TCHAR tchartext[tchartext_size];
+ lucene_utf8_n_to_tchar(stemmed, stemmedLen, tchartext, tchartext_size);
+ token->set(tchartext,token->startOffset(), token->endOffset(), token->type());
+ } else T_BEGIN {
+ buffer_t *norm_buf = t_buffer_create(stemmedLen);
+ normalizer(stemmed, stemmedLen, norm_buf);
+
+ unsigned int tchartext_size =
+ uni_utf8_strlen_n(norm_buf->data, norm_buf->used) + 1;
+ TCHAR tchartext[tchartext_size];
+ lucene_utf8_n_to_tchar((const unsigned char *)norm_buf->data,
+ norm_buf->used, tchartext, tchartext_size);
+ token->set(tchartext,token->startOffset(), token->endOffset(), token->type());
+ } T_END;
+ return token;
+ }
+
+
+CL_NS_END2
diff --git a/src/plugins/fts-lucene/SnowballAnalyzer.h b/src/plugins/fts-lucene/SnowballAnalyzer.h
new file mode 100644
index 0000000..45455c5
--- /dev/null
+++ b/src/plugins/fts-lucene/SnowballAnalyzer.h
@@ -0,0 +1,51 @@
+/*------------------------------------------------------------------------------
+* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team
+*
+* Distributable under the terms of either the Apache License (Version 2.0) or
+* the GNU Lesser General Public License, as specified in the COPYING file.
+------------------------------------------------------------------------------*/
+#ifndef _lucene_analysis_snowball_analyser_
+#define _lucene_analysis_snowball_analyser_
+
+extern "C" {
+#include "lib.h"
+#include "unichar.h"
+};
+#include "CLucene/analysis/AnalysisHeader.h"
+
+CL_CLASS_DEF(util,BufferedReader)
+CL_NS_DEF2(analysis,snowball)
+
+/** Filters {@link StandardTokenizer} with {@link StandardFilter}, {@link
+ * LowerCaseFilter}, {@link StopFilter} and {@link SnowballFilter}.
+ *
+ * Available stemmers are listed in {@link net.sf.snowball.ext}. The name of a
+ * stemmer is the part of the class name before "Stemmer", e.g., the stemmer in
+ * {@link EnglishStemmer} is named "English".
+ */
+class CLUCENE_CONTRIBS_EXPORT SnowballAnalyzer: public Analyzer {
+ char* language;
+ normalizer_func_t *normalizer;
+ CLTCSetList* stopSet;
+ TokenStream *prevstream;
+
+public:
+ /** Builds the named analyzer with no stop words. */
+ SnowballAnalyzer(normalizer_func_t *normalizer, const char* language="english");
+
+ /** Builds the named analyzer with the given stop words.
+ */
+ SnowballAnalyzer(const char* language, const TCHAR** stopWords);
+
+ ~SnowballAnalyzer();
+
+ /** Constructs a {@link StandardTokenizer} filtered by a {@link
+ StandardFilter}, a {@link LowerCaseFilter} and a {@link StopFilter}. */
+ TokenStream* tokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader);
+ TokenStream* tokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader, bool deleteReader);
+ TokenStream* reusableTokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader);
+};
+
+CL_NS_END2
+#endif
+
diff --git a/src/plugins/fts-lucene/SnowballFilter.h b/src/plugins/fts-lucene/SnowballFilter.h
new file mode 100644
index 0000000..6a0ed12
--- /dev/null
+++ b/src/plugins/fts-lucene/SnowballFilter.h
@@ -0,0 +1,42 @@
+/*------------------------------------------------------------------------------
+* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team
+*
+* Distributable under the terms of either the Apache License (Version 2.0) or
+* the GNU Lesser General Public License, as specified in the COPYING file.
+------------------------------------------------------------------------------*/
+#ifndef _lucene_analysis_snowball_filter_
+#define _lucene_analysis_snowball_filter_
+
+#include "CLucene/analysis/AnalysisHeader.h"
+#include "libstemmer.h"
+
+CL_NS_DEF2(analysis,snowball)
+
+/** A filter that stems words using a Snowball-generated stemmer.
+ *
+ * Available stemmers are listed in {@link net.sf.snowball.ext}. The name of a
+ * stemmer is the part of the class name before "Stemmer", e.g., the stemmer in
+ * {@link EnglishStemmer} is named "English".
+ *
+ * Note: todo: This is not thread safe...
+ */
+class CLUCENE_CONTRIBS_EXPORT SnowballFilter: public TokenFilter {
+ struct sb_stemmer * stemmer;
+ normalizer_func_t *normalizer;
+public:
+
+ /** Construct the named stemming filter.
+ *
+ * @param in the input tokens to stem
+ * @param name the name of a stemmer
+ */
+ SnowballFilter(TokenStream* in, normalizer_func_t *normalizer, const char* language, bool deleteTS);
+
+ ~SnowballFilter();
+
+ /** Returns the next input Token, after being stemmed */
+ Token* next(Token* token);
+};
+
+CL_NS_END2
+#endif
diff --git a/src/plugins/fts-lucene/doveadm-fts-lucene.c b/src/plugins/fts-lucene/doveadm-fts-lucene.c
new file mode 100644
index 0000000..a761907
--- /dev/null
+++ b/src/plugins/fts-lucene/doveadm-fts-lucene.c
@@ -0,0 +1,70 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "doveadm-dump.h"
+#include "doveadm-fts.h"
+#include "lucene-wrapper.h"
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+const char *doveadm_fts_lucene_plugin_version = DOVECOT_ABI_VERSION;
+
+void doveadm_fts_lucene_plugin_init(struct module *module);
+void doveadm_fts_lucene_plugin_deinit(void);
+
+static void
+cmd_dump_fts_lucene(const char *path, const char *const *args ATTR_UNUSED)
+{
+ struct lucene_index *index;
+ struct lucene_index_iter *iter;
+ guid_128_t prev_guid;
+ const struct lucene_index_record *rec;
+ bool first = TRUE;
+
+ i_zero(&prev_guid);
+ index = lucene_index_init(path, NULL, NULL);
+ iter = lucene_index_iter_init(index);
+ while ((rec = lucene_index_iter_next(iter)) != NULL) {
+ if (memcmp(prev_guid, rec->mailbox_guid,
+ sizeof(prev_guid)) != 0) {
+ if (first)
+ first = FALSE;
+ else
+ printf("\n");
+ memcpy(prev_guid, rec->mailbox_guid, sizeof(prev_guid));
+ printf("%s: ", guid_128_to_string(prev_guid));
+ }
+ printf("%u", rec->uid);
+ if (rec->part_num != 0)
+ printf("[%u]", rec->part_num);
+ printf("\n");
+ }
+ printf("\n");
+ if (lucene_index_iter_deinit(&iter) < 0)
+ i_error("Lucene index iteration failed");
+ lucene_index_deinit(index);
+}
+
+static bool test_dump_fts_lucene(const char *path)
+{
+ struct stat st;
+
+ path = t_strconcat(path, "/segments.gen", NULL);
+ return stat(path, &st) == 0;
+}
+
+static const struct doveadm_cmd_dump doveadm_cmd_dump_fts_lucene = {
+ "fts-lucene",
+ test_dump_fts_lucene,
+ cmd_dump_fts_lucene
+};
+
+void doveadm_fts_lucene_plugin_init(struct module *module ATTR_UNUSED)
+{
+ doveadm_dump_register(&doveadm_cmd_dump_fts_lucene);
+}
+
+void doveadm_fts_lucene_plugin_deinit(void)
+{
+}
diff --git a/src/plugins/fts-lucene/fts-backend-lucene.c b/src/plugins/fts-lucene/fts-backend-lucene.c
new file mode 100644
index 0000000..963dbdf
--- /dev/null
+++ b/src/plugins/fts-lucene/fts-backend-lucene.c
@@ -0,0 +1,605 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "hex-binary.h"
+#include "strescape.h"
+#include "message-part.h"
+#include "mail-namespace.h"
+#include "mail-storage-private.h"
+#include "fts-expunge-log.h"
+#include "lucene-wrapper.h"
+#include "fts-indexer.h"
+#include "fts-lucene-plugin.h"
+
+#include <wchar.h>
+
+#define LUCENE_INDEX_DIR_NAME "lucene-indexes"
+#define LUCENE_EXPUNGE_LOG_NAME "dovecot-expunges.log"
+#define LUCENE_OPTIMIZE_BATCH_MSGS_COUNT 100
+
+struct lucene_fts_backend {
+ struct fts_backend backend;
+ char *dir_path;
+
+ struct lucene_index *index;
+ struct mailbox *selected_box;
+ unsigned int selected_box_generation;
+ guid_128_t selected_box_guid;
+
+ struct fts_expunge_log *expunge_log;
+
+ bool dir_created:1;
+ bool updating:1;
+};
+
+struct lucene_fts_backend_update_context {
+ struct fts_backend_update_context ctx;
+
+ struct mailbox *box;
+ uint32_t last_uid;
+ uint32_t last_indexed_uid;
+ char *first_box_vname;
+
+ uint32_t uid, part_num;
+ char *hdr_name;
+
+ unsigned int added_msgs;
+ struct fts_expunge_log_append_ctx *expunge_ctx;
+
+ bool lucene_opened;
+ bool last_indexed_uid_set;
+ bool mime_parts;
+};
+
+static int fts_backend_lucene_mkdir(struct lucene_fts_backend *backend)
+{
+ if (backend->dir_created)
+ return 0;
+
+ backend->dir_created = TRUE;
+ if (mailbox_list_mkdir_root(backend->backend.ns->list,
+ backend->dir_path,
+ MAILBOX_LIST_PATH_TYPE_INDEX) < 0)
+ return -1;
+ return 0;
+}
+
+static int
+fts_lucene_get_mailbox_guid(struct mailbox *box, guid_128_t guid_r)
+{
+ struct mailbox_metadata metadata;
+
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID,
+ &metadata) < 0) {
+ i_error("lucene: Couldn't get mailbox %s GUID: %s",
+ box->vname, mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+ memcpy(guid_r, metadata.guid, GUID_128_SIZE);
+ return 0;
+}
+
+static int
+fts_backend_select(struct lucene_fts_backend *backend, struct mailbox *box)
+{
+ guid_128_t guid;
+ unsigned char guid_hex[MAILBOX_GUID_HEX_LENGTH];
+ wchar_t wguid_hex[MAILBOX_GUID_HEX_LENGTH];
+ buffer_t buf;
+ unsigned int i;
+
+ i_assert(box != NULL);
+
+ if (backend->selected_box == box &&
+ backend->selected_box_generation == box->generation_sequence)
+ return 0;
+
+ if (fts_lucene_get_mailbox_guid(box, guid) < 0)
+ return -1;
+ buffer_create_from_data(&buf, guid_hex, MAILBOX_GUID_HEX_LENGTH);
+ binary_to_hex_append(&buf, guid, GUID_128_SIZE);
+ for (i = 0; i < N_ELEMENTS(wguid_hex); i++)
+ wguid_hex[i] = guid_hex[i];
+
+ lucene_index_select_mailbox(backend->index, wguid_hex);
+
+ backend->selected_box = box;
+ memcpy(backend->selected_box_guid, guid,
+ sizeof(backend->selected_box_guid));
+ backend->selected_box_generation = box->generation_sequence;
+ return 0;
+}
+
+static struct fts_backend *fts_backend_lucene_alloc(void)
+{
+ struct lucene_fts_backend *backend;
+
+ backend = i_new(struct lucene_fts_backend, 1);
+ backend->backend = fts_backend_lucene;
+ return &backend->backend;
+}
+
+static int
+fts_backend_lucene_init(struct fts_backend *_backend, const char **error_r)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+ struct fts_lucene_user *fuser =
+ FTS_LUCENE_USER_CONTEXT(_backend->ns->user);
+ const char *path;
+
+ if (fuser == NULL) {
+ /* invalid settings */
+ *error_r = "Invalid fts_lucene settings";
+ return -1;
+ }
+ /* fts already checked that index exists */
+
+ if (fuser->set.use_libfts) {
+ /* change our flags so we get proper input */
+ _backend->flags &= ENUM_NEGATE(FTS_BACKEND_FLAG_FUZZY_SEARCH);
+ _backend->flags |= FTS_BACKEND_FLAG_TOKENIZED_INPUT;
+ }
+ path = mailbox_list_get_root_forced(_backend->ns->list,
+ MAILBOX_LIST_PATH_TYPE_INDEX);
+
+ backend->dir_path = i_strconcat(path, "/"LUCENE_INDEX_DIR_NAME, NULL);
+ backend->index = lucene_index_init(backend->dir_path,
+ _backend->ns->list,
+ &fuser->set);
+
+ path = t_strconcat(backend->dir_path, "/"LUCENE_EXPUNGE_LOG_NAME, NULL);
+ backend->expunge_log = fts_expunge_log_init(path);
+ return 0;
+}
+
+static void fts_backend_lucene_deinit(struct fts_backend *_backend)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+
+ if (backend->index != NULL)
+ lucene_index_deinit(backend->index);
+ if (backend->expunge_log != NULL)
+ fts_expunge_log_deinit(&backend->expunge_log);
+ i_free(backend->dir_path);
+ i_free(backend);
+}
+
+static int
+fts_backend_lucene_get_last_uid(struct fts_backend *_backend,
+ struct mailbox *box, uint32_t *last_uid_r)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+ struct fts_lucene_user *fuser =
+ FTS_LUCENE_USER_CONTEXT_REQUIRE(_backend->ns->user);
+ struct fts_index_header hdr;
+ uint32_t set_checksum;
+ int ret;
+
+ if (fts_index_get_header(box, &hdr)) {
+ set_checksum = fts_lucene_settings_checksum(&fuser->set);
+ ret = fts_index_have_compatible_settings(_backend->ns->list,
+ set_checksum);
+ if (ret < 0)
+ return -1;
+ if (ret == 0) {
+ /* need to rebuild the index */
+ *last_uid_r = 0;
+ } else {
+ *last_uid_r = hdr.last_indexed_uid;
+ }
+ return 0;
+ }
+
+ /* either nothing has been indexed, or the index was corrupted.
+ do it the slow way. */
+ if (fts_backend_select(backend, box) < 0)
+ return -1;
+ if (lucene_index_get_last_uid(backend->index, last_uid_r) < 0)
+ return -1;
+
+ fts_index_set_last_uid(box, *last_uid_r);
+ return 0;
+}
+
+static struct fts_backend_update_context *
+fts_backend_lucene_update_init(struct fts_backend *_backend)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+ struct lucene_fts_backend_update_context *ctx;
+ struct fts_lucene_user *fuser =
+ FTS_LUCENE_USER_CONTEXT_REQUIRE(_backend->ns->user);
+
+ i_assert(!backend->updating);
+
+ ctx = i_new(struct lucene_fts_backend_update_context, 1);
+ ctx->ctx.backend = _backend;
+ ctx->mime_parts = fuser->set.mime_parts;
+ backend->updating = TRUE;
+ return &ctx->ctx;
+}
+
+static bool
+fts_backend_lucene_need_optimize(struct lucene_fts_backend_update_context *ctx)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)ctx->ctx.backend;
+ unsigned int expunges;
+ uint32_t numdocs;
+
+ if (ctx->added_msgs >= LUCENE_OPTIMIZE_BATCH_MSGS_COUNT)
+ return TRUE;
+ if (lucene_index_get_doc_count(backend->index, &numdocs) < 0)
+ return FALSE;
+
+ if (fts_expunge_log_uid_count(backend->expunge_log, &expunges) < 0)
+ return FALSE;
+ return expunges > 0 &&
+ numdocs / expunges <= 50; /* >2% of index has been expunged */
+}
+
+static int
+fts_backend_lucene_update_deinit(struct fts_backend_update_context *_ctx)
+{
+ struct lucene_fts_backend_update_context *ctx =
+ (struct lucene_fts_backend_update_context *)_ctx;
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_ctx->backend;
+ int ret = _ctx->failed ? -1 : 0;
+
+ i_assert(backend->updating);
+
+ backend->updating = FALSE;
+ if (ctx->lucene_opened) {
+ if (lucene_index_build_deinit(backend->index) < 0)
+ ret = -1;
+ }
+
+ if (ctx->expunge_ctx != NULL) {
+ if (fts_expunge_log_append_commit(&ctx->expunge_ctx) < 0) {
+ struct stat st;
+ ret = -1;
+
+ if (stat(backend->dir_path, &st) < 0 && errno == ENOENT) {
+ /* lucene-indexes directory doesn't even exist,
+ so dovecot.index's last_index_uid is wrong.
+ rescan to update them. */
+ (void)lucene_index_rescan(backend->index);
+ ret = 0;
+ }
+ }
+ }
+
+ if (fts_backend_lucene_need_optimize(ctx)) {
+ if (ctx->lucene_opened)
+ (void)fts_backend_optimize(_ctx->backend);
+ else if (ctx->first_box_vname != NULL) {
+ struct mail_user *user = backend->backend.ns->user;
+ const char *cmd, *path;
+ int fd;
+
+ /* the optimize affects all mailboxes within namespace,
+ so just use any mailbox name in it */
+ cmd = t_strdup_printf("OPTIMIZE\t0\t%s\t%s\n",
+ str_tabescape(user->username),
+ str_tabescape(ctx->first_box_vname));
+ fd = fts_indexer_cmd(user, cmd, &path);
+ i_close_fd(&fd);
+ }
+ }
+
+ i_free(ctx->first_box_vname);
+ i_free(ctx);
+ return ret;
+}
+
+static void
+fts_backend_lucene_update_set_mailbox(struct fts_backend_update_context *_ctx,
+ struct mailbox *box)
+{
+ struct lucene_fts_backend_update_context *ctx =
+ (struct lucene_fts_backend_update_context *)_ctx;
+
+ if (ctx->last_uid != 0) {
+ fts_index_set_last_uid(ctx->box, ctx->last_uid);
+ ctx->last_uid = 0;
+ }
+ if (ctx->first_box_vname == NULL && box != NULL)
+ ctx->first_box_vname = i_strdup(box->vname);
+ ctx->box = box;
+ ctx->last_indexed_uid_set = FALSE;
+}
+
+static void
+fts_backend_lucene_update_expunge(struct fts_backend_update_context *_ctx,
+ uint32_t uid)
+{
+ struct lucene_fts_backend_update_context *ctx =
+ (struct lucene_fts_backend_update_context *)_ctx;
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_ctx->backend;
+ struct fts_index_header hdr;
+
+ if (!ctx->last_indexed_uid_set) {
+ if (!fts_index_get_header(ctx->box, &hdr))
+ ctx->last_indexed_uid = 0;
+ else
+ ctx->last_indexed_uid = hdr.last_indexed_uid;
+ ctx->last_indexed_uid_set = TRUE;
+ }
+ if (ctx->last_indexed_uid == 0 ||
+ uid > ctx->last_indexed_uid + 100) {
+ /* don't waste time adding expunge to log for a message that
+ isn't even indexed. this check is racy, because indexer may
+ just be in the middle of indexing this message. we'll
+ attempt to avoid that by skipping the expunging only if
+ indexing hasn't been done for a while (100 msgs). */
+ return;
+ }
+
+ if (ctx->expunge_ctx == NULL) {
+ ctx->expunge_ctx =
+ fts_expunge_log_append_begin(backend->expunge_log);
+ }
+
+ if (fts_backend_select(backend, ctx->box) < 0)
+ _ctx->failed = TRUE;
+
+ fts_expunge_log_append_next(ctx->expunge_ctx,
+ backend->selected_box_guid, uid);
+}
+
+static bool
+fts_backend_lucene_update_set_build_key(struct fts_backend_update_context *_ctx,
+ const struct fts_backend_build_key *key)
+{
+ struct lucene_fts_backend_update_context *ctx =
+ (struct lucene_fts_backend_update_context *)_ctx;
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_ctx->backend;
+
+ if (!ctx->lucene_opened) {
+ if (fts_backend_lucene_mkdir(backend) < 0)
+ ctx->ctx.failed = TRUE;
+ if (lucene_index_build_init(backend->index) < 0)
+ ctx->ctx.failed = TRUE;
+ ctx->lucene_opened = TRUE;
+ }
+
+ if (fts_backend_select(backend, ctx->box) < 0)
+ _ctx->failed = TRUE;
+
+ switch (key->type) {
+ case FTS_BACKEND_BUILD_KEY_HDR:
+ case FTS_BACKEND_BUILD_KEY_MIME_HDR:
+ i_assert(key->hdr_name != NULL);
+
+ i_free(ctx->hdr_name);
+ ctx->hdr_name = i_strdup(key->hdr_name);
+ break;
+ case FTS_BACKEND_BUILD_KEY_BODY_PART:
+ i_free_and_null(ctx->hdr_name);
+ break;
+ case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY:
+ i_unreached();
+ }
+
+ if (key->uid != ctx->last_uid) {
+ i_assert(key->uid >= ctx->last_uid);
+ ctx->last_uid = key->uid;
+ ctx->added_msgs++;
+ }
+
+ ctx->uid = key->uid;
+ if (ctx->mime_parts)
+ ctx->part_num = message_part_to_idx(key->part);
+ return TRUE;
+}
+
+static void
+fts_backend_lucene_update_unset_build_key(struct fts_backend_update_context *_ctx)
+{
+ struct lucene_fts_backend_update_context *ctx =
+ (struct lucene_fts_backend_update_context *)_ctx;
+
+ ctx->uid = 0;
+ ctx->part_num = 0;
+ i_free_and_null(ctx->hdr_name);
+}
+
+static int
+fts_backend_lucene_update_build_more(struct fts_backend_update_context *_ctx,
+ const unsigned char *data, size_t size)
+{
+ struct lucene_fts_backend_update_context *ctx =
+ (struct lucene_fts_backend_update_context *)_ctx;
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_ctx->backend;
+ int ret;
+
+ i_assert(ctx->uid != 0);
+
+ if (_ctx->failed)
+ return -1;
+
+ T_BEGIN {
+ ret = lucene_index_build_more(backend->index, ctx->uid,
+ ctx->part_num, data, size,
+ ctx->hdr_name);
+ } T_END;
+ return ret;
+}
+
+static int
+fts_backend_lucene_refresh(struct fts_backend *_backend)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+
+ if (backend->index != NULL)
+ lucene_index_close(backend->index);
+ return 0;
+}
+
+static int fts_backend_lucene_rescan(struct fts_backend *_backend)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+
+ if (lucene_index_rescan(backend->index) < 0)
+ return -1;
+ return lucene_index_optimize(backend->index);
+}
+
+static int fts_backend_lucene_optimize(struct fts_backend *_backend)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+ int ret;
+
+ ret = lucene_index_expunge_from_log(backend->index,
+ backend->expunge_log);
+ if (ret == 0) {
+ /* log was corrupted, need to rescan */
+ ret = lucene_index_rescan(backend->index);
+ }
+ if (ret >= 0)
+ ret = lucene_index_optimize(backend->index);
+ return ret;
+}
+
+static int
+fts_backend_lucene_lookup(struct fts_backend *_backend, struct mailbox *box,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_result *result)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+ int ret;
+
+ if (fts_backend_select(backend, box) < 0)
+ return -1;
+ T_BEGIN {
+ ret = lucene_index_lookup(backend->index, args, flags, result);
+ } T_END;
+ return ret;
+}
+
+/* a char* hash function from ASU -- from glib */
+static unsigned int wstr_hash(const wchar_t *s)
+{
+ unsigned int g, h = 0;
+
+ while (*s != '\0') {
+ h = (h << 4) + *s;
+ if ((g = h & 0xf0000000UL) != 0) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ s++;
+ }
+
+ return h;
+}
+
+static int
+mailboxes_get_guids(struct mailbox *const boxes[],
+ HASH_TABLE_TYPE(wguid_result) guids,
+ struct fts_multi_result *result)
+{
+ ARRAY(struct fts_result) box_results;
+ struct fts_result *box_result;
+ const char *guid;
+ wchar_t *guid_dup;
+ unsigned int i, j;
+
+ p_array_init(&box_results, result->pool, 32);
+ /* first create the box_results - we'll be using pointers to them
+ later on and appending to the array changes the pointers */
+ for (i = 0; boxes[i] != NULL; i++) {
+ box_result = array_append_space(&box_results);
+ box_result->box = boxes[i];
+ }
+ for (i = 0; boxes[i] != NULL; i++) {
+ if (fts_mailbox_get_guid(boxes[i], &guid) < 0)
+ return -1;
+
+ i_assert(strlen(guid) == MAILBOX_GUID_HEX_LENGTH);
+ guid_dup = t_new(wchar_t, MAILBOX_GUID_HEX_LENGTH + 1);
+ for (j = 0; j < MAILBOX_GUID_HEX_LENGTH; j++)
+ guid_dup[j] = guid[j];
+
+ box_result = array_idx_modifiable(&box_results, i);
+ hash_table_insert(guids, guid_dup, box_result);
+ }
+
+ array_append_zero(&box_results);
+ result->box_results = array_front_modifiable(&box_results);
+ return 0;
+}
+
+static int
+fts_backend_lucene_lookup_multi(struct fts_backend *_backend,
+ struct mailbox *const boxes[],
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result)
+{
+ struct lucene_fts_backend *backend =
+ (struct lucene_fts_backend *)_backend;
+ int ret;
+
+ T_BEGIN {
+ HASH_TABLE_TYPE(wguid_result) guids;
+
+ hash_table_create(&guids, default_pool, 0, wstr_hash, wcscmp);
+ ret = mailboxes_get_guids(boxes, guids, result);
+ if (ret == 0) {
+ ret = lucene_index_lookup_multi(backend->index,
+ guids, args, flags,
+ result);
+ }
+ hash_table_destroy(&guids);
+ } T_END;
+ return ret;
+}
+
+static void fts_backend_lucene_lookup_done(struct fts_backend *_backend)
+{
+ /* the next refresh is going to close the index anyway, so we might as
+ well do it now */
+ (void)fts_backend_lucene_refresh(_backend);
+}
+
+struct fts_backend fts_backend_lucene = {
+ .name = "lucene",
+ .flags = FTS_BACKEND_FLAG_BUILD_FULL_WORDS |
+ FTS_BACKEND_FLAG_FUZZY_SEARCH,
+
+ {
+ fts_backend_lucene_alloc,
+ fts_backend_lucene_init,
+ fts_backend_lucene_deinit,
+ fts_backend_lucene_get_last_uid,
+ fts_backend_lucene_update_init,
+ fts_backend_lucene_update_deinit,
+ fts_backend_lucene_update_set_mailbox,
+ fts_backend_lucene_update_expunge,
+ fts_backend_lucene_update_set_build_key,
+ fts_backend_lucene_update_unset_build_key,
+ fts_backend_lucene_update_build_more,
+ fts_backend_lucene_refresh,
+ fts_backend_lucene_rescan,
+ fts_backend_lucene_optimize,
+ fts_backend_default_can_lookup,
+ fts_backend_lucene_lookup,
+ fts_backend_lucene_lookup_multi,
+ fts_backend_lucene_lookup_done
+ }
+};
diff --git a/src/plugins/fts-lucene/fts-lucene-plugin.c b/src/plugins/fts-lucene/fts-lucene-plugin.c
new file mode 100644
index 0000000..7c58fa7
--- /dev/null
+++ b/src/plugins/fts-lucene/fts-lucene-plugin.c
@@ -0,0 +1,146 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "crc32.h"
+#include "mail-storage-hooks.h"
+#include "lucene-wrapper.h"
+#include "fts-user.h"
+#include "fts-lucene-plugin.h"
+
+const char *fts_lucene_plugin_version = DOVECOT_ABI_VERSION;
+
+struct fts_lucene_user_module fts_lucene_user_module =
+ MODULE_CONTEXT_INIT(&mail_user_module_register);
+
+static int
+fts_lucene_plugin_init_settings(struct mail_user *user,
+ struct fts_lucene_settings *set,
+ const char *str)
+{
+ const char *const *tmp;
+
+ for (tmp = t_strsplit_spaces(str, " "); *tmp != NULL; tmp++) {
+ if (str_begins(*tmp, "default_language=")) {
+ set->default_language =
+ p_strdup(user->pool, *tmp + 17);
+ } else if (str_begins(*tmp, "textcat_conf=")) {
+ set->textcat_conf = p_strdup(user->pool, *tmp + 13);
+ } else if (str_begins(*tmp, "textcat_dir=")) {
+ set->textcat_dir = p_strdup(user->pool, *tmp + 12);
+ } else if (str_begins(*tmp, "whitespace_chars=")) {
+ set->whitespace_chars = p_strdup(user->pool, *tmp + 17);
+ } else if (strcmp(*tmp, "normalize") == 0) {
+ set->normalize = TRUE;
+ } else if (strcmp(*tmp, "no_snowball") == 0) {
+ set->no_snowball = TRUE;
+ } else if (strcmp(*tmp, "mime_parts") == 0) {
+ set->mime_parts = TRUE;
+ } else if (strcmp(*tmp, "use_libfts") == 0) {
+ set->use_libfts = TRUE;
+ } else {
+ i_error("fts_lucene: Invalid setting: %s", *tmp);
+ return -1;
+ }
+ }
+ if (set->textcat_conf != NULL && set->textcat_dir == NULL) {
+ i_error("fts_lucene: textcat_conf set, but textcat_dir unset");
+ return -1;
+ }
+ if (set->textcat_conf == NULL && set->textcat_dir != NULL) {
+ i_error("fts_lucene: textcat_dir set, but textcat_conf unset");
+ return -1;
+ }
+ if (set->whitespace_chars == NULL)
+ set->whitespace_chars = "";
+#ifndef HAVE_FTS_STEMMER
+ if (set->default_language != NULL) {
+ i_error("fts_lucene: default_language set, "
+ "but Dovecot built without stemmer support");
+ return -1;
+ }
+#else
+ if (set->default_language == NULL)
+ set->default_language = "english";
+#endif
+#ifndef HAVE_FTS_TEXTCAT
+ if (set->textcat_conf != NULL) {
+ i_error("fts_lucene: textcat_dir set, "
+ "but Dovecot built without textcat support");
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+uint32_t fts_lucene_settings_checksum(const struct fts_lucene_settings *set)
+{
+ uint32_t crc;
+
+ if (set->use_libfts)
+ return crc32_str("l");
+
+ /* checksum is always different when compiling with/without stemmer */
+ crc = set->default_language == NULL ? 0 :
+ crc32_str(set->default_language);
+ crc = crc32_str_more(crc, set->whitespace_chars);
+ if (set->normalize)
+ crc = crc32_str_more(crc, "n");
+ if (set->no_snowball)
+ crc = crc32_str_more(crc, "s");
+ /* don't include mime_parts here, since changing it doesn't
+ necessarily need the index to be rebuilt */
+ return crc;
+}
+
+static void fts_lucene_mail_user_deinit(struct mail_user *user)
+{
+ struct fts_lucene_user *fuser = FTS_LUCENE_USER_CONTEXT_REQUIRE(user);
+
+ fts_mail_user_deinit(user);
+ fuser->module_ctx.super.deinit(user);
+}
+
+static void fts_lucene_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct fts_lucene_user *fuser;
+ const char *env, *error;
+
+ fuser = p_new(user->pool, struct fts_lucene_user, 1);
+ env = mail_user_plugin_getenv(user, "fts_lucene");
+ if (env == NULL)
+ env = "";
+
+ if (fts_lucene_plugin_init_settings(user, &fuser->set, env) < 0) {
+ /* invalid settings, disabling */
+ return;
+ }
+ if (fts_mail_user_init(user, fuser->set.use_libfts, &error) < 0) {
+ i_error("fts_lucene: %s", error);
+ return;
+ }
+
+ fuser->module_ctx.super = *v;
+ user->vlast = &fuser->module_ctx.super;
+ v->deinit = fts_lucene_mail_user_deinit;
+ MODULE_CONTEXT_SET(user, fts_lucene_user_module, fuser);
+}
+
+static struct mail_storage_hooks fts_lucene_mail_storage_hooks = {
+ .mail_user_created = fts_lucene_mail_user_created
+};
+
+void fts_lucene_plugin_init(struct module *module ATTR_UNUSED)
+{
+ fts_backend_register(&fts_backend_lucene);
+ mail_storage_hooks_add(module, &fts_lucene_mail_storage_hooks);
+}
+
+void fts_lucene_plugin_deinit(void)
+{
+ fts_backend_unregister(fts_backend_lucene.name);
+ mail_storage_hooks_remove(&fts_lucene_mail_storage_hooks);
+ lucene_shutdown();
+}
+
+const char *fts_lucene_plugin_dependencies[] = { "fts", NULL };
diff --git a/src/plugins/fts-lucene/fts-lucene-plugin.h b/src/plugins/fts-lucene/fts-lucene-plugin.h
new file mode 100644
index 0000000..69440fb
--- /dev/null
+++ b/src/plugins/fts-lucene/fts-lucene-plugin.h
@@ -0,0 +1,36 @@
+#ifndef FTS_LUCENE_PLUGIN_H
+#define FTS_LUCENE_PLUGIN_H
+
+#include "module-context.h"
+#include "mail-user.h"
+#include "fts-api-private.h"
+
+#define FTS_LUCENE_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, fts_lucene_user_module)
+#define FTS_LUCENE_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, fts_lucene_user_module)
+
+struct fts_lucene_settings {
+ const char *default_language;
+ const char *textcat_conf, *textcat_dir;
+ const char *whitespace_chars;
+ bool normalize;
+ bool no_snowball;
+ bool mime_parts;
+ bool use_libfts;
+};
+
+struct fts_lucene_user {
+ union mail_user_module_context module_ctx;
+ struct fts_lucene_settings set;
+};
+
+extern struct fts_backend fts_backend_lucene;
+extern MODULE_CONTEXT_DEFINE(fts_lucene_user_module, &mail_user_module_register);
+
+uint32_t fts_lucene_settings_checksum(const struct fts_lucene_settings *set);
+
+void fts_lucene_plugin_init(struct module *module);
+void fts_lucene_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/fts-lucene/lucene-wrapper.cc b/src/plugins/fts-lucene/lucene-wrapper.cc
new file mode 100644
index 0000000..7446693
--- /dev/null
+++ b/src/plugins/fts-lucene/lucene-wrapper.cc
@@ -0,0 +1,1639 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+extern "C" {
+#include "lib.h"
+#include "array.h"
+#include "unichar.h"
+#include "hash.h"
+#include "hex-binary.h"
+#include "ioloop.h"
+#include "unlink-directory.h"
+#include "ioloop.h"
+#include "mail-index.h"
+#include "mail-search.h"
+#include "mail-namespace.h"
+#include "mailbox-list-private.h"
+#include "mail-storage.h"
+#include "fts-expunge-log.h"
+#include "fts-lucene-plugin.h"
+#include "lucene-wrapper.h"
+
+#include <sys/stat.h>
+#ifdef HAVE_LIBEXTTEXTCAT_TEXTCAT_H
+# include <libexttextcat/textcat.h>
+#elif defined (HAVE_LIBTEXTCAT_TEXTCAT_H)
+# include <libtextcat/textcat.h>
+#elif defined (HAVE_FTS_TEXTCAT)
+# include <textcat.h>
+#endif
+};
+#include <CLucene.h>
+#include <CLucene/util/CLStreams.h>
+#include <CLucene/search/MultiPhraseQuery.h>
+#include "SnowballAnalyzer.h"
+
+/* Lucene's default is 10000. Use it here also.. */
+#define MAX_TERMS_PER_DOCUMENT 10000
+#define FTS_LUCENE_MAX_SEARCH_TERMS 1000
+
+#define LUCENE_LOCK_OVERRIDE_SECS 60
+#define LUCENE_INDEX_CLOSE_TIMEOUT_MSECS (120*1000)
+
+using namespace lucene::document;
+using namespace lucene::index;
+using namespace lucene::search;
+using namespace lucene::queryParser;
+using namespace lucene::analysis;
+using namespace lucene::analysis;
+using namespace lucene::util;
+
+struct lucene_query {
+ Query *query;
+ BooleanClause::Occur occur;
+};
+ARRAY_DEFINE_TYPE(lucene_query, struct lucene_query);
+
+struct lucene_analyzer {
+ char *lang;
+ Analyzer *analyzer;
+};
+
+struct lucene_index {
+ char *path;
+ struct mailbox_list *list;
+ struct fts_lucene_settings set;
+ normalizer_func_t *normalizer;
+
+ wchar_t mailbox_guid[MAILBOX_GUID_HEX_LENGTH + 1];
+
+ IndexReader *reader;
+ IndexWriter *writer;
+ IndexSearcher *searcher;
+ struct timeout *to_close;
+
+ buffer_t *normalizer_buf;
+ Analyzer *default_analyzer, *cur_analyzer;
+ ARRAY(struct lucene_analyzer) analyzers;
+
+ Document *doc;
+ uint32_t prev_uid, prev_part_idx;
+ bool no_analyzer;
+};
+
+struct rescan_context {
+ struct lucene_index *index;
+
+ struct mailbox *box;
+ guid_128_t box_guid;
+ int box_ret;
+
+ pool_t pool;
+ HASH_TABLE(uint8_t *, uint8_t *) seen_mailbox_guids;
+
+ ARRAY_TYPE(seq_range) uids;
+ struct seq_range_iter uids_iter;
+ unsigned int uids_iter_n;
+
+ uint32_t last_existing_uid;
+ bool warned;
+};
+
+static void *textcat = NULL;
+#ifdef HAVE_FTS_TEXTCAT
+static bool textcat_broken = FALSE;
+#endif
+static int textcat_refcount = 0;
+
+static void lucene_handle_error(struct lucene_index *index, CLuceneError &err,
+ const char *msg);
+static void rescan_clear_unseen_mailboxes(struct lucene_index *index,
+ struct rescan_context *rescan_ctx);
+
+struct lucene_index *lucene_index_init(const char *path,
+ struct mailbox_list *list,
+ const struct fts_lucene_settings *set)
+{
+ struct lucene_index *index;
+
+ index = i_new(struct lucene_index, 1);
+ index->path = i_strdup(path);
+ index->list = list;
+ if (set != NULL) {
+ index->set = *set;
+ index->normalizer = !set->normalize ? NULL :
+ mailbox_list_get_namespace(list)->user->default_normalizer;
+ } else {
+ /* this is valid only for doveadm dump, so it doesn't matter */
+ index->set.default_language = "";
+ }
+ if (index->set.use_libfts) {
+ index->default_analyzer = _CLNEW KeywordAnalyzer();
+ } else
+#ifdef HAVE_FTS_STEMMER
+ if (set == NULL || !set->no_snowball) {
+ index->default_analyzer =
+ _CLNEW snowball::SnowballAnalyzer(index->normalizer,
+ index->set.default_language);
+ } else
+#endif
+ {
+ index->default_analyzer = _CLNEW standard::StandardAnalyzer();
+ if (index->normalizer != NULL) {
+ index->normalizer_buf =
+ buffer_create_dynamic(default_pool, 1024);
+ }
+ }
+
+ i_array_init(&index->analyzers, 32);
+ textcat_refcount++;
+
+ return index;
+}
+
+void lucene_index_close(struct lucene_index *index)
+{
+ timeout_remove(&index->to_close);
+
+ _CLDELETE(index->searcher);
+ if (index->writer != NULL) {
+ try {
+ index->writer->close();
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "IndexWriter::close");
+ }
+ _CLDELETE(index->writer);
+ }
+ if (index->reader != NULL) {
+ try {
+ index->reader->close();
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "IndexReader::close");
+ }
+ _CLDELETE(index->reader);
+ }
+}
+
+void lucene_index_deinit(struct lucene_index *index)
+{
+ struct lucene_analyzer *a;
+
+ lucene_index_close(index);
+ array_foreach_modifiable(&index->analyzers, a) {
+ i_free(a->lang);
+ _CLDELETE(a->analyzer);
+ }
+ array_free(&index->analyzers);
+ if (--textcat_refcount == 0 && textcat != NULL) {
+#ifdef HAVE_FTS_TEXTCAT
+ textcat_Done(textcat);
+#endif
+ textcat = NULL;
+ }
+ _CLDELETE(index->default_analyzer);
+ if (index->normalizer_buf != NULL)
+ buffer_free(&index->normalizer_buf);
+ i_free(index->path);
+ i_free(index);
+}
+
+static void lucene_data_translate(struct lucene_index *index,
+ wchar_t *data, unsigned int len)
+{
+ const char *whitespace_chars = index->set.whitespace_chars;
+ unsigned int i;
+
+ if (*whitespace_chars == '\0' || index->set.use_libfts)
+ return;
+
+ for (i = 0; i < len; i++) {
+ if (strchr(whitespace_chars, data[i]) != NULL)
+ data[i] = ' ';
+ }
+}
+
+void lucene_utf8_n_to_tchar(const unsigned char *src, size_t srcsize,
+ wchar_t *dest, size_t destsize)
+{
+ ARRAY_TYPE(unichars) dest_arr;
+ buffer_t buf = { { 0, 0 } };
+
+ i_assert(sizeof(wchar_t) == sizeof(unichar_t));
+
+ buffer_create_from_data(&buf, dest, sizeof(wchar_t) * destsize);
+ array_create_from_buffer(&dest_arr, &buf, sizeof(wchar_t));
+ if (uni_utf8_to_ucs4_n(src, srcsize, &dest_arr) < 0)
+ i_unreached();
+ i_assert(array_count(&dest_arr)+1 == destsize);
+ dest[destsize-1] = 0;
+}
+
+static const wchar_t *
+t_lucene_utf8_to_tchar(struct lucene_index *index, const char *str)
+{
+ ARRAY_TYPE(unichars) dest_arr;
+ const unichar_t *chars;
+ wchar_t *ret;
+ unsigned int len;
+
+ i_assert(sizeof(wchar_t) == sizeof(unichar_t));
+
+ t_array_init(&dest_arr, strlen(str) + 1);
+ if (uni_utf8_to_ucs4(str, &dest_arr) < 0)
+ i_unreached();
+ (void)array_append_space(&dest_arr);
+
+ chars = array_get_modifiable(&dest_arr, &len);
+ ret = (wchar_t *)chars;
+ lucene_data_translate(index, ret, len - 1);
+ return ret;
+}
+
+void lucene_index_select_mailbox(struct lucene_index *index,
+ const wchar_t guid[MAILBOX_GUID_HEX_LENGTH])
+{
+ memcpy(index->mailbox_guid, guid,
+ MAILBOX_GUID_HEX_LENGTH * sizeof(wchar_t));
+ index->mailbox_guid[MAILBOX_GUID_HEX_LENGTH] = '\0';
+}
+
+void lucene_index_unselect_mailbox(struct lucene_index *index)
+{
+ memset(index->mailbox_guid, 0, sizeof(index->mailbox_guid));
+}
+
+static void lucene_handle_error(struct lucene_index *index, CLuceneError &err,
+ const char *msg)
+{
+ const char *error, *what = err.what();
+
+ i_error("lucene index %s: %s failed (#%d): %s",
+ index->path, msg, err.number(), what);
+
+ if (index->list != NULL &&
+ (err.number() == CL_ERR_CorruptIndex ||
+ err.number() == CL_ERR_IO)) {
+ /* delete corrupted index. most IO errors are also about
+ missing files and other such corruption.. */
+ if (unlink_directory(index->path, (enum unlink_directory_flags)0, &error) < 0)
+ i_error("unlink_directory(%s) failed: %s", index->path, error);
+ rescan_clear_unseen_mailboxes(index, NULL);
+ }
+}
+
+static int lucene_index_open(struct lucene_index *index)
+{
+ if (index->reader != NULL) {
+ i_assert(index->to_close != NULL);
+ timeout_reset(index->to_close);
+ return 1;
+ }
+
+ if (!IndexReader::indexExists(index->path))
+ return 0;
+
+ try {
+ index->reader = IndexReader::open(index->path);
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "IndexReader::open()");
+ return -1;
+ }
+ i_assert(index->to_close == NULL);
+ index->to_close = timeout_add(LUCENE_INDEX_CLOSE_TIMEOUT_MSECS,
+ lucene_index_close, index);
+ return 1;
+}
+
+static int lucene_index_open_search(struct lucene_index *index)
+{
+ int ret;
+
+ if (index->searcher != NULL)
+ return 1;
+
+ if ((ret = lucene_index_open(index)) <= 0)
+ return ret;
+
+ index->searcher = _CLNEW IndexSearcher(index->reader);
+ return 1;
+}
+
+static int
+lucene_doc_get_uid(struct lucene_index *index, Document *doc, uint32_t *uid_r)
+{
+ Field *field = doc->getField(_T("uid"));
+ const TCHAR *uid = field == NULL ? NULL : field->stringValue();
+ if (uid == NULL) {
+ i_error("lucene: Corrupted FTS index %s: No UID for document",
+ index->path);
+ return -1;
+ }
+
+ uint32_t num = 0;
+ while (*uid != 0) {
+ num = num*10 + (*uid - '0');
+ uid++;
+ }
+ *uid_r = num;
+ return 0;
+}
+
+static uint32_t
+lucene_doc_get_part(struct lucene_index *index, Document *doc)
+{
+ Field *field = doc->getField(_T("part"));
+ const TCHAR *part = field == NULL ? NULL : field->stringValue();
+ if (part == NULL)
+ return 0;
+
+ uint32_t num = 0;
+ while (*part != 0) {
+ num = num*10 + (*part - '0');
+ part++;
+ }
+ return num;
+}
+
+int lucene_index_get_last_uid(struct lucene_index *index, uint32_t *last_uid_r)
+{
+ int ret = 0;
+
+ *last_uid_r = 0;
+
+ if ((ret = lucene_index_open_search(index)) <= 0)
+ return ret;
+
+ Term mailbox_term(_T("box"), index->mailbox_guid);
+ TermQuery query(&mailbox_term);
+
+ uint32_t last_uid = 0;
+ try {
+ Hits *hits = index->searcher->search(&query);
+
+ for (size_t i = 0; i < hits->length(); i++) {
+ uint32_t uid;
+
+ if (lucene_doc_get_uid(index, &hits->doc(i),
+ &uid) < 0) {
+ ret = -1;
+ break;
+ }
+
+ if (uid > last_uid)
+ last_uid = uid;
+ }
+ _CLDELETE(hits);
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "last_uid search");
+ ret = -1;
+ }
+ *last_uid_r = last_uid;
+ return ret;
+}
+
+int lucene_index_get_doc_count(struct lucene_index *index, uint32_t *count_r)
+{
+ int ret;
+
+ if (index->reader == NULL) {
+ lucene_index_close(index);
+ if ((ret = lucene_index_open(index)) < 0)
+ return -1;
+ if (ret == 0) {
+ *count_r = 0;
+ return 0;
+ }
+ }
+ *count_r = index->reader->numDocs();
+ return 0;
+}
+
+static int lucene_settings_check(struct lucene_index *index)
+{
+ uint32_t set_checksum;
+ const char *error;
+ int ret = 0;
+
+ set_checksum = fts_lucene_settings_checksum(&index->set);
+ ret = fts_index_have_compatible_settings(index->list, set_checksum);
+ if (ret != 0)
+ return ret;
+
+ i_warning("fts-lucene: Settings have changed, rebuilding index for mailbox");
+
+ /* settings changed, rebuild index */
+ if (unlink_directory(index->path, (enum unlink_directory_flags)0, &error) < 0) {
+ i_error("unlink_directory(%s) failed: %s", index->path, error);
+ ret = -1;
+ } else {
+ rescan_clear_unseen_mailboxes(index, NULL);
+ }
+ return ret;
+}
+
+int lucene_index_build_init(struct lucene_index *index)
+{
+ const char *lock_path;
+ struct stat st;
+
+ lucene_index_close(index);
+
+ lock_path = t_strdup_printf("%s/write.lock", index->path);
+ if (stat(lock_path, &st) == 0 &&
+ st.st_mtime < time(NULL) - LUCENE_LOCK_OVERRIDE_SECS) {
+ if (unlink(lock_path) < 0)
+ i_error("unlink(%s) failed: %m", lock_path);
+ }
+
+ if (lucene_settings_check(index) < 0)
+ return -1;
+
+ bool exists = IndexReader::indexExists(index->path);
+ try {
+ index->writer = _CLNEW IndexWriter(index->path,
+ index->default_analyzer,
+ !exists);
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "IndexWriter()");
+ return -1;
+ }
+ index->writer->setMaxFieldLength(MAX_TERMS_PER_DOCUMENT);
+ return 0;
+}
+
+#ifdef HAVE_FTS_TEXTCAT
+static Analyzer *get_analyzer(struct lucene_index *index, const char *lang)
+{
+ normalizer_func_t *normalizer = index->normalizer;
+ const struct lucene_analyzer *a;
+ struct lucene_analyzer new_analyzer;
+ Analyzer *analyzer;
+
+ array_foreach(&index->analyzers, a) {
+ if (strcmp(a->lang, lang) == 0)
+ return a->analyzer;
+ }
+
+ memset(&new_analyzer, 0, sizeof(new_analyzer));
+ new_analyzer.lang = i_strdup(lang);
+ new_analyzer.analyzer =
+ _CLNEW snowball::SnowballAnalyzer(normalizer, lang);
+ array_append_i(&index->analyzers.arr, &new_analyzer, 1);
+ return new_analyzer.analyzer;
+}
+
+static void *textcat_init(struct lucene_index *index)
+{
+ const char *textcat_dir = index->set.textcat_dir;
+ unsigned int len;
+
+ if (textcat_dir == NULL)
+ return NULL;
+
+ /* textcat really wants the '/' suffix */
+ len = strlen(textcat_dir);
+ if (len > 0 && textcat_dir[len-1] != '/')
+ textcat_dir = t_strconcat(textcat_dir, "/", NULL);
+
+ return special_textcat_Init(index->set.textcat_conf, textcat_dir);
+}
+
+static Analyzer *
+guess_analyzer(struct lucene_index *index, const void *data, size_t size)
+{
+ const char *lang;
+
+ if (textcat_broken)
+ return NULL;
+
+ if (textcat == NULL) {
+ textcat = textcat_init(index);
+ if (textcat == NULL) {
+ textcat_broken = TRUE;
+ return NULL;
+ }
+ }
+
+ /* try to guess the language */
+ lang = textcat_Classify(textcat, (const char *)data,
+ I_MIN(size, 500));
+ const char *p = strchr(lang, ']');
+ if (lang[0] != '[' || p == NULL)
+ return NULL;
+ lang = t_strdup_until(lang+1, p);
+ if (strcmp(lang, index->set.default_language) == 0)
+ return index->default_analyzer;
+
+ return get_analyzer(index, lang);
+}
+#else
+static Analyzer *
+guess_analyzer(struct lucene_index *index ATTR_UNUSED,
+ const void *data ATTR_UNUSED, size_t size ATTR_UNUSED)
+{
+ return NULL;
+}
+#endif
+
+static int lucene_index_build_flush(struct lucene_index *index)
+{
+ int ret = 0;
+
+ if (index->doc == NULL)
+ return 0;
+
+ try {
+ CL_NS(analysis)::Analyzer *analyzer = NULL;
+
+ if (!index->set.use_libfts) {
+ analyzer = index->cur_analyzer != NULL ?
+ index->cur_analyzer : index->default_analyzer;
+ }
+ index->writer->addDocument(index->doc, analyzer);
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "IndexWriter::addDocument()");
+ ret = -1;
+ }
+
+ _CLDELETE(index->doc);
+ index->doc = NULL;
+ index->cur_analyzer = NULL;
+ return ret;
+}
+
+int lucene_index_build_more(struct lucene_index *index, uint32_t uid,
+ uint32_t part_idx, const unsigned char *data,
+ size_t size, const char *hdr_name)
+{
+ wchar_t id[MAX_INT_STRLEN];
+ size_t namesize, datasize;
+
+ if (uid != index->prev_uid || part_idx != index->prev_part_idx) {
+ if (lucene_index_build_flush(index) < 0)
+ return -1;
+ index->prev_uid = uid;
+ index->prev_part_idx = part_idx;
+
+ index->doc = _CLNEW Document();
+ swprintf(id, N_ELEMENTS(id), L"%u", uid);
+ index->doc->add(*_CLNEW Field(_T("uid"), id, Field::STORE_YES | Field::INDEX_UNTOKENIZED));
+ if (part_idx != 0) {
+ swprintf(id, N_ELEMENTS(id), L"%u", part_idx);
+ index->doc->add(*_CLNEW Field(_T("part"), id, Field::STORE_YES | Field::INDEX_UNTOKENIZED));
+ }
+ index->doc->add(*_CLNEW Field(_T("box"), index->mailbox_guid, Field::STORE_YES | Field::INDEX_UNTOKENIZED));
+ }
+
+ if (index->normalizer_buf != NULL && !index->set.use_libfts) {
+ buffer_set_used_size(index->normalizer_buf, 0);
+ index->normalizer(data, size, index->normalizer_buf);
+ data = (const unsigned char *)index->normalizer_buf->data;
+ size = index->normalizer_buf->used;
+ }
+
+ datasize = uni_utf8_strlen_n(data, size) + 1;
+ wchar_t *dest, *dest_free = NULL;
+ if (datasize < 4096)
+ dest = t_new(wchar_t, datasize);
+ else
+ dest = dest_free = i_new(wchar_t, datasize);
+ lucene_utf8_n_to_tchar(data, size, dest, datasize);
+ lucene_data_translate(index, dest, datasize-1);
+
+ int token_flag = index->set.use_libfts ?
+ Field::INDEX_UNTOKENIZED : Field::INDEX_TOKENIZED;
+ if (hdr_name != NULL) {
+ /* hdr_name should be ASCII, but don't break in case it isn't */
+ hdr_name = t_str_lcase(hdr_name);
+ namesize = uni_utf8_strlen(hdr_name) + 1;
+ wchar_t wname[namesize];
+ lucene_utf8_n_to_tchar((const unsigned char *)hdr_name,
+ strlen(hdr_name), wname, namesize);
+ if (!index->set.use_libfts)
+ index->doc->add(*_CLNEW Field(_T("hdr"), wname, Field::STORE_NO | token_flag));
+ index->doc->add(*_CLNEW Field(_T("hdr"), dest, Field::STORE_NO | token_flag));
+
+ if (fts_header_want_indexed(hdr_name))
+ index->doc->add(*_CLNEW Field(wname, dest, Field::STORE_NO | token_flag));
+ } else if (size > 0) {
+ if (index->cur_analyzer == NULL && !index->set.use_libfts)
+ index->cur_analyzer = guess_analyzer(index, data, size);
+ index->doc->add(*_CLNEW Field(_T("body"), dest, Field::STORE_NO | token_flag));
+ }
+ i_free(dest_free);
+ return 0;
+}
+
+int lucene_index_build_deinit(struct lucene_index *index)
+{
+ int ret = 0;
+
+ if (index->prev_uid == 0) {
+ /* no changes. */
+ return 0;
+ }
+ index->prev_uid = 0;
+ index->prev_part_idx = 0;
+
+ if (index->writer == NULL) {
+ lucene_index_close(index);
+ return -1;
+ }
+
+ if (lucene_index_build_flush(index) < 0)
+ ret = -1;
+
+ try {
+ index->writer->close();
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "IndexWriter::close()");
+ ret = -1;
+ }
+
+ lucene_index_close(index);
+ return ret;
+}
+
+static int
+wcharguid_to_guid(guid_128_t dest, const wchar_t *src)
+{
+ buffer_t buf = { { 0, 0 } };
+ char src_chars[GUID_128_SIZE*2 + 1];
+ unsigned int i;
+
+ for (i = 0; i < sizeof(src_chars)-1; i++) {
+ if ((src[i] >= '0' && src[i] <= '9') ||
+ (src[i] >= 'a' && src[i] <= 'f'))
+ src_chars[i] = src[i];
+ else
+ return -1;
+ }
+ if (src[i] != '\0')
+ return -1;
+ src_chars[i] = '\0';
+
+ buffer_create_from_data(&buf, dest, GUID_128_SIZE);
+ return hex_to_binary(src_chars, &buf);
+}
+
+static int
+rescan_get_uids(struct mailbox *box, ARRAY_TYPE(seq_range) *uids)
+{
+ struct mailbox_status status;
+
+ if (mailbox_get_status(box, STATUS_MESSAGES, &status) < 0)
+ return -1;
+
+ if (status.messages > 0) T_BEGIN {
+ ARRAY_TYPE(seq_range) seqs;
+
+ t_array_init(&seqs, 2);
+ seq_range_array_add_range(&seqs, 1, status.messages);
+ mailbox_get_uid_range(box, &seqs, uids);
+ } T_END;
+ return 0;
+}
+
+static int rescan_finish(struct rescan_context *ctx)
+{
+ int ret;
+
+ ret = fts_index_set_last_uid(ctx->box, ctx->last_existing_uid);
+ mailbox_free(&ctx->box);
+ return ret;
+}
+
+static int
+fts_lucene_get_mailbox_guid(struct lucene_index *index, Document *doc,
+ guid_128_t guid_r)
+{
+ Field *field = doc->getField(_T("box"));
+ const TCHAR *box_guid = field == NULL ? NULL : field->stringValue();
+ if (box_guid == NULL) {
+ i_error("lucene: Corrupted FTS index %s: No mailbox for document",
+ index->path);
+ return -1;
+ }
+
+ if (wcharguid_to_guid(guid_r, box_guid) < 0) {
+ i_error("lucene: Corrupted FTS index %s: "
+ "box field not in expected format", index->path);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+rescan_open_mailbox(struct rescan_context *ctx, Document *doc)
+{
+ guid_128_t guid, *guidp;
+ int ret;
+
+ if (fts_lucene_get_mailbox_guid(ctx->index, doc, guid) < 0)
+ return 0;
+
+ if (memcmp(guid, ctx->box_guid, sizeof(guid)) == 0) {
+ /* same as last one */
+ return ctx->box_ret;
+ }
+ memcpy(ctx->box_guid, guid, sizeof(ctx->box_guid));
+
+ guidp = p_new(ctx->pool, guid_128_t, 1);
+ memcpy(guidp, guid, sizeof(*guidp));
+ hash_table_insert(ctx->seen_mailbox_guids, guidp, guidp);
+
+ if (ctx->box != NULL)
+ rescan_finish(ctx);
+ ctx->box = mailbox_alloc_guid(ctx->index->list, guid,
+ (enum mailbox_flags)0);
+ if (mailbox_open(ctx->box) < 0) {
+ enum mail_error error;
+ const char *errstr;
+
+ errstr = mailbox_get_last_internal_error(ctx->box, &error);
+ if (error == MAIL_ERROR_NOTFOUND)
+ ret = 0;
+ else {
+ i_error("lucene: Couldn't open mailbox %s: %s",
+ mailbox_get_vname(ctx->box), errstr);
+ ret = -1;
+ }
+ mailbox_free(&ctx->box);
+ ctx->box_ret = ret;
+ return ret;
+ }
+ if (mailbox_sync(ctx->box, (enum mailbox_sync_flags)0) < 0) {
+ i_error("lucene: Failed to sync mailbox %s: %s",
+ mailbox_get_vname(ctx->box),
+ mailbox_get_last_internal_error(ctx->box, NULL));
+ mailbox_free(&ctx->box);
+ ctx->box_ret = -1;
+ return -1;
+ }
+
+ array_clear(&ctx->uids);
+ rescan_get_uids(ctx->box, &ctx->uids);
+
+ ctx->warned = FALSE;
+ ctx->last_existing_uid = 0;
+ ctx->uids_iter_n = 0;
+ seq_range_array_iter_init(&ctx->uids_iter, &ctx->uids);
+
+ ctx->box_ret = 1;
+ return 1;
+}
+
+static int
+rescan_next(struct rescan_context *ctx, Document *doc)
+{
+ uint32_t lucene_uid, idx_uid;
+
+ if (lucene_doc_get_uid(ctx->index, doc, &lucene_uid) < 0)
+ return 0;
+
+ if (seq_range_array_iter_nth(&ctx->uids_iter, ctx->uids_iter_n,
+ &idx_uid)) {
+ if (idx_uid == lucene_uid) {
+ ctx->uids_iter_n++;
+ ctx->last_existing_uid = idx_uid;
+ return 1;
+ }
+ if (idx_uid < lucene_uid) {
+ /* lucene is missing an UID from the middle. delete
+ the rest of the messages from this mailbox and
+ reindex. */
+ if (!ctx->warned) {
+ i_warning("lucene: Mailbox %s "
+ "missing UIDs in the middle",
+ mailbox_get_vname(ctx->box));
+ ctx->warned = TRUE;
+ }
+ } else {
+ /* UID has been expunged from index. delete from
+ lucene as well. */
+ }
+ return 0;
+ } else {
+ /* the rest of the messages have been expunged from index */
+ return 0;
+ }
+}
+
+static void
+rescan_clear_unseen_mailbox(struct lucene_index *index,
+ struct rescan_context *rescan_ctx,
+ const char *vname,
+ const struct fts_index_header *hdr)
+{
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+
+ box = mailbox_alloc(index->list, vname,
+ (enum mailbox_flags)0);
+ if (mailbox_open(box) == 0 &&
+ mailbox_get_metadata(box, MAILBOX_METADATA_GUID,
+ &metadata) == 0 &&
+ (rescan_ctx == NULL ||
+ hash_table_lookup(rescan_ctx->seen_mailbox_guids,
+ metadata.guid) == NULL)) {
+ /* this mailbox had no records in lucene index.
+ make sure its last indexed uid is 0 */
+ (void)fts_index_set_header(box, hdr);
+ }
+ mailbox_free(&box);
+}
+
+static void rescan_clear_unseen_mailboxes(struct lucene_index *index,
+ struct rescan_context *rescan_ctx)
+{
+ const enum mailbox_list_iter_flags iter_flags =
+ (enum mailbox_list_iter_flags)
+ (MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ struct fts_index_header hdr;
+ struct mail_namespace *ns = index->list->ns;
+ const char *vname;
+
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.settings_checksum = fts_lucene_settings_checksum(&index->set);
+
+ iter = mailbox_list_iter_init(index->list, "*", iter_flags);
+ while ((info = mailbox_list_iter_next(iter)) != NULL)
+ rescan_clear_unseen_mailbox(index, rescan_ctx, info->vname, &hdr);
+ (void)mailbox_list_iter_deinit(&iter);
+
+ if (ns->prefix_len > 0 &&
+ ns->prefix[ns->prefix_len-1] == mail_namespace_get_sep(ns)) {
+ /* namespace prefix itself isn't returned by the listing */
+ vname = t_strndup(index->list->ns->prefix,
+ index->list->ns->prefix_len-1);
+ rescan_clear_unseen_mailbox(index, rescan_ctx, vname, &hdr);
+ }
+}
+
+int lucene_index_rescan(struct lucene_index *index)
+{
+ static const TCHAR *sort_fields[] = { _T("box"), _T("uid"), NULL };
+ struct rescan_context ctx;
+ bool failed = false;
+ int ret;
+
+ i_assert(index->list != NULL);
+
+ if ((ret = lucene_index_open_search(index)) < 0)
+ return ret;
+
+ Term term(_T("box"), _T("*"));
+ WildcardQuery query(&term);
+ Sort sort(sort_fields);
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.index = index;
+ ctx.pool = pool_alloconly_create("guids", 1024);
+ hash_table_create(&ctx.seen_mailbox_guids, ctx.pool, 0,
+ guid_128_hash, guid_128_cmp);
+ i_array_init(&ctx.uids, 128);
+
+ if (ret > 0) try {
+ Hits *hits = index->searcher->search(&query, &sort);
+
+ for (size_t i = 0; i < hits->length(); i++) {
+ ret = rescan_open_mailbox(&ctx, &hits->doc(i));
+ if (ret > 0)
+ ret = rescan_next(&ctx, &hits->doc(i));
+ if (ret < 0)
+ failed = true;
+ else if (ret == 0)
+ index->reader->deleteDocument(hits->id(i));
+ }
+ _CLDELETE(hits);
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "rescan search");
+ failed = true;
+ }
+ lucene_index_close(index);
+ if (ctx.box != NULL)
+ rescan_finish(&ctx);
+ array_free(&ctx.uids);
+
+ rescan_clear_unseen_mailboxes(index, &ctx);
+ hash_table_destroy(&ctx.seen_mailbox_guids);
+ pool_unref(&ctx.pool);
+ return failed ? -1 : 0;
+}
+
+static void guid128_to_wguid(const guid_128_t guid,
+ wchar_t wguid_hex[MAILBOX_GUID_HEX_LENGTH + 1])
+{
+ buffer_t buf = { { 0, 0 } };
+ unsigned char guid_hex[MAILBOX_GUID_HEX_LENGTH];
+ unsigned int i;
+
+ buffer_create_from_data(&buf, guid_hex, MAILBOX_GUID_HEX_LENGTH);
+ binary_to_hex_append(&buf, guid, GUID_128_SIZE);
+ for (i = 0; i < MAILBOX_GUID_HEX_LENGTH; i++)
+ wguid_hex[i] = guid_hex[i];
+ wguid_hex[i] = '\0';
+}
+
+static bool
+lucene_index_add_uid_filter(BooleanQuery *query,
+ const struct fts_expunge_log_read_record *rec)
+{
+ struct seq_range_iter iter;
+ wchar_t wuid[MAX_INT_STRLEN];
+ unsigned int n;
+ uint32_t uid;
+
+ /* RangeQuery and WildcardQuery work by enumerating through all terms
+ that match them, and then adding TermQueries for them. So we can
+ simply do the same directly, and if it looks like there are too
+ many terms just go through everything. */
+
+ if (seq_range_count(&rec->uids) > FTS_LUCENE_MAX_SEARCH_TERMS)
+ return false;
+
+ seq_range_array_iter_init(&iter, &rec->uids); n = 0;
+ while (seq_range_array_iter_nth(&iter, n++, &uid)) {
+ swprintf(wuid, N_ELEMENTS(wuid), L"%u", uid);
+
+ Term *term = _CLNEW Term(_T("uid"), wuid);
+ query->add(_CLNEW TermQuery(term), true, BooleanClause::SHOULD);
+ _CLDECDELETE(term);
+ }
+ return true;
+}
+
+static int
+lucene_index_expunge_record(struct lucene_index *index,
+ const struct fts_expunge_log_read_record *rec)
+{
+ int ret;
+
+ if ((ret = lucene_index_open_search(index)) <= 0)
+ return ret;
+
+ BooleanQuery query;
+ BooleanQuery uids_query;
+
+ if (lucene_index_add_uid_filter(&uids_query, rec))
+ query.add(&uids_query, BooleanClause::MUST);
+
+ wchar_t wguid[MAILBOX_GUID_HEX_LENGTH + 1];
+ guid128_to_wguid(rec->mailbox_guid, wguid);
+ Term term(_T("box"), wguid);
+ TermQuery mailbox_query(&term);
+ query.add(&mailbox_query, BooleanClause::MUST);
+
+ try {
+ Hits *hits = index->searcher->search(&query);
+
+ for (size_t i = 0; i < hits->length(); i++) {
+ uint32_t uid;
+
+ if (lucene_doc_get_uid(index, &hits->doc(i),
+ &uid) < 0 ||
+ seq_range_exists(&rec->uids, uid))
+ index->reader->deleteDocument(hits->id(i));
+ }
+ _CLDELETE(hits);
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "expunge search");
+ ret = -1;
+ }
+ return ret < 0 ? -1 : 0;
+}
+
+int lucene_index_expunge_from_log(struct lucene_index *index,
+ struct fts_expunge_log *log)
+{
+ struct fts_expunge_log_read_ctx *ctx;
+ const struct fts_expunge_log_read_record *rec;
+ int ret = 0, ret2;
+
+ ctx = fts_expunge_log_read_begin(log);
+ while ((rec = fts_expunge_log_read_next(ctx)) != NULL) {
+ if (lucene_index_expunge_record(index, rec) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+
+ lucene_index_close(index);
+
+ ret2 = fts_expunge_log_read_end(&ctx);
+ if (ret < 0 || ret2 < 0)
+ return -1;
+ return ret2;
+}
+
+int lucene_index_optimize(struct lucene_index *index)
+{
+ int ret = 0;
+
+ if (!IndexReader::indexExists(index->path))
+ return 0;
+ if (IndexReader::isLocked(index->path))
+ IndexReader::unlock(index->path);
+
+ IndexWriter *writer = NULL;
+ try {
+ writer = _CLNEW IndexWriter(index->path, index->default_analyzer, false);
+ writer->optimize();
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "IndexWriter::optimize()");
+ ret = -1;
+ }
+ try {
+ writer->close();
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "IndexWriter::close()");
+ ret = -1;
+ }
+ if (writer != NULL)
+ _CLDELETE(writer);
+ return ret;
+}
+
+// Mostly copy&pasted from CLucene's QueryParser
+static Query* getFieldQuery(Analyzer *analyzer, const TCHAR* _field, const TCHAR* queryText, bool fuzzy) {
+ // Use the analyzer to get all the tokens, and then build a TermQuery,
+ // PhraseQuery, or nothing based on the term count
+
+ StringReader reader(queryText);
+ TokenStream* source = analyzer->tokenStream(_field, &reader);
+
+ CLVector<CL_NS(analysis)::Token*, Deletor::Object<CL_NS(analysis)::Token> > v;
+ CL_NS(analysis)::Token* t = NULL;
+ int32_t positionCount = 0;
+ bool severalTokensAtSamePosition = false;
+
+ while (true) {
+ t = _CLNEW Token();
+ try {
+ Token* _t = source->next(t);
+ if (_t == NULL) _CLDELETE(t);
+ }_CLCATCH_ERR(CL_ERR_IO, _CLLDELETE(source);_CLLDELETE(t);,{
+ t = NULL;
+ });
+ if (t == NULL)
+ break;
+ v.push_back(t);
+ if (t->getPositionIncrement() != 0)
+ positionCount += t->getPositionIncrement();
+ else
+ severalTokensAtSamePosition = true;
+ }
+ try {
+ source->close();
+ }
+ _CLCATCH_ERR_CLEANUP(CL_ERR_IO, {_CLLDELETE(source);_CLLDELETE(t);} ); /* cleanup */
+ _CLLDELETE(source);
+
+ if (v.size() == 0)
+ return NULL;
+ else if (v.size() == 1) {
+ Term* tm = _CLNEW Term(_field, v.at(0)->termBuffer());
+ Query* ret;
+ if (fuzzy)
+ ret = _CLNEW FuzzyQuery( tm );
+ else
+ ret = _CLNEW TermQuery( tm );
+ _CLDECDELETE(tm);
+ return ret;
+ } else {
+ if (severalTokensAtSamePosition) {
+ if (positionCount == 1) {
+ // no phrase query:
+ BooleanQuery* q = _CLNEW BooleanQuery(true);
+ for(size_t i=0; i<v.size(); i++ ){
+ Term* tm = _CLNEW Term(_field, v.at(i)->termBuffer());
+ q->add(_CLNEW TermQuery(tm), true, BooleanClause::SHOULD);
+ _CLDECDELETE(tm);
+ }
+ return q;
+ }else {
+ MultiPhraseQuery* mpq = _CLNEW MultiPhraseQuery();
+ CLArrayList<Term*> multiTerms;
+ int32_t position = -1;
+ for (size_t i = 0; i < v.size(); i++) {
+ t = v.at(i);
+ if (t->getPositionIncrement() > 0 && multiTerms.size() > 0) {
+ ValueArray<Term*> termsArray(multiTerms.size());
+ multiTerms.toArray(termsArray.values);
+ mpq->add(&termsArray,position);
+ multiTerms.clear();
+ }
+ position += t->getPositionIncrement();
+ multiTerms.push_back(_CLNEW Term(_field, t->termBuffer()));
+ }
+ ValueArray<Term*> termsArray(multiTerms.size());
+ multiTerms.toArray(termsArray.values);
+ mpq->add(&termsArray,position);
+ return mpq;
+ }
+ }else {
+ PhraseQuery* pq = _CLNEW PhraseQuery();
+ int32_t position = -1;
+
+ for (size_t i = 0; i < v.size(); i++) {
+ t = v.at(i);
+ Term* tm = _CLNEW Term(_field, t->termBuffer());
+ position += t->getPositionIncrement();
+ pq->add(tm,position);
+ _CLDECDELETE(tm);
+ }
+ return pq;
+ }
+ }
+}
+
+static Query *
+lucene_get_query_str(struct lucene_index *index,
+ const TCHAR *key, const char *str, bool fuzzy)
+{
+ const TCHAR *wvalue;
+ Analyzer *analyzer;
+
+ if (index->set.use_libfts) {
+ const wchar_t *wstr = t_lucene_utf8_to_tchar(index, str);
+ Term* tm = _CLNEW Term(key, wstr);
+ Query* ret;
+ if (fuzzy)
+ ret = _CLNEW FuzzyQuery( tm );
+ else
+ ret = _CLNEW TermQuery( tm );
+ _CLDECDELETE(tm);
+ return ret;
+ }
+
+ if (index->normalizer_buf != NULL) {
+ buffer_set_used_size(index->normalizer_buf, 0);
+ index->normalizer(str, strlen(str), index->normalizer_buf);
+ buffer_append_c(index->normalizer_buf, '\0');
+ str = (const char *)index->normalizer_buf->data;
+ }
+
+ wvalue = t_lucene_utf8_to_tchar(index, str);
+ analyzer = guess_analyzer(index, str, strlen(str));
+ if (analyzer == NULL) {
+ analyzer = index->default_analyzer;
+ i_assert(analyzer != NULL);
+ }
+
+ return getFieldQuery(analyzer, key, wvalue, fuzzy);
+}
+
+static Query *
+lucene_get_query(struct lucene_index *index,
+ const TCHAR *key, const struct mail_search_arg *arg)
+{
+ return lucene_get_query_str(index, key, arg->value.str, arg->fuzzy);
+}
+
+static bool
+lucene_add_definite_query(struct lucene_index *index,
+ ARRAY_TYPE(lucene_query) &queries,
+ struct mail_search_arg *arg,
+ enum fts_lookup_flags flags)
+{
+ bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
+ Query *q;
+
+ if (arg->no_fts)
+ return false;
+
+ if (arg->match_not && !and_args) {
+ /* FIXME: we could handle this by doing multiple queries.. */
+ return false;
+ }
+
+ switch (arg->type) {
+ case SEARCH_TEXT: {
+ Query *q1 = lucene_get_query(index, _T("hdr"), arg);
+ Query *q2 = lucene_get_query(index, _T("body"), arg);
+
+ if (q1 == NULL && q2 == NULL)
+ q = NULL;
+ else {
+ BooleanQuery *bq = _CLNEW BooleanQuery();
+ if (q1 != NULL)
+ bq->add(q1, true, BooleanClause::SHOULD);
+ if (q2 != NULL)
+ bq->add(q2, true, BooleanClause::SHOULD);
+ q = bq;
+ }
+ break;
+ }
+ case SEARCH_BODY:
+ q = lucene_get_query(index, _T("body"), arg);
+ break;
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ if (!fts_header_want_indexed(arg->hdr_field_name) ||
+ *arg->value.str == '\0')
+ return false;
+
+ q = lucene_get_query(index,
+ t_lucene_utf8_to_tchar(index, t_str_lcase(arg->hdr_field_name)),
+ arg);
+ break;
+ default:
+ return false;
+ }
+
+ if (q == NULL) {
+ /* couldn't handle this search after all (e.g. trying to search
+ a stop word) */
+ return false;
+ }
+
+ struct lucene_query *lq = array_append_space(&queries);
+ lq->query = q;
+ if (!and_args)
+ lq->occur = BooleanClause::SHOULD;
+ else if (!arg->match_not)
+ lq->occur = BooleanClause::MUST;
+ else
+ lq->occur = BooleanClause::MUST_NOT;
+ return true;
+}
+
+static bool
+lucene_add_maybe_query(struct lucene_index *index,
+ ARRAY_TYPE(lucene_query) &queries,
+ struct mail_search_arg *arg,
+ enum fts_lookup_flags flags)
+{
+ bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
+ Query *q = NULL;
+
+ if (arg->no_fts)
+ return false;
+
+ if (arg->match_not) {
+ /* FIXME: we could handle this by doing multiple queries.. */
+ return false;
+ }
+
+ switch (arg->type) {
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ if (*arg->value.str == '\0' && !index->set.use_libfts) {
+ /* checking potential existence of the header name */
+ q = lucene_get_query_str(index, _T("hdr"),
+ t_str_lcase(arg->hdr_field_name), FALSE);
+ break;
+ }
+
+ if (fts_header_want_indexed(arg->hdr_field_name))
+ return false;
+
+ /* we can check if the search key exists in some header and
+ filter out the messages that have no chance of matching */
+ q = lucene_get_query(index, _T("hdr"), arg);
+ break;
+ default:
+ return false;
+ }
+
+ if (q == NULL) {
+ /* couldn't handle this search after all (e.g. trying to search
+ a stop word) */
+ return false;
+ }
+ struct lucene_query *lq = array_append_space(&queries);
+ lq->query = q;
+ if (!and_args)
+ lq->occur = BooleanClause::SHOULD;
+ else if (!arg->match_not)
+ lq->occur = BooleanClause::MUST;
+ else
+ lq->occur = BooleanClause::MUST_NOT;
+ return true;
+}
+
+static bool queries_have_non_must_nots(ARRAY_TYPE(lucene_query) &queries)
+{
+ const struct lucene_query *lq;
+
+ array_foreach(&queries, lq) {
+ if (lq->occur != BooleanClause::MUST_NOT)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void search_query_add(BooleanQuery &query,
+ ARRAY_TYPE(lucene_query) &queries)
+{
+ BooleanQuery *search_query = _CLNEW BooleanQuery();
+ const struct lucene_query *lq;
+
+ if (queries_have_non_must_nots(queries)) {
+ array_foreach(&queries, lq)
+ search_query->add(lq->query, true, lq->occur);
+ query.add(search_query, true, BooleanClause::MUST);
+ } else {
+ array_foreach(&queries, lq)
+ search_query->add(lq->query, true, BooleanClause::SHOULD);
+ query.add(search_query, true, BooleanClause::MUST_NOT);
+ }
+}
+
+static int
+lucene_index_search(struct lucene_index *index,
+ ARRAY_TYPE(lucene_query) &queries,
+ struct fts_result *result, ARRAY_TYPE(seq_range) *uids_r)
+{
+ struct fts_score_map *score;
+ int ret = 0;
+
+ BooleanQuery query;
+ search_query_add(query, queries);
+
+ Term mailbox_term(_T("box"), index->mailbox_guid);
+ TermQuery mailbox_query(&mailbox_term);
+ query.add(&mailbox_query, BooleanClause::MUST);
+
+ try {
+ Hits *hits = index->searcher->search(&query);
+
+ uint32_t last_uid = 0;
+ if (result != NULL)
+ result->scores_sorted = true;
+
+ for (size_t i = 0; i < hits->length(); i++) {
+ uint32_t uid;
+
+ if (lucene_doc_get_uid(index, &hits->doc(i),
+ &uid) < 0) {
+ ret = -1;
+ break;
+ }
+
+ if (seq_range_array_add(uids_r, uid)) {
+ /* duplicate result */
+ } else if (result != NULL) {
+ if (uid < last_uid)
+ result->scores_sorted = false;
+ last_uid = uid;
+
+ score = array_append_space(&result->scores);
+ score->uid = uid;
+ score->score = hits->score(i);
+ }
+ }
+ _CLDELETE(hits);
+ return ret;
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "search");
+ return -1;
+ }
+}
+
+int lucene_index_lookup(struct lucene_index *index,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_result *result)
+{
+ struct mail_search_arg *arg;
+
+ if (lucene_index_open_search(index) <= 0)
+ return -1;
+
+ ARRAY_TYPE(lucene_query) def_queries;
+ t_array_init(&def_queries, 16);
+ bool have_definites = false;
+
+ for (arg = args; arg != NULL; arg = arg->next) {
+ if (lucene_add_definite_query(index, def_queries, arg, flags)) {
+ arg->match_always = true;
+ have_definites = true;
+ }
+ }
+
+ if (have_definites) {
+ ARRAY_TYPE(seq_range) *uids_arr =
+ (flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0 ?
+ &result->definite_uids : &result->maybe_uids;
+ if (lucene_index_search(index, def_queries, result,
+ uids_arr) < 0)
+ return -1;
+ }
+
+ if (have_definites) {
+ /* FIXME: mixing up definite + maybe queries is broken. if the
+ definite query matched, it'll just assume that the maybe
+ queries matched as well */
+ return 0;
+ }
+
+ ARRAY_TYPE(lucene_query) maybe_queries;
+ t_array_init(&maybe_queries, 16);
+ bool have_maybies = false;
+
+ for (arg = args; arg != NULL; arg = arg->next) {
+ if (lucene_add_maybe_query(index, maybe_queries, arg, flags)) {
+ arg->match_always = true;
+ have_maybies = true;
+ }
+ }
+
+ if (have_maybies) {
+ if (lucene_index_search(index, maybe_queries, NULL,
+ &result->maybe_uids) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+lucene_index_search_multi(struct lucene_index *index,
+ HASH_TABLE_TYPE(wguid_result) guids,
+ ARRAY_TYPE(lucene_query) &queries,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result)
+{
+ struct fts_score_map *score;
+ int ret = 0;
+
+ BooleanQuery query;
+ search_query_add(query, queries);
+
+ BooleanQuery mailbox_query;
+ struct hash_iterate_context *iter;
+ void *key, *value;
+ iter = hash_table_iterate_init(guids);
+ while (hash_table_iterate(iter, guids, &key, &value)) {
+ Term *term = _CLNEW Term(_T("box"), (wchar_t *)key);
+ TermQuery *q = _CLNEW TermQuery(term);
+ mailbox_query.add(q, true, BooleanClause::SHOULD);
+ }
+ hash_table_iterate_deinit(&iter);
+
+ query.add(&mailbox_query, BooleanClause::MUST);
+ try {
+ Hits *hits = index->searcher->search(&query);
+
+ for (size_t i = 0; i < hits->length(); i++) {
+ uint32_t uid;
+
+ Field *field = hits->doc(i).getField(_T("box"));
+ const TCHAR *box_guid = field == NULL ? NULL : field->stringValue();
+ if (box_guid == NULL) {
+ i_error("lucene: Corrupted FTS index %s: No mailbox for document",
+ index->path);
+ ret = -1;
+ break;
+ }
+ struct fts_result *br =
+ hash_table_lookup(guids, box_guid);
+ if (br == NULL) {
+ i_warning("lucene: Returned unexpected mailbox with GUID %ls", box_guid);
+ continue;
+ }
+
+ if (lucene_doc_get_uid(index, &hits->doc(i),
+ &uid) < 0) {
+ ret = -1;
+ break;
+ }
+
+ ARRAY_TYPE(seq_range) *uids_arr =
+ (flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0 ?
+ &br->maybe_uids : &br->definite_uids;
+ if (!array_is_created(uids_arr)) {
+ p_array_init(uids_arr, result->pool, 32);
+ p_array_init(&br->scores, result->pool, 32);
+ }
+ if (seq_range_array_add(uids_arr, uid)) {
+ /* duplicate result */
+ } else {
+ score = array_append_space(&br->scores);
+ score->uid = uid;
+ score->score = hits->score(i);
+ }
+ }
+ _CLDELETE(hits);
+ return ret;
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "multi search");
+ return -1;
+ }
+}
+
+int lucene_index_lookup_multi(struct lucene_index *index,
+ HASH_TABLE_TYPE(wguid_result) guids,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result)
+{
+ struct mail_search_arg *arg;
+
+ if (lucene_index_open_search(index) <= 0)
+ return -1;
+
+ ARRAY_TYPE(lucene_query) def_queries;
+ t_array_init(&def_queries, 16);
+ bool have_definites = false;
+
+ for (arg = args; arg != NULL; arg = arg->next) {
+ if (lucene_add_definite_query(index, def_queries, arg, flags)) {
+ arg->match_always = true;
+ have_definites = true;
+ }
+ }
+
+ if (have_definites) {
+ if (lucene_index_search_multi(index, guids, def_queries, flags,
+ result) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+struct lucene_index_iter {
+ struct lucene_index *index;
+ struct lucene_index_record rec;
+
+ Term *term;
+ WildcardQuery *query;
+ Sort *sort;
+
+ Hits *hits;
+ size_t i;
+ bool failed;
+};
+
+struct lucene_index_iter *
+lucene_index_iter_init(struct lucene_index *index)
+{
+ static const TCHAR *sort_fields[] = { _T("box"), _T("uid"), NULL };
+ struct lucene_index_iter *iter;
+ int ret;
+
+ iter = i_new(struct lucene_index_iter, 1);
+ iter->index = index;
+ if ((ret = lucene_index_open_search(index)) <= 0) {
+ if (ret < 0)
+ iter->failed = true;
+ return iter;
+ }
+
+ iter->term = _CLNEW Term(_T("box"), _T("*"));
+ iter->query = _CLNEW WildcardQuery(iter->term);
+ iter->sort = _CLNEW Sort(sort_fields);
+
+ try {
+ iter->hits = index->searcher->search(iter->query, iter->sort);
+ } catch (CLuceneError &err) {
+ lucene_handle_error(index, err, "rescan search");
+ iter->failed = true;
+ }
+ return iter;
+}
+
+const struct lucene_index_record *
+lucene_index_iter_next(struct lucene_index_iter *iter)
+{
+ if (iter->hits == NULL)
+ return NULL;
+ if (iter->i == iter->hits->length())
+ return NULL;
+
+ Document *doc = &iter->hits->doc(iter->i);
+ iter->i++;
+
+ memset(&iter->rec, 0, sizeof(iter->rec));
+ (void)fts_lucene_get_mailbox_guid(iter->index, doc,
+ iter->rec.mailbox_guid);
+ (void)lucene_doc_get_uid(iter->index, doc, &iter->rec.uid);
+ iter->rec.part_num = lucene_doc_get_part(iter->index, doc);
+ return &iter->rec;
+}
+
+int lucene_index_iter_deinit(struct lucene_index_iter **_iter)
+{
+ struct lucene_index_iter *iter = *_iter;
+ int ret = iter->failed ? -1 : 0;
+
+ *_iter = NULL;
+ if (iter->hits != NULL)
+ _CLDELETE(iter->hits);
+ if (iter->query != NULL) {
+ _CLDELETE(iter->query);
+ _CLDELETE(iter->sort);
+ _CLDELETE(iter->term);
+ }
+ i_free(iter);
+ return ret;
+}
+
+void lucene_shutdown(void)
+{
+ _lucene_shutdown();
+}
diff --git a/src/plugins/fts-lucene/lucene-wrapper.h b/src/plugins/fts-lucene/lucene-wrapper.h
new file mode 100644
index 0000000..270e902
--- /dev/null
+++ b/src/plugins/fts-lucene/lucene-wrapper.h
@@ -0,0 +1,67 @@
+#ifndef LUCENE_WRAPPER_H
+#define LUCENE_WRAPPER_H
+
+#include "fts-api-private.h"
+#include "guid.h"
+
+struct mailbox_list;
+struct fts_expunge_log;
+struct fts_lucene_settings;
+
+#define MAILBOX_GUID_HEX_LENGTH (GUID_128_SIZE*2)
+
+struct lucene_index_record {
+ guid_128_t mailbox_guid;
+ uint32_t uid, part_num;
+};
+
+HASH_TABLE_DEFINE_TYPE(wguid_result, wchar_t *, struct fts_result *);
+
+struct lucene_index *
+lucene_index_init(const char *path, struct mailbox_list *list,
+ const struct fts_lucene_settings *set)
+ ATTR_NULL(2, 3);
+void lucene_index_deinit(struct lucene_index *index);
+
+void lucene_index_select_mailbox(struct lucene_index *index,
+ const wchar_t guid[MAILBOX_GUID_HEX_LENGTH]);
+void lucene_index_unselect_mailbox(struct lucene_index *index);
+int lucene_index_get_last_uid(struct lucene_index *index, uint32_t *last_uid_r);
+int lucene_index_get_doc_count(struct lucene_index *index, uint32_t *count_r);
+
+int lucene_index_build_init(struct lucene_index *index);
+int lucene_index_build_more(struct lucene_index *index, uint32_t uid,
+ uint32_t part_num, const unsigned char *data,
+ size_t size, const char *hdr_name);
+int lucene_index_build_deinit(struct lucene_index *index);
+
+void lucene_index_close(struct lucene_index *index);
+int lucene_index_rescan(struct lucene_index *index);
+int lucene_index_expunge_from_log(struct lucene_index *index,
+ struct fts_expunge_log *log);
+int lucene_index_optimize(struct lucene_index *index);
+
+int lucene_index_lookup(struct lucene_index *index,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_result *result);
+
+int lucene_index_lookup_multi(struct lucene_index *index,
+ HASH_TABLE_TYPE(wguid_result) guids,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result);
+
+struct lucene_index_iter *
+lucene_index_iter_init(struct lucene_index *index);
+const struct lucene_index_record *
+lucene_index_iter_next(struct lucene_index_iter *iter);
+int lucene_index_iter_deinit(struct lucene_index_iter **iter);
+
+/* internal: */
+void lucene_utf8_n_to_tchar(const unsigned char *src, size_t srcsize,
+ wchar_t *dest, size_t destsize);
+
+void lucene_shutdown(void);
+
+#endif
diff --git a/src/plugins/fts-lucene/textcat.conf b/src/plugins/fts-lucene/textcat.conf
new file mode 100644
index 0000000..d75c4fe
--- /dev/null
+++ b/src/plugins/fts-lucene/textcat.conf
@@ -0,0 +1,25 @@
+#
+# A sample config file for the language models
+# provided with Gertjan van Noords language guesser
+# (http://odur.let.rug.nl/~vannoord/TextCat/)
+#
+# Notes:
+# - You may consider eliminating a couple of small languages from this
+# list because they cause false positives with big languages and are
+# bad for performance. (Do you really want to recognize Drents?)
+# - Putting the most probable languages at the top of the list
+# improves performance, because this will raise the threshold for
+# likely candidates more quickly.
+#
+LM/english.lm english
+LM/italian.lm italian
+LM/danish.lm danish
+LM/dutch.lm dutch
+LM/finnish.lm finnish
+LM/french.lm french
+LM/german.lm german
+LM/norwegian.lm norwegian
+LM/portuguese.lm portuguese
+LM/russian.lm russian
+LM/spanish.lm spanish
+LM/swedish.lm swedish
diff --git a/src/plugins/fts-solr/Makefile.am b/src/plugins/fts-solr/Makefile.am
new file mode 100644
index 0000000..cd1e17b
--- /dev/null
+++ b/src/plugins/fts-solr/Makefile.am
@@ -0,0 +1,64 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/fts
+
+NOPLUGIN_LDFLAGS =
+lib21_fts_solr_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib21_fts_solr_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+fts_plugin_dep = ../fts/lib20_fts_plugin.la
+endif
+
+lib21_fts_solr_plugin_la_LIBADD = \
+ $(fts_plugin_dep) \
+ -lexpat
+
+lib21_fts_solr_plugin_la_SOURCES = \
+ fts-backend-solr.c \
+ fts-backend-solr-old.c \
+ fts-solr-plugin.c \
+ solr-response.c \
+ solr-connection.c
+
+noinst_HEADERS = \
+ fts-solr-plugin.h \
+ solr-response.h \
+ solr-connection.h
+
+test_programs = \
+ test-solr-response
+
+test_libs = \
+ ../../lib-test/libtest.la \
+ ../../lib-charset/libcharset.la \
+ ../../lib/liblib.la \
+ $(MODULE_LIBS)
+
+noinst_PROGRAMS = test-solr-response
+
+test_solr_response_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir)/src/lib-test
+test_solr_response_SOURCES = \
+ solr-response.c \
+ test-solr-response.c
+test_solr_response_LDADD = \
+ $(test_libs) -lexpat
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+check: check-am check-test
+check-test: all-am
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/plugins/fts-solr/Makefile.in b/src/plugins/fts-solr/Makefile.in
new file mode 100644
index 0000000..ee248f8
--- /dev/null
+++ b/src/plugins/fts-solr/Makefile.in
@@ -0,0 +1,965 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = test-solr-response$(EXEEXT)
+subdir = src/plugins/fts-solr
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib21_fts_solr_plugin_la_DEPENDENCIES = $(fts_plugin_dep)
+am_lib21_fts_solr_plugin_la_OBJECTS = fts-backend-solr.lo \
+ fts-backend-solr-old.lo fts-solr-plugin.lo solr-response.lo \
+ solr-connection.lo
+lib21_fts_solr_plugin_la_OBJECTS = \
+ $(am_lib21_fts_solr_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib21_fts_solr_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib21_fts_solr_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_test_solr_response_OBJECTS = \
+ test_solr_response-solr-response.$(OBJEXT) \
+ test_solr_response-test-solr-response.$(OBJEXT)
+test_solr_response_OBJECTS = $(am_test_solr_response_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = ../../lib-test/libtest.la \
+ ../../lib-charset/libcharset.la ../../lib/liblib.la \
+ $(am__DEPENDENCIES_1)
+test_solr_response_DEPENDENCIES = $(am__DEPENDENCIES_2)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/fts-backend-solr-old.Plo \
+ ./$(DEPDIR)/fts-backend-solr.Plo \
+ ./$(DEPDIR)/fts-solr-plugin.Plo \
+ ./$(DEPDIR)/solr-connection.Plo ./$(DEPDIR)/solr-response.Plo \
+ ./$(DEPDIR)/test_solr_response-solr-response.Po \
+ ./$(DEPDIR)/test_solr_response-test-solr-response.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib21_fts_solr_plugin_la_SOURCES) \
+ $(test_solr_response_SOURCES)
+DIST_SOURCES = $(lib21_fts_solr_plugin_la_SOURCES) \
+ $(test_solr_response_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/fts
+
+lib21_fts_solr_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib21_fts_solr_plugin.la
+
+@DOVECOT_PLUGIN_DEPS_TRUE@fts_plugin_dep = ../fts/lib20_fts_plugin.la
+lib21_fts_solr_plugin_la_LIBADD = \
+ $(fts_plugin_dep) \
+ -lexpat
+
+lib21_fts_solr_plugin_la_SOURCES = \
+ fts-backend-solr.c \
+ fts-backend-solr-old.c \
+ fts-solr-plugin.c \
+ solr-response.c \
+ solr-connection.c
+
+noinst_HEADERS = \
+ fts-solr-plugin.h \
+ solr-response.h \
+ solr-connection.h
+
+test_programs = \
+ test-solr-response
+
+test_libs = \
+ ../../lib-test/libtest.la \
+ ../../lib-charset/libcharset.la \
+ ../../lib/liblib.la \
+ $(MODULE_LIBS)
+
+test_solr_response_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir)/src/lib-test
+
+test_solr_response_SOURCES = \
+ solr-response.c \
+ test-solr-response.c
+
+test_solr_response_LDADD = \
+ $(test_libs) -lexpat
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fts-solr/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/fts-solr/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib21_fts_solr_plugin.la: $(lib21_fts_solr_plugin_la_OBJECTS) $(lib21_fts_solr_plugin_la_DEPENDENCIES) $(EXTRA_lib21_fts_solr_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib21_fts_solr_plugin_la_LINK) -rpath $(moduledir) $(lib21_fts_solr_plugin_la_OBJECTS) $(lib21_fts_solr_plugin_la_LIBADD) $(LIBS)
+
+test-solr-response$(EXEEXT): $(test_solr_response_OBJECTS) $(test_solr_response_DEPENDENCIES) $(EXTRA_test_solr_response_DEPENDENCIES)
+ @rm -f test-solr-response$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_solr_response_OBJECTS) $(test_solr_response_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-backend-solr-old.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-backend-solr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-solr-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/solr-connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/solr-response.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_solr_response-solr-response.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_solr_response-test-solr-response.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+test_solr_response-solr-response.o: solr-response.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_solr_response-solr-response.o -MD -MP -MF $(DEPDIR)/test_solr_response-solr-response.Tpo -c -o test_solr_response-solr-response.o `test -f 'solr-response.c' || echo '$(srcdir)/'`solr-response.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_solr_response-solr-response.Tpo $(DEPDIR)/test_solr_response-solr-response.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='solr-response.c' object='test_solr_response-solr-response.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_solr_response-solr-response.o `test -f 'solr-response.c' || echo '$(srcdir)/'`solr-response.c
+
+test_solr_response-solr-response.obj: solr-response.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_solr_response-solr-response.obj -MD -MP -MF $(DEPDIR)/test_solr_response-solr-response.Tpo -c -o test_solr_response-solr-response.obj `if test -f 'solr-response.c'; then $(CYGPATH_W) 'solr-response.c'; else $(CYGPATH_W) '$(srcdir)/solr-response.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_solr_response-solr-response.Tpo $(DEPDIR)/test_solr_response-solr-response.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='solr-response.c' object='test_solr_response-solr-response.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_solr_response-solr-response.obj `if test -f 'solr-response.c'; then $(CYGPATH_W) 'solr-response.c'; else $(CYGPATH_W) '$(srcdir)/solr-response.c'; fi`
+
+test_solr_response-test-solr-response.o: test-solr-response.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_solr_response-test-solr-response.o -MD -MP -MF $(DEPDIR)/test_solr_response-test-solr-response.Tpo -c -o test_solr_response-test-solr-response.o `test -f 'test-solr-response.c' || echo '$(srcdir)/'`test-solr-response.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_solr_response-test-solr-response.Tpo $(DEPDIR)/test_solr_response-test-solr-response.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-solr-response.c' object='test_solr_response-test-solr-response.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_solr_response-test-solr-response.o `test -f 'test-solr-response.c' || echo '$(srcdir)/'`test-solr-response.c
+
+test_solr_response-test-solr-response.obj: test-solr-response.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_solr_response-test-solr-response.obj -MD -MP -MF $(DEPDIR)/test_solr_response-test-solr-response.Tpo -c -o test_solr_response-test-solr-response.obj `if test -f 'test-solr-response.c'; then $(CYGPATH_W) 'test-solr-response.c'; else $(CYGPATH_W) '$(srcdir)/test-solr-response.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_solr_response-test-solr-response.Tpo $(DEPDIR)/test_solr_response-test-solr-response.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-solr-response.c' object='test_solr_response-test-solr-response.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_solr_response-test-solr-response.obj `if test -f 'test-solr-response.c'; then $(CYGPATH_W) 'test-solr-response.c'; else $(CYGPATH_W) '$(srcdir)/test-solr-response.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/fts-backend-solr-old.Plo
+ -rm -f ./$(DEPDIR)/fts-backend-solr.Plo
+ -rm -f ./$(DEPDIR)/fts-solr-plugin.Plo
+ -rm -f ./$(DEPDIR)/solr-connection.Plo
+ -rm -f ./$(DEPDIR)/solr-response.Plo
+ -rm -f ./$(DEPDIR)/test_solr_response-solr-response.Po
+ -rm -f ./$(DEPDIR)/test_solr_response-test-solr-response.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/fts-backend-solr-old.Plo
+ -rm -f ./$(DEPDIR)/fts-backend-solr.Plo
+ -rm -f ./$(DEPDIR)/fts-solr-plugin.Plo
+ -rm -f ./$(DEPDIR)/solr-connection.Plo
+ -rm -f ./$(DEPDIR)/solr-response.Plo
+ -rm -f ./$(DEPDIR)/test_solr_response-solr-response.Po
+ -rm -f ./$(DEPDIR)/test_solr_response-test-solr-response.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+check: check-am check-test
+check-test: all-am
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/fts-solr/fts-backend-solr-old.c b/src/plugins/fts-solr/fts-backend-solr-old.c
new file mode 100644
index 0000000..20b891b
--- /dev/null
+++ b/src/plugins/fts-solr/fts-backend-solr-old.c
@@ -0,0 +1,879 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "hash.h"
+#include "strescape.h"
+#include "unichar.h"
+#include "iostream-ssl.h"
+#include "http-url.h"
+#include "imap-utf7.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "mail-search.h"
+#include "fts-api.h"
+#include "solr-connection.h"
+#include "fts-solr-plugin.h"
+
+#include <ctype.h>
+
+#define SOLR_CMDBUF_SIZE (1024*64)
+#define SOLR_MAX_MULTI_ROWS 100000
+
+struct solr_fts_backend {
+ struct fts_backend backend;
+ struct solr_connection *solr_conn;
+ char *id_username, *id_namespace;
+ struct mail_namespace *default_ns;
+};
+
+struct solr_fts_backend_update_context {
+ struct fts_backend_update_context ctx;
+
+ struct mailbox *cur_box;
+ char *id_box_name;
+
+ struct solr_connection_post *post;
+ uint32_t prev_uid, uid_validity;
+ string_t *cmd, *hdr;
+
+ bool headers_open;
+ bool body_open;
+ bool documents_added;
+};
+
+static const char *solr_escape_chars = "+-&|!(){}[]^\"~*?:\\/ ";
+
+static bool is_valid_xml_char(unichar_t chr)
+{
+ /* Valid characters in XML:
+
+ #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
+ [#x10000-#x10FFFF]
+
+ This function gets called only for #x80 and higher */
+ if (chr > 0xd7ff && chr < 0xe000)
+ return FALSE;
+ if (chr > 0xfffd && chr < 0x10000)
+ return FALSE;
+ return chr < 0x10ffff;
+}
+
+static void
+xml_encode_data(string_t *dest, const unsigned char *data, size_t len)
+{
+ unichar_t chr;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ switch (data[i]) {
+ case '&':
+ str_append(dest, "&amp;");
+ break;
+ case '<':
+ str_append(dest, "&lt;");
+ break;
+ case '>':
+ str_append(dest, "&gt;");
+ break;
+ case '\t':
+ case '\n':
+ case '\r':
+ /* exceptions to the following control char check */
+ str_append_c(dest, data[i]);
+ break;
+ default:
+ if (data[i] < 32) {
+ /* SOLR doesn't like control characters.
+ replace them with spaces. */
+ str_append_c(dest, ' ');
+ } else if (data[i] >= 0x80) {
+ /* make sure the character is valid for XML
+ so we don't get XML parser errors */
+ int char_len =
+ uni_utf8_get_char_n(data + i, len - i, &chr);
+ i_assert(char_len > 0); /* input is valid UTF8 */
+ if (is_valid_xml_char(chr))
+ str_append_data(dest, data + i, char_len);
+ else {
+ str_append_data(dest, utf8_replacement_char,
+ UTF8_REPLACEMENT_CHAR_LEN);
+ }
+ i += char_len - 1;
+ } else {
+ str_append_c(dest, data[i]);
+ }
+ break;
+ }
+ }
+}
+
+static void xml_encode(string_t *dest, const char *str)
+{
+ xml_encode_data(dest, (const unsigned char *)str, strlen(str));
+}
+
+static const char *solr_escape_id_str(const char *str)
+{
+ string_t *tmp;
+ const char *p;
+
+ for (p = str; *p != '\0'; p++) {
+ if (*p == '/' || *p == '!')
+ break;
+ }
+ if (*p == '\0')
+ return str;
+
+ tmp = t_str_new(64);
+ for (p = str; *p != '\0'; p++) {
+ switch (*p) {
+ case '/':
+ str_append(tmp, "!\\");
+ break;
+ case '!':
+ str_append(tmp, "!!");
+ break;
+ default:
+ str_append_c(tmp, *p);
+ break;
+ }
+ }
+ return str_c(tmp);
+}
+
+static const char *solr_escape(const char *str)
+{
+ string_t *ret;
+ unsigned int i;
+
+ if (str[0] == '\0')
+ return "\"\"";
+
+ ret = t_str_new(strlen(str) + 16);
+ for (i = 0; str[i] != '\0'; i++) {
+ if (strchr(solr_escape_chars, str[i]) != NULL)
+ str_append_c(ret, '\\');
+ str_append_c(ret, str[i]);
+ }
+ return str_c(ret);
+}
+
+static void solr_quote(string_t *dest, const char *str)
+{
+ str_append(dest, solr_escape(str));
+}
+
+static void solr_quote_http(string_t *dest, const char *str)
+{
+ http_url_escape_param(dest, solr_escape(str));
+}
+
+static void fts_solr_set_default_ns(struct solr_fts_backend *backend)
+{
+ struct mail_namespace *ns = backend->backend.ns;
+ struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT_REQUIRE(ns->user);
+ const struct fts_solr_settings *set = &fuser->set;
+ const char *str;
+
+ if (backend->default_ns != NULL)
+ return;
+
+ if (set->default_ns_prefix != NULL) {
+ backend->default_ns =
+ mail_namespace_find_prefix(ns->user->namespaces,
+ set->default_ns_prefix);
+ if (backend->default_ns == NULL) {
+ i_error("fts_solr: default_ns setting points to "
+ "nonexistent namespace");
+ }
+ }
+ if (backend->default_ns == NULL) {
+ backend->default_ns =
+ mail_namespace_find_inbox(ns->user->namespaces);
+ }
+ while (backend->default_ns->alias_for != NULL)
+ backend->default_ns = backend->default_ns->alias_for;
+
+ if (ns != backend->default_ns) {
+ str = solr_escape_id_str(ns->prefix);
+ backend->id_namespace = i_strdup(str);
+ }
+}
+
+static void fts_box_name_get_root(struct mail_namespace **ns, const char **name)
+{
+ struct mail_namespace *orig_ns = *ns;
+
+ while ((*ns)->alias_for != NULL)
+ *ns = (*ns)->alias_for;
+
+ if (**name == '\0' && *ns != orig_ns &&
+ ((*ns)->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* ugly workaround to allow selecting INBOX from a Maildir/
+ when it's not in the inbox=yes namespace. */
+ *name = "INBOX";
+ }
+}
+
+static const char *
+fts_box_get_root(struct mailbox *box, struct mail_namespace **ns_r)
+{
+ struct mail_namespace *ns = mailbox_get_namespace(box);
+ const char *name;
+
+ if (t_imap_utf8_to_utf7(box->name, &name) < 0)
+ i_unreached();
+
+ fts_box_name_get_root(&ns, &name);
+ *ns_r = ns;
+ return name;
+}
+
+static struct fts_backend *fts_backend_solr_alloc(void)
+{
+ struct solr_fts_backend *backend;
+
+ backend = i_new(struct solr_fts_backend, 1);
+ backend->backend = fts_backend_solr_old;
+ return &backend->backend;
+}
+
+static int
+fts_backend_solr_init(struct fts_backend *_backend, const char **error_r)
+{
+ struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+ struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(_backend->ns->user);
+ struct ssl_iostream_settings ssl_set;
+ const char *str;
+
+ if (fuser == NULL) {
+ *error_r = "Invalid fts_solr setting";
+ return -1;
+ }
+
+ mail_user_init_ssl_client_settings(_backend->ns->user, &ssl_set);
+ if (solr_connection_init(&fuser->set, &ssl_set,
+ _backend->ns->user->event,
+ &backend->solr_conn, error_r) < 0)
+ return -1;
+
+ str = solr_escape_id_str(_backend->ns->user->username);
+ backend->id_username = i_strdup(str);
+ return 0;
+}
+
+static void fts_backend_solr_deinit(struct fts_backend *_backend)
+{
+ struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+
+ solr_connection_deinit(&backend->solr_conn);
+ i_free(backend->id_namespace);
+ i_free(backend->id_username);
+ i_free(backend);
+}
+
+static void
+solr_add_ns_query(string_t *str, struct solr_fts_backend *backend,
+ struct mail_namespace *ns, bool neg)
+{
+ while (ns->alias_for != NULL)
+ ns = ns->alias_for;
+
+ if (ns == backend->default_ns || *ns->prefix == '\0') {
+ if (!neg)
+ str_append(str, " -ns:[* TO *]");
+ else
+ str_append(str, " +ns:[* TO *]");
+ } else {
+ if (!neg)
+ str_append(str, " +ns:");
+ else
+ str_append(str, " -ns:");
+ solr_quote(str, ns->prefix);
+ }
+}
+
+static void
+solr_add_ns_query_http(string_t *str, struct solr_fts_backend *backend,
+ struct mail_namespace *ns)
+{
+ string_t *tmp;
+
+ tmp = t_str_new(64);
+ solr_add_ns_query(tmp, backend, ns, FALSE);
+ http_url_escape_param(str, str_c(tmp));
+}
+
+static int
+fts_backend_solr_get_last_uid_fallback(struct solr_fts_backend *backend,
+ struct mailbox *box,
+ uint32_t *last_uid_r)
+{
+ struct mail_namespace *ns;
+ struct mailbox_status status;
+ struct solr_result **results;
+ const struct seq_range *uidvals;
+ const char *box_name;
+ unsigned int count;
+ string_t *str;
+ pool_t pool;
+ int ret = 0;
+
+ str = t_str_new(256);
+ str_append(str, "fl=uid&rows=1&sort=uid+desc&q=");
+
+ box_name = fts_box_get_root(box, &ns);
+
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY, &status);
+ str_printfa(str, "uidv:%u+AND+box:", status.uidvalidity);
+ solr_quote_http(str, box_name);
+ solr_add_ns_query_http(str, backend, ns);
+ str_append(str, "+AND+user:");
+ solr_quote_http(str, ns->user->username);
+
+ pool = pool_alloconly_create("solr last uid lookup", 1024);
+ if (solr_connection_select(backend->solr_conn, str_c(str),
+ pool, &results) < 0)
+ ret = -1;
+ else if (results[0] == NULL) {
+ /* no UIDs */
+ *last_uid_r = 0;
+ } else {
+ uidvals = array_get(&results[0]->uids, &count);
+ i_assert(count > 0);
+ if (count == 1 && uidvals[0].seq1 == uidvals[0].seq2) {
+ *last_uid_r = uidvals[0].seq1;
+ } else {
+ i_error("fts_solr: Last UID lookup returned multiple rows");
+ ret = -1;
+ }
+ }
+ pool_unref(&pool);
+ return ret;
+}
+
+static int
+fts_backend_solr_get_last_uid(struct fts_backend *_backend,
+ struct mailbox *box, uint32_t *last_uid_r)
+{
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)_backend;
+ struct fts_index_header hdr;
+
+ if (fts_index_get_header(box, &hdr)) {
+ *last_uid_r = hdr.last_indexed_uid;
+ return 0;
+ }
+
+ /* either nothing has been indexed, or the index was corrupted.
+ do it the slow way. */
+ if (fts_backend_solr_get_last_uid_fallback(backend, box, last_uid_r) < 0)
+ return -1;
+
+ fts_index_set_last_uid(box, *last_uid_r);
+ return 0;
+}
+
+static struct fts_backend_update_context *
+fts_backend_solr_update_init(struct fts_backend *_backend)
+{
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)_backend;
+ struct solr_fts_backend_update_context *ctx;
+
+ ctx = i_new(struct solr_fts_backend_update_context, 1);
+ ctx->ctx.backend = _backend;
+ ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE);
+ ctx->hdr = str_new(default_pool, 4096);
+ fts_solr_set_default_ns(backend);
+ return &ctx->ctx;
+}
+
+static void xml_encode_id(struct solr_fts_backend_update_context *ctx,
+ string_t *str, uint32_t uid)
+{
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)ctx->ctx.backend;
+
+ if (uid != 0)
+ str_printfa(str, "%u/", uid);
+ else
+ str_append(str, "L/");
+
+ if (backend->id_namespace != NULL) {
+ xml_encode(str, backend->id_namespace);
+ str_append_c(str, '/');
+ }
+ str_printfa(str, "%u/", ctx->uid_validity);
+ xml_encode(str, backend->id_username);
+ str_append_c(str, '/');
+ xml_encode(str, ctx->id_box_name);
+}
+
+static void
+fts_backend_solr_add_doc_prefix(struct solr_fts_backend_update_context *ctx,
+ uint32_t uid)
+{
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)ctx->ctx.backend;
+ struct mailbox *box = ctx->cur_box;
+ struct mail_namespace *ns;
+ const char *box_name;
+
+ ctx->documents_added = TRUE;
+
+ str_printfa(ctx->cmd, "<doc>"
+ "<field name=\"uid\">%u</field>"
+ "<field name=\"uidv\">%u</field>",
+ uid, ctx->uid_validity);
+
+ box_name = fts_box_get_root(box, &ns);
+
+ if (ns != backend->default_ns) {
+ str_append(ctx->cmd, "<field name=\"ns\">");
+ xml_encode(ctx->cmd, ns->prefix);
+ str_append(ctx->cmd, "</field>");
+ }
+ str_append(ctx->cmd, "<field name=\"box\">");
+ xml_encode(ctx->cmd, box_name);
+ str_append(ctx->cmd, "</field><field name=\"user\">");
+ xml_encode(ctx->cmd, ns->user->username);
+ str_append(ctx->cmd, "</field>");
+}
+
+static int
+fts_backed_solr_build_commit(struct solr_fts_backend_update_context *ctx)
+{
+ if (ctx->post == NULL)
+ return 0;
+
+ str_append(ctx->cmd, "</doc></add>");
+
+ solr_connection_post_more(ctx->post, str_data(ctx->cmd),
+ str_len(ctx->cmd));
+ return solr_connection_post_end(&ctx->post);
+}
+
+static int
+fts_backend_solr_update_deinit(struct fts_backend_update_context *_ctx)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)_ctx->backend;
+ const char *str;
+ int ret;
+
+ ret = fts_backed_solr_build_commit(ctx);
+
+ /* commit and wait until the documents we just indexed are
+ visible to the following search */
+ str = t_strdup_printf("<commit waitFlush=\"false\" "
+ "waitSearcher=\"%s\"/>",
+ ctx->documents_added ? "true" : "false");
+ if (solr_connection_post(backend->solr_conn, str) < 0)
+ ret = -1;
+
+ str_free(&ctx->cmd);
+ str_free(&ctx->hdr);
+ i_free(ctx->id_box_name);
+ i_free(ctx);
+ return ret;
+}
+
+static void
+fts_backend_solr_update_set_mailbox(struct fts_backend_update_context *_ctx,
+ struct mailbox *box)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+ struct mailbox_status status;
+ struct mail_namespace *ns;
+
+ if (ctx->prev_uid != 0) {
+ fts_index_set_last_uid(ctx->cur_box, ctx->prev_uid);
+ ctx->prev_uid = 0;
+ }
+
+ ctx->cur_box = box;
+ ctx->uid_validity = 0;
+ i_free_and_null(ctx->id_box_name);
+
+ if (box != NULL) {
+ ctx->id_box_name = i_strdup(fts_box_get_root(box, &ns));
+
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY, &status);
+ ctx->uid_validity = status.uidvalidity;
+ }
+}
+
+static void
+fts_backend_solr_update_expunge(struct fts_backend_update_context *_ctx,
+ uint32_t uid)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)_ctx->backend;
+
+ T_BEGIN {
+ string_t *cmd;
+
+ cmd = t_str_new(256);
+ str_append(cmd, "<delete><id>");
+ xml_encode_id(ctx, cmd, uid);
+ str_append(cmd, "</id></delete>");
+
+ (void)solr_connection_post(backend->solr_conn, str_c(cmd));
+ } T_END;
+}
+
+static void
+fts_backend_solr_uid_changed(struct solr_fts_backend_update_context *ctx,
+ uint32_t uid)
+{
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)ctx->ctx.backend;
+
+ if (ctx->post == NULL) {
+ i_assert(ctx->prev_uid == 0);
+
+ ctx->post = solr_connection_post_begin(backend->solr_conn);
+ str_append(ctx->cmd, "<add>");
+ } else {
+ ctx->headers_open = FALSE;
+ if (ctx->body_open) {
+ ctx->body_open = FALSE;
+ str_append(ctx->cmd, "</field>");
+ }
+ str_append(ctx->cmd, "<field name=\"hdr\">");
+ str_append_str(ctx->cmd, ctx->hdr);
+ str_append(ctx->cmd, "</field>");
+ str_truncate(ctx->hdr, 0);
+
+ str_append(ctx->cmd, "</doc>");
+ }
+ ctx->prev_uid = uid;
+
+ fts_backend_solr_add_doc_prefix(ctx, uid);
+ str_printfa(ctx->cmd, "<field name=\"id\">");
+ xml_encode_id(ctx, ctx->cmd, uid);
+ str_append(ctx->cmd, "</field>");
+}
+
+static bool
+fts_backend_solr_update_set_build_key(struct fts_backend_update_context *_ctx,
+ const struct fts_backend_build_key *key)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+
+ if (key->uid != ctx->prev_uid)
+ fts_backend_solr_uid_changed(ctx, key->uid);
+
+ switch (key->type) {
+ case FTS_BACKEND_BUILD_KEY_HDR:
+ case FTS_BACKEND_BUILD_KEY_MIME_HDR:
+ xml_encode(ctx->hdr, key->hdr_name);
+ str_append(ctx->hdr, ": ");
+ ctx->headers_open = TRUE;
+ break;
+ case FTS_BACKEND_BUILD_KEY_BODY_PART:
+ ctx->headers_open = FALSE;
+ if (!ctx->body_open) {
+ ctx->body_open = TRUE;
+ str_append(ctx->cmd, "<field name=\"body\">");
+ }
+ break;
+ case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY:
+ i_unreached();
+ }
+ return TRUE;
+}
+
+static void
+fts_backend_solr_update_unset_build_key(struct fts_backend_update_context *_ctx)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+
+ if (ctx->headers_open)
+ str_append_c(ctx->hdr, '\n');
+ else {
+ i_assert(ctx->body_open);
+ str_append_c(ctx->cmd, '\n');
+ }
+}
+
+static int
+fts_backend_solr_update_build_more(struct fts_backend_update_context *_ctx,
+ const unsigned char *data, size_t size)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+
+ xml_encode_data(ctx->cmd, data, size);
+ if (str_len(ctx->cmd) > SOLR_CMDBUF_SIZE-128) {
+ solr_connection_post_more(ctx->post, str_data(ctx->cmd),
+ str_len(ctx->cmd));
+ str_truncate(ctx->cmd, 0);
+ }
+ return 0;
+}
+
+static int fts_backend_solr_refresh(struct fts_backend *backend ATTR_UNUSED)
+{
+ return 0;
+}
+
+static int fts_backend_solr_optimize(struct fts_backend *backend ATTR_UNUSED)
+{
+ return 0;
+}
+
+static bool
+solr_add_definite_query(string_t *str, struct mail_search_arg *arg)
+{
+ if (arg->no_fts)
+ return FALSE;
+ switch (arg->type) {
+ case SEARCH_TEXT: {
+ if (arg->match_not)
+ str_append_c(str, '-');
+ str_append(str, "(hdr:");
+ solr_quote_http(str, arg->value.str);
+ str_append(str, "+OR+body:");
+ solr_quote_http(str, arg->value.str);
+ str_append(str, ")");
+ break;
+ }
+ case SEARCH_BODY:
+ if (arg->match_not)
+ str_append_c(str, '-');
+ str_append(str, "body:");
+ solr_quote_http(str, arg->value.str);
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+solr_add_definite_query_args(string_t *str, struct mail_search_arg *arg,
+ bool and_args)
+{
+ size_t last_len;
+
+ last_len = str_len(str);
+ for (; arg != NULL; arg = arg->next) {
+ if (solr_add_definite_query(str, arg)) {
+ arg->match_always = TRUE;
+ last_len = str_len(str);
+ if (and_args)
+ str_append(str, "+AND+");
+ else
+ str_append(str, "+OR+");
+ }
+ }
+ if (str_len(str) == last_len)
+ return FALSE;
+
+ str_truncate(str, last_len);
+ return TRUE;
+}
+
+static int
+fts_backend_solr_lookup(struct fts_backend *_backend, struct mailbox *box,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_result *result)
+{
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)_backend;
+ bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
+ struct mail_namespace *ns;
+ struct mailbox_status status;
+ string_t *str;
+ const char *box_name;
+ pool_t pool;
+ struct solr_result **results;
+ int ret;
+
+ fts_solr_set_default_ns(backend);
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT,
+ &status);
+
+ str = t_str_new(256);
+ str_printfa(str, "fl=uid,score&rows=%u&sort=uid+asc&q=%%7b!lucene+q.op%%3dAND%%7d",
+ status.uidnext);
+
+ if (!solr_add_definite_query_args(str, args, and_args)) {
+ /* can't search this query */
+ return 0;
+ }
+
+ /* use a separate filter query for selecting the mailbox. it shouldn't
+ affect the score and there could be some caching benefits too. */
+ str_append(str, "&fq=%2Buser:");
+ solr_quote_http(str, box->storage->user->username);
+ box_name = fts_box_get_root(box, &ns);
+ str_printfa(str, "+%%2Buidv:%u+%%2Bbox:", status.uidvalidity);
+ solr_quote_http(str, box_name);
+ solr_add_ns_query_http(str, backend, ns);
+
+ pool = pool_alloconly_create("fts solr search", 1024);
+ ret = solr_connection_select(backend->solr_conn, str_c(str),
+ pool, &results);
+ if (ret == 0 && results[0] != NULL) {
+ if ((flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0)
+ array_append_array(&result->definite_uids, &results[0]->uids);
+ else
+ array_append_array(&result->maybe_uids, &results[0]->uids);
+ array_append_array(&result->scores, &results[0]->scores);
+ }
+ result->scores_sorted = TRUE;
+ pool_unref(&pool);
+ return ret;
+}
+
+static char *
+mailbox_get_id(struct solr_fts_backend *backend, struct mail_namespace *ns,
+ const char *mailbox, uint32_t uidvalidity)
+{
+ string_t *str = t_str_new(64);
+
+ str_printfa(str, "%u\001", uidvalidity);
+ str_append(str, mailbox);
+ if (ns != backend->default_ns)
+ str_printfa(str, "\001%s", ns->prefix);
+ return str_c_modifiable(str);
+}
+
+static int
+solr_search_multi(struct solr_fts_backend *backend, string_t *str,
+ struct mailbox *const boxes[],
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result)
+{
+ struct solr_result **solr_results;
+ struct fts_result *fts_result;
+ ARRAY(struct fts_result) fts_results;
+ struct mail_namespace *ns;
+ struct mailbox_status status;
+ HASH_TABLE(char *, struct mailbox *) mailboxes;
+ struct mailbox *box;
+ const char *box_name;
+ char *box_id;
+ unsigned int i;
+ size_t len;
+
+ /* use a separate filter query for selecting the mailbox. it shouldn't
+ affect the score and there could be some caching benefits too. */
+ str_append(str, "&fq=%2Buser:");
+ if (backend->backend.ns->owner != NULL)
+ solr_quote_http(str, backend->backend.ns->owner->username);
+ else
+ str_append(str, "%22%22");
+
+ hash_table_create(&mailboxes, default_pool, 0, str_hash, strcmp);
+ str_append(str, "%2B(");
+ len = str_len(str);
+ for (i = 0; boxes[i] != NULL; i++) {
+ if (str_len(str) != len)
+ str_append(str, "+OR+");
+
+ box_name = fts_box_get_root(boxes[i], &ns);
+ mailbox_get_open_status(boxes[i], STATUS_UIDVALIDITY, &status);
+ str_printfa(str, "%%2B(%%2Buidv:%u+%%2Bbox:", status.uidvalidity);
+ solr_quote_http(str, box_name);
+ solr_add_ns_query_http(str, backend, ns);
+ str_append_c(str, ')');
+
+ box_id = mailbox_get_id(backend, ns, box_name, status.uidvalidity);
+ hash_table_insert(mailboxes, box_id, boxes[i]);
+ }
+ str_append_c(str, ')');
+
+ if (solr_connection_select(backend->solr_conn, str_c(str),
+ result->pool, &solr_results) < 0) {
+ hash_table_destroy(&mailboxes);
+ return -1;
+ }
+
+ p_array_init(&fts_results, result->pool, 32);
+ for (i = 0; solr_results[i] != NULL; i++) {
+ box = hash_table_lookup(mailboxes, solr_results[i]->box_id);
+ if (box == NULL) {
+ i_warning("fts_solr: Lookup returned unexpected mailbox "
+ "with id=%s", solr_results[i]->box_id);
+ continue;
+ }
+ fts_result = array_append_space(&fts_results);
+ fts_result->box = box;
+ if ((flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0)
+ fts_result->definite_uids = solr_results[i]->uids;
+ else
+ fts_result->maybe_uids = solr_results[i]->uids;
+ fts_result->scores = solr_results[i]->scores;
+ fts_result->scores_sorted = TRUE;
+ }
+ array_append_zero(&fts_results);
+ result->box_results = array_front_modifiable(&fts_results);
+ hash_table_destroy(&mailboxes);
+ return 0;
+}
+
+static int
+fts_backend_solr_lookup_multi(struct fts_backend *_backend,
+ struct mailbox *const boxes[],
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result)
+{
+ bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)_backend;
+ string_t *str;
+
+ fts_solr_set_default_ns(backend);
+
+ str = t_str_new(256);
+ str_printfa(str, "fl=ns,box,uidv,uid,score&rows=%u&sort=box+asc,uid+asc&q=%%7b!lucene+q.op%%3dAND%%7d",
+ SOLR_MAX_MULTI_ROWS);
+
+ if (solr_add_definite_query_args(str, args, and_args)) {
+ if (solr_search_multi(backend, str, boxes, flags, result) < 0)
+ return -1;
+ }
+ /* FIXME: maybe_uids could be handled also with some more work.. */
+ return 0;
+}
+
+struct fts_backend fts_backend_solr_old = {
+ .name = "solr_old",
+ .flags = 0,
+
+ {
+ fts_backend_solr_alloc,
+ fts_backend_solr_init,
+ fts_backend_solr_deinit,
+ fts_backend_solr_get_last_uid,
+ fts_backend_solr_update_init,
+ fts_backend_solr_update_deinit,
+ fts_backend_solr_update_set_mailbox,
+ fts_backend_solr_update_expunge,
+ fts_backend_solr_update_set_build_key,
+ fts_backend_solr_update_unset_build_key,
+ fts_backend_solr_update_build_more,
+ fts_backend_solr_refresh,
+ NULL,
+ fts_backend_solr_optimize,
+ fts_backend_default_can_lookup,
+ fts_backend_solr_lookup,
+ fts_backend_solr_lookup_multi,
+ NULL
+ }
+};
diff --git a/src/plugins/fts-solr/fts-backend-solr.c b/src/plugins/fts-solr/fts-backend-solr.c
new file mode 100644
index 0000000..0ac0f18
--- /dev/null
+++ b/src/plugins/fts-solr/fts-backend-solr.c
@@ -0,0 +1,984 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "hash.h"
+#include "strescape.h"
+#include "unichar.h"
+#include "iostream-ssl.h"
+#include "http-url.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "mail-search.h"
+#include "fts-api.h"
+#include "solr-connection.h"
+#include "fts-solr-plugin.h"
+
+#include <ctype.h>
+
+#define SOLR_CMDBUF_SIZE (1024*64)
+#define SOLR_CMDBUF_FLUSH_SIZE (SOLR_CMDBUF_SIZE-128)
+#define SOLR_MAX_MULTI_ROWS 100000
+
+/* If header is larger than this, truncate it. */
+#define SOLR_HEADER_MAX_SIZE (1024*1024)
+/* If SOLR_HEADER_MAX_SIZE was already reached, write still to individual
+ header fields as long as they're smaller than this */
+#define SOLR_HEADER_LINE_MAX_TRUNC_SIZE 1024
+
+#define SOLR_QUERY_MAX_MAILBOX_COUNT 10
+
+struct solr_fts_backend {
+ struct fts_backend backend;
+ struct solr_connection *solr_conn;
+};
+
+struct solr_fts_field {
+ char *key;
+ string_t *value;
+};
+
+struct solr_fts_backend_update_context {
+ struct fts_backend_update_context ctx;
+
+ struct mailbox *cur_box;
+ char box_guid[MAILBOX_GUID_HEX_LENGTH+1];
+
+ struct solr_connection_post *post;
+ uint32_t prev_uid;
+ string_t *cmd, *cur_value, *cur_value2;
+ string_t *cmd_expunge;
+ ARRAY(struct solr_fts_field) fields;
+
+ uint32_t last_indexed_uid;
+ unsigned int mails_since_flush;
+
+ bool tokenized_input:1;
+ bool last_indexed_uid_set:1;
+ bool body_open:1;
+ bool documents_added:1;
+ bool expunges:1;
+ bool truncate_header:1;
+};
+
+static const char *solr_escape_chars = "+-&|!(){}[]^\"~*?:\\/ ";
+
+static bool is_valid_xml_char(unichar_t chr)
+{
+ /* Valid characters in XML:
+
+ #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
+ [#x10000-#x10FFFF]
+
+ This function gets called only for #x80 and higher */
+ if (chr > 0xd7ff && chr < 0xe000)
+ return FALSE;
+ if (chr > 0xfffd && chr < 0x10000)
+ return FALSE;
+ return chr < 0x10ffff;
+}
+
+static size_t
+xml_encode_data_max(string_t *dest, const unsigned char *data, size_t len,
+ unsigned int max_len)
+{
+ unichar_t chr;
+ size_t i;
+
+ i_assert(max_len > 0 || len == 0);
+
+ if (max_len > len)
+ max_len = len;
+ for (i = 0; i < max_len; i++) {
+ switch (data[i]) {
+ case '&':
+ str_append(dest, "&amp;");
+ break;
+ case '<':
+ str_append(dest, "&lt;");
+ break;
+ case '>':
+ str_append(dest, "&gt;");
+ break;
+ case '\t':
+ case '\n':
+ case '\r':
+ /* exceptions to the following control char check */
+ str_append_c(dest, data[i]);
+ break;
+ default:
+ if (data[i] < 32) {
+ /* SOLR doesn't like control characters.
+ replace them with spaces. */
+ str_append_c(dest, ' ');
+ } else if (data[i] >= 0x80) {
+ /* make sure the character is valid for XML
+ so we don't get XML parser errors */
+ int char_len =
+ uni_utf8_get_char_n(data + i, len - i, &chr);
+ i_assert(char_len > 0); /* input is valid UTF8 */
+ if (is_valid_xml_char(chr))
+ str_append_data(dest, data + i, char_len);
+ else {
+ str_append_data(dest, utf8_replacement_char,
+ UTF8_REPLACEMENT_CHAR_LEN);
+ }
+ i += char_len - 1;
+ } else {
+ str_append_c(dest, data[i]);
+ }
+ break;
+ }
+ }
+ return i;
+}
+
+static void
+xml_encode_data(string_t *dest, const unsigned char *data, size_t len)
+{
+ (void)xml_encode_data_max(dest, data, len, len);
+}
+
+static void xml_encode(string_t *dest, const char *str)
+{
+ xml_encode_data(dest, (const unsigned char *)str, strlen(str));
+}
+
+static const char *solr_escape(const char *str)
+{
+ string_t *ret;
+ unsigned int i;
+
+ ret = t_str_new(strlen(str) + 16);
+ for (i = 0; str[i] != '\0'; i++) {
+ if (strchr(solr_escape_chars, str[i]) != NULL)
+ str_append_c(ret, '\\');
+ str_append_c(ret, str[i]);
+ }
+ return str_c(ret);
+}
+
+static void solr_quote_http(string_t *dest, const char *str)
+{
+ if (str[0] != '\0')
+ http_url_escape_param(dest, solr_escape(str));
+ else
+ str_append(dest, "%22%22");
+}
+
+static struct fts_backend *fts_backend_solr_alloc(void)
+{
+ struct solr_fts_backend *backend;
+
+ backend = i_new(struct solr_fts_backend, 1);
+ backend->backend = fts_backend_solr;
+ return &backend->backend;
+}
+
+static int
+fts_backend_solr_init(struct fts_backend *_backend, const char **error_r)
+{
+ struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+ struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(_backend->ns->user);
+ struct ssl_iostream_settings ssl_set;
+
+ if (fuser == NULL) {
+ *error_r = "Invalid fts_solr setting";
+ return -1;
+ }
+ if (fuser->set.use_libfts) {
+ /* change our flags so we get proper input */
+ _backend->flags &= ENUM_NEGATE(FTS_BACKEND_FLAG_FUZZY_SEARCH);
+ _backend->flags |= FTS_BACKEND_FLAG_TOKENIZED_INPUT;
+ }
+
+ mail_user_init_ssl_client_settings(_backend->ns->user, &ssl_set);
+ return solr_connection_init(&fuser->set, &ssl_set,
+ _backend->ns->user->event,
+ &backend->solr_conn, error_r);
+}
+
+static void fts_backend_solr_deinit(struct fts_backend *_backend)
+{
+ struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+
+ solr_connection_deinit(&backend->solr_conn);
+ i_free(backend);
+}
+
+static int
+get_last_uid_fallback(struct fts_backend *_backend, struct mailbox *box,
+ uint32_t *last_uid_r)
+{
+ struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+ const struct seq_range *uidvals;
+ const char *box_guid;
+ unsigned int count;
+ struct solr_result **results;
+ string_t *str;
+ pool_t pool;
+ int ret = 0;
+
+ str = t_str_new(256);
+ str_append(str, "wt=xml&fl=uid&rows=1&sort=uid+desc&q=");
+
+ if (fts_mailbox_get_guid(box, &box_guid) < 0)
+ return -1;
+
+ str_printfa(str, "box:%s+AND+user:", box_guid);
+ if (_backend->ns->owner != NULL)
+ solr_quote_http(str, _backend->ns->owner->username);
+ else
+ str_append(str, "%22%22");
+
+ pool = pool_alloconly_create("solr last uid lookup", 1024);
+ if (solr_connection_select(backend->solr_conn, str_c(str),
+ pool, &results) < 0)
+ ret = -1;
+ else if (results[0] == NULL) {
+ /* no UIDs */
+ *last_uid_r = 0;
+ } else {
+ uidvals = array_get(&results[0]->uids, &count);
+ i_assert(count > 0);
+ if (count == 1 && uidvals[0].seq1 == uidvals[0].seq2) {
+ *last_uid_r = uidvals[0].seq1;
+ } else {
+ i_error("fts_solr: Last UID lookup returned multiple rows");
+ ret = -1;
+ }
+ }
+ pool_unref(&pool);
+ return ret;
+}
+
+static int
+fts_backend_solr_get_last_uid(struct fts_backend *_backend,
+ struct mailbox *box, uint32_t *last_uid_r)
+{
+ struct fts_index_header hdr;
+
+ if (fts_index_get_header(box, &hdr)) {
+ *last_uid_r = hdr.last_indexed_uid;
+ return 0;
+ }
+
+ /* either nothing has been indexed, or the index was corrupted.
+ do it the slow way. */
+ if (get_last_uid_fallback(_backend, box, last_uid_r) < 0)
+ return -1;
+
+ fts_index_set_last_uid(box, *last_uid_r);
+ return 0;
+}
+
+static struct fts_backend_update_context *
+fts_backend_solr_update_init(struct fts_backend *_backend)
+{
+ struct solr_fts_backend_update_context *ctx;
+
+ ctx = i_new(struct solr_fts_backend_update_context, 1);
+ ctx->ctx.backend = _backend;
+ ctx->tokenized_input =
+ (_backend->flags & FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0;
+ i_array_init(&ctx->fields, 16);
+ return &ctx->ctx;
+}
+
+static void xml_encode_id(struct solr_fts_backend_update_context *ctx,
+ string_t *str, uint32_t uid)
+{
+ str_printfa(str, "%u/%s", uid, ctx->box_guid);
+ if (ctx->ctx.backend->ns->owner != NULL) {
+ str_append_c(str, '/');
+ xml_encode(str, ctx->ctx.backend->ns->owner->username);
+ }
+}
+
+static void
+fts_backend_solr_doc_open(struct solr_fts_backend_update_context *ctx,
+ uint32_t uid)
+{
+ ctx->documents_added = TRUE;
+
+ str_printfa(ctx->cmd, "<doc>"
+ "<field name=\"uid\">%u</field>"
+ "<field name=\"box\">%s</field>",
+ uid, ctx->box_guid);
+ str_append(ctx->cmd, "<field name=\"user\">");
+ if (ctx->ctx.backend->ns->owner != NULL)
+ xml_encode(ctx->cmd, ctx->ctx.backend->ns->owner->username);
+ str_append(ctx->cmd, "</field>");
+
+ str_printfa(ctx->cmd, "<field name=\"id\">");
+ xml_encode_id(ctx, ctx->cmd, uid);
+ str_append(ctx->cmd, "</field>");
+}
+
+static string_t *
+fts_solr_field_get(struct solr_fts_backend_update_context *ctx, const char *key)
+{
+ const struct solr_fts_field *field;
+ struct solr_fts_field new_field;
+
+ /* there are only a few fields. this lookup is fast enough. */
+ array_foreach(&ctx->fields, field) {
+ if (strcasecmp(field->key, key) == 0)
+ return field->value;
+ }
+
+ i_zero(&new_field);
+ new_field.key = str_lcase(i_strdup(key));
+ new_field.value = str_new(default_pool, 128);
+ array_push_back(&ctx->fields, &new_field);
+ return new_field.value;
+}
+
+static void
+fts_backend_solr_doc_close(struct solr_fts_backend_update_context *ctx)
+{
+ struct solr_fts_field *field;
+
+ if (ctx->body_open) {
+ ctx->body_open = FALSE;
+ str_append(ctx->cmd, "</field>");
+ }
+ array_foreach_modifiable(&ctx->fields, field) {
+ str_printfa(ctx->cmd, "<field name=\"%s\">", field->key);
+ /* the values are already xml-escaped */
+ str_append_str(ctx->cmd, field->value);
+ str_append(ctx->cmd, "</field>");
+ str_truncate(field->value, 0);
+ }
+ str_append(ctx->cmd, "</doc>");
+}
+
+static int
+fts_backed_solr_build_flush(struct solr_fts_backend_update_context *ctx)
+{
+ if (ctx->post == NULL)
+ return 0;
+
+ fts_backend_solr_doc_close(ctx);
+ str_append(ctx->cmd, "</add>");
+ ctx->mails_since_flush = 0;
+
+ solr_connection_post_more(ctx->post, str_data(ctx->cmd),
+ str_len(ctx->cmd));
+ str_truncate(ctx->cmd, 0);
+ return solr_connection_post_end(&ctx->post);
+}
+
+static void
+fts_backend_solr_expunge_flush(struct solr_fts_backend_update_context *ctx)
+{
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)ctx->ctx.backend;
+
+ str_append(ctx->cmd_expunge, "</delete>");
+ (void)solr_connection_post(backend->solr_conn, str_c(ctx->cmd_expunge));
+ str_truncate(ctx->cmd_expunge, 0);
+ str_append(ctx->cmd_expunge, "<delete>");
+}
+
+static int
+fts_backend_solr_update_deinit(struct fts_backend_update_context *_ctx)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)_ctx->backend;
+ struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(_ctx->backend->ns->user);
+ struct solr_fts_field *field;
+ const char *str;
+ int ret = _ctx->failed ? -1 : 0;
+
+ if (fts_backed_solr_build_flush(ctx) < 0)
+ ret = -1;
+
+ if (ctx->documents_added || ctx->expunges) {
+ /* commit and wait until the documents we just indexed are
+ visible to the following search */
+ if (ctx->expunges)
+ fts_backend_solr_expunge_flush(ctx);
+ if (fuser->set.soft_commit) {
+ str = t_strdup_printf("<commit softCommit=\"true\" waitSearcher=\"%s\"/>",
+ ctx->documents_added ? "true" : "false");
+ if (solr_connection_post(backend->solr_conn, str) < 0)
+ ret = -1;
+ }
+ }
+
+ str_free(&ctx->cmd);
+ str_free(&ctx->cmd_expunge);
+ array_foreach_modifiable(&ctx->fields, field) {
+ str_free(&field->value);
+ i_free(field->key);
+ }
+ array_free(&ctx->fields);
+ i_free(ctx);
+ return ret;
+}
+
+static void
+fts_backend_solr_update_set_mailbox(struct fts_backend_update_context *_ctx,
+ struct mailbox *box)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+ const char *box_guid;
+
+ if (ctx->prev_uid != 0) {
+ i_assert(ctx->cur_box != NULL);
+
+ /* flush solr between mailboxes, so we don't wrongly update
+ last_uid before we know it has succeeded */
+ if (fts_backed_solr_build_flush(ctx) < 0)
+ _ctx->failed = TRUE;
+ else if (!_ctx->failed)
+ fts_index_set_last_uid(ctx->cur_box, ctx->prev_uid);
+ ctx->prev_uid = 0;
+ }
+
+ if (box != NULL) {
+ if (fts_mailbox_get_guid(box, &box_guid) < 0)
+ _ctx->failed = TRUE;
+
+ i_assert(strlen(box_guid) == sizeof(ctx->box_guid)-1);
+ memcpy(ctx->box_guid, box_guid, sizeof(ctx->box_guid)-1);
+ } else {
+ memset(ctx->box_guid, 0, sizeof(ctx->box_guid));
+ }
+ ctx->cur_box = box;
+}
+
+static void
+fts_backend_solr_update_expunge(struct fts_backend_update_context *_ctx,
+ uint32_t uid)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+ struct fts_index_header hdr;
+
+ if (!ctx->last_indexed_uid_set) {
+ if (!fts_index_get_header(ctx->cur_box, &hdr))
+ ctx->last_indexed_uid = 0;
+ else
+ ctx->last_indexed_uid = hdr.last_indexed_uid;
+ ctx->last_indexed_uid_set = TRUE;
+ }
+ if (ctx->last_indexed_uid == 0 ||
+ uid > ctx->last_indexed_uid + 100) {
+ /* don't waste time asking Solr to expunge a message that is
+ highly unlikely to be indexed at this time. */
+ return;
+ }
+ if (!ctx->expunges) {
+ ctx->expunges = TRUE;
+ ctx->cmd_expunge = str_new(default_pool, 1024);
+ str_append(ctx->cmd_expunge, "<delete>");
+ }
+
+ if (str_len(ctx->cmd_expunge) >= SOLR_CMDBUF_FLUSH_SIZE)
+ fts_backend_solr_expunge_flush(ctx);
+
+ str_append(ctx->cmd_expunge, "<id>");
+ xml_encode_id(ctx, ctx->cmd_expunge, uid);
+ str_append(ctx->cmd_expunge, "</id>");
+}
+
+static void
+fts_backend_solr_uid_changed(struct solr_fts_backend_update_context *ctx,
+ uint32_t uid)
+{
+ struct solr_fts_backend *backend =
+ (struct solr_fts_backend *)ctx->ctx.backend;
+ struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(ctx->ctx.backend->ns->user);
+
+ if (ctx->mails_since_flush >= fuser->set.batch_size) {
+ if (fts_backed_solr_build_flush(ctx) < 0)
+ ctx->ctx.failed = TRUE;
+ }
+ ctx->mails_since_flush++;
+ if (ctx->post == NULL) {
+ if (ctx->cmd == NULL)
+ ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE);
+ ctx->post = solr_connection_post_begin(backend->solr_conn);
+ str_append(ctx->cmd, "<add>");
+ } else {
+ fts_backend_solr_doc_close(ctx);
+ }
+ ctx->prev_uid = uid;
+ ctx->truncate_header = FALSE;
+ fts_backend_solr_doc_open(ctx, uid);
+}
+
+static bool
+fts_backend_solr_update_set_build_key(struct fts_backend_update_context *_ctx,
+ const struct fts_backend_build_key *key)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+
+ if (key->uid != ctx->prev_uid)
+ fts_backend_solr_uid_changed(ctx, key->uid);
+
+ switch (key->type) {
+ case FTS_BACKEND_BUILD_KEY_HDR:
+ if (fts_header_want_indexed(key->hdr_name)) {
+ ctx->cur_value2 =
+ fts_solr_field_get(ctx, key->hdr_name);
+ }
+ /* fall through */
+ case FTS_BACKEND_BUILD_KEY_MIME_HDR:
+ ctx->cur_value = fts_solr_field_get(ctx, "hdr");
+ xml_encode(ctx->cur_value, key->hdr_name);
+ str_append(ctx->cur_value, ": ");
+ break;
+ case FTS_BACKEND_BUILD_KEY_BODY_PART:
+ if (!ctx->body_open) {
+ ctx->body_open = TRUE;
+ str_append(ctx->cmd, "<field name=\"body\">");
+ }
+ ctx->cur_value = ctx->cmd;
+ break;
+ case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY:
+ i_unreached();
+ }
+ return TRUE;
+}
+
+static void
+fts_backend_solr_update_unset_build_key(struct fts_backend_update_context *_ctx)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+
+ /* There can be multiple duplicate keys (duplicate header lines,
+ multiple MIME body parts). Make sure they are separated by
+ whitespace. */
+ str_append_c(ctx->cur_value, '\n');
+ ctx->cur_value = NULL;
+ if (ctx->cur_value2 != NULL) {
+ str_append_c(ctx->cur_value2, '\n');
+ ctx->cur_value2 = NULL;
+ }
+}
+
+static int
+fts_backend_solr_update_build_more(struct fts_backend_update_context *_ctx,
+ const unsigned char *data, size_t size)
+{
+ struct solr_fts_backend_update_context *ctx =
+ (struct solr_fts_backend_update_context *)_ctx;
+ size_t len;
+
+ if (_ctx->failed)
+ return -1;
+
+ if (ctx->cur_value2 == NULL && ctx->cur_value == ctx->cmd) {
+ /* we're writing to message body. if size is huge,
+ flush it once in a while */
+ while (size >= SOLR_CMDBUF_FLUSH_SIZE) {
+ if (str_len(ctx->cmd) >= SOLR_CMDBUF_FLUSH_SIZE) {
+ solr_connection_post_more(ctx->post,
+ str_data(ctx->cmd),
+ str_len(ctx->cmd));
+ str_truncate(ctx->cmd, 0);
+ }
+ len = xml_encode_data_max(ctx->cmd, data, size,
+ SOLR_CMDBUF_FLUSH_SIZE -
+ str_len(ctx->cmd));
+ i_assert(len > 0);
+ i_assert(len <= size);
+ data += len;
+ size -= len;
+ }
+ xml_encode_data(ctx->cmd, data, size);
+ if (ctx->tokenized_input)
+ str_append_c(ctx->cmd, ' ');
+ } else {
+ if (!ctx->truncate_header) {
+ xml_encode_data(ctx->cur_value, data, size);
+ if (ctx->tokenized_input)
+ str_append_c(ctx->cur_value, ' ');
+ }
+ if (ctx->cur_value2 != NULL &&
+ (!ctx->truncate_header ||
+ str_len(ctx->cur_value2) < SOLR_HEADER_LINE_MAX_TRUNC_SIZE)) {
+ xml_encode_data(ctx->cur_value2, data, size);
+ if (ctx->tokenized_input)
+ str_append_c(ctx->cur_value2, ' ');
+ }
+ }
+
+ if (str_len(ctx->cmd) >= SOLR_CMDBUF_FLUSH_SIZE) {
+ solr_connection_post_more(ctx->post, str_data(ctx->cmd),
+ str_len(ctx->cmd));
+ str_truncate(ctx->cmd, 0);
+ }
+ if (!ctx->truncate_header &&
+ str_len(ctx->cur_value) >= SOLR_HEADER_MAX_SIZE) {
+ /* a large header */
+ i_assert(ctx->cur_value != ctx->cmd);
+
+ i_warning("fts-solr(%s): Mailbox %s UID=%u header size is huge, truncating",
+ ctx->cur_box->storage->user->username,
+ mailbox_get_vname(ctx->cur_box), ctx->prev_uid);
+ ctx->truncate_header = TRUE;
+ }
+ return 0;
+}
+
+static int fts_backend_solr_refresh(struct fts_backend *backend ATTR_UNUSED)
+{
+ return 0;
+}
+
+static int fts_backend_solr_rescan(struct fts_backend *backend)
+{
+ /* FIXME: proper rescan needed. for now we'll just reset the
+ last-uids */
+ return fts_backend_reset_last_uids(backend);
+}
+
+static int fts_backend_solr_optimize(struct fts_backend *backend ATTR_UNUSED)
+{
+ return 0;
+}
+
+static bool solr_need_escaping(const char *str)
+{
+ for (; *str != '\0'; str++) {
+ if (strchr(solr_escape_chars, *str) != NULL)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void solr_add_str_arg(string_t *str, struct mail_search_arg *arg)
+{
+ /* currently we'll just disable fuzzy searching if there are any
+ parameters that need escaping. solr doesn't seem to give good
+ fuzzy results even if we did escape them.. */
+ if (!arg->fuzzy || arg->value.str[0] == '\0' ||
+ solr_need_escaping(arg->value.str))
+ solr_quote_http(str, arg->value.str);
+ else {
+ http_url_escape_param(str, arg->value.str);
+ str_append_c(str, '~');
+ }
+}
+
+static bool
+solr_add_definite_query(string_t *str, struct mail_search_arg *arg)
+{
+ if (arg->no_fts)
+ return FALSE;
+ switch (arg->type) {
+ case SEARCH_TEXT: {
+ if (arg->match_not)
+ str_append_c(str, '-');
+ str_append(str, "(hdr:");
+ solr_add_str_arg(str, arg);
+ str_append(str, "+OR+body:");
+ solr_add_str_arg(str, arg);
+ str_append(str, ")");
+ break;
+ }
+ case SEARCH_BODY:
+ if (arg->match_not)
+ str_append_c(str, '-');
+ str_append(str, "body:");
+ solr_add_str_arg(str, arg);
+ break;
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ if (!fts_header_want_indexed(arg->hdr_field_name))
+ return FALSE;
+
+ if (arg->match_not)
+ str_append_c(str, '-');
+ str_append(str, t_str_lcase(arg->hdr_field_name));
+ str_append_c(str, ':');
+ solr_add_str_arg(str, arg);
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+solr_add_definite_query_args(string_t *str, struct mail_search_arg *arg,
+ bool and_args)
+{
+ size_t last_len;
+
+ last_len = str_len(str);
+ for (; arg != NULL; arg = arg->next) {
+ if (solr_add_definite_query(str, arg)) {
+ arg->match_always = TRUE;
+ last_len = str_len(str);
+ if (and_args)
+ str_append(str, "+AND+");
+ else
+ str_append(str, "+OR+");
+ }
+ }
+ if (str_len(str) == last_len)
+ return FALSE;
+
+ str_truncate(str, last_len);
+ return TRUE;
+}
+
+static bool
+solr_add_maybe_query(string_t *str, struct mail_search_arg *arg)
+{
+ if (arg->no_fts)
+ return FALSE;
+ switch (arg->type) {
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ if (fts_header_want_indexed(arg->hdr_field_name))
+ return FALSE;
+ if (arg->match_not) {
+ /* all matches would be definite, but all non-matches
+ would be maybies. too much trouble to optimize. */
+ return FALSE;
+ }
+
+ /* we can check if the search key exists in some header and
+ filter out the messages that have no chance of matching */
+ str_append(str, "hdr:");
+ if (*arg->value.str != '\0')
+ solr_quote_http(str, arg->value.str);
+ else {
+ /* checking potential existence of the header name */
+ solr_quote_http(str, t_str_lcase(arg->hdr_field_name));
+ }
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+solr_add_maybe_query_args(string_t *str, struct mail_search_arg *arg,
+ bool and_args)
+{
+ size_t last_len;
+
+ last_len = str_len(str);
+ for (; arg != NULL; arg = arg->next) {
+ if (solr_add_maybe_query(str, arg)) {
+ arg->match_always = TRUE;
+ last_len = str_len(str);
+ if (and_args)
+ str_append(str, "+AND+");
+ else
+ str_append(str, "+OR+");
+ }
+ }
+ if (str_len(str) == last_len)
+ return FALSE;
+
+ str_truncate(str, last_len);
+ return TRUE;
+}
+
+static int solr_search(struct fts_backend *_backend, string_t *str,
+ const char *box_guid, ARRAY_TYPE(seq_range) *uids_r,
+ ARRAY_TYPE(fts_score_map) *scores_r)
+{
+ struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+ pool_t pool = pool_alloconly_create("fts solr search", 1024);
+ struct solr_result **results;
+ int ret;
+
+ /* use a separate filter query for selecting the mailbox. it shouldn't
+ affect the score and there could be some caching benefits too. */
+ str_printfa(str, "&fq=%%2Bbox:%s+%%2Buser:", box_guid);
+ if (_backend->ns->owner != NULL)
+ solr_quote_http(str, _backend->ns->owner->username);
+ else
+ str_append(str, "%22%22");
+
+ ret = solr_connection_select(backend->solr_conn, str_c(str),
+ pool, &results);
+ if (ret == 0 && results[0] != NULL) {
+ array_append_array(uids_r, &results[0]->uids);
+ array_append_array(scores_r, &results[0]->scores);
+ }
+ pool_unref(&pool);
+ return ret;
+}
+
+static int
+fts_backend_solr_lookup(struct fts_backend *_backend, struct mailbox *box,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_result *result)
+{
+ bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
+ struct mailbox_status status;
+ string_t *str;
+ const char *box_guid;
+ size_t prefix_len;
+
+ if (fts_mailbox_get_guid(box, &box_guid) < 0)
+ return -1;
+ mailbox_get_open_status(box, STATUS_UIDNEXT, &status);
+
+ str = t_str_new(256);
+ str_printfa(str, "wt=xml&fl=uid,score&rows=%u&sort=uid+asc&q=%%7b!lucene+q.op%%3dAND%%7d",
+ status.uidnext);
+ prefix_len = str_len(str);
+
+ if (solr_add_definite_query_args(str, args, and_args)) {
+ ARRAY_TYPE(seq_range) *uids_arr =
+ (flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0 ?
+ &result->definite_uids : &result->maybe_uids;
+ if (solr_search(_backend, str, box_guid,
+ uids_arr, &result->scores) < 0)
+ return -1;
+ }
+ str_truncate(str, prefix_len);
+ if (solr_add_maybe_query_args(str, args, and_args)) {
+ if (solr_search(_backend, str, box_guid,
+ &result->maybe_uids, &result->scores) < 0)
+ return -1;
+ }
+ result->scores_sorted = TRUE;
+ return 0;
+}
+
+static int
+solr_search_multi(struct fts_backend *_backend, string_t *str,
+ struct mailbox *const boxes[], enum fts_lookup_flags flags,
+ struct fts_multi_result *result)
+{
+ struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
+ struct solr_result **solr_results;
+ struct fts_result *fts_result;
+ ARRAY(struct fts_result) fts_results;
+ HASH_TABLE(char *, struct mailbox *) mailboxes;
+ struct mailbox *box;
+ const char *box_guid;
+ unsigned int i;
+ size_t len;
+ bool search_all_mailboxes;
+
+ /* use a separate filter query for selecting the mailbox. it shouldn't
+ affect the score and there could be some caching benefits too. */
+ str_append(str, "&fq=%2Buser:");
+ if (_backend->ns->owner != NULL)
+ solr_quote_http(str, _backend->ns->owner->username);
+ else
+ str_append(str, "%22%22");
+
+ hash_table_create(&mailboxes, default_pool, 0, str_hash, strcmp);
+ for (i = 0; boxes[i] != NULL; i++) ;
+ search_all_mailboxes = i > SOLR_QUERY_MAX_MAILBOX_COUNT;
+ if (!search_all_mailboxes)
+ str_append(str, "+%2B(");
+ len = str_len(str);
+
+ for (i = 0; boxes[i] != NULL; i++) {
+ if (fts_mailbox_get_guid(boxes[i], &box_guid) < 0)
+ continue;
+
+ if (!search_all_mailboxes) {
+ if (str_len(str) != len)
+ str_append(str, "+OR+");
+ str_printfa(str, "box:%s", box_guid);
+ }
+ hash_table_insert(mailboxes, t_strdup_noconst(box_guid),
+ boxes[i]);
+ }
+ if (!search_all_mailboxes)
+ str_append_c(str, ')');
+
+ if (solr_connection_select(backend->solr_conn, str_c(str),
+ result->pool, &solr_results) < 0) {
+ hash_table_destroy(&mailboxes);
+ return -1;
+ }
+
+ p_array_init(&fts_results, result->pool, 32);
+ for (i = 0; solr_results[i] != NULL; i++) {
+ box = hash_table_lookup(mailboxes, solr_results[i]->box_id);
+ if (box == NULL) {
+ if (!search_all_mailboxes) {
+ i_warning("fts_solr: Lookup returned unexpected mailbox "
+ "with guid=%s", solr_results[i]->box_id);
+ }
+ continue;
+ }
+ fts_result = array_append_space(&fts_results);
+ fts_result->box = box;
+ if ((flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0)
+ fts_result->definite_uids = solr_results[i]->uids;
+ else
+ fts_result->maybe_uids = solr_results[i]->uids;
+ fts_result->scores = solr_results[i]->scores;
+ fts_result->scores_sorted = TRUE;
+ }
+ array_append_zero(&fts_results);
+ result->box_results = array_front_modifiable(&fts_results);
+ hash_table_destroy(&mailboxes);
+ return 0;
+}
+
+static int
+fts_backend_solr_lookup_multi(struct fts_backend *backend,
+ struct mailbox *const boxes[],
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result)
+{
+ bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
+ string_t *str;
+
+ str = t_str_new(256);
+ str_printfa(str, "wt=xml&fl=box,uid,score&rows=%u&sort=box+asc,uid+asc&q=%%7b!lucene+q.op%%3dAND%%7d",
+ SOLR_MAX_MULTI_ROWS);
+
+ if (solr_add_definite_query_args(str, args, and_args)) {
+ if (solr_search_multi(backend, str, boxes, flags, result) < 0)
+ return -1;
+ }
+ /* FIXME: maybe_uids could be handled also with some more work.. */
+ return 0;
+}
+
+struct fts_backend fts_backend_solr = {
+ .name = "solr",
+ .flags = FTS_BACKEND_FLAG_FUZZY_SEARCH,
+
+ {
+ fts_backend_solr_alloc,
+ fts_backend_solr_init,
+ fts_backend_solr_deinit,
+ fts_backend_solr_get_last_uid,
+ fts_backend_solr_update_init,
+ fts_backend_solr_update_deinit,
+ fts_backend_solr_update_set_mailbox,
+ fts_backend_solr_update_expunge,
+ fts_backend_solr_update_set_build_key,
+ fts_backend_solr_update_unset_build_key,
+ fts_backend_solr_update_build_more,
+ fts_backend_solr_refresh,
+ fts_backend_solr_rescan,
+ fts_backend_solr_optimize,
+ fts_backend_default_can_lookup,
+ fts_backend_solr_lookup,
+ fts_backend_solr_lookup_multi,
+ NULL
+ }
+};
diff --git a/src/plugins/fts-solr/fts-solr-plugin.c b/src/plugins/fts-solr/fts-solr-plugin.c
new file mode 100644
index 0000000..5899784
--- /dev/null
+++ b/src/plugins/fts-solr/fts-solr-plugin.c
@@ -0,0 +1,131 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "http-client.h"
+#include "mail-user.h"
+#include "mail-storage-hooks.h"
+#include "solr-connection.h"
+#include "fts-user.h"
+#include "fts-solr-plugin.h"
+
+#define DEFAULT_SOLR_BATCH_SIZE 1000
+
+const char *fts_solr_plugin_version = DOVECOT_ABI_VERSION;
+struct http_client *solr_http_client = NULL;
+
+struct fts_solr_user_module fts_solr_user_module =
+ MODULE_CONTEXT_INIT(&mail_user_module_register);
+
+static int
+fts_solr_plugin_init_settings(struct mail_user *user,
+ struct fts_solr_settings *set, const char *str)
+{
+ const char *const *tmp;
+
+ if (str == NULL)
+ str = "";
+
+ set->batch_size = DEFAULT_SOLR_BATCH_SIZE;
+ set->soft_commit = TRUE;
+
+ for (tmp = t_strsplit_spaces(str, " "); *tmp != NULL; tmp++) {
+ if (str_begins(*tmp, "url=")) {
+ set->url = p_strdup(user->pool, *tmp + 4);
+ } else if (strcmp(*tmp, "debug") == 0) {
+ set->debug = TRUE;
+ } else if (strcmp(*tmp, "use_libfts") == 0) {
+ set->use_libfts = TRUE;
+ } else if (str_begins(*tmp, "default_ns=")) {
+ set->default_ns_prefix =
+ p_strdup(user->pool, *tmp + 11);
+ } else if (str_begins(*tmp, "rawlog_dir=")) {
+ set->rawlog_dir = p_strdup(user->pool, *tmp + 11);
+ } else if (str_begins(*tmp, "batch_size=")) {
+ if (str_to_uint(*tmp+11, &set->batch_size) < 0 ||
+ set->batch_size == 0) {
+ i_error("fts_solr: batch_size must be a positive integer");
+ return -1;
+ }
+ } else if (str_begins(*tmp, "soft_commit=")) {
+ if (strcmp(*tmp + 12, "yes") == 0) {
+ set->soft_commit = TRUE;
+ } else if (strcmp(*tmp + 12, "no") == 0) {
+ set->soft_commit = FALSE;
+ } else {
+ i_error("fts_solr: Invalid setting for soft_commit: %s", *tmp+12);
+ return -1;
+ }
+ } else {
+ i_error("fts_solr: Invalid setting: %s", *tmp);
+ return -1;
+ }
+ }
+ if (set->url == NULL) {
+ i_error("fts_solr: url setting missing");
+ return -1;
+ }
+ return 0;
+}
+
+static void fts_solr_mail_user_deinit(struct mail_user *user)
+{
+ struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT_REQUIRE(user);
+
+ fts_mail_user_deinit(user);
+ fuser->module_ctx.super.deinit(user);
+}
+
+static void fts_solr_mail_user_create(struct mail_user *user, const char *env)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct fts_solr_user *fuser;
+ const char *error;
+
+ fuser = p_new(user->pool, struct fts_solr_user, 1);
+ if (fts_solr_plugin_init_settings(user, &fuser->set, env) < 0) {
+ /* invalid settings, disabling */
+ return;
+ }
+ if (fts_mail_user_init(user, fuser->set.use_libfts, &error) < 0) {
+ i_error("fts-solr: %s", error);
+ return;
+ }
+
+ fuser->module_ctx.super = *v;
+ user->vlast = &fuser->module_ctx.super;
+ v->deinit = fts_solr_mail_user_deinit;
+ MODULE_CONTEXT_SET(user, fts_solr_user_module, fuser);
+}
+
+static void fts_solr_mail_user_created(struct mail_user *user)
+{
+ const char *env;
+
+ env = mail_user_plugin_getenv(user, "fts_solr");
+ if (env != NULL)
+ fts_solr_mail_user_create(user, env);
+}
+
+static struct mail_storage_hooks fts_solr_mail_storage_hooks = {
+ .mail_user_created = fts_solr_mail_user_created
+};
+
+void fts_solr_plugin_init(struct module *module)
+{
+ fts_backend_register(&fts_backend_solr);
+ fts_backend_register(&fts_backend_solr_old);
+ mail_storage_hooks_add(module, &fts_solr_mail_storage_hooks);
+}
+
+void fts_solr_plugin_deinit(void)
+{
+ fts_backend_unregister(fts_backend_solr.name);
+ fts_backend_unregister(fts_backend_solr_old.name);
+ mail_storage_hooks_remove(&fts_solr_mail_storage_hooks);
+ if (solr_http_client != NULL)
+ http_client_deinit(&solr_http_client);
+
+}
+
+const char *fts_solr_plugin_dependencies[] = { "fts", NULL };
diff --git a/src/plugins/fts-solr/fts-solr-plugin.h b/src/plugins/fts-solr/fts-solr-plugin.h
new file mode 100644
index 0000000..abc1b66
--- /dev/null
+++ b/src/plugins/fts-solr/fts-solr-plugin.h
@@ -0,0 +1,35 @@
+#ifndef FTS_SOLR_PLUGIN_H
+#define FTS_SOLR_PLUGIN_H
+
+#include "module-context.h"
+#include "mail-user.h"
+#include "fts-api-private.h"
+
+#define FTS_SOLR_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, fts_solr_user_module)
+#define FTS_SOLR_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, fts_solr_user_module)
+
+struct fts_solr_settings {
+ const char *url, *default_ns_prefix, *rawlog_dir;
+ unsigned int batch_size;
+ bool use_libfts;
+ bool debug;
+ bool soft_commit;
+};
+
+struct fts_solr_user {
+ union mail_user_module_context module_ctx;
+ struct fts_solr_settings set;
+};
+
+extern const char *fts_solr_plugin_dependencies[];
+extern struct fts_backend fts_backend_solr;
+extern struct fts_backend fts_backend_solr_old;
+extern MODULE_CONTEXT_DEFINE(fts_solr_user_module, &mail_user_module_register);
+extern struct http_client *solr_http_client;
+
+void fts_solr_plugin_init(struct module *module);
+void fts_solr_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/fts-solr/solr-connection.c b/src/plugins/fts-solr/solr-connection.c
new file mode 100644
index 0000000..41a4fee
--- /dev/null
+++ b/src/plugins/fts-solr/solr-connection.c
@@ -0,0 +1,327 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "http-url.h"
+#include "http-client.h"
+#include "fts-solr-plugin.h"
+#include "solr-connection.h"
+
+#include <expat.h>
+
+struct solr_lookup_context {
+ pool_t result_pool;
+ struct istream *payload;
+ struct io *io;
+
+ int request_status;
+
+ struct solr_response_parser *parser;
+ struct solr_result **results;
+};
+
+struct solr_connection_post {
+ struct solr_connection *conn;
+
+ struct http_client_request *http_req;
+ int request_status;
+
+ bool failed:1;
+};
+
+struct solr_connection {
+ struct event *event;
+ char *http_host;
+ in_port_t http_port;
+ char *http_base_url;
+ char *http_failure;
+ char *http_user;
+ char *http_password;
+
+ bool debug:1;
+ bool posting:1;
+ bool http_ssl:1;
+};
+
+/* Regardless of the specified URL, make sure path ends in '/' */
+static char *solr_connection_create_http_base_url(struct http_url *http_url)
+{
+ if (http_url->path == NULL)
+ return i_strconcat("/", http_url->enc_query, NULL);
+ size_t len = strlen(http_url->path);
+ if (len > 0 && http_url->path[len-1] != '/')
+ return i_strconcat(http_url->path, "/",
+ http_url->enc_query, NULL);
+ /* http_url->path is NULL on empty path, so this is impossible. */
+ i_assert(len != 0);
+ return i_strconcat(http_url->path, http_url->enc_query, NULL);
+}
+
+int solr_connection_init(const struct fts_solr_settings *solr_set,
+ const struct ssl_iostream_settings *ssl_client_set,
+ struct event *event_parent,
+ struct solr_connection **conn_r, const char **error_r)
+{
+ struct http_client_settings http_set;
+ struct solr_connection *conn;
+ struct http_url *http_url;
+ const char *error;
+
+ if (http_url_parse(solr_set->url, NULL, HTTP_URL_ALLOW_USERINFO_PART,
+ pool_datastack_create(), &http_url, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "fts_solr: Failed to parse HTTP url: %s", error);
+ return -1;
+ }
+
+ conn = i_new(struct solr_connection, 1);
+ conn->event = event_create(event_parent);
+ conn->http_host = i_strdup(http_url->host.name);
+ conn->http_port = http_url->port;
+ conn->http_base_url = solr_connection_create_http_base_url(http_url);
+ conn->http_ssl = http_url->have_ssl;
+ if (http_url->user != NULL) {
+ conn->http_user = i_strdup(http_url->user);
+ /* allow empty password */
+ conn->http_password = i_strdup(http_url->password != NULL ?
+ http_url->password : "");
+ }
+
+ conn->debug = solr_set->debug;
+
+ if (solr_http_client == NULL) {
+ i_zero(&http_set);
+ http_set.max_idle_time_msecs = 5*1000;
+ http_set.max_parallel_connections = 1;
+ http_set.max_pipelined_requests = 1;
+ http_set.max_redirects = 1;
+ http_set.max_attempts = 3;
+ http_set.connect_timeout_msecs = 5*1000;
+ http_set.request_timeout_msecs = 60*1000;
+ http_set.ssl = ssl_client_set;
+ http_set.debug = solr_set->debug;
+ http_set.rawlog_dir = solr_set->rawlog_dir;
+ http_set.event_parent = conn->event;
+
+ /* FIXME: We should initialize a shared client instead. However,
+ this is currently not possible due to an obscure bug
+ in the blocking HTTP payload API, which causes
+ conflicts with other HTTP applications like FTS Tika.
+ Using a private client will provide a quick fix for
+ now. */
+ solr_http_client = http_client_init_private(&http_set);
+ }
+
+ *conn_r = conn;
+ return 0;
+}
+
+void solr_connection_deinit(struct solr_connection **_conn)
+{
+ struct solr_connection *conn = *_conn;
+
+ *_conn = NULL;
+ event_unref(&conn->event);
+ i_free(conn->http_host);
+ i_free(conn->http_base_url);
+ i_free(conn->http_user);
+ i_free(conn->http_password);
+ i_free(conn);
+}
+
+static void solr_connection_payload_input(struct solr_lookup_context *lctx)
+{
+ int ret;
+
+ /* read payload */
+ ret = solr_response_parse(lctx->parser, &lctx->results);
+
+ if (ret == 0) {
+ /* we will be called again for more data */
+ } else {
+ if (lctx->payload->stream_errno != 0) {
+ i_assert(ret < 0);
+ i_error("fts_solr: "
+ "failed to read payload from HTTP server: %s",
+ i_stream_get_error(lctx->payload));
+ }
+ if (ret < 0)
+ lctx->request_status = -1;
+ solr_response_parser_deinit(&lctx->parser);
+ io_remove(&lctx->io);
+ }
+}
+
+static void
+solr_connection_select_response(const struct http_response *response,
+ struct solr_lookup_context *lctx)
+{
+ if (response->status / 100 != 2) {
+ i_error("fts_solr: Lookup failed: %s",
+ http_response_get_message(response));
+ lctx->request_status = -1;
+ return;
+ }
+
+ if (response->payload == NULL) {
+ i_error("fts_solr: Lookup failed: Empty response payload");
+ lctx->request_status = -1;
+ return;
+ }
+
+ lctx->parser = solr_response_parser_init(lctx->result_pool,
+ response->payload);
+ lctx->payload = response->payload;
+ lctx->io = io_add_istream(response->payload,
+ solr_connection_payload_input, lctx);
+ solr_connection_payload_input(lctx);
+}
+
+int solr_connection_select(struct solr_connection *conn, const char *query,
+ pool_t pool, struct solr_result ***box_results_r)
+{
+ struct solr_lookup_context lctx;
+ struct http_client_request *http_req;
+ const char *url;
+
+ i_zero(&lctx);
+ lctx.result_pool = pool;
+
+ i_free_and_null(conn->http_failure);
+ url = t_strconcat(conn->http_base_url, "select?", query, NULL);
+
+ http_req = http_client_request(solr_http_client, "GET",
+ conn->http_host, url,
+ solr_connection_select_response,
+ &lctx);
+ if (conn->http_user != NULL) {
+ http_client_request_set_auth_simple(
+ http_req, conn->http_user, conn->http_password);
+ }
+ http_client_request_set_port(http_req, conn->http_port);
+ http_client_request_set_ssl(http_req, conn->http_ssl);
+ http_client_request_submit(http_req);
+
+ lctx.request_status = 0;
+ http_client_wait(solr_http_client);
+
+ if (lctx.request_status < 0)
+ return -1;
+
+ *box_results_r = lctx.results;
+ return 0;
+}
+
+static void
+solr_connection_update_response(const struct http_response *response,
+ struct solr_connection_post *post)
+{
+ if (response->status / 100 != 2) {
+ i_error("fts_solr: Indexing failed: %s",
+ http_response_get_message(response));
+ post->request_status = -1;
+ }
+}
+
+static struct http_client_request *
+solr_connection_post_request(struct solr_connection_post *post)
+{
+ struct solr_connection *conn = post->conn;
+ struct http_client_request *http_req;
+ const char *url;
+
+ url = t_strconcat(conn->http_base_url, "update", NULL);
+
+ http_req = http_client_request(solr_http_client, "POST",
+ conn->http_host, url,
+ solr_connection_update_response, post);
+ if (conn->http_user != NULL) {
+ http_client_request_set_auth_simple(
+ http_req, conn->http_user, conn->http_password);
+ }
+ http_client_request_set_port(http_req, conn->http_port);
+ http_client_request_set_ssl(http_req, conn->http_ssl);
+ http_client_request_add_header(http_req, "Content-Type", "text/xml");
+ return http_req;
+}
+
+struct solr_connection_post *
+solr_connection_post_begin(struct solr_connection *conn)
+{
+ struct solr_connection_post *post;
+
+ i_assert(!conn->posting);
+ conn->posting = TRUE;
+
+ post = i_new(struct solr_connection_post, 1);
+ post->conn = conn;
+ post->http_req = solr_connection_post_request(post);
+ return post;
+}
+
+void solr_connection_post_more(struct solr_connection_post *post,
+ const unsigned char *data, size_t size)
+{
+ i_assert(post->conn->posting);
+
+ if (post->failed)
+ return;
+
+ if (post->request_status == 0) {
+ (void)http_client_request_send_payload(
+ &post->http_req, data, size);
+ }
+ if (post->request_status < 0)
+ post->failed = TRUE;
+}
+
+int solr_connection_post_end(struct solr_connection_post **_post)
+{
+ struct solr_connection_post *post = *_post;
+ struct solr_connection *conn = post->conn;
+ int ret = post->failed ? -1 : 0;
+
+ i_assert(conn->posting);
+
+ *_post = NULL;
+
+ if (!post->failed) {
+ if (http_client_request_finish_payload(&post->http_req) < 0 ||
+ post->request_status < 0) {
+ ret = -1;
+ }
+ } else {
+ http_client_request_abort(&post->http_req);
+ }
+ i_free(post);
+
+ conn->posting = FALSE;
+ return ret;
+}
+
+int solr_connection_post(struct solr_connection *conn, const char *cmd)
+{
+ struct istream *post_payload;
+ struct solr_connection_post post;
+
+ i_assert(!conn->posting);
+
+ i_zero(&post);
+ post.conn = conn;
+
+ post.http_req = solr_connection_post_request(&post);
+ post_payload = i_stream_create_from_data(cmd, strlen(cmd));
+ http_client_request_set_payload(post.http_req, post_payload, TRUE);
+ i_stream_unref(&post_payload);
+ http_client_request_submit(post.http_req);
+
+ post.request_status = 0;
+ http_client_wait(solr_http_client);
+
+ return post.request_status;
+}
diff --git a/src/plugins/fts-solr/solr-connection.h b/src/plugins/fts-solr/solr-connection.h
new file mode 100644
index 0000000..ebad8be
--- /dev/null
+++ b/src/plugins/fts-solr/solr-connection.h
@@ -0,0 +1,26 @@
+#ifndef SOLR_CONNECTION_H
+#define SOLR_CONNECTION_H
+
+#include "solr-response.h"
+
+struct solr_connection;
+struct fts_solr_settings;
+
+int solr_connection_init(const struct fts_solr_settings *solr_set,
+ const struct ssl_iostream_settings *ssl_client_set,
+ struct event *event_parent,
+ struct solr_connection **conn_r,
+ const char **error_r);
+void solr_connection_deinit(struct solr_connection **conn);
+
+int solr_connection_select(struct solr_connection *conn, const char *query,
+ pool_t pool, struct solr_result ***box_results_r);
+int solr_connection_post(struct solr_connection *conn, const char *cmd);
+
+struct solr_connection_post *
+solr_connection_post_begin(struct solr_connection *conn);
+void solr_connection_post_more(struct solr_connection_post *post,
+ const unsigned char *data, size_t size);
+int solr_connection_post_end(struct solr_connection_post **post);
+
+#endif
diff --git a/src/plugins/fts-solr/solr-response.c b/src/plugins/fts-solr/solr-response.c
new file mode 100644
index 0000000..65a6a1f
--- /dev/null
+++ b/src/plugins/fts-solr/solr-response.c
@@ -0,0 +1,372 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "str.h"
+#include "istream.h"
+#include "solr-response.h"
+
+#include <expat.h>
+
+#define MAX_VALUE_LEN 2048
+
+enum solr_xml_response_state {
+ SOLR_XML_RESPONSE_STATE_ROOT,
+ SOLR_XML_RESPONSE_STATE_RESPONSE,
+ SOLR_XML_RESPONSE_STATE_RESULT,
+ SOLR_XML_RESPONSE_STATE_DOC,
+ SOLR_XML_RESPONSE_STATE_CONTENT
+};
+
+enum solr_xml_content_state {
+ SOLR_XML_CONTENT_STATE_NONE = 0,
+ SOLR_XML_CONTENT_STATE_UID,
+ SOLR_XML_CONTENT_STATE_SCORE,
+ SOLR_XML_CONTENT_STATE_MAILBOX,
+ SOLR_XML_CONTENT_STATE_NAMESPACE,
+ SOLR_XML_CONTENT_STATE_UIDVALIDITY,
+ SOLR_XML_CONTENT_STATE_ERROR
+};
+
+struct solr_response_parser {
+ XML_Parser xml_parser;
+ struct istream *input;
+
+ enum solr_xml_response_state state;
+ enum solr_xml_content_state content_state;
+ int depth;
+ string_t *buffer;
+
+ uint32_t uid, uidvalidity;
+ float score;
+ char *mailbox, *ns;
+
+ pool_t result_pool;
+ /* box_id -> solr_result */
+ HASH_TABLE(char *, struct solr_result *) mailboxes;
+ ARRAY(struct solr_result *) results;
+
+ bool xml_failed:1;
+};
+
+static int
+solr_xml_parse(struct solr_response_parser *parser,
+ const void *data, size_t size, bool done)
+{
+ enum XML_Error err;
+ int line, col;
+
+ if (parser->xml_failed)
+ return -1;
+
+ if (XML_Parse(parser->xml_parser, data, size, done ? 1 : 0) != 0)
+ return 0;
+
+ err = XML_GetErrorCode(parser->xml_parser);
+ if (err != XML_ERROR_FINISHED) {
+ line = XML_GetCurrentLineNumber(parser->xml_parser);
+ col = XML_GetCurrentColumnNumber(parser->xml_parser);
+ i_error("fts_solr: Invalid XML input at %d:%d: %s "
+ "(near: %.*s)", line, col, XML_ErrorString(err),
+ (int)I_MIN(size, 128), (const char *)data);
+ parser->xml_failed = TRUE;
+ return -1;
+ }
+ return 0;
+}
+
+static const char *attrs_get_name(const char **attrs)
+{
+ for (; *attrs != NULL; attrs += 2) {
+ if (strcmp(attrs[0], "name") == 0)
+ return attrs[1];
+ }
+ return "";
+}
+
+static void
+solr_lookup_xml_start(void *context, const char *name, const char **attrs)
+{
+ struct solr_response_parser *parser = context;
+ const char *name_attr;
+
+ i_assert(parser->depth >= (int)parser->state);
+
+ parser->depth++;
+ if (parser->depth - 1 > (int)parser->state) {
+ /* skipping over unwanted elements */
+ return;
+ }
+
+ str_truncate(parser->buffer, 0);
+
+ /* response -> result -> doc */
+ switch (parser->state) {
+ case SOLR_XML_RESPONSE_STATE_ROOT:
+ if (strcmp(name, "response") == 0)
+ parser->state++;
+ break;
+ case SOLR_XML_RESPONSE_STATE_RESPONSE:
+ if (strcmp(name, "result") == 0)
+ parser->state++;
+ break;
+ case SOLR_XML_RESPONSE_STATE_RESULT:
+ if (strcmp(name, "doc") == 0) {
+ parser->state++;
+ parser->uid = 0;
+ parser->score = 0;
+ i_free_and_null(parser->mailbox);
+ i_free_and_null(parser->ns);
+ parser->uidvalidity = 0;
+ }
+ break;
+ case SOLR_XML_RESPONSE_STATE_DOC:
+ name_attr = attrs_get_name(attrs);
+ if (strcmp(name_attr, "uid") == 0)
+ parser->content_state = SOLR_XML_CONTENT_STATE_UID;
+ else if (strcmp(name_attr, "score") == 0)
+ parser->content_state = SOLR_XML_CONTENT_STATE_SCORE;
+ else if (strcmp(name_attr, "box") == 0)
+ parser->content_state = SOLR_XML_CONTENT_STATE_MAILBOX;
+ else if (strcmp(name_attr, "ns") == 0)
+ parser->content_state = SOLR_XML_CONTENT_STATE_NAMESPACE;
+ else if (strcmp(name_attr, "uidv") == 0)
+ parser->content_state = SOLR_XML_CONTENT_STATE_UIDVALIDITY;
+ else
+ break;
+ parser->state++;
+ break;
+ case SOLR_XML_RESPONSE_STATE_CONTENT:
+ break;
+ }
+}
+
+static struct solr_result *
+solr_result_get(struct solr_response_parser *parser, const char *box_id)
+{
+ struct solr_result *result;
+ char *box_id_dup;
+
+ result = hash_table_lookup(parser->mailboxes, box_id);
+ if (result != NULL)
+ return result;
+
+ box_id_dup = p_strdup(parser->result_pool, box_id);
+ result = p_new(parser->result_pool, struct solr_result, 1);
+ result->box_id = box_id_dup;
+ p_array_init(&result->uids, parser->result_pool, 32);
+ p_array_init(&result->scores, parser->result_pool, 32);
+ hash_table_insert(parser->mailboxes, box_id_dup, result);
+ array_push_back(&parser->results, &result);
+ return result;
+}
+
+static int solr_lookup_add_doc(struct solr_response_parser *parser)
+{
+ struct fts_score_map *score;
+ struct solr_result *result;
+ const char *box_id;
+
+ if (parser->uid == 0) {
+ i_error("fts_solr: uid missing from inside doc");
+ return -1;
+ }
+
+ if (parser->mailbox == NULL) {
+ /* looking up from a single mailbox only */
+ box_id = "";
+ } else if (parser->uidvalidity != 0) {
+ /* old style lookup */
+ string_t *str = t_str_new(64);
+ str_printfa(str, "%u\001", parser->uidvalidity);
+ str_append(str, parser->mailbox);
+ if (parser->ns != NULL)
+ str_printfa(str, "\001%s", parser->ns);
+ box_id = str_c(str);
+ } else {
+ /* new style lookup */
+ box_id = parser->mailbox;
+ }
+ result = solr_result_get(parser, box_id);
+
+ if (seq_range_array_add(&result->uids, parser->uid)) {
+ /* duplicate result */
+ } else if (parser->score != 0) {
+ score = array_append_space(&result->scores);
+ score->uid = parser->uid;
+ score->score = parser->score;
+ }
+ return 0;
+}
+
+static void solr_lookup_xml_end(void *context, const char *name ATTR_UNUSED)
+{
+ struct solr_response_parser *parser = context;
+ string_t *buf = parser->buffer;
+ int ret;
+
+ switch (parser->content_state) {
+ case SOLR_XML_CONTENT_STATE_NONE:
+ break;
+ case SOLR_XML_CONTENT_STATE_UID:
+ if (str_to_uint32(str_c(buf), &parser->uid) < 0 ||
+ parser->uid == 0) {
+ i_error("fts_solr: received invalid uid '%s'",
+ str_c(buf));
+ parser->content_state = SOLR_XML_CONTENT_STATE_ERROR;
+ }
+ break;
+ case SOLR_XML_CONTENT_STATE_SCORE:
+ parser->score = strtod(str_c(buf), NULL);
+ break;
+ case SOLR_XML_CONTENT_STATE_MAILBOX:
+ parser->mailbox = i_strdup(str_c(buf));
+ break;
+ case SOLR_XML_CONTENT_STATE_NAMESPACE:
+ parser->ns = i_strdup(str_c(buf));
+ break;
+ case SOLR_XML_CONTENT_STATE_UIDVALIDITY:
+ if (str_to_uint32(str_c(buf), &parser->uidvalidity) < 0)
+ i_error("fts_solr: received invalid uidvalidity");
+ break;
+ case SOLR_XML_CONTENT_STATE_ERROR:
+ return;
+ }
+
+ i_assert(parser->depth >= (int)parser->state);
+
+ if (parser->state == SOLR_XML_RESPONSE_STATE_CONTENT &&
+ parser->content_state == SOLR_XML_CONTENT_STATE_MAILBOX &&
+ parser->mailbox == NULL) {
+ /* mailbox is namespace prefix */
+ parser->mailbox = i_strdup("");
+ }
+
+ if (parser->depth == (int)parser->state) {
+ ret = 0;
+ if (parser->state == SOLR_XML_RESPONSE_STATE_DOC) {
+ T_BEGIN {
+ ret = solr_lookup_add_doc(parser);
+ } T_END;
+ }
+ parser->state--;
+ if (ret < 0)
+ parser->content_state = SOLR_XML_CONTENT_STATE_ERROR;
+ else
+ parser->content_state = SOLR_XML_CONTENT_STATE_NONE;
+ }
+ parser->depth--;
+}
+
+static void solr_lookup_xml_data(void *context, const char *str, int len)
+{
+ struct solr_response_parser *parser = context;
+
+ switch (parser->content_state) {
+ case SOLR_XML_CONTENT_STATE_NONE:
+ case SOLR_XML_CONTENT_STATE_ERROR:
+ /* ignore element data */
+ return;
+ case SOLR_XML_CONTENT_STATE_UID:
+ case SOLR_XML_CONTENT_STATE_SCORE:
+ case SOLR_XML_CONTENT_STATE_MAILBOX:
+ case SOLR_XML_CONTENT_STATE_NAMESPACE:
+ case SOLR_XML_CONTENT_STATE_UIDVALIDITY:
+ break;
+ }
+
+ if (str_len(parser->buffer) + len > MAX_VALUE_LEN) {
+ i_error("fts_solr: XML element data length out of range");
+ parser->content_state = SOLR_XML_CONTENT_STATE_ERROR;
+ return;
+ }
+
+ str_append_data(parser->buffer, str, len);
+}
+
+struct solr_response_parser *
+solr_response_parser_init(pool_t result_pool, struct istream *input)
+{
+ struct solr_response_parser *parser;
+
+ parser = i_new(struct solr_response_parser, 1);
+
+ parser->xml_parser = XML_ParserCreate("UTF-8");
+ if (parser->xml_parser == NULL) {
+ i_fatal_status(FATAL_OUTOFMEM,
+ "fts_solr: Failed to allocate XML parser");
+ }
+
+ parser->buffer = str_new(default_pool, 256);
+ hash_table_create(&parser->mailboxes, default_pool, 0,
+ str_hash, strcmp);
+
+ parser->result_pool = result_pool;
+ pool_ref(result_pool);
+ p_array_init(&parser->results, result_pool, 32);
+
+ parser->input = input;
+ i_stream_ref(input);
+
+ parser->xml_failed = FALSE;
+ XML_SetElementHandler(parser->xml_parser,
+ solr_lookup_xml_start, solr_lookup_xml_end);
+ XML_SetCharacterDataHandler(parser->xml_parser, solr_lookup_xml_data);
+ XML_SetUserData(parser->xml_parser, parser);
+
+ return parser;
+}
+
+void solr_response_parser_deinit(struct solr_response_parser **_parser)
+{
+ struct solr_response_parser *parser = *_parser;
+
+ *_parser = NULL;
+
+ if (parser == NULL)
+ return;
+
+ str_free(&parser->buffer);
+ hash_table_destroy(&parser->mailboxes);
+ XML_ParserFree(parser->xml_parser);
+ i_stream_unref(&parser->input);
+ pool_unref(&parser->result_pool);
+ i_free(parser);
+}
+
+int solr_response_parse(struct solr_response_parser *parser,
+ struct solr_result ***box_results_r)
+{
+ const unsigned char *data;
+ size_t size;
+ int stream_errno, ret;
+
+ i_assert(parser->input != NULL);
+ i_zero(box_results_r);
+
+ /* read payload */
+ while ((ret = i_stream_read_more(parser->input, &data, &size)) > 0) {
+ (void)solr_xml_parse(parser, data, size, FALSE);
+ i_stream_skip(parser->input, size);
+ }
+
+ if (ret == 0) {
+ /* we will be called again for more data */
+ return 0;
+ }
+
+ stream_errno = parser->input->stream_errno;
+ i_stream_unref(&parser->input);
+
+ if (parser->content_state == SOLR_XML_CONTENT_STATE_ERROR)
+ return -1;
+ if (stream_errno != 0)
+ return -1;
+
+ ret = solr_xml_parse(parser, "", 0, TRUE);
+
+ array_append_zero(&parser->results);
+ *box_results_r = array_front_modifiable(&parser->results);
+ return (ret == 0 ? 1 : -1);
+}
diff --git a/src/plugins/fts-solr/solr-response.h b/src/plugins/fts-solr/solr-response.h
new file mode 100644
index 0000000..1d5cdd5
--- /dev/null
+++ b/src/plugins/fts-solr/solr-response.h
@@ -0,0 +1,23 @@
+#ifndef SOLR_RESPONSE_H
+#define SOLR_RESPONSE_H
+
+#include "seq-range-array.h"
+#include "fts-api.h"
+
+struct solr_response_parser;
+
+struct solr_result {
+ const char *box_id;
+
+ ARRAY_TYPE(seq_range) uids;
+ ARRAY_TYPE(fts_score_map) scores;
+};
+
+struct solr_response_parser *
+solr_response_parser_init(pool_t result_pool, struct istream *input);
+void solr_response_parser_deinit(struct solr_response_parser **_parser);
+
+int solr_response_parse(struct solr_response_parser *parser,
+ struct solr_result ***box_results_r);
+
+#endif
diff --git a/src/plugins/fts-solr/test-solr-response.c b/src/plugins/fts-solr/test-solr-response.c
new file mode 100644
index 0000000..8add6db
--- /dev/null
+++ b/src/plugins/fts-solr/test-solr-response.c
@@ -0,0 +1,295 @@
+/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "istream.h"
+#include "solr-response.h"
+#include "test-common.h"
+
+#include <unistd.h>
+
+static bool debug = FALSE;
+
+struct solr_response_test_result {
+ const char *box_id;
+ struct fts_score_map *scores;
+};
+
+struct solr_response_test {
+ const char *input;
+
+ struct solr_response_test_result *results;
+};
+
+struct fts_score_map test_results1_scores[] = {
+ { .score = 0.042314477, .uid = 1 },
+ { .score = 0.06996078, .uid = 2, },
+ { .score = 0.020381179, .uid = 3 },
+ { .score = 0.020381179, .uid = 4 },
+ { .score = 5.510487E-4, .uid = 6 },
+ { .score = 0.0424253, .uid = 7 },
+ { .score = 0.04215967, .uid = 8 },
+ { .score = 0.02470572, .uid = 9 },
+ { .score = 0.05936369, .uid = 10 },
+ { .score = 0.048221838, .uid = 11 },
+ { .score = 7.793006E-4, .uid = 12 },
+ { .score = 2.7900032E-4, .uid = 13 },
+ { .score = 0.02088323, .uid = 14 },
+ { .score = 0.011646388, .uid = 15 },
+ { .score = 1.3776218E-4, .uid = 17 },
+ { .score = 2.386111E-4, .uid = 19 },
+ { .score = 2.7552436E-4, .uid = 20 },
+ { .score = 4.772222E-4, .uid = 23 },
+ { .score = 4.772222E-4, .uid = 24 },
+ { .score = 5.965277E-4, .uid = 25 },
+ { .score = 0.0471366, .uid = 26 },
+ { .score = 0.0471366, .uid = 50 },
+ { .score = 0.047274362, .uid = 51 },
+ { .score = 0.053303234, .uid = 56 },
+ { .score = 5.445528E-4, .uid = 62 },
+ { .score = 2.922377E-4, .uid = 66 },
+ { .score = 0.02623833, .uid = 68 },
+ { .score = 3.4440547E-4, .uid = 70 },
+ { .score = 2.922377E-4, .uid = 74 },
+ { .score = 2.7552436E-4, .uid = 76 },
+ { .score = 1.3776218E-4, .uid = 77 },
+ { .score = 0, .uid = 0 },
+};
+
+struct solr_response_test_result test_results1[] = {
+ {
+ .box_id = "",
+ .scores = test_results1_scores,
+ },
+ {
+ .box_id = NULL
+ }
+};
+
+static const struct solr_response_test tests[] = {
+ {
+ .input =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<response>\n"
+ "<lst name=\"responseHeader\"><int name=\"status\""
+ ">0</int><int name=\"QTime\">3</int><lst name=\"pa"
+ "rams\"><str name=\"wt\">xml</str><str name=\"fl\""
+ ">uid,score</str><str name=\"rows\">4023</str><str"
+ " name=\"sort\">uid asc</str><str name=\"q\">{!luc"
+ "ene q.op=AND}subject:pierreserveur OR from:pierre"
+ "serveur OR to:pierreserveur OR cc:pierreserveur O"
+ "R bcc:pierreserveur OR body:pierreserveur</str><s"
+ "tr name=\"fq\">+box:fa74101044cb607d5f0900001de14"
+ "712 +user:jpierreserveur</str></lst></lst><result"
+ " name=\"response\" numFound=\"31\" start=\"0\" ma"
+ "xScore=\"0.06996078\"><doc><float name=\"score\">"
+ "0.042314477</float><long name=\"uid\">1</long></d"
+ "oc><doc><float name=\"score\">0.06996078</float><"
+ "long name=\"uid\">2</long></doc><doc><float name="
+ "\"score\">0.020381179</float><long name=\"uid\">3"
+ "</long></doc><doc><float name=\"score\">0.0203811"
+ "79</float><long name=\"uid\">4</long></doc><doc><"
+ "float name=\"score\">5.510487E-4</float><long nam"
+ "e=\"uid\">6</long></doc><doc><float name=\"score\""
+ ">0.0424253</float><long name=\"uid\">7</long></do"
+ "c><doc><float name=\"score\">0.04215967</float><l"
+ "ong name=\"uid\">8</long></doc><doc><float name=\""
+ "score\">0.02470572</float><long name=\"uid\">9</l"
+ "ong></doc><doc><float name=\"score\">0.05936369</"
+ "float><long name=\"uid\">10</long></doc><doc><flo"
+ "at name=\"score\">0.048221838</float><long name=\""
+ "uid\">11</long></doc><doc><float name=\"score\">7"
+ ".793006E-4</float><long name=\"uid\">12</long></d"
+ "oc><doc><float name=\"score\">2.7900032E-4</float"
+ "><long name=\"uid\">13</long></doc><doc><float na"
+ "me=\"score\">0.02088323</float><long name=\"uid\""
+ ">14</long></doc><doc><float name=\"score\">0.0116"
+ "46388</float><long name=\"uid\">15</long></doc><d"
+ "oc><float name=\"score\">1.3776218E-4</float><lon"
+ "g name=\"uid\">17</long></doc><doc><float name=\""
+ "score\">2.386111E-4</float><long name=\"uid\">19<"
+ "/long></doc><doc><float name=\"score\">2.7552436E"
+ "-4</float><long name=\"uid\">20</long></doc><doc>"
+ "<float name=\"score\">4.772222E-4</float><long na"
+ "me=\"uid\">23</long></doc><doc><float name=\"scor"
+ "e\">4.772222E-4</float><long name=\"uid\">24</lon"
+ "g></doc><doc><float name=\"score\">5.965277E-4</f"
+ "loat><long name=\"uid\">25</long></doc><doc><floa"
+ "t name=\"score\">0.0471366</float><long name=\"ui"
+ "d\">26</long></doc><doc><float name=\"score\">0.0"
+ "471366</float><long name=\"uid\">50</long></doc><"
+ "doc><float name=\"score\">0.047274362</float><lon"
+ "g name=\"uid\">51</long></doc><doc><float name=\""
+ "score\">0.053303234</float><long name=\"uid\">56<"
+ "/long></doc><doc><float name=\"score\">5.445528E-"
+ "4</float><long name=\"uid\">62</long></doc><doc><"
+ "float name=\"score\">2.922377E-4</float><long nam"
+ "e=\"uid\">66</long></doc><doc><float name=\"score"
+ "\">0.02623833</float><long name=\"uid\">68</long>"
+ "</doc><doc><float name=\"score\">3.4440547E-4</fl"
+ "oat><long name=\"uid\">70</long></doc><doc><float"
+ " name=\"score\">2.922377E-4</float><long name=\"u"
+ "id\">74</long></doc><doc><float name=\"score\">2."
+ "7552436E-4</float><long name=\"uid\">76</long></d"
+ "oc><doc><float name=\"score\">1.3776218E-4</float"
+ "><long name=\"uid\">77</long></doc></result>\n"
+ "</response>\n",
+ .results = test_results1,
+ },
+};
+
+static const unsigned tests_count = N_ELEMENTS(tests);
+
+static void
+test_solr_result(const struct solr_response_test_result *test_results,
+ struct solr_result **parse_results)
+{
+ unsigned int rcount, i;
+
+ for (i = 0; test_results[i].box_id != NULL; i++);
+ rcount = i;
+
+ for (i = 0; parse_results[i] != NULL; i++);
+
+ test_out_quiet("result count equal", i == rcount);
+ if (test_has_failed())
+ return;
+
+ for (i = 0; i < rcount && parse_results[i] != NULL; i++) {
+ unsigned int scount, j;
+ const struct fts_score_map *tscores = test_results[i].scores;
+ const struct fts_score_map *pscores =
+ array_get(&parse_results[i]->scores, &scount);
+
+ test_out_quiet(t_strdup_printf("box id equal[%u]", i),
+ strcmp(test_results[i].box_id,
+ parse_results[i]->box_id) == 0);
+
+ for (j = 0; tscores[j].uid != 0; j++);
+ test_out_quiet(t_strdup_printf("scores count equal[%u]", i),
+ j == scount);
+ if (j != scount)
+ continue;
+
+ for (j = 0; j < scount; j++) {
+ test_out_quiet(
+ t_strdup_printf("score uid equal[%u/%u]", i, j),
+ pscores[j].uid == tscores[j].uid);
+ test_out_quiet(
+ t_strdup_printf("score value equal[%u/%u]", i, j),
+ pscores[j].score == tscores[j].score);
+ }
+ }
+}
+
+static void test_solr_response_parser(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < tests_count; i++) T_BEGIN {
+ const struct solr_response_test *test;
+ const char *text;
+ unsigned int pos, text_len;
+ struct istream *input;
+ struct solr_response_parser *parser;
+ struct solr_result **box_results;
+ const char *error = NULL;
+ pool_t pool;
+ int ret = 0;
+
+ test = &tests[i];
+ text = test->input;
+ text_len = strlen(text);
+
+ test_begin(t_strdup_printf("solr response [%d]", i));
+
+ input = test_istream_create_data(text, text_len);
+ pool = pool_alloconly_create("solr response", 4096);
+ parser = solr_response_parser_init(pool, input);
+
+ ret = solr_response_parse(parser, &box_results);
+
+ test_out_reason("parse ok (buffer)", ret > 0, error);
+ if (ret > 0)
+ test_solr_result(test->results, box_results);
+
+ solr_response_parser_deinit(&parser);
+ pool_unref(&pool);
+ i_stream_unref(&input);
+
+ input = test_istream_create_data(text, text_len);
+ pool = pool_alloconly_create("solr response", 4096);
+ parser = solr_response_parser_init(pool, input);
+
+ ret = 0;
+ for (pos = 0; pos <= text_len && ret == 0; pos++) {
+ test_istream_set_size(input, pos);
+ ret = solr_response_parse(parser, &box_results);
+ }
+
+ test_out_reason("parse ok (trickle)", ret > 0, error);
+ if (ret > 0)
+ test_solr_result(test->results, box_results);
+
+ solr_response_parser_deinit(&parser);
+ pool_unref(&pool);
+ i_stream_unref(&input);
+
+ test_end();
+
+ } T_END;
+}
+
+static void test_solr_response_file(const char *file)
+{
+ pool_t pool;
+ struct istream *input;
+ struct solr_response_parser *parser;
+ struct solr_result **box_results;
+ int ret = 0;
+
+ pool = pool_alloconly_create("solr response", 4096);
+ input = i_stream_create_file(file, 1024);
+ parser = solr_response_parser_init(pool, input);
+
+ while ((ret = solr_response_parse(parser, &box_results)) == 0);
+
+ if (ret < 0)
+ i_fatal("Failed to read response");
+
+ solr_response_parser_deinit(&parser);
+ i_stream_unref(&input);
+ pool_unref(&pool);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+
+ static void (*test_functions[])(void) = {
+ test_solr_response_parser,
+ NULL
+ };
+
+ while ((c = getopt(argc, argv, "D")) > 0) {
+ switch (c) {
+ case 'D':
+ debug = TRUE;
+ break;
+ default:
+ i_fatal("Usage: %s [-D]", argv[0]);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ test_solr_response_file(argv[0]);
+ return 0;
+ }
+
+ return test_run(test_functions);
+}
+
+
diff --git a/src/plugins/fts-squat/Makefile.am b/src/plugins/fts-squat/Makefile.am
new file mode 100644
index 0000000..6c8181c
--- /dev/null
+++ b/src/plugins/fts-squat/Makefile.am
@@ -0,0 +1,47 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/fts
+
+NOPLUGIN_LDFLAGS =
+lib21_fts_squat_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib21_fts_squat_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+lib21_fts_squat_plugin_la_LIBADD = \
+ ../fts/lib20_fts_plugin.la
+endif
+
+lib21_fts_squat_plugin_la_SOURCES = \
+ fts-squat-plugin.c \
+ fts-backend-squat.c \
+ squat-trie.c \
+ squat-uidlist.c
+
+noinst_HEADERS = \
+ fts-squat-plugin.h \
+ squat-trie.h \
+ squat-trie-private.h \
+ squat-uidlist.h
+
+noinst_PROGRAMS = squat-test
+
+squat_test_SOURCES = \
+ squat-test.c
+
+common_objects = \
+ squat-trie.lo \
+ squat-uidlist.lo
+
+squat_test_LDADD = \
+ $(common_objects) \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+squat_test_DEPENDENCIES = \
+ $(common_objects) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
diff --git a/src/plugins/fts-squat/Makefile.in b/src/plugins/fts-squat/Makefile.in
new file mode 100644
index 0000000..443a7d0
--- /dev/null
+++ b/src/plugins/fts-squat/Makefile.in
@@ -0,0 +1,883 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = squat-test$(EXEEXT)
+subdir = src/plugins/fts-squat
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+@DOVECOT_PLUGIN_DEPS_TRUE@lib21_fts_squat_plugin_la_DEPENDENCIES = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../fts/lib20_fts_plugin.la
+am_lib21_fts_squat_plugin_la_OBJECTS = fts-squat-plugin.lo \
+ fts-backend-squat.lo squat-trie.lo squat-uidlist.lo
+lib21_fts_squat_plugin_la_OBJECTS = \
+ $(am_lib21_fts_squat_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib21_fts_squat_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib21_fts_squat_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_squat_test_OBJECTS = squat-test.$(OBJEXT)
+squat_test_OBJECTS = $(am_squat_test_OBJECTS)
+am__DEPENDENCIES_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/fts-backend-squat.Plo \
+ ./$(DEPDIR)/fts-squat-plugin.Plo ./$(DEPDIR)/squat-test.Po \
+ ./$(DEPDIR)/squat-trie.Plo ./$(DEPDIR)/squat-uidlist.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib21_fts_squat_plugin_la_SOURCES) $(squat_test_SOURCES)
+DIST_SOURCES = $(lib21_fts_squat_plugin_la_SOURCES) \
+ $(squat_test_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/fts
+
+lib21_fts_squat_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib21_fts_squat_plugin.la
+
+@DOVECOT_PLUGIN_DEPS_TRUE@lib21_fts_squat_plugin_la_LIBADD = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../fts/lib20_fts_plugin.la
+
+lib21_fts_squat_plugin_la_SOURCES = \
+ fts-squat-plugin.c \
+ fts-backend-squat.c \
+ squat-trie.c \
+ squat-uidlist.c
+
+noinst_HEADERS = \
+ fts-squat-plugin.h \
+ squat-trie.h \
+ squat-trie-private.h \
+ squat-uidlist.h
+
+squat_test_SOURCES = \
+ squat-test.c
+
+common_objects = \
+ squat-trie.lo \
+ squat-uidlist.lo
+
+squat_test_LDADD = \
+ $(common_objects) \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+
+squat_test_DEPENDENCIES = \
+ $(common_objects) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fts-squat/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/fts-squat/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib21_fts_squat_plugin.la: $(lib21_fts_squat_plugin_la_OBJECTS) $(lib21_fts_squat_plugin_la_DEPENDENCIES) $(EXTRA_lib21_fts_squat_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib21_fts_squat_plugin_la_LINK) -rpath $(moduledir) $(lib21_fts_squat_plugin_la_OBJECTS) $(lib21_fts_squat_plugin_la_LIBADD) $(LIBS)
+
+squat-test$(EXEEXT): $(squat_test_OBJECTS) $(squat_test_DEPENDENCIES) $(EXTRA_squat_test_DEPENDENCIES)
+ @rm -f squat-test$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(squat_test_OBJECTS) $(squat_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-backend-squat.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-squat-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/squat-test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/squat-trie.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/squat-uidlist.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/fts-backend-squat.Plo
+ -rm -f ./$(DEPDIR)/fts-squat-plugin.Plo
+ -rm -f ./$(DEPDIR)/squat-test.Po
+ -rm -f ./$(DEPDIR)/squat-trie.Plo
+ -rm -f ./$(DEPDIR)/squat-uidlist.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/fts-backend-squat.Plo
+ -rm -f ./$(DEPDIR)/fts-squat-plugin.Plo
+ -rm -f ./$(DEPDIR)/squat-test.Po
+ -rm -f ./$(DEPDIR)/squat-trie.Plo
+ -rm -f ./$(DEPDIR)/squat-uidlist.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/fts-squat/fts-backend-squat.c b/src/plugins/fts-squat/fts-backend-squat.c
new file mode 100644
index 0000000..fbd7bbe
--- /dev/null
+++ b/src/plugins/fts-squat/fts-backend-squat.c
@@ -0,0 +1,497 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "unichar.h"
+#include "mail-user.h"
+#include "mail-namespace.h"
+#include "mail-storage-private.h"
+#include "mail-search-build.h"
+#include "squat-trie.h"
+#include "fts-squat-plugin.h"
+
+
+#define SQUAT_FILE_PREFIX "dovecot.index.search"
+
+struct squat_fts_backend {
+ struct fts_backend backend;
+
+ struct mailbox *box;
+ struct squat_trie *trie;
+
+ unsigned int partial_len, full_len;
+ bool refresh;
+};
+
+struct squat_fts_backend_update_context {
+ struct fts_backend_update_context ctx;
+ struct squat_trie_build_context *build_ctx;
+
+ enum squat_index_type squat_type;
+ uint32_t uid;
+ string_t *hdr;
+
+ bool failed;
+};
+
+static struct fts_backend *fts_backend_squat_alloc(void)
+{
+ struct squat_fts_backend *backend;
+
+ backend = i_new(struct squat_fts_backend, 1);
+ backend->backend = fts_backend_squat;
+ return &backend->backend;
+}
+
+static int
+fts_backend_squat_init(struct fts_backend *_backend, const char **error_r)
+{
+ struct squat_fts_backend *backend =
+ (struct squat_fts_backend *)_backend;
+ const char *const *tmp, *env;
+ unsigned int len;
+
+ env = mail_user_plugin_getenv(_backend->ns->user, "fts_squat");
+ if (env == NULL)
+ return 0;
+
+ for (tmp = t_strsplit_spaces(env, " "); *tmp != NULL; tmp++) {
+ if (str_begins(*tmp, "partial=")) {
+ if (str_to_uint(*tmp + 8, &len) < 0 || len == 0) {
+ *error_r = t_strdup_printf(
+ "Invalid partial length: %s", *tmp + 8);
+ return -1;
+ }
+ backend->partial_len = len;
+ } else if (str_begins(*tmp, "full=")) {
+ if (str_to_uint(*tmp + 5, &len) < 0 || len == 0) {
+ *error_r = t_strdup_printf(
+ "Invalid full length: %s", *tmp + 5);
+ return -1;
+ }
+ backend->full_len = len;
+ } else {
+ *error_r = t_strdup_printf("Invalid setting: %s", *tmp);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void
+fts_backend_squat_unset_box(struct squat_fts_backend *backend)
+{
+ if (backend->trie != NULL)
+ squat_trie_deinit(&backend->trie);
+ backend->box = NULL;
+}
+
+static void fts_backend_squat_deinit(struct fts_backend *_backend)
+{
+ struct squat_fts_backend *backend =
+ (struct squat_fts_backend *)_backend;
+
+ fts_backend_squat_unset_box(backend);
+ i_free(backend);
+}
+
+static int
+fts_backend_squat_set_box(struct squat_fts_backend *backend,
+ struct mailbox *box)
+{
+ const struct mailbox_permissions *perm;
+ struct mail_storage *storage;
+ struct mailbox_status status;
+ const char *path;
+ enum squat_index_flags flags = 0;
+ int ret;
+
+ if (backend->box == box)
+ {
+ if (backend->refresh) {
+ ret = squat_trie_refresh(backend->trie);
+ if (ret < 0)
+ return ret;
+ backend->refresh = FALSE;
+ }
+ return 0;
+ }
+ fts_backend_squat_unset_box(backend);
+ backend->refresh = FALSE;
+ if (box == NULL)
+ return 0;
+
+ perm = mailbox_get_permissions(box);
+ storage = mailbox_get_storage(box);
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX, &path) <= 0)
+ i_unreached(); /* fts already checked this */
+
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY, &status);
+ if (storage->set->mmap_disable)
+ flags |= SQUAT_INDEX_FLAG_MMAP_DISABLE;
+ if (storage->set->mail_nfs_index)
+ flags |= SQUAT_INDEX_FLAG_NFS_FLUSH;
+ if (storage->set->dotlock_use_excl)
+ flags |= SQUAT_INDEX_FLAG_DOTLOCK_USE_EXCL;
+
+ backend->trie =
+ squat_trie_init(t_strconcat(path, "/"SQUAT_FILE_PREFIX, NULL),
+ status.uidvalidity,
+ storage->set->parsed_lock_method,
+ flags, perm->file_create_mode,
+ perm->file_create_gid);
+
+ if (backend->partial_len != 0)
+ squat_trie_set_partial_len(backend->trie, backend->partial_len);
+ if (backend->full_len != 0)
+ squat_trie_set_full_len(backend->trie, backend->full_len);
+ backend->box = box;
+ return squat_trie_open(backend->trie);
+}
+
+static int
+fts_backend_squat_get_last_uid(struct fts_backend *_backend,
+ struct mailbox *box, uint32_t *last_uid_r)
+{
+ struct squat_fts_backend *backend =
+ (struct squat_fts_backend *)_backend;
+
+ int ret = fts_backend_squat_set_box(backend, box);
+ if (ret < 0)
+ return -1;
+ return squat_trie_get_last_uid(backend->trie, last_uid_r);
+}
+
+static struct fts_backend_update_context *
+fts_backend_squat_update_init(struct fts_backend *_backend)
+{
+ struct squat_fts_backend_update_context *ctx;
+
+ ctx = i_new(struct squat_fts_backend_update_context, 1);
+ ctx->ctx.backend = _backend;
+ ctx->hdr = str_new(default_pool, 1024*32);
+ return &ctx->ctx;
+}
+
+static int get_all_msg_uids(struct mailbox *box, ARRAY_TYPE(seq_range) *uids)
+{
+ struct mailbox_transaction_context *t;
+ struct mail_search_context *search_ctx;
+ struct mail_search_args *search_args;
+ struct mail *mail;
+ int ret;
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+ search_ctx = mailbox_search_init(t, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ while (mailbox_search_next(search_ctx, &mail)) {
+ /* *2 because even/odd is for body/header */
+ seq_range_array_add_range(uids, mail->uid * 2,
+ mail->uid * 2 + 1);
+ }
+ ret = mailbox_search_deinit(&search_ctx);
+ (void)mailbox_transaction_commit(&t);
+ return ret;
+}
+
+static int
+fts_backend_squat_update_uid_changed(struct squat_fts_backend_update_context *ctx)
+{
+ int ret = 0;
+
+ if (ctx->uid == 0)
+ return 0;
+
+ if (squat_trie_build_more(ctx->build_ctx, ctx->uid,
+ SQUAT_INDEX_TYPE_HEADER,
+ str_data(ctx->hdr), str_len(ctx->hdr)) < 0)
+ ret = -1;
+ str_truncate(ctx->hdr, 0);
+ return ret;
+}
+
+static int
+fts_backend_squat_build_deinit(struct squat_fts_backend_update_context *ctx)
+{
+ struct squat_fts_backend *backend =
+ (struct squat_fts_backend *)ctx->ctx.backend;
+ ARRAY_TYPE(seq_range) uids;
+ int ret = 0;
+
+ if (ctx->build_ctx == NULL)
+ return 0;
+
+ if (fts_backend_squat_update_uid_changed(ctx) < 0)
+ ret = -1;
+
+ i_array_init(&uids, 1024);
+ if (get_all_msg_uids(backend->box, &uids) < 0) {
+ (void)squat_trie_build_deinit(&ctx->build_ctx, NULL);
+ ret = -1;
+ } else {
+ seq_range_array_invert(&uids, 2, (uint32_t)-2);
+ if (squat_trie_build_deinit(&ctx->build_ctx, &uids) < 0)
+ ret = -1;
+ }
+ array_free(&uids);
+ return ret;
+}
+
+static int
+fts_backend_squat_update_deinit(struct fts_backend_update_context *_ctx)
+{
+ struct squat_fts_backend_update_context *ctx =
+ (struct squat_fts_backend_update_context *)_ctx;
+ int ret = ctx->failed ? -1 : 0;
+
+ if (fts_backend_squat_build_deinit(ctx) < 0)
+ ret = -1;
+ str_free(&ctx->hdr);
+ i_free(ctx);
+ return ret;
+}
+
+static void
+fts_backend_squat_update_set_mailbox(struct fts_backend_update_context *_ctx,
+ struct mailbox *box)
+{
+ struct squat_fts_backend_update_context *ctx =
+ (struct squat_fts_backend_update_context *)_ctx;
+ struct squat_fts_backend *backend =
+ (struct squat_fts_backend *)ctx->ctx.backend;
+
+ if (fts_backend_squat_build_deinit(ctx) < 0)
+ ctx->failed = TRUE;
+ if (fts_backend_squat_set_box(backend, box) < 0)
+ ctx->failed = TRUE;
+ else if (box != NULL) {
+ if (squat_trie_build_init(backend->trie, &ctx->build_ctx) < 0)
+ ctx->failed = TRUE;
+ }
+}
+
+static void
+fts_backend_squat_update_expunge(struct fts_backend_update_context *_ctx ATTR_UNUSED,
+ uint32_t last_uid ATTR_UNUSED)
+{
+ /* FIXME */
+}
+
+static bool
+fts_backend_squat_update_set_build_key(struct fts_backend_update_context *_ctx,
+ const struct fts_backend_build_key *key)
+{
+ struct squat_fts_backend_update_context *ctx =
+ (struct squat_fts_backend_update_context *)_ctx;
+
+ if (ctx->failed)
+ return FALSE;
+
+ if (key->uid != ctx->uid) {
+ if (fts_backend_squat_update_uid_changed(ctx) < 0)
+ ctx->failed = TRUE;
+ }
+
+ switch (key->type) {
+ case FTS_BACKEND_BUILD_KEY_HDR:
+ case FTS_BACKEND_BUILD_KEY_MIME_HDR:
+ str_printfa(ctx->hdr, "%s: ", key->hdr_name);
+ ctx->squat_type = SQUAT_INDEX_TYPE_HEADER;
+ break;
+ case FTS_BACKEND_BUILD_KEY_BODY_PART:
+ ctx->squat_type = SQUAT_INDEX_TYPE_BODY;
+ break;
+ case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY:
+ i_unreached();
+ }
+ ctx->uid = key->uid;
+ return TRUE;
+}
+
+static void
+fts_backend_squat_update_unset_build_key(struct fts_backend_update_context *_ctx)
+{
+ struct squat_fts_backend_update_context *ctx =
+ (struct squat_fts_backend_update_context *)_ctx;
+
+ if (ctx->squat_type == SQUAT_INDEX_TYPE_HEADER)
+ str_append_c(ctx->hdr, '\n');
+}
+
+static int
+fts_backend_squat_update_build_more(struct fts_backend_update_context *_ctx,
+ const unsigned char *data, size_t size)
+{
+ struct squat_fts_backend_update_context *ctx =
+ (struct squat_fts_backend_update_context *)_ctx;
+
+ if (ctx->squat_type == SQUAT_INDEX_TYPE_HEADER) {
+ str_append_data(ctx->hdr, data, size);
+ return 0;
+ }
+ return squat_trie_build_more(ctx->build_ctx, ctx->uid, ctx->squat_type,
+ data, size);
+}
+
+static int fts_backend_squat_refresh(struct fts_backend *_backend)
+{
+ struct squat_fts_backend *backend =
+ (struct squat_fts_backend *)_backend;
+
+ backend->refresh = TRUE;
+ return 0;
+}
+
+static int fts_backend_squat_optimize(struct fts_backend *_backend ATTR_UNUSED)
+{
+ /* FIXME: drop expunged messages */
+ return 0;
+}
+
+static int squat_lookup_arg(struct squat_fts_backend *backend,
+ const struct mail_search_arg *arg, bool and_args,
+ ARRAY_TYPE(seq_range) *definite_uids,
+ ARRAY_TYPE(seq_range) *maybe_uids)
+{
+ enum squat_index_type squat_type;
+ ARRAY_TYPE(seq_range) tmp_definite_uids, tmp_maybe_uids;
+ string_t *dtc;
+ uint32_t last_uid;
+ int ret;
+
+ switch (arg->type) {
+ case SEARCH_TEXT:
+ squat_type = SQUAT_INDEX_TYPE_HEADER |
+ SQUAT_INDEX_TYPE_BODY;
+ break;
+ case SEARCH_BODY:
+ squat_type = SQUAT_INDEX_TYPE_BODY;
+ break;
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ squat_type = SQUAT_INDEX_TYPE_HEADER;
+ break;
+ default:
+ return 0;
+ }
+
+ i_array_init(&tmp_definite_uids, 128);
+ i_array_init(&tmp_maybe_uids, 128);
+
+ dtc = t_str_new(128);
+ if (backend->backend.ns->user->
+ default_normalizer(arg->value.str, strlen(arg->value.str), dtc) < 0)
+ i_panic("squat: search key not utf8");
+
+ ret = squat_trie_lookup(backend->trie, str_c(dtc), squat_type,
+ &tmp_definite_uids, &tmp_maybe_uids);
+ if (arg->match_not) {
+ /* definite -> non-match
+ maybe -> maybe
+ non-match -> maybe */
+ array_clear(&tmp_maybe_uids);
+
+ if (squat_trie_get_last_uid(backend->trie, &last_uid) < 0)
+ i_unreached();
+ seq_range_array_add_range(&tmp_maybe_uids, 1, last_uid);
+ seq_range_array_remove_seq_range(&tmp_maybe_uids,
+ &tmp_definite_uids);
+ array_clear(&tmp_definite_uids);
+ }
+
+ if (and_args) {
+ /* AND:
+ definite && definite -> definite
+ definite && maybe -> maybe
+ maybe && maybe -> maybe */
+
+ /* put definites among maybies, so they can be intersected */
+ seq_range_array_merge(maybe_uids, definite_uids);
+ seq_range_array_merge(&tmp_maybe_uids, &tmp_definite_uids);
+
+ seq_range_array_intersect(maybe_uids, &tmp_maybe_uids);
+ seq_range_array_intersect(definite_uids, &tmp_definite_uids);
+ /* remove duplicate maybies that are also definites */
+ seq_range_array_remove_seq_range(maybe_uids, definite_uids);
+ } else {
+ /* OR:
+ definite || definite -> definite
+ definite || maybe -> definite
+ maybe || maybe -> maybe */
+
+ /* remove maybies that are now definites */
+ seq_range_array_remove_seq_range(&tmp_maybe_uids,
+ definite_uids);
+ seq_range_array_remove_seq_range(maybe_uids,
+ &tmp_definite_uids);
+
+ seq_range_array_merge(definite_uids, &tmp_definite_uids);
+ seq_range_array_merge(maybe_uids, &tmp_maybe_uids);
+ }
+
+ array_free(&tmp_definite_uids);
+ array_free(&tmp_maybe_uids);
+ return ret < 0 ? -1 : 1;
+}
+
+static int
+fts_backend_squat_lookup(struct fts_backend *_backend, struct mailbox *box,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_result *result)
+{
+ struct squat_fts_backend *backend =
+ (struct squat_fts_backend *)_backend;
+ bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
+ bool first = TRUE;
+ int ret;
+
+ ret = fts_backend_squat_set_box(backend, box);
+ if (ret < 0)
+ return -1;
+
+ for (; args != NULL; args = args->next) {
+ ret = squat_lookup_arg(backend, args, first ? FALSE : and_args,
+ &result->definite_uids,
+ &result->maybe_uids);
+ if (ret < 0)
+ return -1;
+ if (ret > 0) {
+ args->match_always = TRUE;
+ first = FALSE;
+ }
+ }
+ return 0;
+}
+
+struct fts_backend fts_backend_squat = {
+ .name = "squat",
+ .flags = FTS_BACKEND_FLAG_NORMALIZE_INPUT,
+
+ {
+ fts_backend_squat_alloc,
+ fts_backend_squat_init,
+ fts_backend_squat_deinit,
+ fts_backend_squat_get_last_uid,
+ fts_backend_squat_update_init,
+ fts_backend_squat_update_deinit,
+ fts_backend_squat_update_set_mailbox,
+ fts_backend_squat_update_expunge,
+ fts_backend_squat_update_set_build_key,
+ fts_backend_squat_update_unset_build_key,
+ fts_backend_squat_update_build_more,
+ fts_backend_squat_refresh,
+ NULL,
+ fts_backend_squat_optimize,
+ fts_backend_default_can_lookup,
+ fts_backend_squat_lookup,
+ NULL,
+ NULL
+ }
+};
diff --git a/src/plugins/fts-squat/fts-squat-plugin.c b/src/plugins/fts-squat/fts-squat-plugin.c
new file mode 100644
index 0000000..59d9383
--- /dev/null
+++ b/src/plugins/fts-squat/fts-squat-plugin.c
@@ -0,0 +1,18 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "fts-squat-plugin.h"
+
+const char *fts_squat_plugin_version = DOVECOT_ABI_VERSION;
+
+void fts_squat_plugin_init(struct module *module ATTR_UNUSED)
+{
+ fts_backend_register(&fts_backend_squat);
+}
+
+void fts_squat_plugin_deinit(void)
+{
+ fts_backend_unregister(fts_backend_squat.name);
+}
+
+const char *fts_squat_plugin_dependencies[] = { "fts", NULL };
diff --git a/src/plugins/fts-squat/fts-squat-plugin.h b/src/plugins/fts-squat/fts-squat-plugin.h
new file mode 100644
index 0000000..0d6bfcb
--- /dev/null
+++ b/src/plugins/fts-squat/fts-squat-plugin.h
@@ -0,0 +1,14 @@
+#ifndef FTS_SQUAT_PLUGIN_H
+#define FTS_SQUAT_PLUGIN_H
+
+#include "fts-api-private.h"
+
+struct module;
+
+extern const char *fts_squat_plugin_dependencies[];
+extern struct fts_backend fts_backend_squat;
+
+void fts_squat_plugin_init(struct module *module);
+void fts_squat_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/fts-squat/squat-test.c b/src/plugins/fts-squat/squat-test.c
new file mode 100644
index 0000000..b55646c
--- /dev/null
+++ b/src/plugins/fts-squat/squat-test.c
@@ -0,0 +1,197 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "file-lock.h"
+#include "istream.h"
+#include "time-util.h"
+#include "unichar.h"
+#include "squat-trie.h"
+#include "squat-uidlist.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/time.h>
+
+static void result_print(ARRAY_TYPE(seq_range) *result)
+{
+ const struct seq_range *range;
+ unsigned int i, count;
+
+ range = array_get(result, &count);
+ for (i = 0; i < count; i++) {
+ if (i != 0)
+ printf(",");
+ printf("%u", range[i].seq1);
+ if (range[i].seq1 != range[i].seq2)
+ printf("-%u", range[i].seq2);
+ }
+ printf("\n");
+}
+
+int main(int argc ATTR_UNUSED, char *argv[])
+{
+ const char *trie_path = "/tmp/squat-test-index.search";
+ const char *uidlist_path = "/tmp/squat-test-index.search.uids";
+ struct squat_trie *trie;
+ struct squat_trie_build_context *build_ctx;
+ struct istream *input;
+ struct stat trie_st, uidlist_st;
+ ARRAY_TYPE(seq_range) definite_uids, maybe_uids;
+ char *line, *str, buf[4096];
+ buffer_t *valid;
+ int ret, fd;
+ unsigned int last = 0, seq = 1, node_count, uidlist_count;
+ size_t len;
+ enum squat_index_type index_type;
+ bool data_header = TRUE, first = TRUE, skip_body = FALSE;
+ bool mime_header = TRUE;
+ size_t trie_mem, uidlist_mem;
+ clock_t clock_start, clock_end;
+ struct timeval tv_start, tv_end;
+ double cputime;
+
+ lib_init();
+ i_unlink_if_exists(trie_path);
+ i_unlink_if_exists(uidlist_path);
+ trie = squat_trie_init(trie_path, time(NULL),
+ FILE_LOCK_METHOD_FCNTL, 0, 0600, (gid_t)-1);
+
+ clock_start = clock();
+ i_gettimeofday(&tv_start);
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd == -1)
+ return 1;
+
+ if (squat_trie_build_init(trie, &build_ctx) < 0)
+ return 1;
+
+ valid = buffer_create_dynamic(default_pool, 4096);
+ input = i_stream_create_fd(fd, SIZE_MAX);
+ ret = 0;
+ while (ret == 0 && (line = i_stream_read_next_line(input)) != NULL) {
+ if (last != input->v_offset/(1024*100)) {
+ fprintf(stderr, "\r%ukB", (unsigned)(input->v_offset/1024));
+ fflush(stderr);
+ last = input->v_offset/(1024*100);
+ }
+ if (str_begins(line, "From ")) {
+ if (!first)
+ seq++;
+ data_header = TRUE;
+ skip_body = FALSE;
+ mime_header = TRUE;
+ continue;
+ }
+ first = FALSE;
+
+ if (str_begins(line, "--")) {
+ skip_body = FALSE;
+ mime_header = TRUE;
+ }
+
+ if (mime_header) {
+ if (*line == '\0') {
+ data_header = FALSE;
+ mime_header = FALSE;
+ continue;
+ }
+
+ if (strncasecmp(line, "Content-Type:", 13) == 0 &&
+ strncasecmp(line, "Content-Type: text/", 19) != 0 &&
+ strncasecmp(line, "Content-Type: message/", 22) != 0)
+ skip_body = TRUE;
+ else if (strncasecmp(line, "Content-Transfer-Encoding: base64", 33) == 0)
+ skip_body = TRUE;
+ } else if (skip_body)
+ continue;
+ if (*line == '\0')
+ continue;
+
+ /* we're actually indexing here headers as bodies and bodies
+ as headers. it doesn't really matter in this test, and
+ fixing it would require storing headers temporarily
+ elsewhere and index them only after the body */
+ index_type = !data_header ? SQUAT_INDEX_TYPE_HEADER :
+ SQUAT_INDEX_TYPE_BODY;
+
+ buffer_set_used_size(valid, 0);
+ len = strlen(line);
+ if (uni_utf8_get_valid_data((const unsigned char *)line,
+ len, valid)) {
+ ret = squat_trie_build_more(build_ctx, seq, index_type,
+ (const void *)line, len);
+ } else if (valid->used > 0) {
+ ret = squat_trie_build_more(build_ctx, seq, index_type,
+ valid->data, valid->used);
+ }
+ }
+ buffer_free(&valid);
+ if (squat_trie_build_deinit(&build_ctx, NULL) < 0)
+ ret = -1;
+ if (ret < 0) {
+ printf("build broken\n");
+ return 1;
+ }
+
+ clock_end = clock();
+ i_gettimeofday(&tv_end);
+
+ cputime = (double)(clock_end - clock_start) / CLOCKS_PER_SEC;
+ fprintf(stderr, "\n - Index time: %.2f CPU seconds, "
+ "%.2f real seconds (%.02fMB/CPUs)\n", cputime,
+ timeval_diff_msecs(&tv_end, &tv_start)/1000.0,
+ input->v_offset / cputime / (1024*1024));
+
+ if (stat(trie_path, &trie_st) < 0)
+ i_error("stat(%s) failed: %m", trie_path);
+ if (stat(uidlist_path, &uidlist_st) < 0)
+ i_error("stat(%s) failed: %m", uidlist_path);
+
+ trie_mem = squat_trie_mem_used(trie, &node_count);
+ uidlist_mem = squat_uidlist_mem_used(squat_trie_get_uidlist(trie),
+ &uidlist_count);
+ fprintf(stderr, " - memory: %uk for trie, %uk for uidlist\n",
+ (unsigned)(trie_mem/1024), (unsigned)(uidlist_mem/1024));
+ fprintf(stderr, " - %"PRIuUOFF_T" bytes in %u nodes (%.02f%%)\n",
+ trie_st.st_size, node_count,
+ trie_st.st_size / (float)input->v_offset * 100.0);
+ fprintf(stderr, " - %"PRIuUOFF_T" bytes in %u UID lists (%.02f%%)\n",
+ uidlist_st.st_size, uidlist_count,
+ uidlist_st.st_size / (float)input->v_offset * 100.0);
+ fprintf(stderr, " - %"PRIuUOFF_T" bytes total of %"
+ PRIuUOFF_T" (%.02f%%)\n",
+ (trie_st.st_size + uidlist_st.st_size), input->v_offset,
+ (trie_st.st_size + uidlist_st.st_size) /
+ (float)input->v_offset * 100.0);
+
+ i_stream_unref(&input);
+ i_close_fd(&fd);
+
+ i_array_init(&definite_uids, 128);
+ i_array_init(&maybe_uids, 128);
+ while ((str = fgets(buf, sizeof(buf), stdin)) != NULL) {
+ ret = strlen(str)-1;
+ str[ret] = 0;
+
+ i_gettimeofday(&tv_start);
+ ret = squat_trie_lookup(trie, str, SQUAT_INDEX_TYPE_HEADER |
+ SQUAT_INDEX_TYPE_BODY,
+ &definite_uids, &maybe_uids);
+ if (ret < 0)
+ printf("error\n");
+ else {
+ i_gettimeofday(&tv_end);
+ printf(" - Search took %.05f CPU seconds\n",
+ timeval_diff_usecs(&tv_end, &tv_start)/1000000.0);
+ printf(" - definite uids: ");
+ result_print(&definite_uids);
+ printf(" - maybe uids: ");
+ result_print(&maybe_uids);
+ }
+ }
+ return 0;
+}
diff --git a/src/plugins/fts-squat/squat-trie-private.h b/src/plugins/fts-squat/squat-trie-private.h
new file mode 100644
index 0000000..b079554
--- /dev/null
+++ b/src/plugins/fts-squat/squat-trie-private.h
@@ -0,0 +1,192 @@
+#ifndef SQUAT_TRIE_PRIVATE_H
+#define SQUAT_TRIE_PRIVATE_H
+
+#include "file-dotlock.h"
+#include "squat-trie.h"
+
+#define SQUAT_TRIE_VERSION 2
+#define SQUAT_TRIE_LOCK_TIMEOUT 60
+#define SQUAT_TRIE_DOTLOCK_STALE_TIMEOUT (15*60)
+
+struct squat_file_header {
+ uint8_t version;
+ uint8_t unused[3];
+
+ uint32_t indexid;
+ uint32_t uidvalidity;
+ uint32_t used_file_size;
+ uint32_t deleted_space;
+ uint32_t node_count;
+
+ uint32_t root_offset;
+ uint32_t root_unused_uids;
+ uint32_t root_next_uid;
+ uint32_t root_uidlist_idx;
+
+ uint8_t partial_len;
+ uint8_t full_len;
+ uint8_t normalize_map[256];
+};
+
+/*
+ node file: FIXME: no up-to-date
+
+ struct squat_file_header;
+
+ // children are written before their parents
+ node[] {
+ uint8_t child_count;
+ unsigned char chars[child_count];
+ packed neg_diff_to_first_child_offset; // relative to node
+ packed diff_to_prev_offset[child_count-1];
+ packed[child_count] {
+ // unused_uids_count == uid if have_uid_offset bit is zero
+ (unused_uids_count << 1) | (have_uid_offset);
+ [diff_to_prev_uid_offset;] // first one is relative to zero
+ }
+ }
+*/
+
+struct squat_node {
+ unsigned int child_count:8;
+
+ /* children.leaf_string contains this many bytes */
+ unsigned int leaf_string_length:16;
+
+ /* TRUE = children.data contains our children.
+ FALSE = children.offset contains offset to our children in the
+ index file. */
+ bool children_not_mapped:1;
+ /* When allocating our children, use a sequential array. */
+ bool want_sequential:1;
+ /* This node's children are in a sequential array, meaning that the
+ first SEQUENTIAL_COUNT children have chars[n] = n. */
+ bool have_sequential:1;
+
+ /* Number of UIDs that exists in parent node but not in this one
+ (i.e. number of UIDs [0..next_uid-1] not in this node's uidlist).
+ This is mainly used when adding new UIDs to our children to set
+ the UID to be relative to this node's UID list. */
+ uint32_t unused_uids;
+
+ /* next_uid=0 means there are no UIDs in this node, otherwise
+ next_uid-1 is the last UID added to this node. */
+ uint32_t next_uid;
+ uint32_t uid_list_idx;
+
+ /*
+ struct {
+ unsigned char chars[child_count];
+ struct squat_node[child_count];
+ } *children;
+ */
+ union {
+ /* children_not_mapped determines if data or offset should
+ be used. */
+ void *data;
+ unsigned char *leaf_string;
+ unsigned char static_leaf_string[sizeof(void *)];
+ uint32_t offset;
+ } children;
+};
+/* Return pointer to node.children.chars[] */
+#define NODE_CHILDREN_CHARS(node) \
+ ((unsigned char *)(node)->children.data)
+/* Return pointer to node.children.node[] */
+#define NODE_CHILDREN_NODES(_node) \
+ ((struct squat_node *)(NODE_CHILDREN_CHARS(_node) + \
+ MEM_ALIGN((_node)->child_count)))
+/* Return number of bytes allocated in node.children.data */
+#define NODE_CHILDREN_ALLOC_SIZE(child_count) \
+ (MEM_ALIGN(child_count) + \
+ ((child_count) / 8 + 1) * 8 * sizeof(struct squat_node))
+/* Return TRUE if children.leaf_string is set. */
+#define NODE_IS_DYNAMIC_LEAF(node) \
+ ((node)->leaf_string_length > \
+ sizeof((node)->children.static_leaf_string))
+/* Return node's leaf string. Assumes that it is set. */
+#define NODE_LEAF_STRING(node) \
+ (NODE_IS_DYNAMIC_LEAF(node) ? \
+ (node)->children.leaf_string : (node)->children.static_leaf_string)
+struct squat_trie {
+ struct squat_node root;
+ struct squat_uidlist *uidlist;
+
+ struct squat_file_header hdr;
+ size_t node_alloc_size;
+ unsigned int unmapped_child_count;
+
+ enum squat_index_flags flags;
+ enum file_lock_method lock_method;
+ mode_t create_mode;
+ gid_t create_gid;
+ uint32_t uidvalidity;
+
+ char *path;
+ int fd;
+ struct file_cache *file_cache;
+ struct dotlock_settings dotlock_set;
+
+ uoff_t locked_file_size;
+ const void *data;
+ size_t data_size;
+
+ void *mmap_base;
+ size_t mmap_size;
+
+ unsigned char default_normalize_map[256];
+ unsigned int default_partial_len;
+ unsigned int default_full_len;
+
+ bool corrupted:1;
+};
+
+#define SQUAT_PACK_MAX_SIZE ((sizeof(uint32_t) * 8 + 7) / 7)
+
+static inline void squat_pack_num(uint8_t **p, uint32_t num)
+{
+ /* number continues as long as the highest bit is set */
+ while (num >= 0x80) {
+ **p = (num & 0x7f) | 0x80;
+ *p += 1;
+ num >>= 7;
+ }
+
+ **p = num;
+ *p += 1;
+}
+
+static inline uint32_t squat_unpack_num(const uint8_t **p, const uint8_t *end)
+{
+ const uint8_t *c = *p;
+ uint32_t value = 0;
+ unsigned int bits = 0;
+
+ for (;;) {
+ if (unlikely(c == end)) {
+ /* we should never see EOF */
+ return 0;
+ }
+
+ value |= (*c & 0x7f) << bits;
+ if (*c < 0x80)
+ break;
+
+ bits += 7;
+ c++;
+ }
+
+ if (unlikely(bits >= 32)) {
+ /* broken input */
+ *p = end;
+ return 0;
+ }
+
+ *p = c + 1;
+ return value;
+}
+
+int squat_trie_create_fd(struct squat_trie *trie, const char *path, int flags);
+void squat_trie_delete(struct squat_trie *trie);
+
+#endif
diff --git a/src/plugins/fts-squat/squat-trie.c b/src/plugins/fts-squat/squat-trie.c
new file mode 100644
index 0000000..d006817
--- /dev/null
+++ b/src/plugins/fts-squat/squat-trie.c
@@ -0,0 +1,2096 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "read-full.h"
+#include "istream.h"
+#include "ostream.h"
+#include "unichar.h"
+#include "nfs-workarounds.h"
+#include "file-cache.h"
+#include "seq-range-array.h"
+#include "squat-uidlist.h"
+#include "squat-trie-private.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#define DEFAULT_NORMALIZE_MAP_CHARS \
+ "EOTIRSACDNLMVUGPHBFWYXKJQZ0123456789@.-+#$%_&"
+#define DEFAULT_PARTIAL_LEN 4
+#define DEFAULT_FULL_LEN 4
+
+#define MAX_FAST_LEVEL 3
+#define SEQUENTIAL_COUNT 46
+
+#define TRIE_BYTES_LEFT(n) \
+ ((n) * SQUAT_PACK_MAX_SIZE)
+#define TRIE_READAHEAD_SIZE \
+ I_MAX(4096, 1 + 256 + TRIE_BYTES_LEFT(256))
+
+struct squat_trie_build_context {
+ struct squat_trie *trie;
+ struct ostream *output;
+ struct squat_uidlist_build_context *uidlist_build_ctx;
+
+ struct file_lock *file_lock;
+ struct dotlock *dotlock;
+
+ uint32_t first_uid;
+ bool compress_nodes:1;
+};
+
+struct squat_trie_iterate_node {
+ struct squat_node *node;
+ ARRAY_TYPE(seq_range) shifts;
+ unsigned int idx;
+};
+
+struct squat_trie_iterate_context {
+ struct squat_trie *trie;
+ struct squat_trie_iterate_node cur;
+ ARRAY(struct squat_trie_iterate_node) parents;
+ bool failed;
+};
+
+static int squat_trie_map(struct squat_trie *trie, bool building);
+
+void squat_trie_delete(struct squat_trie *trie)
+{
+ i_unlink_if_exists(trie->path);
+ squat_uidlist_delete(trie->uidlist);
+}
+
+static void squat_trie_set_corrupted(struct squat_trie *trie)
+{
+ trie->corrupted = TRUE;
+ i_error("Corrupted file %s", trie->path);
+ squat_trie_delete(trie);
+}
+
+static void squat_trie_normalize_map_build(struct squat_trie *trie)
+{
+ static unsigned char valid_chars[] =
+ DEFAULT_NORMALIZE_MAP_CHARS;
+ unsigned int i, j;
+
+ memset(trie->default_normalize_map, 0,
+ sizeof(trie->default_normalize_map));
+
+#if 1
+ for (i = 0, j = 1; i < sizeof(valid_chars)-1; i++) {
+ unsigned char chr = valid_chars[i];
+
+ if (chr >= 'A' && chr <= 'Z')
+ trie->default_normalize_map[chr-'A'+'a'] = j;
+ trie->default_normalize_map[chr] = j++;
+ }
+ i_assert(j <= SEQUENTIAL_COUNT);
+
+ for (i = 128; i < 256; i++)
+ trie->default_normalize_map[i] = j++;
+#else
+ for (i = 0; i < sizeof(valid_chars)-1; i++) {
+ unsigned char chr = valid_chars[i];
+
+ if (chr >= 'A' && chr <= 'Z')
+ trie->default_normalize_map[chr-'A'+'a'] = chr;
+ trie->default_normalize_map[chr] = chr;
+ }
+ for (i = 128; i < 256; i++)
+ trie->default_normalize_map[i] = i_toupper(i);
+#endif
+}
+
+static void node_free(struct squat_trie *trie, struct squat_node *node)
+{
+ struct squat_node *children;
+ unsigned int i;
+
+ if (node->leaf_string_length > 0) {
+ if (NODE_IS_DYNAMIC_LEAF(node))
+ i_free(node->children.leaf_string);
+ } else if (!node->children_not_mapped) {
+ children = NODE_CHILDREN_NODES(node);
+
+ trie->node_alloc_size -=
+ NODE_CHILDREN_ALLOC_SIZE(node->child_count);
+ for (i = 0; i < node->child_count; i++)
+ node_free(trie, &children[i]);
+
+ i_free(node->children.data);
+ }
+}
+
+struct squat_trie *
+squat_trie_init(const char *path, uint32_t uidvalidity,
+ enum file_lock_method lock_method, enum squat_index_flags flags,
+ mode_t mode, gid_t gid)
+{
+ struct squat_trie *trie;
+
+ trie = i_new(struct squat_trie, 1);
+ trie->path = i_strdup(path);
+ trie->uidlist = squat_uidlist_init(trie);
+ trie->fd = -1;
+ trie->lock_method = lock_method;
+ trie->uidvalidity = uidvalidity;
+ trie->flags = flags;
+ trie->create_mode = mode;
+ trie->create_gid = gid;
+ squat_trie_normalize_map_build(trie);
+
+ trie->dotlock_set.use_excl_lock =
+ (flags & SQUAT_INDEX_FLAG_DOTLOCK_USE_EXCL) != 0;
+ trie->dotlock_set.nfs_flush = (flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0;
+ trie->dotlock_set.timeout = SQUAT_TRIE_LOCK_TIMEOUT;
+ trie->dotlock_set.stale_timeout = SQUAT_TRIE_DOTLOCK_STALE_TIMEOUT;
+ trie->default_partial_len = DEFAULT_PARTIAL_LEN;
+ trie->default_full_len = DEFAULT_FULL_LEN;
+ return trie;
+}
+
+static void squat_trie_close_fd(struct squat_trie *trie)
+{
+ trie->data = NULL;
+ trie->data_size = 0;
+
+ if (trie->mmap_size != 0) {
+ if (munmap(trie->mmap_base, trie->mmap_size) < 0)
+ i_error("munmap(%s) failed: %m", trie->path);
+ trie->mmap_base = NULL;
+ trie->mmap_size = 0;
+ }
+ i_close_fd_path(&trie->fd, trie->path);
+}
+
+static void squat_trie_close(struct squat_trie *trie)
+{
+ trie->corrupted = FALSE;
+ node_free(trie, &trie->root);
+ i_zero(&trie->root);
+ i_zero(&trie->hdr);
+
+ squat_trie_close_fd(trie);
+ if (trie->file_cache != NULL)
+ file_cache_free(&trie->file_cache);
+ trie->locked_file_size = 0;
+}
+
+void squat_trie_deinit(struct squat_trie **_trie)
+{
+ struct squat_trie *trie = *_trie;
+
+ *_trie = NULL;
+ squat_trie_close(trie);
+ squat_uidlist_deinit(trie->uidlist);
+ i_free(trie->path);
+ i_free(trie);
+}
+
+void squat_trie_set_partial_len(struct squat_trie *trie, unsigned int len)
+{
+ trie->default_partial_len = len;
+}
+
+void squat_trie_set_full_len(struct squat_trie *trie, unsigned int len)
+{
+ trie->default_full_len = len;
+}
+
+static void squat_trie_header_init(struct squat_trie *trie)
+{
+ i_zero(&trie->hdr);
+ trie->hdr.version = SQUAT_TRIE_VERSION;
+ trie->hdr.indexid = time(NULL);
+ trie->hdr.uidvalidity = trie->uidvalidity;
+ trie->hdr.partial_len = trie->default_partial_len;
+ trie->hdr.full_len = trie->default_full_len;
+
+ i_assert(sizeof(trie->hdr.normalize_map) ==
+ sizeof(trie->default_normalize_map));
+ memcpy(trie->hdr.normalize_map, trie->default_normalize_map,
+ sizeof(trie->hdr.normalize_map));
+}
+
+static int squat_trie_open_fd(struct squat_trie *trie)
+{
+ trie->fd = open(trie->path, O_RDWR);
+ if (trie->fd == -1) {
+ if (errno == ENOENT) {
+ squat_trie_header_init(trie);
+ return 0;
+ }
+ i_error("open(%s) failed: %m", trie->path);
+ return -1;
+ }
+ if (trie->file_cache != NULL)
+ file_cache_set_fd(trie->file_cache, trie->fd);
+ return 0;
+}
+
+int squat_trie_open(struct squat_trie *trie)
+{
+ squat_trie_close(trie);
+
+ if (squat_trie_open_fd(trie) < 0)
+ return -1;
+ return squat_trie_map(trie, FALSE);
+}
+
+static int squat_trie_is_file_stale(struct squat_trie *trie)
+{
+ struct stat st, st2;
+
+ if ((trie->flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0)
+ nfs_flush_file_handle_cache(trie->path);
+ if (nfs_safe_stat(trie->path, &st) < 0) {
+ if (errno == ENOENT)
+ return 1;
+
+ i_error("stat(%s) failed: %m", trie->path);
+ return -1;
+ }
+ if (fstat(trie->fd, &st2) < 0) {
+ if (errno == ESTALE)
+ return 1;
+ i_error("fstat(%s) failed: %m", trie->path);
+ return -1;
+ }
+ trie->locked_file_size = st2.st_size;
+
+ if (st.st_ino == st2.st_ino && CMP_DEV_T(st.st_dev, st2.st_dev)) {
+ i_assert(trie->locked_file_size >= trie->data_size);
+ return 0;
+ }
+ return 1;
+}
+
+int squat_trie_refresh(struct squat_trie *trie)
+{
+ int ret;
+
+ ret = squat_trie_is_file_stale(trie);
+ if (ret > 0)
+ ret = squat_trie_open(trie);
+ return ret;
+}
+
+static int squat_trie_lock(struct squat_trie *trie, int lock_type,
+ struct file_lock **file_lock_r,
+ struct dotlock **dotlock_r)
+{
+ const char *error;
+ int ret;
+
+ i_assert(trie->fd != -1);
+
+ *file_lock_r = NULL;
+ *dotlock_r = NULL;
+
+ for (;;) {
+ if (trie->lock_method != FILE_LOCK_METHOD_DOTLOCK) {
+ struct file_lock_settings lock_set = {
+ .lock_method = trie->lock_method,
+ };
+ ret = file_wait_lock(trie->fd, trie->path, lock_type,
+ &lock_set, SQUAT_TRIE_LOCK_TIMEOUT,
+ file_lock_r, &error);
+ if (ret < 0) {
+ i_error("squat trie %s: %s",
+ trie->path, error);
+ }
+ } else {
+ ret = file_dotlock_create(&trie->dotlock_set,
+ trie->path, 0, dotlock_r);
+ }
+ if (ret == 0) {
+ i_error("squat trie %s: Locking timed out", trie->path);
+ return 0;
+ }
+ if (ret < 0)
+ return -1;
+
+ /* if the trie has been compressed, we need to reopen the
+ file and try to lock again */
+ ret = squat_trie_is_file_stale(trie);
+ if (ret == 0)
+ break;
+
+ if (*file_lock_r != NULL)
+ file_unlock(file_lock_r);
+ else
+ file_dotlock_delete(dotlock_r);
+ if (ret < 0)
+ return -1;
+
+ squat_trie_close(trie);
+ if (squat_trie_open_fd(trie) < 0)
+ return -1;
+ if (trie->fd == -1)
+ return 0;
+ }
+
+ if ((trie->flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0)
+ nfs_flush_read_cache_locked(trie->path, trie->fd);
+ return 1;
+}
+
+static void
+node_make_sequential(struct squat_trie *trie, struct squat_node *node, int level)
+{
+ const unsigned int alloc_size =
+ NODE_CHILDREN_ALLOC_SIZE(SEQUENTIAL_COUNT);
+ struct squat_node *children;
+ unsigned char *chars;
+ unsigned int i;
+
+ i_assert(node->child_count == 0);
+
+ trie->node_alloc_size += alloc_size;
+
+ node->want_sequential = FALSE;
+ node->have_sequential = TRUE;
+
+ node->child_count = SEQUENTIAL_COUNT;
+ node->children.data = i_malloc(alloc_size);
+
+ chars = NODE_CHILDREN_CHARS(node);
+ for (i = 0; i < SEQUENTIAL_COUNT; i++)
+ chars[i] = i;
+
+ if (level < MAX_FAST_LEVEL) {
+ children = NODE_CHILDREN_NODES(node);
+ for (i = 0; i < SEQUENTIAL_COUNT; i++)
+ children[i].want_sequential = TRUE;
+ }
+}
+
+static unsigned int
+node_add_child(struct squat_trie *trie, struct squat_node *node,
+ unsigned char chr, int level)
+{
+ unsigned int old_child_count = node->child_count;
+ struct squat_node *children, *old_children;
+ unsigned char *chars;
+ size_t old_size, new_size;
+
+ i_assert(node->leaf_string_length == 0);
+
+ if (node->want_sequential) {
+ node_make_sequential(trie, node, level);
+
+ if (chr < SEQUENTIAL_COUNT)
+ return chr;
+ old_child_count = SEQUENTIAL_COUNT;
+ }
+
+ node->child_count++;
+ new_size = NODE_CHILDREN_ALLOC_SIZE(node->child_count);
+
+ if (old_child_count == 0) {
+ /* first child */
+ node->children.data = i_malloc(new_size);
+ trie->node_alloc_size += new_size;
+ } else {
+ old_size = NODE_CHILDREN_ALLOC_SIZE(old_child_count);
+ if (old_size != new_size) {
+ trie->node_alloc_size += new_size - old_size;
+ node->children.data = i_realloc(node->children.data,
+ old_size, new_size);
+ }
+
+ children = NODE_CHILDREN_NODES(node);
+ old_children = (void *)(NODE_CHILDREN_CHARS(node) +
+ MEM_ALIGN(old_child_count));
+ if (children != old_children) {
+ memmove(children, old_children,
+ old_child_count * sizeof(struct squat_node));
+ }
+ }
+
+ chars = NODE_CHILDREN_CHARS(node);
+ i_assert(chars != NULL);
+ chars[node->child_count - 1] = chr;
+ return node->child_count - 1;
+}
+
+static int
+trie_file_cache_read(struct squat_trie *trie, size_t offset, size_t size)
+{
+ if (trie->file_cache == NULL)
+ return 0;
+
+ if (file_cache_read(trie->file_cache, offset, size) < 0) {
+ i_error("read(%s) failed: %m", trie->path);
+ return -1;
+ }
+ trie->data = file_cache_get_map(trie->file_cache, &trie->data_size);
+ return 0;
+}
+
+static int
+node_read_children(struct squat_trie *trie, struct squat_node *node, int level)
+{
+ const uint8_t *data, *end;
+ const unsigned char *child_chars;
+ struct squat_node *child, *children = NULL;
+ uoff_t node_offset;
+ unsigned int i, child_idx, child_count;
+ uoff_t base_offset;
+ uint32_t num;
+
+ i_assert(node->children_not_mapped);
+ i_assert(!node->have_sequential);
+ i_assert(trie->unmapped_child_count > 0);
+ i_assert(trie->data_size <= trie->locked_file_size);
+
+ trie->unmapped_child_count--;
+ node_offset = node->children.offset;
+ node->children_not_mapped = FALSE;
+ node->children.data = NULL;
+
+ if (trie_file_cache_read(trie, node_offset, TRIE_READAHEAD_SIZE) < 0)
+ return -1;
+ if (unlikely(node_offset >= trie->data_size)) {
+ squat_trie_set_corrupted(trie);
+ return -1;
+ }
+
+ data = CONST_PTR_OFFSET(trie->data, node_offset);
+ end = CONST_PTR_OFFSET(trie->data, trie->data_size);
+ child_count = *data++;
+ if (unlikely(node_offset + child_count >= trie->data_size)) {
+ squat_trie_set_corrupted(trie);
+ return -1;
+ }
+
+ if (child_count == 0)
+ return 0;
+
+ child_chars = data;
+ data += child_count;
+
+ /* get child offsets */
+ base_offset = node_offset;
+ for (i = 0; i < child_count; i++) {
+ /* we always start with !have_sequential, so at i=0 this
+ check always goes to add the first child */
+ if (node->have_sequential && child_chars[i] < SEQUENTIAL_COUNT)
+ child_idx = child_chars[i];
+ else {
+ child_idx = node_add_child(trie, node, child_chars[i],
+ level);
+ children = NODE_CHILDREN_NODES(node);
+ }
+
+ i_assert(children != NULL);
+
+ child = &children[child_idx];
+
+ /* 1) child offset */
+ num = squat_unpack_num(&data, end);
+ if (num == 0) {
+ /* no children */
+ } else {
+ if ((num & 1) != 0) {
+ base_offset += num >> 1;
+ } else {
+ base_offset -= num >> 1;
+ }
+ if (base_offset >= trie->locked_file_size) {
+ squat_trie_set_corrupted(trie);
+ return -1;
+ }
+ trie->unmapped_child_count++;
+ child->children_not_mapped = TRUE;
+ child->children.offset = base_offset;
+ }
+
+ /* 2) uidlist */
+ child->uid_list_idx = squat_unpack_num(&data, end);
+ if (child->uid_list_idx == 0) {
+ /* we don't write nodes with empty uidlists */
+ squat_trie_set_corrupted(trie);
+ return -1;
+ }
+ if (!UIDLIST_IS_SINGLETON(child->uid_list_idx)) {
+ /* 3) next uid */
+ child->next_uid = squat_unpack_num(&data, end) + 1;
+ } else {
+ uint32_t idx = child->uid_list_idx;
+
+ child->next_uid = 1 +
+ squat_uidlist_singleton_last_uid(idx);
+ }
+
+ /* 4) unused uids + leaf string flag */
+ num = squat_unpack_num(&data, end);
+ child->unused_uids = num >> 1;
+ if ((num & 1) != 0) {
+ /* leaf string */
+ unsigned int len;
+ unsigned char *dest;
+
+ /* 5) leaf string length */
+ len = child->leaf_string_length =
+ squat_unpack_num(&data, end) + 1;
+ if (!NODE_IS_DYNAMIC_LEAF(child))
+ dest = child->children.static_leaf_string;
+ else {
+ dest = child->children.leaf_string =
+ i_malloc(len);
+ }
+
+ if (trie->file_cache != NULL) {
+ /* the string may be long -
+ recalculate the end pos */
+ size_t offset, size;
+
+ offset = (const char *)data -
+ (const char *)trie->data;
+ size = len + TRIE_BYTES_LEFT(child_count - i);
+
+ if (trie_file_cache_read(trie, offset,
+ size) < 0)
+ return -1;
+ data = CONST_PTR_OFFSET(trie->data, offset);
+ end = CONST_PTR_OFFSET(trie->data,
+ trie->data_size);
+ child_chars = CONST_PTR_OFFSET(trie->data,
+ node_offset + 1);
+ }
+
+ if ((size_t)(end - data) < len) {
+ squat_trie_set_corrupted(trie);
+ return -1;
+ }
+ memcpy(dest, data, len);
+ data += len;
+ }
+ }
+ if (unlikely(data == end)) {
+ /* we should never get this far */
+ squat_trie_set_corrupted(trie);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+node_write_children(struct squat_trie_build_context *ctx,
+ struct squat_node *node, const uoff_t *node_offsets)
+{
+ struct squat_node *children;
+ const unsigned char *chars;
+ uint8_t child_count, buf[SQUAT_PACK_MAX_SIZE * 5], *bufp;
+ uoff_t base_offset;
+ unsigned int i;
+
+ chars = NODE_CHILDREN_CHARS(node);
+ children = NODE_CHILDREN_NODES(node);
+
+ base_offset = ctx->output->offset;
+ child_count = node->child_count;
+ o_stream_nsend(ctx->output, &child_count, 1);
+ o_stream_nsend(ctx->output, chars, child_count);
+
+ for (i = 0; i < child_count; i++) {
+ bufp = buf;
+ /* 1) child offset */
+ if (node_offsets[i] == 0)
+ *bufp++ = 0;
+ else if (node_offsets[i] >= base_offset) {
+ squat_pack_num(&bufp,
+ ((node_offsets[i] - base_offset) << 1) | 1);
+ base_offset = node_offsets[i];
+ } else {
+ squat_pack_num(&bufp,
+ (base_offset - node_offsets[i]) << 1);
+ base_offset = node_offsets[i];
+ }
+
+ /* 2) uidlist */
+ squat_pack_num(&bufp, children[i].uid_list_idx);
+ if (!UIDLIST_IS_SINGLETON(children[i].uid_list_idx)) {
+ /* 3) next uid */
+ squat_pack_num(&bufp, children[i].next_uid - 1);
+ }
+
+ if (children[i].leaf_string_length == 0) {
+ /* 4a) unused uids */
+ squat_pack_num(&bufp, children[i].unused_uids << 1);
+ o_stream_nsend(ctx->output, buf, bufp - buf);
+ } else {
+ i_assert(node_offsets[i] == 0);
+ /* 4b) unused uids + flag */
+ squat_pack_num(&bufp, (children[i].unused_uids << 1) | 1);
+ /* 5) leaf string length */
+ squat_pack_num(&bufp, children[i].leaf_string_length - 1);
+ o_stream_nsend(ctx->output, buf, bufp - buf);
+ o_stream_nsend(ctx->output,
+ NODE_LEAF_STRING(&children[i]),
+ children[i].leaf_string_length);
+ }
+ }
+}
+
+static inline void
+node_add_uid(struct squat_trie_build_context *ctx, uint32_t uid,
+ struct squat_node *node)
+{
+ if (uid < node->next_uid) {
+ /* duplicate */
+ return;
+ }
+ node->unused_uids += uid - node->next_uid;
+ node->next_uid = uid + 1;
+
+ node->uid_list_idx =
+ squat_uidlist_build_add_uid(ctx->uidlist_build_ctx,
+ node->uid_list_idx, uid);
+}
+
+static void
+node_split_string(struct squat_trie_build_context *ctx, struct squat_node *node)
+{
+ struct squat_node *child;
+ unsigned char *str;
+ unsigned int uid, idx, leafstr_len = node->leaf_string_length;
+
+ i_assert(leafstr_len > 0);
+
+ /* make a copy of the leaf string and convert to normal node by
+ removing it. */
+ str = t_malloc_no0(leafstr_len);
+ if (!NODE_IS_DYNAMIC_LEAF(node))
+ memcpy(str, node->children.static_leaf_string, leafstr_len);
+ else {
+ memcpy(str, node->children.leaf_string, leafstr_len);
+ i_free(node->children.leaf_string);
+ }
+ node->leaf_string_length = 0;
+
+ /* create a new child node for the rest of the string */
+ idx = node_add_child(ctx->trie, node, str[0], MAX_FAST_LEVEL);
+ child = NODE_CHILDREN_NODES(node) + idx;
+
+ /* update uidlist to contain all of parent's UIDs */
+ child->next_uid = node->next_uid - node->unused_uids;
+ for (uid = 0; uid < child->next_uid; uid++) {
+ child->uid_list_idx =
+ squat_uidlist_build_add_uid(ctx->uidlist_build_ctx,
+ child->uid_list_idx, uid);
+ }
+
+ i_assert(!child->have_sequential && child->children.data == NULL);
+ if (leafstr_len > 1) {
+ /* make the child a leaf string */
+ leafstr_len--;
+ child->leaf_string_length = leafstr_len;
+ if (!NODE_IS_DYNAMIC_LEAF(child)) {
+ memcpy(child->children.static_leaf_string,
+ str + 1, leafstr_len);
+ } else {
+ child->children.leaf_string = i_malloc(leafstr_len);
+ memcpy(child->children.leaf_string,
+ str + 1, leafstr_len);
+ }
+ }
+}
+
+static bool
+node_leaf_string_add_or_split(struct squat_trie_build_context *ctx,
+ struct squat_node *node,
+ const unsigned char *data, unsigned int data_len)
+{
+ const unsigned char *str = NODE_LEAF_STRING(node);
+ const unsigned int leafstr_len = node->leaf_string_length;
+ unsigned int i;
+
+ if (data_len != leafstr_len) {
+ /* different lengths, can't match */
+ T_BEGIN {
+ node_split_string(ctx, node);
+ } T_END;
+ return FALSE;
+ }
+
+ for (i = 0; i < data_len; i++) {
+ if (data[i] != str[i]) {
+ /* non-match */
+ T_BEGIN {
+ node_split_string(ctx, node);
+ } T_END;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static int squat_build_add(struct squat_trie_build_context *ctx, uint32_t uid,
+ const unsigned char *data, unsigned int size)
+{
+ struct squat_trie *trie = ctx->trie;
+ struct squat_node *node = &trie->root;
+ const unsigned char *end = data + size;
+ unsigned char *chars;
+ unsigned int idx;
+ int level = 0;
+
+ for (;;) {
+ if (node->children_not_mapped) {
+ if (unlikely(node_read_children(trie, node, level) < 0))
+ return -1;
+ }
+
+ if (node->leaf_string_length != 0) {
+ /* the whole string must match or we need to split
+ the node */
+ if (node_leaf_string_add_or_split(ctx, node, data,
+ end - data)) {
+ node_add_uid(ctx, uid, node);
+ return 0;
+ }
+ }
+
+ node_add_uid(ctx, uid, node);
+
+ if (unlikely(uid < node->unused_uids)) {
+ squat_trie_set_corrupted(trie);
+ return -1;
+ }
+ /* child node's UIDs are relative to ours. so for example if
+ we're adding UID 4 and this node now has [2,4] UIDs,
+ unused_uids=3 and so the child node will be adding
+ UID 4-3 = 1. */
+ uid -= node->unused_uids;
+
+ if (data == end)
+ return 0;
+ level++;
+
+ if (node->have_sequential) {
+ i_assert(node->child_count >= SEQUENTIAL_COUNT);
+ if (*data < SEQUENTIAL_COUNT) {
+ idx = *data;
+ goto found;
+ }
+ idx = SEQUENTIAL_COUNT;
+ } else {
+ idx = 0;
+ }
+ chars = NODE_CHILDREN_CHARS(node);
+ for (; idx < node->child_count; idx++) {
+ if (chars[idx] == *data)
+ goto found;
+ }
+ break;
+ found:
+ data++;
+ node = NODE_CHILDREN_NODES(node) + idx;
+ }
+
+ /* create new children */
+ i_assert(node->leaf_string_length == 0);
+
+ for (;;) {
+ idx = node_add_child(trie, node, *data,
+ size - (end - data) + 1);
+ node = NODE_CHILDREN_NODES(node) + idx;
+
+ node_add_uid(ctx, uid, node);
+ uid = 0;
+
+ if (++data == end)
+ break;
+
+ if (!node->have_sequential) {
+ /* convert the node into a leaf string */
+ unsigned int len = end - data;
+
+ i_assert(node->children.data == NULL);
+ node->leaf_string_length = len;
+ if (!NODE_IS_DYNAMIC_LEAF(node)) {
+ memcpy(node->children.static_leaf_string,
+ data, len);
+ } else {
+ node->children.leaf_string = i_malloc(len);
+ memcpy(node->children.leaf_string, data, len);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+squat_build_word_bytes(struct squat_trie_build_context *ctx, uint32_t uid,
+ const unsigned char *data, unsigned int size)
+{
+ struct squat_trie *trie = ctx->trie;
+ unsigned int i;
+
+ if (trie->hdr.full_len <= trie->hdr.partial_len)
+ i = 0;
+ else {
+ /* the first word is longer than others */
+ if (squat_build_add(ctx, uid, data,
+ I_MIN(size, trie->hdr.full_len)) < 0)
+ return -1;
+ i = 1;
+ }
+
+ for (; i < size; i++) {
+ if (squat_build_add(ctx, uid, data + i,
+ I_MIN(trie->hdr.partial_len, size-i)) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+squat_build_word(struct squat_trie_build_context *ctx, uint32_t uid,
+ const unsigned char *data, const uint8_t *char_lengths,
+ unsigned int size)
+{
+ struct squat_trie *trie = ctx->trie;
+ unsigned int i, j, bytelen;
+
+ if (char_lengths == NULL) {
+ /* optimization path: all characters are bytes */
+ return squat_build_word_bytes(ctx, uid, data, size);
+ }
+
+ if (trie->hdr.full_len <= trie->hdr.partial_len)
+ i = 0;
+ else {
+ /* the first word is longer than others */
+ bytelen = 0;
+ for (j = 0; j < trie->hdr.full_len && bytelen < size; j++)
+ bytelen += char_lengths[bytelen];
+ i_assert(bytelen <= size);
+
+ if (squat_build_add(ctx, uid, data, bytelen) < 0)
+ return -1;
+ i = char_lengths[0];
+ }
+
+ for (; i < size; i += char_lengths[i]) {
+ bytelen = 0;
+ for (j = 0; j < trie->hdr.partial_len && i+bytelen < size; j++)
+ bytelen += char_lengths[i + bytelen];
+ i_assert(i + bytelen <= size);
+
+ if (squat_build_add(ctx, uid, data + i, bytelen) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static unsigned char *
+squat_data_normalize(struct squat_trie *trie, const unsigned char *data,
+ unsigned int size)
+{
+ static const unsigned char replacement_utf8[] = { 0xef, 0xbf, 0xbd };
+ unsigned char *dest;
+ unsigned int i;
+
+ dest = t_malloc_no0(size);
+ for (i = 0; i < size; i++) {
+ if (data[i] == replacement_utf8[0] && i + 2 < size &&
+ data[i+1] == replacement_utf8[1] &&
+ data[i+2] == replacement_utf8[2]) {
+ /* Don't index replacement character */
+ dest[i++] = 0;
+ dest[i++] = 0;
+ dest[i] = 0;
+ } else {
+ dest[i] = trie->hdr.normalize_map[data[i]];
+ }
+ }
+ return dest;
+}
+
+static int
+squat_trie_build_more_real(struct squat_trie_build_context *ctx,
+ uint32_t uid, enum squat_index_type type,
+ const unsigned char *input, unsigned int size)
+{
+ struct squat_trie *trie = ctx->trie;
+ const unsigned char *data;
+ uint8_t *char_lengths;
+ unsigned int i, start = 0;
+ bool multibyte_chars = FALSE;
+ int ret = 0;
+
+ uid = uid * 2 + (type == SQUAT_INDEX_TYPE_HEADER ? 1 : 0);
+
+ char_lengths = t_malloc_no0(size);
+ data = squat_data_normalize(trie, input, size);
+ for (i = 0; i < size; i++) {
+ char_lengths[i] = uni_utf8_char_bytes(input[i]);
+ if (char_lengths[i] != 1)
+ multibyte_chars = TRUE;
+ if (data[i] != '\0')
+ continue;
+
+ while (start < i && data[start] == '\0')
+ start++;
+ if (i != start) {
+ if (squat_build_word(ctx, uid, data + start,
+ !multibyte_chars ? NULL :
+ char_lengths + start,
+ i - start) < 0) {
+ ret = -1;
+ start = i;
+ break;
+ }
+ }
+ start = i + 1;
+ }
+ while (start < i && data[start] == '\0')
+ start++;
+ if (i != start) {
+ if (squat_build_word(ctx, uid, data + start,
+ !multibyte_chars ? NULL :
+ char_lengths + start, i - start) < 0)
+ ret = -1;
+ }
+ return ret;
+}
+
+int squat_trie_build_more(struct squat_trie_build_context *ctx,
+ uint32_t uid, enum squat_index_type type,
+ const unsigned char *input, unsigned int size)
+{
+ int ret = 0;
+
+ if (size != 0) T_BEGIN {
+ ret = squat_trie_build_more_real(ctx, uid, type, input, size);
+ } T_END;
+ return ret;
+}
+
+static void
+node_drop_unused_children(struct squat_trie *trie, struct squat_node *node)
+{
+ unsigned char *chars;
+ struct squat_node *children_src, *children_dest;
+ unsigned int i, j, orig_child_count = node->child_count;
+
+ chars = NODE_CHILDREN_CHARS(node);
+ children_src = NODE_CHILDREN_NODES(node);
+
+ /* move chars */
+ for (i = j = 0; i < orig_child_count; i++) {
+ if (children_src[i].next_uid != 0)
+ chars[j++] = chars[i];
+ }
+ node->child_count = j;
+
+ /* move children. note that children_dest may point to different
+ location than children_src, although they both point to the
+ same node. */
+ children_dest = NODE_CHILDREN_NODES(node);
+ for (i = j = 0; i < orig_child_count; i++) {
+ if (children_src[i].next_uid != 0)
+ children_dest[j++] = children_src[i];
+ else
+ node_free(trie, &children_src[i]);
+ }
+}
+
+static int
+squat_write_node(struct squat_trie_build_context *ctx, struct squat_node *node,
+ uoff_t *node_offset_r, int level)
+{
+ struct squat_trie *trie = ctx->trie;
+ struct squat_node *children;
+ unsigned int i;
+ uoff_t *node_offsets;
+ uint8_t child_count;
+ int ret;
+
+ i_assert(node->next_uid != 0);
+
+ if (node->children_not_mapped && ctx->compress_nodes) {
+ if (node_read_children(trie, node, MAX_FAST_LEVEL) < 0)
+ return -1;
+ }
+
+ node->have_sequential = FALSE;
+ node_drop_unused_children(trie, node);
+
+ child_count = node->child_count;
+ if (child_count == 0) {
+ i_assert(!node->children_not_mapped ||
+ node->leaf_string_length == 0);
+ *node_offset_r = !node->children_not_mapped ? 0 :
+ node->children.offset;
+ return 0;
+ }
+ i_assert(!node->children_not_mapped);
+
+ trie->hdr.node_count++;
+
+ children = NODE_CHILDREN_NODES(node);
+ node_offsets = t_new(uoff_t, child_count);
+ for (i = 0; i < child_count; i++) {
+ T_BEGIN {
+ ret = squat_write_node(ctx, &children[i],
+ &node_offsets[i], level + 1);
+ } T_END;
+ if (ret < 0)
+ return -1;
+ }
+
+ *node_offset_r = ctx->output->offset;
+ node_write_children(ctx, node, node_offsets);
+ return 0;
+}
+
+static int squat_write_nodes(struct squat_trie_build_context *ctx)
+{
+ struct squat_trie *trie = ctx->trie;
+ uoff_t node_offset;
+ int ret;
+
+ if (ctx->trie->root.next_uid == 0)
+ return 0;
+
+ T_BEGIN {
+ ret = squat_write_node(ctx, &ctx->trie->root, &node_offset, 0);
+ } T_END;
+ if (ret < 0)
+ return -1;
+
+ trie->hdr.root_offset = node_offset;
+ trie->hdr.root_unused_uids = trie->root.unused_uids;
+ trie->hdr.root_next_uid = trie->root.next_uid;
+ trie->hdr.root_uidlist_idx = trie->root.uid_list_idx;
+ return 0;
+}
+
+static struct squat_trie_iterate_context *
+squat_trie_iterate_init(struct squat_trie *trie)
+{
+ struct squat_trie_iterate_context *ctx;
+
+ ctx = i_new(struct squat_trie_iterate_context, 1);
+ ctx->trie = trie;
+ ctx->cur.node = &trie->root;
+ i_array_init(&ctx->parents, trie->hdr.partial_len*2);
+ return ctx;
+}
+
+static int
+squat_trie_iterate_deinit(struct squat_trie_iterate_context *ctx)
+{
+ struct squat_trie_iterate_node *node;
+ int ret = ctx->failed ? -1 : 0;
+
+ if (array_is_created(&ctx->cur.shifts)) {
+ array_foreach_modifiable(&ctx->parents, node)
+ array_free(&node->shifts);
+ array_free(&ctx->cur.shifts);
+ }
+ array_free(&ctx->parents);
+ i_free(ctx);
+ return ret;
+}
+
+static struct squat_node *
+squat_trie_iterate_first(struct squat_trie_iterate_context *ctx)
+{
+ if (ctx->cur.node->children_not_mapped) {
+ if (node_read_children(ctx->trie, ctx->cur.node, 1) < 0) {
+ ctx->failed = TRUE;
+ return NULL;
+ }
+ }
+ return ctx->cur.node;
+}
+
+static struct squat_node *
+squat_trie_iterate_next(struct squat_trie_iterate_context *ctx,
+ ARRAY_TYPE(seq_range) *shifts_r)
+{
+ struct squat_trie_iterate_node *iter_nodes;
+ struct squat_node *children;
+ unsigned int count, shift_count = 0;
+
+ while (ctx->cur.idx == ctx->cur.node->child_count ||
+ ctx->cur.node->uid_list_idx == 0)
+ {
+ iter_nodes = array_get_modifiable(&ctx->parents, &count);
+ if (count == 0)
+ return NULL;
+
+ if (array_is_created(&ctx->cur.shifts))
+ array_free(&ctx->cur.shifts);
+ ctx->cur = iter_nodes[count-1];
+ array_delete(&ctx->parents, count-1, 1);
+ }
+
+ *shifts_r = ctx->cur.shifts;
+ if (array_is_created(&ctx->cur.shifts))
+ shift_count = array_count(&ctx->cur.shifts);
+
+ children = NODE_CHILDREN_NODES(ctx->cur.node);
+ while (children[ctx->cur.idx++].uid_list_idx == 0) {
+ if (ctx->cur.idx == ctx->cur.node->child_count) {
+ /* no more non-empty children in this node */
+ return squat_trie_iterate_next(ctx, shifts_r);
+ }
+ }
+ array_push_back(&ctx->parents, &ctx->cur);
+ ctx->cur.node = &children[ctx->cur.idx-1];
+ ctx->cur.idx = 0;
+ if (shift_count != 0)
+ i_array_init(&ctx->cur.shifts, shift_count);
+ else
+ i_zero(&ctx->cur.shifts);
+ return squat_trie_iterate_first(ctx);
+}
+
+static void
+squat_uidlist_update_expunged_uids(const ARRAY_TYPE(seq_range) *shifts_arr,
+ ARRAY_TYPE(seq_range) *child_shifts,
+ ARRAY_TYPE(seq_range) *uids_arr,
+ struct squat_trie *trie,
+ struct squat_node *node, bool do_shifts)
+{
+ const struct seq_range *shifts;
+ struct seq_range *uids, shift;
+ unsigned int i, uid_idx, uid_count, shift_count;
+ uint32_t child_shift_seq1, child_shift_count, seq_high;
+ unsigned int shift_sum = 0, child_sum = 0;
+
+ if (!array_is_created(shifts_arr)) {
+ i_assert(node->uid_list_idx != 0 || node->child_count == 0);
+ return;
+ }
+
+ /* we'll recalculate this */
+ node->unused_uids = 0;
+
+ uids = array_get_modifiable(uids_arr, &uid_count);
+ shifts = array_get(shifts_arr, &shift_count);
+ for (i = 0, uid_idx = 0, seq_high = 0;; ) {
+ /* skip UID ranges until we skip/overlap shifts */
+ while (uid_idx < uid_count &&
+ (i == shift_count ||
+ I_MAX(shifts[i].seq1, seq_high) > uids[uid_idx].seq2))
+ {
+ i_assert(uids[uid_idx].seq1 >= shift_sum);
+ uids[uid_idx].seq1 -= shift_sum;
+ uids[uid_idx].seq2 -= shift_sum;
+ child_sum += uids[uid_idx].seq2 -
+ uids[uid_idx].seq1 + 1;
+
+ if (uid_idx > 0 &&
+ uids[uid_idx-1].seq2 >= uids[uid_idx].seq1 - 1) {
+ /* we can merge this and the previous range */
+ i_assert(uids[uid_idx-1].seq2 ==
+ uids[uid_idx].seq1 - 1);
+ uids[uid_idx-1].seq2 = uids[uid_idx].seq2;
+ array_delete(uids_arr, uid_idx, 1);
+ uids = array_get_modifiable(uids_arr,
+ &uid_count);
+ } else {
+ if (uid_idx == 0)
+ node->unused_uids += uids[0].seq1;
+ else {
+ node->unused_uids +=
+ uids[uid_idx].seq1 -
+ uids[uid_idx-1].seq2 - 1;
+ }
+ uid_idx++;
+ }
+ }
+ if (uid_idx == uid_count)
+ break;
+
+ shift.seq1 = I_MAX(shifts[i].seq1, seq_high);
+ shift.seq2 = shifts[i].seq2;
+ if (shift.seq2 < uids[uid_idx].seq1) {
+ /* shift is entirely before UID range */
+ shift_sum += shift.seq2 - shift.seq1 + 1;
+ i++;
+ } else {
+ /* handle shifts before UID range */
+ if (shift.seq1 < uids[uid_idx].seq1) {
+ shift_sum += uids[uid_idx].seq1 - shift.seq1;
+ shift.seq1 = uids[uid_idx].seq1;
+ }
+ /* update child shifts */
+ child_shift_seq1 = child_sum +
+ shift.seq1 - uids[uid_idx].seq1;
+ child_shift_count =
+ I_MIN(shift.seq2, uids[uid_idx].seq2) -
+ shift.seq1 + 1;
+ seq_range_array_add_range(child_shifts,
+ child_shift_seq1,
+ child_shift_seq1 +
+ child_shift_count - 1);
+ child_sum += child_shift_count;
+
+ /* if the shifts continue after the UID range,
+ treat it in the next loop iteration */
+ if (shift.seq2 <= uids[uid_idx].seq2)
+ i++;
+ else
+ seq_high = uids[uid_idx].seq2 + 1;
+
+ /* update UIDs - no matter where within the UID range
+ the shifts happened, the result is the same:
+ shift number of UIDs are removed, and the rest
+ are decreased by shift_sum.
+
+ 123 uids child_shifts
+ a) s -> 12 1
+ b) s -> 12 2
+ c) s -> 12 3
+ */
+ if (uids[uid_idx].seq1 +
+ child_shift_count > uids[uid_idx].seq2) {
+ /* removed completely */
+ array_delete(uids_arr, uid_idx, 1);
+ uids = array_get_modifiable(uids_arr,
+ &uid_count);
+ } else if (do_shifts) {
+ /* the next loop iteration fixes the UIDs */
+ uids[uid_idx].seq1 += child_shift_count;
+ } else {
+ seq_range_array_remove_range(uids_arr,
+ shift.seq1,
+ I_MIN(shift.seq2, uids[uid_idx].seq2));
+ uids = array_get_modifiable(uids_arr,
+ &uid_count);
+ }
+ shift_sum += child_shift_count;
+ }
+ if (!do_shifts) {
+ /* root node - UIDs are only removed, not shifted */
+ shift_sum = 0;
+ }
+ }
+
+ if (uid_count == 0) {
+ /* no UIDs left, delete the node's children and mark it
+ unused */
+ if (!NODE_IS_DYNAMIC_LEAF(node))
+ node_free(trie, node);
+
+ node->child_count = 0;
+ node->have_sequential = FALSE;
+ node->next_uid = 0;
+ } else {
+ if (do_shifts)
+ node->next_uid = uids[uid_count-1].seq2 + 1;
+ else {
+ node->unused_uids += (node->next_uid - 1) -
+ uids[uid_count-1].seq2;
+ }
+ }
+}
+
+static int
+squat_trie_expunge_uidlists(struct squat_trie_build_context *ctx,
+ struct squat_uidlist_rebuild_context *rebuild_ctx,
+ struct squat_trie_iterate_context *iter,
+ const ARRAY_TYPE(seq_range) *expunged_uids)
+{
+ struct squat_node *node;
+ ARRAY_TYPE(seq_range) uid_range, root_shifts, shifts;
+ bool shift = FALSE;
+ int ret = 0;
+
+ node = squat_trie_iterate_first(iter);
+ if (node->uid_list_idx == 0)
+ return 0;
+
+ i_array_init(&uid_range, 1024);
+ i_array_init(&root_shifts, array_count(expunged_uids));
+ array_append_array(&root_shifts, expunged_uids);
+
+ if (array_count(expunged_uids) > 0)
+ i_array_init(&iter->cur.shifts, array_count(expunged_uids));
+
+ shifts = root_shifts;
+ do {
+ i_assert(node->uid_list_idx != 0);
+ array_clear(&uid_range);
+ if (squat_uidlist_get_seqrange(ctx->trie->uidlist,
+ node->uid_list_idx,
+ &uid_range) < 0) {
+ ret = -1;
+ break;
+ }
+ squat_uidlist_update_expunged_uids(&shifts, &iter->cur.shifts,
+ &uid_range, ctx->trie, node,
+ shift);
+ node->uid_list_idx =
+ squat_uidlist_rebuild_nextu(rebuild_ctx, &uid_range);
+ i_assert(node->uid_list_idx != 0 || node->next_uid == 0);
+
+ node = squat_trie_iterate_next(iter, &shifts);
+ shift = TRUE;
+ } while (node != NULL);
+ array_free(&uid_range);
+ array_free(&root_shifts);
+ return ret;
+}
+
+static int
+squat_trie_renumber_uidlists2(struct squat_trie_build_context *ctx,
+ struct squat_uidlist_rebuild_context *rebuild_ctx,
+ struct squat_trie_iterate_context *iter)
+{
+ struct squat_node *node;
+ ARRAY_TYPE(seq_range) shifts;
+ ARRAY_TYPE(uint32_t) uids;
+ int ret = 0;
+
+ node = squat_trie_iterate_first(iter);
+ if (node->uid_list_idx == 0)
+ return 0;
+
+ i_array_init(&uids, 1024);
+ while (node != NULL) {
+ i_assert(node->uid_list_idx != 0);
+ if (!UIDLIST_IS_SINGLETON(node->uid_list_idx)) {
+ /* rebuild the uidlist */
+ array_clear(&uids);
+ if (squat_uidlist_get(ctx->trie->uidlist,
+ node->uid_list_idx, &uids) < 0) {
+ ret = -1;
+ break;
+ }
+ node->uid_list_idx =
+ squat_uidlist_rebuild_next(rebuild_ctx, &uids);
+ }
+ node = squat_trie_iterate_next(iter, &shifts);
+ }
+ array_free(&uids);
+ return ret;
+}
+
+static int
+squat_trie_renumber_uidlists(struct squat_trie_build_context *ctx,
+ const ARRAY_TYPE(seq_range) *expunged_uids,
+ bool compress)
+{
+ struct squat_trie_iterate_context *iter;
+ struct squat_uidlist_rebuild_context *rebuild_ctx;
+ time_t now;
+ int ret = 0;
+
+ if ((ret = squat_uidlist_rebuild_init(ctx->uidlist_build_ctx,
+ compress, &rebuild_ctx)) <= 0)
+ return ret;
+
+ now = time(NULL);
+ ctx->trie->hdr.indexid =
+ I_MAX((unsigned int)now, ctx->trie->hdr.indexid + 1);
+
+ iter = squat_trie_iterate_init(ctx->trie);
+ if (expunged_uids != NULL) {
+ ret = squat_trie_expunge_uidlists(ctx, rebuild_ctx, iter,
+ expunged_uids);
+ } else {
+ ret = squat_trie_renumber_uidlists2(ctx, rebuild_ctx, iter);
+ }
+ if (squat_trie_iterate_deinit(iter) < 0)
+ ret = -1;
+
+ /* lock the trie before we rename uidlist */
+ i_assert(ctx->file_lock == NULL && ctx->dotlock == NULL);
+ if (squat_trie_lock(ctx->trie, F_WRLCK,
+ &ctx->file_lock, &ctx->dotlock) <= 0)
+ ret = -1;
+ return squat_uidlist_rebuild_finish(rebuild_ctx, ret < 0);
+}
+
+static bool squat_trie_check_header(struct squat_trie *trie)
+{
+ if (trie->hdr.version != SQUAT_TRIE_VERSION ||
+ trie->hdr.uidvalidity != trie->uidvalidity)
+ return FALSE;
+
+ if (trie->hdr.partial_len > trie->hdr.full_len) {
+ i_error("Corrupted %s: partial len > full len", trie->path);
+ return FALSE;
+ }
+ if (trie->hdr.full_len == 0) {
+ i_error("Corrupted %s: full len=0", trie->path);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int squat_trie_map_header(struct squat_trie *trie)
+{
+ int ret;
+
+ if (trie->locked_file_size == 0) {
+ /* newly created file */
+ squat_trie_header_init(trie);
+ return 1;
+ }
+ i_assert(trie->fd != -1);
+
+ if ((trie->flags & SQUAT_INDEX_FLAG_MMAP_DISABLE) != 0) {
+ ret = pread_full(trie->fd, &trie->hdr, sizeof(trie->hdr), 0);
+ if (ret <= 0) {
+ if (ret < 0) {
+ i_error("pread(%s) failed: %m", trie->path);
+ return -1;
+ }
+ i_error("Corrupted %s: File too small", trie->path);
+ return 0;
+ }
+ trie->data = NULL;
+ trie->data_size = 0;
+ } else {
+ if (trie->locked_file_size < sizeof(trie->hdr)) {
+ i_error("Corrupted %s: File too small", trie->path);
+ return 0;
+ }
+ if (trie->mmap_size != 0) {
+ if (munmap(trie->mmap_base, trie->mmap_size) < 0)
+ i_error("munmap(%s) failed: %m", trie->path);
+ }
+
+ trie->mmap_size = trie->locked_file_size;
+ trie->mmap_base = mmap(NULL, trie->mmap_size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, trie->fd, 0);
+ if (trie->mmap_base == MAP_FAILED) {
+ trie->data = trie->mmap_base = NULL;
+ trie->data_size = trie->mmap_size = 0;
+ i_error("mmap(%s) failed: %m", trie->path);
+ return -1;
+ }
+ memcpy(&trie->hdr, trie->mmap_base, sizeof(trie->hdr));
+ trie->data = trie->mmap_base;
+ trie->data_size = trie->mmap_size;
+ }
+
+ return squat_trie_check_header(trie) ? 1 : 0;
+}
+
+static int squat_trie_map(struct squat_trie *trie, bool building)
+{
+ struct file_lock *file_lock = NULL;
+ struct dotlock *dotlock = NULL;
+ bool changed;
+ int ret;
+
+ if (trie->fd != -1) {
+ if (squat_trie_lock(trie, F_RDLCK, &file_lock, &dotlock) <= 0)
+ return -1;
+ if ((trie->flags & SQUAT_INDEX_FLAG_MMAP_DISABLE) != 0 &&
+ trie->file_cache == NULL)
+ trie->file_cache = file_cache_new_path(trie->fd, trie->path);
+ }
+
+ ret = squat_trie_map_header(trie);
+ if (ret == 0) {
+ if (file_lock != NULL)
+ file_unlock(&file_lock);
+ else
+ file_dotlock_delete(&dotlock);
+ squat_trie_delete(trie);
+ squat_trie_close(trie);
+ squat_trie_header_init(trie);
+ }
+ changed = trie->root.children.offset != trie->hdr.root_offset;
+
+ if (changed || trie->hdr.root_offset == 0) {
+ node_free(trie, &trie->root);
+ i_zero(&trie->root);
+ trie->root.want_sequential = TRUE;
+ trie->root.unused_uids = trie->hdr.root_unused_uids;
+ trie->root.next_uid = trie->hdr.root_next_uid;
+ trie->root.uid_list_idx = trie->hdr.root_uidlist_idx;
+ trie->root.children.offset = trie->hdr.root_offset;
+
+ if (trie->hdr.root_offset == 0) {
+ trie->unmapped_child_count = 0;
+ trie->root.children_not_mapped = FALSE;
+ } else {
+ trie->unmapped_child_count = 1;
+ trie->root.children_not_mapped = TRUE;
+ }
+ }
+
+ if (ret >= 0 && !building) {
+ /* do this while we're still locked */
+ ret = squat_uidlist_refresh(trie->uidlist);
+ }
+
+ if (file_lock != NULL)
+ file_unlock(&file_lock);
+ if (dotlock != NULL)
+ file_dotlock_delete(&dotlock);
+ if (ret < 0)
+ return -1;
+
+ return trie->hdr.root_offset == 0 || !changed ? 0 :
+ node_read_children(trie, &trie->root, 1);
+}
+
+int squat_trie_create_fd(struct squat_trie *trie, const char *path, int flags)
+{
+ mode_t old_mask;
+ int fd;
+
+ old_mask = umask(0);
+ fd = open(path, O_RDWR | O_CREAT | flags, trie->create_mode);
+ umask(old_mask);
+ if (fd == -1) {
+ i_error("creat(%s) failed: %m", path);
+ return -1;
+ }
+ if (trie->create_gid != (gid_t)-1) {
+ if (fchown(fd, (uid_t)-1, trie->create_gid) < 0) {
+ i_error("fchown(%s, -1, %ld) failed: %m",
+ path, (long)trie->create_gid);
+ i_close_fd(&fd);
+ return -1;
+ }
+ }
+ return fd;
+}
+
+int squat_trie_build_init(struct squat_trie *trie,
+ struct squat_trie_build_context **ctx_r)
+{
+ struct squat_trie_build_context *ctx;
+ struct squat_uidlist_build_context *uidlist_build_ctx;
+
+ if (trie->fd == -1) {
+ trie->fd = squat_trie_create_fd(trie, trie->path, 0);
+ if (trie->fd == -1)
+ return -1;
+
+ if (trie->file_cache != NULL)
+ file_cache_set_fd(trie->file_cache, trie->fd);
+ i_assert(trie->locked_file_size == 0);
+ }
+
+ /* uidlist locks building */
+ if (squat_uidlist_build_init(trie->uidlist, &uidlist_build_ctx) < 0)
+ return -1;
+
+ if (squat_trie_map(trie, TRUE) < 0) {
+ squat_uidlist_build_deinit(&uidlist_build_ctx);
+ return -1;
+ }
+
+ ctx = i_new(struct squat_trie_build_context, 1);
+ ctx->trie = trie;
+ ctx->uidlist_build_ctx = uidlist_build_ctx;
+ ctx->first_uid = trie->root.next_uid;
+
+ *ctx_r = ctx;
+ return 0;
+}
+
+static int squat_trie_write_lock(struct squat_trie_build_context *ctx)
+{
+ if (ctx->file_lock != NULL || ctx->dotlock != NULL)
+ return 0;
+
+ if (squat_trie_lock(ctx->trie, F_WRLCK,
+ &ctx->file_lock, &ctx->dotlock) <= 0)
+ return -1;
+ return 0;
+}
+
+static int squat_trie_write(struct squat_trie_build_context *ctx)
+{
+ struct squat_trie *trie = ctx->trie;
+ struct file_lock *file_lock = NULL;
+ struct ostream *output;
+ const char *path, *error;
+ int fd = -1, ret = 0;
+
+ if ((trie->hdr.used_file_size > sizeof(trie->hdr) &&
+ trie->unmapped_child_count < trie->hdr.node_count/4) || 1) {
+ /* we might as well recreate the file */
+ ctx->compress_nodes = TRUE;
+
+ path = t_strconcat(trie->path, ".tmp", NULL);
+ fd = squat_trie_create_fd(trie, path, O_TRUNC);
+ if (fd == -1)
+ return -1;
+
+ if (trie->lock_method != FILE_LOCK_METHOD_DOTLOCK) {
+ struct file_lock_settings lock_set = {
+ .lock_method = trie->lock_method,
+ };
+ ret = file_wait_lock(fd, path, F_WRLCK, &lock_set,
+ SQUAT_TRIE_LOCK_TIMEOUT,
+ &file_lock, &error);
+ if (ret <= 0) {
+ i_error("file_wait_lock(%s) failed: %s",
+ path, error);
+ i_close_fd(&fd);
+ return -1;
+ }
+ }
+
+ output = o_stream_create_fd(fd, 0);
+ o_stream_cork(output);
+ o_stream_nsend(output, &trie->hdr, sizeof(trie->hdr));
+ } else {
+ /* we need to lock only while header is being written */
+ path = trie->path;
+ ctx->compress_nodes =
+ trie->hdr.used_file_size == sizeof(trie->hdr);
+
+ if (trie->hdr.used_file_size == 0) {
+ /* lock before opening the file, in case we reopen it */
+ if (squat_trie_write_lock(ctx) < 0)
+ return -1;
+ }
+ output = o_stream_create_fd(trie->fd, 0);
+ o_stream_cork(output);
+
+ if (trie->hdr.used_file_size != 0)
+ (void)o_stream_seek(output, trie->hdr.used_file_size);
+ else
+ o_stream_nsend(output, &trie->hdr, sizeof(trie->hdr));
+ }
+
+ ctx->output = output;
+ ret = squat_write_nodes(ctx);
+ ctx->output = NULL;
+
+ /* write 1 byte guard at the end of file, so that we can verify broken
+ squat_unpack_num() input by checking if data==end */
+ o_stream_nsend(output, "", 1);
+
+ if (trie->corrupted)
+ ret = -1;
+ if (ret == 0)
+ ret = squat_trie_write_lock(ctx);
+ if (ret == 0) {
+ trie->hdr.used_file_size = output->offset;
+ (void)o_stream_seek(output, 0);
+ o_stream_nsend(output, &trie->hdr, sizeof(trie->hdr));
+ }
+ if (o_stream_finish(output) < 0) {
+ i_error("write(%s) failed: %s", path,
+ o_stream_get_error(output));
+ ret = -1;
+ }
+ o_stream_destroy(&output);
+
+ if (fd == -1) {
+ /* appended to the existing file */
+ i_assert(file_lock == NULL);
+ return ret;
+ }
+
+ /* recreating the trie file */
+ if (ret < 0) {
+ if (close(fd) < 0)
+ i_error("close(%s) failed: %m", path);
+ fd = -1;
+ } else if (rename(path, trie->path) < 0) {
+ i_error("rename(%s, %s) failed: %m", path, trie->path);
+ ret = -1;
+ }
+
+ if (ret < 0) {
+ i_unlink_if_exists(path);
+ file_lock_free(&file_lock);
+ } else {
+ squat_trie_close_fd(trie);
+ trie->fd = fd;
+ trie->locked_file_size = trie->hdr.used_file_size;
+ if (trie->file_cache != NULL)
+ file_cache_set_fd(trie->file_cache, trie->fd);
+
+ file_lock_free(&ctx->file_lock);
+ ctx->file_lock = file_lock;
+ }
+ return ret;
+}
+
+int squat_trie_build_deinit(struct squat_trie_build_context **_ctx,
+ const ARRAY_TYPE(seq_range) *expunged_uids)
+{
+ struct squat_trie_build_context *ctx = *_ctx;
+ bool compress, unlock = TRUE;
+ int ret;
+
+ *_ctx = NULL;
+
+ compress = (ctx->trie->root.next_uid - ctx->first_uid) > 10;
+
+ /* keep trie locked while header is being written and when files are
+ being renamed, so that while trie is read locked, uidlist can't
+ change under. */
+ squat_uidlist_build_flush(ctx->uidlist_build_ctx);
+ ret = squat_trie_renumber_uidlists(ctx, expunged_uids, compress);
+ if (ret == 0) {
+ ret = squat_trie_write(ctx);
+ if (ret < 0)
+ unlock = FALSE;
+ }
+
+ if (ret == 0)
+ ret = squat_uidlist_build_finish(ctx->uidlist_build_ctx);
+ if (ctx->file_lock != NULL) {
+ if (unlock)
+ file_unlock(&ctx->file_lock);
+ else
+ file_lock_free(&ctx->file_lock);
+ }
+ if (ctx->dotlock != NULL)
+ file_dotlock_delete(&ctx->dotlock);
+ squat_uidlist_build_deinit(&ctx->uidlist_build_ctx);
+
+ i_free(ctx);
+ return ret;
+}
+
+int squat_trie_get_last_uid(struct squat_trie *trie, uint32_t *last_uid_r)
+{
+ if (trie->fd == -1) {
+ if (squat_trie_open(trie) < 0)
+ return -1;
+ }
+
+ *last_uid_r = I_MAX((trie->root.next_uid+1)/2, 1) - 1;
+ return 0;
+}
+
+static int
+squat_trie_lookup_data(struct squat_trie *trie, const unsigned char *data,
+ unsigned int size, ARRAY_TYPE(seq_range) *uids)
+{
+ struct squat_node *node = &trie->root;
+ unsigned char *chars;
+ unsigned int idx;
+ int level = 0;
+
+ array_clear(uids);
+
+ for (;;) {
+ if (node->children_not_mapped) {
+ if (node_read_children(trie, node, level) < 0)
+ return -1;
+ }
+ if (node->leaf_string_length != 0) {
+ unsigned int len = node->leaf_string_length;
+ const unsigned char *str;
+
+ if (len > sizeof(node->children.static_leaf_string))
+ str = node->children.leaf_string;
+ else
+ str = node->children.static_leaf_string;
+
+ if (size > len || memcmp(data, str, size) != 0)
+ return 0;
+
+ /* match */
+ break;
+ }
+
+ if (size == 0)
+ break;
+ level++;
+
+ if (node->have_sequential) {
+ if (*data < SEQUENTIAL_COUNT) {
+ idx = *data;
+ goto found;
+ }
+ idx = SEQUENTIAL_COUNT;
+ } else {
+ idx = 0;
+ }
+ chars = NODE_CHILDREN_CHARS(node);
+ for (; idx < node->child_count; idx++) {
+ if (chars[idx] == *data)
+ goto found;
+ }
+ return 0;
+ found:
+ /* follow to children */
+ if (level == 1) {
+ /* root level, add all UIDs */
+ if (squat_uidlist_get_seqrange(trie->uidlist,
+ node->uid_list_idx,
+ uids) < 0)
+ return -1;
+ } else {
+ if (squat_uidlist_filter(trie->uidlist,
+ node->uid_list_idx, uids) < 0)
+ return -1;
+ }
+ data++;
+ size--;
+ node = NODE_CHILDREN_NODES(node) + idx;
+ }
+
+ if (squat_uidlist_filter(trie->uidlist, node->uid_list_idx, uids) < 0)
+ return -1;
+ return 1;
+}
+
+static void
+squat_trie_filter_type(enum squat_index_type type,
+ const ARRAY_TYPE(seq_range) *src,
+ ARRAY_TYPE(seq_range) *dest)
+{
+ const struct seq_range *src_range;
+ struct seq_range new_range;
+ unsigned int i, count, mask;
+ uint32_t next_seq, uid;
+
+ array_clear(dest);
+ src_range = array_get(src, &count);
+ if (count == 0)
+ return;
+
+ if ((type & SQUAT_INDEX_TYPE_HEADER) != 0 &&
+ (type & SQUAT_INDEX_TYPE_BODY) != 0) {
+ /* everything is fine, just fix the UIDs */
+ new_range.seq1 = src_range[0].seq1 / 2;
+ new_range.seq2 = src_range[0].seq2 / 2;
+ for (i = 1; i < count; i++) {
+ next_seq = src_range[i].seq1 / 2;
+ if (next_seq == new_range.seq2 + 1) {
+ /* we can continue the previous range */
+ } else {
+ array_push_back(dest, &new_range);
+ new_range.seq1 = src_range[i].seq1 / 2;
+ }
+ new_range.seq2 = src_range[i].seq2 / 2;
+ }
+ array_push_back(dest, &new_range);
+ return;
+ }
+
+ /* we'll have to drop either header or body UIDs */
+ mask = (type & SQUAT_INDEX_TYPE_HEADER) != 0 ? 1 : 0;
+ for (i = 0; i < count; i++) {
+ for (uid = src_range[i].seq1; uid <= src_range[i].seq2; uid++) {
+ if ((uid & 1) == mask)
+ seq_range_array_add(dest, uid/2);
+ }
+ }
+}
+
+struct squat_trie_lookup_context {
+ struct squat_trie *trie;
+ enum squat_index_type type;
+
+ ARRAY_TYPE(seq_range) *definite_uids, *maybe_uids;
+ ARRAY_TYPE(seq_range) tmp_uids, tmp_uids2;
+ bool first;
+};
+
+static int
+squat_trie_lookup_partial(struct squat_trie_lookup_context *ctx,
+ const unsigned char *data, uint8_t *char_lengths,
+ unsigned int size)
+{
+ const unsigned int partial_len = ctx->trie->hdr.partial_len;
+ unsigned int char_idx, max_chars, i, j, bytelen;
+ int ret;
+
+ for (i = 0, max_chars = 0; i < size; max_chars++)
+ i += char_lengths[i];
+ i_assert(max_chars > 0);
+
+ i = 0; char_idx = 0;
+ do {
+ bytelen = 0;
+ for (j = 0; j < partial_len && i+bytelen < size; j++)
+ bytelen += char_lengths[i + bytelen];
+
+ ret = squat_trie_lookup_data(ctx->trie, data + i, bytelen,
+ &ctx->tmp_uids);
+ if (ret <= 0) {
+ array_clear(ctx->maybe_uids);
+ return ret;
+ }
+
+ if (ctx->first) {
+ squat_trie_filter_type(ctx->type, &ctx->tmp_uids,
+ ctx->maybe_uids);
+ ctx->first = FALSE;
+ } else {
+ squat_trie_filter_type(ctx->type, &ctx->tmp_uids,
+ &ctx->tmp_uids2);
+ seq_range_array_intersect(ctx->maybe_uids,
+ &ctx->tmp_uids2);
+ }
+ i += char_lengths[i];
+ char_idx++;
+ } while (max_chars - char_idx >= partial_len);
+ return 1;
+}
+
+static void squat_trie_add_unknown(struct squat_trie *trie,
+ ARRAY_TYPE(seq_range) *maybe_uids)
+{
+ struct seq_range *range, new_range;
+ unsigned int count;
+ uint32_t last_uid;
+
+ last_uid = I_MAX((trie->root.next_uid+1)/2, 1) - 1;
+
+ range = array_get_modifiable(maybe_uids, &count);
+ if (count > 0 && range[count-1].seq2 == last_uid) {
+ /* increase the range */
+ range[count-1].seq2 = (uint32_t)-1;
+ } else {
+ new_range.seq1 = last_uid + 1;
+ new_range.seq2 = (uint32_t)-1;
+ array_push_back(maybe_uids, &new_range);
+ }
+}
+
+static int
+squat_trie_lookup_real(struct squat_trie *trie, const char *str,
+ enum squat_index_type type,
+ ARRAY_TYPE(seq_range) *definite_uids,
+ ARRAY_TYPE(seq_range) *maybe_uids)
+{
+ struct squat_trie_lookup_context ctx;
+ unsigned char *data;
+ uint8_t *char_lengths;
+ unsigned int i, start, bytes, str_bytelen, str_charlen;
+ bool searched = FALSE;
+ int ret = 0;
+
+ array_clear(definite_uids);
+ array_clear(maybe_uids);
+
+ i_zero(&ctx);
+ ctx.trie = trie;
+ ctx.type = type;
+ ctx.definite_uids = definite_uids;
+ ctx.maybe_uids = maybe_uids;
+ i_array_init(&ctx.tmp_uids, 128);
+ i_array_init(&ctx.tmp_uids2, 128);
+ ctx.first = TRUE;
+
+ str_bytelen = strlen(str);
+ char_lengths = str_bytelen == 0 ? NULL : t_malloc0(str_bytelen);
+ for (i = 0, str_charlen = 0; i < str_bytelen; str_charlen++) {
+ bytes = uni_utf8_char_bytes(str[i]);
+ char_lengths[i] = bytes;
+ i += bytes;
+ }
+ data = squat_data_normalize(trie, (const unsigned char *)str,
+ str_bytelen);
+
+ for (i = start = 0; i < str_bytelen && ret >= 0; i += char_lengths[i]) {
+ if (data[i] != '\0')
+ continue;
+
+ /* string has nonindexed characters.
+ search it in parts. */
+ if (i != start) {
+ ret = squat_trie_lookup_partial(&ctx, data + start,
+ char_lengths + start,
+ i - start);
+ searched = TRUE;
+ }
+ start = i + char_lengths[i];
+ }
+
+ if (start == 0) {
+ if (str_charlen <= trie->hdr.partial_len ||
+ trie->hdr.full_len > trie->hdr.partial_len) {
+ ret = squat_trie_lookup_data(trie, data, str_bytelen,
+ &ctx.tmp_uids);
+ if (ret > 0) {
+ squat_trie_filter_type(type, &ctx.tmp_uids,
+ definite_uids);
+ }
+ } else {
+ array_clear(definite_uids);
+ }
+
+ if (str_charlen <= trie->hdr.partial_len ||
+ trie->hdr.partial_len == 0) {
+ /* we have the result */
+ array_clear(maybe_uids);
+ } else {
+ ret = squat_trie_lookup_partial(&ctx, data + start,
+ char_lengths + start,
+ i - start);
+ }
+ } else if (str_bytelen > 0) {
+ /* string has nonindexed characters. finish the search. */
+ array_clear(definite_uids);
+ if (i != start && ret >= 0) {
+ ret = squat_trie_lookup_partial(&ctx, data + start,
+ char_lengths + start,
+ i - start);
+ } else if (!searched) {
+ /* string has only nonindexed chars,
+ list all root UIDs as maybes */
+ ret = squat_uidlist_get_seqrange(trie->uidlist,
+ trie->root.uid_list_idx,
+ &ctx.tmp_uids);
+ squat_trie_filter_type(type, &ctx.tmp_uids,
+ maybe_uids);
+ }
+ } else {
+ /* zero string length - list all root UIDs as definite
+ answers */
+#if 0 /* FIXME: this code is never actually reached now. */
+ ret = squat_uidlist_get_seqrange(trie->uidlist,
+ trie->root.uid_list_idx,
+ &ctx.tmp_uids);
+ squat_trie_filter_type(type, &ctx.tmp_uids,
+ definite_uids);
+#else
+ i_unreached();
+#endif
+ }
+ seq_range_array_remove_seq_range(maybe_uids, definite_uids);
+ squat_trie_add_unknown(trie, maybe_uids);
+ array_free(&ctx.tmp_uids);
+ array_free(&ctx.tmp_uids2);
+ return ret < 0 ? -1 : 0;
+}
+
+int squat_trie_lookup(struct squat_trie *trie, const char *str,
+ enum squat_index_type type,
+ ARRAY_TYPE(seq_range) *definite_uids,
+ ARRAY_TYPE(seq_range) *maybe_uids)
+{
+ int ret;
+
+ T_BEGIN {
+ ret = squat_trie_lookup_real(trie, str, type,
+ definite_uids, maybe_uids);
+ } T_END;
+ return ret;
+}
+
+struct squat_uidlist *squat_trie_get_uidlist(struct squat_trie *trie)
+{
+ return trie->uidlist;
+}
+
+size_t squat_trie_mem_used(struct squat_trie *trie, unsigned int *count_r)
+{
+ *count_r = trie->hdr.node_count;
+ return trie->node_alloc_size;
+}
diff --git a/src/plugins/fts-squat/squat-trie.h b/src/plugins/fts-squat/squat-trie.h
new file mode 100644
index 0000000..91530b8
--- /dev/null
+++ b/src/plugins/fts-squat/squat-trie.h
@@ -0,0 +1,54 @@
+#ifndef SQUAT_TRIE_H
+#define SQUAT_TRIE_H
+
+#include "file-lock.h"
+#include "seq-range-array.h"
+
+enum squat_index_flags {
+ SQUAT_INDEX_FLAG_MMAP_DISABLE = 0x01,
+ SQUAT_INDEX_FLAG_NFS_FLUSH = 0x02,
+ SQUAT_INDEX_FLAG_DOTLOCK_USE_EXCL = 0x04
+};
+
+enum squat_index_type {
+ SQUAT_INDEX_TYPE_HEADER = 0x01,
+ SQUAT_INDEX_TYPE_BODY = 0x02
+};
+
+struct squat_trie_build_context;
+
+struct squat_trie *
+squat_trie_init(const char *path, uint32_t uidvalidity,
+ enum file_lock_method lock_method,
+ enum squat_index_flags flags, mode_t mode, gid_t gid);
+void squat_trie_deinit(struct squat_trie **trie);
+
+void squat_trie_set_partial_len(struct squat_trie *trie, unsigned int len);
+void squat_trie_set_full_len(struct squat_trie *trie, unsigned int len);
+
+int squat_trie_open(struct squat_trie *trie);
+int squat_trie_refresh(struct squat_trie *trie);
+
+int squat_trie_build_init(struct squat_trie *trie,
+ struct squat_trie_build_context **ctx_r);
+/* bodies must be added before headers */
+int squat_trie_build_more(struct squat_trie_build_context *ctx,
+ uint32_t uid, enum squat_index_type type,
+ const unsigned char *data, unsigned int size);
+/* if expunged_uids is non-NULL, they may be removed from the index if they
+ still exist. */
+int squat_trie_build_deinit(struct squat_trie_build_context **ctx,
+ const ARRAY_TYPE(seq_range) *expunged_uids)
+ ATTR_NULL(2);
+
+int squat_trie_get_last_uid(struct squat_trie *trie, uint32_t *last_uid_r);
+/* type specifies if we're looking at header, body or both */
+int squat_trie_lookup(struct squat_trie *trie, const char *str,
+ enum squat_index_type type,
+ ARRAY_TYPE(seq_range) *definite_uids,
+ ARRAY_TYPE(seq_range) *maybe_uids);
+
+struct squat_uidlist *squat_trie_get_uidlist(struct squat_trie *trie);
+size_t squat_trie_mem_used(struct squat_trie *trie, unsigned int *count_r);
+
+#endif
diff --git a/src/plugins/fts-squat/squat-uidlist.c b/src/plugins/fts-squat/squat-uidlist.c
new file mode 100644
index 0000000..facb8d0
--- /dev/null
+++ b/src/plugins/fts-squat/squat-uidlist.c
@@ -0,0 +1,1624 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "sort.h"
+#include "bsearch-insert-pos.h"
+#include "file-cache.h"
+#include "file-lock.h"
+#include "read-full.h"
+#include "write-full.h"
+#include "ostream.h"
+#include "mmap-util.h"
+#include "squat-trie-private.h"
+#include "squat-uidlist.h"
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#define UIDLIST_LIST_SIZE 31
+#define UIDLIST_BLOCK_LIST_COUNT 100
+#define UID_LIST_MASK_RANGE 0x80000000U
+
+/* set = points to uidlist index number, unset = points to uidlist offset */
+#define UID_LIST_POINTER_MASK_LIST_IDX 0x80000000U
+
+#define UIDLIST_PACKED_FLAG_BITMASK 1
+#define UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER 2
+
+struct uidlist_list {
+ unsigned int uid_count:31;
+ bool uid_begins_with_pointer:1;
+ uint32_t uid_list[UIDLIST_LIST_SIZE];
+};
+
+struct squat_uidlist {
+ struct squat_trie *trie;
+
+ char *path;
+ int fd;
+ struct file_cache *file_cache;
+
+ struct file_lock *file_lock;
+ struct dotlock *dotlock;
+ uoff_t locked_file_size;
+
+ void *mmap_base;
+ size_t mmap_size;
+ struct squat_uidlist_file_header hdr;
+
+ const void *data;
+ size_t data_size;
+
+ unsigned int cur_block_count;
+ const uint32_t *cur_block_offsets;
+ const uint32_t *cur_block_end_indexes;
+
+ size_t max_size;
+ bool corrupted:1;
+ bool building:1;
+};
+
+struct squat_uidlist_build_context {
+ struct squat_uidlist *uidlist;
+ struct ostream *output;
+
+ ARRAY_TYPE(uint32_t) block_offsets;
+ ARRAY_TYPE(uint32_t) block_end_indexes;
+
+ ARRAY(struct uidlist_list) lists;
+ uint32_t list_start_idx;
+
+ struct squat_uidlist_file_header build_hdr;
+ bool need_reopen:1;
+};
+
+struct squat_uidlist_rebuild_context {
+ struct squat_uidlist *uidlist;
+ struct squat_uidlist_build_context *build_ctx;
+
+ int fd;
+ struct ostream *output;
+
+ ARRAY_TYPE(uint32_t) new_block_offsets, new_block_end_indexes;
+ uoff_t cur_block_start_offset;
+
+ uint32_t list_sizes[UIDLIST_BLOCK_LIST_COUNT];
+ uint32_t next_uid_list_idx;
+ unsigned int list_idx;
+ unsigned int new_count;
+};
+
+static void squat_uidlist_close(struct squat_uidlist *uidlist);
+
+void squat_uidlist_delete(struct squat_uidlist *uidlist)
+{
+ i_unlink_if_exists(uidlist->path);
+}
+
+static void squat_uidlist_set_corrupted(struct squat_uidlist *uidlist,
+ const char *reason)
+{
+ if (uidlist->corrupted)
+ return;
+ uidlist->corrupted = TRUE;
+
+ i_error("Corrupted squat uidlist file %s: %s", uidlist->path, reason);
+ squat_trie_delete(uidlist->trie);
+}
+
+static int
+uidlist_write_array(struct ostream *output, const uint32_t *uid_list,
+ unsigned int uid_count, uint32_t packed_flags,
+ uint32_t offset, bool write_size, uint32_t *size_r)
+{
+ uint8_t *uidbuf, *bufp, sizebuf[SQUAT_PACK_MAX_SIZE], *sizebufp;
+ uint8_t listbuf[SQUAT_PACK_MAX_SIZE], *listbufp = listbuf;
+ uint32_t uid, uid2, prev, base_uid, size_value;
+ unsigned int i, bitmask_len, uid_list_len;
+ unsigned int idx, max_idx, mask;
+ bool datastack;
+ int num;
+
+ if ((packed_flags & UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER) != 0)
+ squat_pack_num(&listbufp, offset);
+
+ /* @UNSAFE */
+ base_uid = uid_list[0] & ~UID_LIST_MASK_RANGE;
+ datastack = uid_count < 1024*8/SQUAT_PACK_MAX_SIZE;
+ if (datastack)
+ uidbuf = t_malloc_no0(SQUAT_PACK_MAX_SIZE * uid_count);
+ else
+ uidbuf = i_malloc(SQUAT_PACK_MAX_SIZE * uid_count);
+ bufp = uidbuf;
+ squat_pack_num(&bufp, base_uid);
+
+ bitmask_len = (uid_list[uid_count-1] - base_uid + 7) / 8 +
+ (bufp - uidbuf);
+ if (bitmask_len < uid_count) {
+ bitmask_build:
+ i_assert(bitmask_len < SQUAT_PACK_MAX_SIZE*uid_count);
+
+ memset(bufp, 0, bitmask_len - (bufp - uidbuf));
+ if ((uid_list[0] & UID_LIST_MASK_RANGE) == 0) {
+ i = 1;
+ uid = i == uid_count ? 0 : uid_list[i];
+ } else {
+ i = 0;
+ uid = uid_list[0] + 1;
+ }
+ base_uid++;
+
+ for (; i < uid_count; i++) {
+ i_assert((uid & ~UID_LIST_MASK_RANGE) >= base_uid);
+ if ((uid & UID_LIST_MASK_RANGE) == 0) {
+ uid -= base_uid;
+ uid2 = uid;
+ } else {
+ uid &= ~UID_LIST_MASK_RANGE;
+ uid -= base_uid;
+ uid2 = uid_list[i+1] - base_uid;
+ i++;
+ }
+
+ if (uid2 - uid < 3*8) {
+ for (; uid <= uid2; uid++)
+ bufp[uid / 8] |= 1 << (uid % 8);
+ } else {
+ /* first byte */
+ idx = uid / 8;
+ num = uid % 8;
+ if (num != 0) {
+ uid += 8 - num;
+ for (mask = 0; num < 8; num++)
+ mask |= 1 << num;
+ bufp[idx++] |= mask;
+ }
+
+ /* middle bytes */
+ num = uid2 % 8;
+ max_idx = idx + (uid2 - num - uid)/8;
+ for (; idx < max_idx; idx++, uid += 8)
+ bufp[idx] = 0xff;
+
+ /* last byte */
+ for (mask = 0; num >= 0; num--)
+ mask |= 1 << num;
+ bufp[idx] |= mask;
+ }
+ uid = i+1 == uid_count ? 0 : uid_list[i+1];
+ }
+ uid_list_len = bitmask_len;
+ packed_flags |= UIDLIST_PACKED_FLAG_BITMASK;
+ } else {
+ bufp = uidbuf;
+ prev = 0;
+ for (i = 0; i < uid_count; i++) {
+ uid = uid_list[i];
+ if (unlikely((uid & ~UID_LIST_MASK_RANGE) < prev)) {
+ if (!datastack)
+ i_free(uidbuf);
+ return -1;
+ }
+ if ((uid & UID_LIST_MASK_RANGE) == 0) {
+ squat_pack_num(&bufp, (uid - prev) << 1);
+ prev = uid + 1;
+ } else {
+ uid &= ~UID_LIST_MASK_RANGE;
+ squat_pack_num(&bufp, 1 | (uid - prev) << 1);
+ squat_pack_num(&bufp, uid_list[i+1] - uid - 1);
+ prev = uid_list[i+1] + 1;
+ i++;
+ }
+ }
+ uid_list_len = bufp - uidbuf;
+ if (uid_list_len > bitmask_len) {
+ bufp = uidbuf;
+ squat_pack_num(&bufp, base_uid);
+ goto bitmask_build;
+ }
+ }
+
+ size_value = ((uid_list_len +
+ (listbufp - listbuf)) << 2) | packed_flags;
+ if (write_size) {
+ sizebufp = sizebuf;
+ squat_pack_num(&sizebufp, size_value);
+ o_stream_nsend(output, sizebuf, sizebufp - sizebuf);
+ }
+ o_stream_nsend(output, listbuf, listbufp - listbuf);
+ o_stream_nsend(output, uidbuf, uid_list_len);
+ if (!datastack)
+ i_free(uidbuf);
+
+ *size_r = size_value;
+ return 0;
+}
+
+static int
+uidlist_write(struct ostream *output, const struct uidlist_list *list,
+ bool write_size, uint32_t *size_r)
+{
+ const uint32_t *uid_list = list->uid_list;
+ uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp;
+ uint32_t uid_count = list->uid_count;
+ uint32_t packed_flags = 0;
+ uint32_t offset = 0;
+ int ret;
+
+ if (list->uid_begins_with_pointer) {
+ /* continued UID list */
+ packed_flags |= UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER;
+ if ((uid_list[0] & UID_LIST_POINTER_MASK_LIST_IDX) != 0) {
+ offset = ((uid_list[0] & ~UID_LIST_POINTER_MASK_LIST_IDX) << 1) | 1;
+ if (list->uid_count == 1) {
+ bufp = buf;
+ squat_pack_num(&bufp, offset);
+ o_stream_nsend(output, buf, bufp - buf);
+ *size_r = (bufp - buf) << 2 | packed_flags;
+ return 0;
+ }
+ } else if (unlikely(output->offset <= uid_list[0])) {
+ i_assert(output->closed);
+ return -1;
+ } else {
+ i_assert(list->uid_count > 1);
+ offset = (output->offset - uid_list[0]) << 1;
+ }
+ uid_list++;
+ uid_count--;
+ }
+
+ T_BEGIN {
+ ret = uidlist_write_array(output, uid_list, uid_count,
+ packed_flags, offset,
+ write_size, size_r);
+ } T_END;
+ return ret;
+}
+
+static void squat_uidlist_map_blocks_set_pointers(struct squat_uidlist *uidlist)
+{
+ const void *base;
+ size_t end_index_size, end_size;
+
+ base = CONST_PTR_OFFSET(uidlist->data, uidlist->hdr.block_list_offset +
+ sizeof(uint32_t));
+
+ end_index_size = uidlist->cur_block_count * sizeof(uint32_t);
+ end_size = end_index_size + uidlist->cur_block_count * sizeof(uint32_t);
+ if (end_size <= uidlist->data_size) {
+ uidlist->cur_block_end_indexes = base;
+ uidlist->cur_block_offsets =
+ CONST_PTR_OFFSET(base, end_index_size);
+ } else {
+ uidlist->cur_block_end_indexes = NULL;
+ uidlist->cur_block_offsets = NULL;
+ }
+}
+
+static int uidlist_file_cache_read(struct squat_uidlist *uidlist,
+ size_t offset, size_t size)
+{
+ if (uidlist->file_cache == NULL)
+ return 0;
+
+ if (file_cache_read(uidlist->file_cache, offset, size) < 0) {
+ i_error("read(%s) failed: %m", uidlist->path);
+ return -1;
+ }
+ uidlist->data = file_cache_get_map(uidlist->file_cache,
+ &uidlist->data_size);
+ squat_uidlist_map_blocks_set_pointers(uidlist);
+ return 0;
+}
+
+static int squat_uidlist_map_blocks(struct squat_uidlist *uidlist)
+{
+ const struct squat_uidlist_file_header *hdr = &uidlist->hdr;
+ const void *base;
+ uint32_t block_count, blocks_offset, blocks_size, i, verify_count;
+
+ if (hdr->block_list_offset == 0) {
+ /* empty file */
+ uidlist->cur_block_count = 0;
+ return 1;
+ }
+
+ /* get number of blocks */
+ if (uidlist_file_cache_read(uidlist, hdr->block_list_offset,
+ sizeof(block_count)) < 0)
+ return -1;
+ blocks_offset = hdr->block_list_offset + sizeof(block_count);
+ if (blocks_offset > uidlist->data_size) {
+ squat_uidlist_set_corrupted(uidlist, "block list outside file");
+ return 0;
+ }
+
+ i_assert(uidlist->data != NULL);
+ base = CONST_PTR_OFFSET(uidlist->data, hdr->block_list_offset);
+ memcpy(&block_count, base, sizeof(block_count));
+
+ /* map the blocks */
+ blocks_size = block_count * sizeof(uint32_t)*2;
+ if (uidlist_file_cache_read(uidlist, blocks_offset, blocks_size) < 0)
+ return -1;
+ if (blocks_offset + blocks_size > uidlist->data_size) {
+ squat_uidlist_set_corrupted(uidlist, "block list outside file");
+ return 0;
+ }
+
+ uidlist->cur_block_count = block_count;
+ squat_uidlist_map_blocks_set_pointers(uidlist);
+
+ i_assert(uidlist->cur_block_end_indexes != NULL);
+
+ /* verify just a couple of the end indexes to make sure they
+ look correct */
+ verify_count = I_MIN(block_count, 8);
+ for (i = 1; i < verify_count; i++) {
+ if (unlikely(uidlist->cur_block_end_indexes[i-1] >=
+ uidlist->cur_block_end_indexes[i])) {
+ squat_uidlist_set_corrupted(uidlist,
+ "block list corrupted");
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int squat_uidlist_map_header(struct squat_uidlist *uidlist)
+{
+ if (uidlist->hdr.indexid == 0) {
+ /* still being built */
+ return 1;
+ }
+ if (uidlist->hdr.indexid != uidlist->trie->hdr.indexid) {
+ /* see if trie was recreated */
+ (void)squat_trie_open(uidlist->trie);
+ }
+ if (uidlist->hdr.indexid != uidlist->trie->hdr.indexid) {
+ squat_uidlist_set_corrupted(uidlist, "wrong indexid");
+ return 0;
+ }
+ if (uidlist->hdr.used_file_size < sizeof(uidlist->hdr) ||
+ (uidlist->hdr.used_file_size > uidlist->mmap_size &&
+ uidlist->mmap_base != NULL)) {
+ squat_uidlist_set_corrupted(uidlist, "broken used_file_size");
+ return 0;
+ }
+ return squat_uidlist_map_blocks(uidlist);
+}
+
+static void squat_uidlist_unmap(struct squat_uidlist *uidlist)
+{
+ if (uidlist->mmap_size != 0) {
+ if (munmap(uidlist->mmap_base, uidlist->mmap_size) < 0)
+ i_error("munmap(%s) failed: %m", uidlist->path);
+ uidlist->mmap_base = NULL;
+ uidlist->mmap_size = 0;
+ }
+ uidlist->cur_block_count = 0;
+ uidlist->cur_block_end_indexes = NULL;
+ uidlist->cur_block_offsets = NULL;
+}
+
+static int squat_uidlist_mmap(struct squat_uidlist *uidlist)
+{
+ struct stat st;
+
+ if (fstat(uidlist->fd, &st) < 0) {
+ i_error("fstat(%s) failed: %m", uidlist->path);
+ return -1;
+ }
+ if (st.st_size < (off_t)sizeof(uidlist->hdr)) {
+ squat_uidlist_set_corrupted(uidlist, "File too small");
+ return -1;
+ }
+
+ squat_uidlist_unmap(uidlist);
+ uidlist->mmap_size = st.st_size;
+ uidlist->mmap_base = mmap(NULL, uidlist->mmap_size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, uidlist->fd, 0);
+ if (uidlist->mmap_base == MAP_FAILED) {
+ uidlist->data = uidlist->mmap_base = NULL;
+ uidlist->data_size = uidlist->mmap_size = 0;
+ i_error("mmap(%s) failed: %m", uidlist->path);
+ return -1;
+ }
+ uidlist->data = uidlist->mmap_base;
+ uidlist->data_size = uidlist->mmap_size;
+ return 0;
+}
+
+static int squat_uidlist_map(struct squat_uidlist *uidlist)
+{
+ const struct squat_uidlist_file_header *mmap_hdr = uidlist->mmap_base;
+ int ret;
+
+ if (mmap_hdr != NULL && !uidlist->building &&
+ uidlist->hdr.block_list_offset == mmap_hdr->block_list_offset) {
+ /* file hasn't changed */
+ return 1;
+ }
+
+ if ((uidlist->trie->flags & SQUAT_INDEX_FLAG_MMAP_DISABLE) == 0) {
+ if (mmap_hdr == NULL || uidlist->building ||
+ uidlist->mmap_size < mmap_hdr->used_file_size) {
+ if (squat_uidlist_mmap(uidlist) < 0)
+ return -1;
+ }
+
+ if (!uidlist->building) {
+ memcpy(&uidlist->hdr, uidlist->mmap_base,
+ sizeof(uidlist->hdr));
+ }
+ } else if (uidlist->building) {
+ /* we want to update blocks mapping, but using the header
+ in memory */
+ } else {
+ ret = pread_full(uidlist->fd, &uidlist->hdr,
+ sizeof(uidlist->hdr), 0);
+ if (ret <= 0) {
+ if (ret < 0) {
+ i_error("pread(%s) failed: %m", uidlist->path);
+ return -1;
+ }
+ i_error("Corrupted %s: File too small", uidlist->path);
+ return 0;
+ }
+ uidlist->data = NULL;
+ uidlist->data_size = 0;
+ }
+ if (uidlist->file_cache == NULL &&
+ (uidlist->trie->flags & SQUAT_INDEX_FLAG_MMAP_DISABLE) != 0)
+ uidlist->file_cache = file_cache_new_path(uidlist->fd, uidlist->path);
+ return squat_uidlist_map_header(uidlist);
+}
+
+static int squat_uidlist_read_to_memory(struct squat_uidlist *uidlist)
+{
+ size_t i, page_size = mmap_get_page_size();
+
+ if (uidlist->file_cache != NULL) {
+ return uidlist_file_cache_read(uidlist, 0,
+ uidlist->hdr.used_file_size);
+ }
+ /* Tell the kernel we're going to use the uidlist data, so it loads
+ it into memory and keeps it there. */
+ (void)madvise(uidlist->mmap_base, uidlist->mmap_size, MADV_WILLNEED);
+ /* It also speeds up a bit for us to sequentially load everything
+ into memory, although at least Linux catches up quite fast even
+ without this code. Compiler can quite easily optimize away this
+ entire for loop, but volatile seems to help with gcc 4.2. */
+ for (i = 0; i < uidlist->mmap_size; i += page_size)
+ ((const volatile char *)uidlist->data)[i];
+ return 0;
+}
+
+static void squat_uidlist_free_from_memory(struct squat_uidlist *uidlist)
+{
+ size_t page_size = mmap_get_page_size();
+
+ if (uidlist->file_cache != NULL) {
+ file_cache_invalidate(uidlist->file_cache,
+ page_size, UOFF_T_MAX);
+ } else {
+ (void)madvise(uidlist->mmap_base, uidlist->mmap_size,
+ MADV_DONTNEED);
+ }
+}
+
+struct squat_uidlist *squat_uidlist_init(struct squat_trie *trie)
+{
+ struct squat_uidlist *uidlist;
+
+ uidlist = i_new(struct squat_uidlist, 1);
+ uidlist->trie = trie;
+ uidlist->path = i_strconcat(trie->path, ".uids", NULL);
+ uidlist->fd = -1;
+
+ return uidlist;
+}
+
+void squat_uidlist_deinit(struct squat_uidlist *uidlist)
+{
+ squat_uidlist_close(uidlist);
+
+ i_free(uidlist->path);
+ i_free(uidlist);
+}
+
+static int squat_uidlist_open(struct squat_uidlist *uidlist)
+{
+ squat_uidlist_close(uidlist);
+
+ uidlist->fd = open(uidlist->path, O_RDWR);
+ if (uidlist->fd == -1) {
+ if (errno == ENOENT) {
+ i_zero(&uidlist->hdr);
+ return 0;
+ }
+ i_error("open(%s) failed: %m", uidlist->path);
+ return -1;
+ }
+ return squat_uidlist_map(uidlist) <= 0 ? -1 : 0;
+}
+
+static void squat_uidlist_close(struct squat_uidlist *uidlist)
+{
+ i_assert(!uidlist->building);
+
+ squat_uidlist_unmap(uidlist);
+ if (uidlist->file_cache != NULL)
+ file_cache_free(&uidlist->file_cache);
+ file_lock_free(&uidlist->file_lock);
+ if (uidlist->dotlock != NULL)
+ file_dotlock_delete(&uidlist->dotlock);
+ i_close_fd_path(&uidlist->fd, uidlist->path);
+ uidlist->corrupted = FALSE;
+}
+
+int squat_uidlist_refresh(struct squat_uidlist *uidlist)
+{
+ /* we assume here that trie is locked, so that we don't need to worry
+ about it when reading the header */
+ if (uidlist->fd == -1 ||
+ uidlist->hdr.indexid != uidlist->trie->hdr.indexid) {
+ if (squat_uidlist_open(uidlist) < 0)
+ return -1;
+ } else {
+ if (squat_uidlist_map(uidlist) <= 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int squat_uidlist_is_file_stale(struct squat_uidlist *uidlist)
+{
+ struct stat st, st2;
+
+ i_assert(uidlist->fd != -1);
+
+ if (stat(uidlist->path, &st) < 0) {
+ if (errno == ENOENT)
+ return 1;
+
+ i_error("stat(%s) failed: %m", uidlist->path);
+ return -1;
+ }
+ if (fstat(uidlist->fd, &st2) < 0) {
+ i_error("fstat(%s) failed: %m", uidlist->path);
+ return -1;
+ }
+ uidlist->locked_file_size = st2.st_size;
+
+ return st.st_ino == st2.st_ino &&
+ CMP_DEV_T(st.st_dev, st2.st_dev) ? 0 : 1;
+}
+
+static int squat_uidlist_lock(struct squat_uidlist *uidlist)
+{
+ const char *error;
+ int ret;
+
+ for (;;) {
+ i_assert(uidlist->fd != -1);
+ i_assert(uidlist->file_lock == NULL);
+ i_assert(uidlist->dotlock == NULL);
+
+ if (uidlist->trie->lock_method != FILE_LOCK_METHOD_DOTLOCK) {
+ struct file_lock_settings lock_set = {
+ .lock_method = uidlist->trie->lock_method,
+ };
+ ret = file_wait_lock(uidlist->fd, uidlist->path,
+ F_WRLCK, &lock_set,
+ SQUAT_TRIE_LOCK_TIMEOUT,
+ &uidlist->file_lock, &error);
+ if (ret < 0) {
+ i_error("squat uidlist %s: %s",
+ uidlist->path, error);
+ }
+ } else {
+ ret = file_dotlock_create(&uidlist->trie->dotlock_set,
+ uidlist->path, 0,
+ &uidlist->dotlock);
+ }
+ if (ret == 0) {
+ i_error("squat uidlist %s: Locking timed out",
+ uidlist->path);
+ return 0;
+ }
+ if (ret < 0)
+ return -1;
+
+ ret = squat_uidlist_is_file_stale(uidlist);
+ if (ret == 0)
+ break;
+
+ if (uidlist->file_lock != NULL)
+ file_unlock(&uidlist->file_lock);
+ else
+ file_dotlock_delete(&uidlist->dotlock);
+ if (ret < 0)
+ return -1;
+
+ squat_uidlist_close(uidlist);
+ uidlist->fd = squat_trie_create_fd(uidlist->trie,
+ uidlist->path, 0);
+ if (uidlist->fd == -1)
+ return -1;
+ }
+ return 1;
+}
+
+static int squat_uidlist_open_or_create(struct squat_uidlist *uidlist)
+{
+ int ret;
+
+ if (uidlist->fd == -1) {
+ uidlist->fd = squat_trie_create_fd(uidlist->trie,
+ uidlist->path, 0);
+ if (uidlist->fd == -1)
+ return -1;
+ }
+ if (squat_uidlist_lock(uidlist) <= 0)
+ return -1;
+
+ if (uidlist->locked_file_size != 0) {
+ if ((ret = squat_uidlist_map(uidlist)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* broken file, truncate */
+ if (ftruncate(uidlist->fd, 0) < 0) {
+ i_error("ftruncate(%s) failed: %m",
+ uidlist->path);
+ return -1;
+ }
+ uidlist->locked_file_size = 0;
+ }
+ }
+ if (uidlist->locked_file_size == 0) {
+ /* write using 0 until we're finished */
+ i_zero(&uidlist->hdr);
+ if (write_full(uidlist->fd, &uidlist->hdr,
+ sizeof(uidlist->hdr)) < 0) {
+ i_error("write(%s) failed: %m", uidlist->path);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int squat_uidlist_build_init(struct squat_uidlist *uidlist,
+ struct squat_uidlist_build_context **ctx_r)
+{
+ struct squat_uidlist_build_context *ctx;
+ int ret;
+
+ i_assert(!uidlist->building);
+
+ ret = squat_uidlist_open_or_create(uidlist);
+ if (ret == 0 &&
+ lseek(uidlist->fd, uidlist->hdr.used_file_size, SEEK_SET) < 0) {
+ i_error("lseek(%s) failed: %m", uidlist->path);
+ ret = -1;
+ }
+
+ if (ret < 0) {
+ if (uidlist->file_lock != NULL)
+ file_unlock(&uidlist->file_lock);
+ if (uidlist->dotlock != NULL)
+ file_dotlock_delete(&uidlist->dotlock);
+ return -1;
+ }
+
+ ctx = i_new(struct squat_uidlist_build_context, 1);
+ ctx->uidlist = uidlist;
+ ctx->output = o_stream_create_fd(uidlist->fd, 0);
+ if (ctx->output->offset == 0) {
+ struct squat_uidlist_file_header hdr;
+
+ i_zero(&hdr);
+ o_stream_nsend(ctx->output, &hdr, sizeof(hdr));
+ }
+ o_stream_cork(ctx->output);
+ i_array_init(&ctx->lists, 10240);
+ i_array_init(&ctx->block_offsets, 128);
+ i_array_init(&ctx->block_end_indexes, 128);
+ ctx->list_start_idx = uidlist->hdr.count;
+ ctx->build_hdr = uidlist->hdr;
+
+ uidlist->building = TRUE;
+ *ctx_r = ctx;
+ return 0;
+}
+
+static void
+uidlist_write_block_list_and_header(struct squat_uidlist_build_context *ctx,
+ struct ostream *output,
+ ARRAY_TYPE(uint32_t) *block_offsets,
+ ARRAY_TYPE(uint32_t) *block_end_indexes,
+ bool write_old_blocks)
+{
+ struct squat_uidlist *uidlist = ctx->uidlist;
+ unsigned int align, old_block_count, new_block_count;
+ uint32_t block_offset_count;
+ uoff_t block_list_offset;
+
+ i_assert(uidlist->trie->hdr.indexid != 0);
+ ctx->build_hdr.indexid = uidlist->trie->hdr.indexid;
+
+ if (array_count(block_end_indexes) == 0) {
+ ctx->build_hdr.used_file_size = output->offset;
+ ctx->build_hdr.block_list_offset = 0;
+ uidlist->hdr = ctx->build_hdr;
+ return;
+ }
+
+ align = output->offset % sizeof(uint32_t);
+ if (align != 0) {
+ static char null[sizeof(uint32_t)-1] = { 0, };
+
+ o_stream_nsend(output, null, sizeof(uint32_t) - align);
+ }
+ block_list_offset = output->offset;
+
+ new_block_count = array_count(block_offsets);
+ old_block_count = write_old_blocks ? uidlist->cur_block_count : 0;
+
+ block_offset_count = new_block_count + old_block_count;
+ o_stream_nsend(output, &block_offset_count, sizeof(block_offset_count));
+ /* write end indexes */
+ o_stream_nsend(output, uidlist->cur_block_end_indexes,
+ old_block_count * sizeof(uint32_t));
+ o_stream_nsend(output, array_front(block_end_indexes),
+ new_block_count * sizeof(uint32_t));
+ /* write offsets */
+ o_stream_nsend(output, uidlist->cur_block_offsets,
+ old_block_count * sizeof(uint32_t));
+ o_stream_nsend(output, array_front(block_offsets),
+ new_block_count * sizeof(uint32_t));
+ (void)o_stream_flush(output);
+
+ /* update header - it's written later when trie is locked */
+ ctx->build_hdr.block_list_offset = block_list_offset;
+ ctx->build_hdr.used_file_size = output->offset;
+ uidlist->hdr = ctx->build_hdr;
+}
+
+void squat_uidlist_build_flush(struct squat_uidlist_build_context *ctx)
+{
+ struct uidlist_list *lists;
+ uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp;
+ unsigned int i, j, count, max;
+ uint32_t block_offset, block_end_idx, start_offset;
+ uint32_t list_sizes[UIDLIST_BLOCK_LIST_COUNT];
+ size_t mem_size;
+
+ if (ctx->uidlist->corrupted)
+ return;
+
+ lists = array_get_modifiable(&ctx->lists, &count);
+ if (count == 0)
+ return;
+
+ /* write the lists and save the written sizes to uid_list[0] */
+ for (i = 0; i < count; i += UIDLIST_BLOCK_LIST_COUNT) {
+ start_offset = ctx->output->offset;
+ max = I_MIN(count - i, UIDLIST_BLOCK_LIST_COUNT);
+ for (j = 0; j < max; j++) {
+ if (uidlist_write(ctx->output, &lists[i+j],
+ FALSE, &list_sizes[j]) < 0) {
+ squat_uidlist_set_corrupted(ctx->uidlist,
+ "Broken uidlists");
+ return;
+ }
+ }
+
+ block_offset = ctx->output->offset;
+ block_end_idx = ctx->list_start_idx + i + max;
+ array_push_back(&ctx->block_offsets, &block_offset);
+ array_push_back(&ctx->block_end_indexes, &block_end_idx);
+
+ /* write the full size of the uidlists */
+ bufp = buf;
+ squat_pack_num(&bufp, block_offset - start_offset);
+ o_stream_nsend(ctx->output, buf, bufp - buf);
+
+ /* write the sizes/flags */
+ for (j = 0; j < max; j++) {
+ bufp = buf;
+ squat_pack_num(&bufp, list_sizes[j]);
+ o_stream_nsend(ctx->output, buf, bufp - buf);
+ }
+ }
+
+ mem_size = ctx->lists.arr.buffer->used +
+ ctx->block_offsets.arr.buffer->used +
+ ctx->block_end_indexes.arr.buffer->used;
+ if (ctx->uidlist->max_size < mem_size)
+ ctx->uidlist->max_size = mem_size;
+
+ ctx->list_start_idx += count;
+ array_clear(&ctx->lists);
+
+ uidlist_write_block_list_and_header(ctx, ctx->output,
+ &ctx->block_offsets,
+ &ctx->block_end_indexes, TRUE);
+
+ (void)squat_uidlist_map(ctx->uidlist);
+
+ array_clear(&ctx->block_offsets);
+ array_clear(&ctx->block_end_indexes);
+}
+
+int squat_uidlist_build_finish(struct squat_uidlist_build_context *ctx)
+{
+ if (ctx->uidlist->corrupted)
+ return -1;
+
+ if (!ctx->output->closed) {
+ (void)o_stream_seek(ctx->output, 0);
+ o_stream_nsend(ctx->output,
+ &ctx->build_hdr, sizeof(ctx->build_hdr));
+ (void)o_stream_seek(ctx->output, ctx->build_hdr.used_file_size);
+ }
+
+ if (o_stream_finish(ctx->output) < 0) {
+ i_error("write() to %s failed: %s", ctx->uidlist->path,
+ o_stream_get_error(ctx->output));
+ return -1;
+ }
+ return 0;
+}
+
+void squat_uidlist_build_deinit(struct squat_uidlist_build_context **_ctx)
+{
+ struct squat_uidlist_build_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+
+ i_assert(array_count(&ctx->lists) == 0 || ctx->uidlist->corrupted);
+ i_assert(ctx->uidlist->building);
+ ctx->uidlist->building = FALSE;
+
+ if (ctx->uidlist->file_lock != NULL)
+ file_unlock(&ctx->uidlist->file_lock);
+ else
+ file_dotlock_delete(&ctx->uidlist->dotlock);
+
+ if (ctx->need_reopen)
+ (void)squat_uidlist_open(ctx->uidlist);
+
+ array_free(&ctx->block_offsets);
+ array_free(&ctx->block_end_indexes);
+ array_free(&ctx->lists);
+ o_stream_ignore_last_errors(ctx->output);
+ o_stream_unref(&ctx->output);
+ i_free(ctx);
+}
+
+int squat_uidlist_rebuild_init(struct squat_uidlist_build_context *build_ctx,
+ bool compress,
+ struct squat_uidlist_rebuild_context **ctx_r)
+{
+ struct squat_uidlist_rebuild_context *ctx;
+ struct squat_uidlist_file_header hdr;
+ const char *temp_path;
+ int fd;
+
+ if (build_ctx->build_hdr.link_count == 0)
+ return 0;
+
+ if (!compress) {
+ if (build_ctx->build_hdr.link_count <
+ build_ctx->build_hdr.count*2/3)
+ return 0;
+ }
+
+ /* make sure the entire uidlist is in memory before beginning,
+ otherwise the pages are faulted to memory in random order which
+ takes forever. */
+ if (squat_uidlist_read_to_memory(build_ctx->uidlist) < 0)
+ return -1;
+
+ temp_path = t_strconcat(build_ctx->uidlist->path, ".tmp", NULL);
+ fd = squat_trie_create_fd(build_ctx->uidlist->trie, temp_path, O_TRUNC);
+ if (fd == -1)
+ return -1;
+
+ ctx = i_new(struct squat_uidlist_rebuild_context, 1);
+ ctx->uidlist = build_ctx->uidlist;
+ ctx->build_ctx = build_ctx;
+ ctx->fd = fd;
+ ctx->output = o_stream_create_fd(ctx->fd, 0);
+ ctx->next_uid_list_idx = 0x100;
+ o_stream_cork(ctx->output);
+
+ i_zero(&hdr);
+ o_stream_nsend(ctx->output, &hdr, sizeof(hdr));
+
+ ctx->cur_block_start_offset = ctx->output->offset;
+ i_array_init(&ctx->new_block_offsets,
+ build_ctx->build_hdr.count / UIDLIST_BLOCK_LIST_COUNT);
+ i_array_init(&ctx->new_block_end_indexes,
+ build_ctx->build_hdr.count / UIDLIST_BLOCK_LIST_COUNT);
+ *ctx_r = ctx;
+ return 1;
+}
+
+static void
+uidlist_rebuild_flush_block(struct squat_uidlist_rebuild_context *ctx)
+{
+ uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp;
+ uint32_t block_offset, block_end_idx;
+ unsigned int i;
+
+ ctx->new_count += ctx->list_idx;
+
+ block_offset = ctx->output->offset;
+ block_end_idx = ctx->new_count;
+ array_push_back(&ctx->new_block_offsets, &block_offset);
+ array_push_back(&ctx->new_block_end_indexes, &block_end_idx);
+
+ /* this block's contents started from cur_block_start_offset and
+ ended to current offset. write the size of this area. */
+ bufp = buf;
+ squat_pack_num(&bufp, block_offset - ctx->cur_block_start_offset);
+ o_stream_nsend(ctx->output, buf, bufp - buf);
+
+ /* write the sizes/flags */
+ for (i = 0; i < ctx->list_idx; i++) {
+ bufp = buf;
+ squat_pack_num(&bufp, ctx->list_sizes[i]);
+ o_stream_nsend(ctx->output, buf, bufp - buf);
+ }
+ ctx->cur_block_start_offset = ctx->output->offset;
+}
+
+uint32_t squat_uidlist_rebuild_next(struct squat_uidlist_rebuild_context *ctx,
+ const ARRAY_TYPE(uint32_t) *uids)
+{
+ int ret;
+
+ T_BEGIN {
+ ret = uidlist_write_array(ctx->output, array_front(uids),
+ array_count(uids), 0, 0, FALSE,
+ &ctx->list_sizes[ctx->list_idx]);
+ } T_END;
+ if (ret < 0)
+ squat_uidlist_set_corrupted(ctx->uidlist, "Broken uidlists");
+
+ if (++ctx->list_idx == UIDLIST_BLOCK_LIST_COUNT) {
+ uidlist_rebuild_flush_block(ctx);
+ ctx->list_idx = 0;
+ }
+ return ctx->next_uid_list_idx++ << 1;
+}
+
+uint32_t squat_uidlist_rebuild_nextu(struct squat_uidlist_rebuild_context *ctx,
+ const ARRAY_TYPE(seq_range) *uids)
+{
+ const struct seq_range *range;
+ ARRAY_TYPE(uint32_t) tmp_uids;
+ uint32_t seq, uid1, ret;
+ unsigned int i, count;
+
+ range = array_get(uids, &count);
+ if (count == 0)
+ return 0;
+
+ if (range[count-1].seq2 < 8) {
+ /* we can use a singleton bitmask */
+ ret = 0;
+ for (i = 0; i < count; i++) {
+ for (seq = range[i].seq1; seq <= range[i].seq2; seq++)
+ ret |= 1 << (seq+1);
+ }
+ return ret;
+ }
+ if (count == 1 && range[0].seq1 == range[0].seq2) {
+ /* single UID */
+ return (range[0].seq1 << 1) | 1;
+ }
+
+ /* convert seq range to our internal representation and use the
+ normal _rebuild_next() to write it */
+ i_array_init(&tmp_uids, 128);
+ for (i = 0; i < count; i++) {
+ if (range[i].seq1 == range[i].seq2)
+ array_push_back(&tmp_uids, &range[i].seq1);
+ else {
+ uid1 = range[i].seq1 | UID_LIST_MASK_RANGE;
+ array_push_back(&tmp_uids, &uid1);
+ array_push_back(&tmp_uids, &range[i].seq2);
+ }
+ }
+ ret = squat_uidlist_rebuild_next(ctx, &tmp_uids);
+ array_free(&tmp_uids);
+ return ret;
+}
+
+int squat_uidlist_rebuild_finish(struct squat_uidlist_rebuild_context *ctx,
+ bool cancel)
+{
+ const char *temp_path;
+ int ret = 1;
+
+ if (ctx->list_idx != 0)
+ uidlist_rebuild_flush_block(ctx);
+ if (cancel || ctx->uidlist->corrupted)
+ ret = 0;
+
+ temp_path = t_strconcat(ctx->uidlist->path, ".tmp", NULL);
+ if (ret > 0) {
+ ctx->build_ctx->build_hdr.indexid =
+ ctx->uidlist->trie->hdr.indexid;
+ ctx->build_ctx->build_hdr.count = ctx->new_count;
+ ctx->build_ctx->build_hdr.link_count = 0;
+ uidlist_write_block_list_and_header(ctx->build_ctx, ctx->output,
+ &ctx->new_block_offsets,
+ &ctx->new_block_end_indexes,
+ FALSE);
+ (void)o_stream_seek(ctx->output, 0);
+ o_stream_nsend(ctx->output, &ctx->build_ctx->build_hdr,
+ sizeof(ctx->build_ctx->build_hdr));
+ (void)o_stream_seek(ctx->output,
+ ctx->build_ctx->build_hdr.used_file_size);
+
+ if (ctx->uidlist->corrupted)
+ ret = -1;
+ else if (o_stream_finish(ctx->output) < 0) {
+ i_error("write(%s) failed: %s", temp_path,
+ o_stream_get_error(ctx->output));
+ ret = -1;
+ } else if (rename(temp_path, ctx->uidlist->path) < 0) {
+ i_error("rename(%s, %s) failed: %m",
+ temp_path, ctx->uidlist->path);
+ ret = -1;
+ }
+ ctx->build_ctx->need_reopen = TRUE;
+ } else {
+ o_stream_abort(ctx->output);
+ }
+
+ /* we no longer require the entire uidlist to be in memory,
+ let it be used for something more useful. */
+ squat_uidlist_free_from_memory(ctx->uidlist);
+
+ o_stream_unref(&ctx->output);
+ if (close(ctx->fd) < 0)
+ i_error("close(%s) failed: %m", temp_path);
+
+ if (ret <= 0)
+ i_unlink(temp_path);
+ array_free(&ctx->new_block_offsets);
+ array_free(&ctx->new_block_end_indexes);
+ i_free(ctx);
+ return ret < 0 ? -1 : 0;
+}
+
+static void
+uidlist_flush(struct squat_uidlist_build_context *ctx,
+ struct uidlist_list *list, uint32_t uid)
+{
+ uint32_t size, offset = ctx->output->offset;
+
+ ctx->build_hdr.link_count++;
+ if (uidlist_write(ctx->output, list, TRUE, &size) < 0)
+ squat_uidlist_set_corrupted(ctx->uidlist, "Broken uidlists");
+
+ list->uid_count = 2;
+ list->uid_begins_with_pointer = TRUE;
+
+ list->uid_list[0] = offset;
+ list->uid_list[1] = uid;
+}
+
+static struct uidlist_list *
+uidlist_add_new(struct squat_uidlist_build_context *ctx, unsigned int count,
+ uint32_t *uid_list_idx_r)
+{
+ struct uidlist_list *list;
+
+ i_assert(array_count(&ctx->lists) +
+ ctx->list_start_idx == ctx->build_hdr.count);
+ *uid_list_idx_r = (ctx->build_hdr.count + 0x100) << 1;
+ list = array_append_space(&ctx->lists);
+ ctx->build_hdr.count++;
+
+ list->uid_count = count;
+ return list;
+}
+
+uint32_t squat_uidlist_build_add_uid(struct squat_uidlist_build_context *ctx,
+ uint32_t uid_list_idx, uint32_t uid)
+{
+ struct uidlist_list *list;
+ unsigned int idx, mask;
+ uint32_t *p;
+
+ if ((uid_list_idx & 1) != 0) {
+ /* adding second UID */
+ uint32_t prev_uid = uid_list_idx >> 1;
+
+ i_assert(prev_uid != uid);
+ list = uidlist_add_new(ctx, 2, &uid_list_idx);
+ list->uid_list[0] = prev_uid;
+ if (prev_uid + 1 == uid)
+ list->uid_list[0] |= UID_LIST_MASK_RANGE;
+ list->uid_list[1] = uid;
+ return uid_list_idx;
+ } else if (uid_list_idx < (0x100 << 1)) {
+ uint32_t old_list_idx;
+
+ if (uid < 8) {
+ /* UID lists containing only UIDs 0-7 are saved as
+ uidlist values 2..511. think of it as a bitmask. */
+ uid_list_idx |= 1 << (uid + 1);
+ i_assert((uid_list_idx & 1) == 0);
+ return uid_list_idx;
+ }
+
+ if (uid_list_idx == 0) {
+ /* first UID */
+ return (uid << 1) | 1;
+ }
+
+ /* create a new list */
+ old_list_idx = uid_list_idx >> 1;
+ list = uidlist_add_new(ctx, 1, &uid_list_idx);
+ /* add the first UID ourself */
+ idx = 0;
+ i_assert((old_list_idx & 0xff) != 0);
+ for (mask = 1; mask <= 128; mask <<= 1, idx++) {
+ if ((old_list_idx & mask) != 0) {
+ list->uid_list[0] = idx;
+ idx++; mask <<= 1;
+ break;
+ }
+ }
+ for (; mask <= 128; mask <<= 1, idx++) {
+ if ((old_list_idx & mask) != 0) {
+ (void)squat_uidlist_build_add_uid(ctx,
+ uid_list_idx, idx);
+ }
+ }
+ }
+
+ /* add to existing list */
+ idx = (uid_list_idx >> 1) - 0x100;
+ if (idx < ctx->list_start_idx) {
+ list = uidlist_add_new(ctx, 2, &uid_list_idx);
+ list->uid_list[0] = UID_LIST_POINTER_MASK_LIST_IDX | idx;
+ list->uid_list[1] = uid;
+ list->uid_begins_with_pointer = TRUE;
+ ctx->build_hdr.link_count++;
+ return uid_list_idx;
+ }
+
+ idx -= ctx->list_start_idx;
+ if (idx >= array_count(&ctx->lists)) {
+ squat_uidlist_set_corrupted(ctx->uidlist,
+ "missing/broken uidlist");
+ return 0;
+ }
+ list = array_idx_modifiable(&ctx->lists, idx);
+ i_assert(list->uid_count > 0);
+
+ p = &list->uid_list[list->uid_count-1];
+ i_assert(uid != *p || ctx->uidlist->corrupted ||
+ (list->uid_count == 1 && list->uid_begins_with_pointer));
+ if (uid == *p + 1 &&
+ (list->uid_count > 1 || !list->uid_begins_with_pointer)) {
+ /* use a range */
+ if (list->uid_count > 1 && (p[-1] & UID_LIST_MASK_RANGE) != 0 &&
+ (list->uid_count > 2 || !list->uid_begins_with_pointer)) {
+ /* increase the existing range */
+ *p += 1;
+ return uid_list_idx;
+ }
+
+ if (list->uid_count == UIDLIST_LIST_SIZE) {
+ uidlist_flush(ctx, list, uid);
+ return uid_list_idx;
+ }
+ /* create a new range */
+ *p |= UID_LIST_MASK_RANGE;
+ } else {
+ if (list->uid_count == UIDLIST_LIST_SIZE) {
+ uidlist_flush(ctx, list, uid);
+ return uid_list_idx;
+ }
+ }
+
+ p++;
+ list->uid_count++;
+
+ *p = uid;
+ return uid_list_idx;
+}
+
+static void uidlist_array_append(ARRAY_TYPE(uint32_t) *uids, uint32_t uid)
+{
+ uint32_t *uidlist;
+ unsigned int count;
+
+ uidlist = array_get_modifiable(uids, &count);
+ if (count == 0) {
+ array_push_back(uids, &uid);
+ return;
+ }
+ if (uidlist[count-1] + 1 == uid) {
+ if (count > 1 && (uidlist[count-2] &
+ UID_LIST_MASK_RANGE) != 0) {
+ uidlist[count-1]++;
+ return;
+ }
+ uidlist[count-1] |= UID_LIST_MASK_RANGE;
+ }
+ array_push_back(uids, &uid);
+}
+
+static void uidlist_array_append_range(ARRAY_TYPE(uint32_t) *uids,
+ uint32_t uid1, uint32_t uid2)
+{
+ uint32_t *uidlist;
+ unsigned int count;
+
+ i_assert(uid1 < uid2);
+
+ uidlist = array_get_modifiable(uids, &count);
+ if (count == 0) {
+ uid1 |= UID_LIST_MASK_RANGE;
+ array_push_back(uids, &uid1);
+ array_push_back(uids, &uid2);
+ return;
+ }
+ if (uidlist[count-1] + 1 == uid1) {
+ if (count > 1 && (uidlist[count-2] &
+ UID_LIST_MASK_RANGE) != 0) {
+ uidlist[count-1] = uid2;
+ return;
+ }
+ uidlist[count-1] |= UID_LIST_MASK_RANGE;
+ } else {
+ uid1 |= UID_LIST_MASK_RANGE;
+ array_push_back(uids, &uid1);
+ }
+ array_push_back(uids, &uid2);
+}
+
+static int
+squat_uidlist_get_at_offset(struct squat_uidlist *uidlist, uoff_t offset,
+ uint32_t num, ARRAY_TYPE(uint32_t) *uids)
+{
+ const uint32_t *uid_list;
+ const uint8_t *p, *end;
+ uint32_t size, base_uid, next_uid, flags, prev;
+ uoff_t uidlist_data_offset;
+ unsigned int i, j, count;
+
+ if (num != 0)
+ uidlist_data_offset = offset;
+ else {
+ /* not given, read it */
+ if (uidlist_file_cache_read(uidlist, offset,
+ SQUAT_PACK_MAX_SIZE) < 0)
+ return -1;
+
+ p = CONST_PTR_OFFSET(uidlist->data, offset);
+ end = CONST_PTR_OFFSET(uidlist->data, uidlist->data_size);
+ num = squat_unpack_num(&p, end);
+ uidlist_data_offset = p - (const uint8_t *)uidlist->data;
+ }
+ size = num >> 2;
+
+ if (uidlist_file_cache_read(uidlist, uidlist_data_offset, size) < 0)
+ return -1;
+ if (uidlist_data_offset + size > uidlist->data_size) {
+ squat_uidlist_set_corrupted(uidlist,
+ "size points outside file");
+ return -1;
+ }
+
+ p = CONST_PTR_OFFSET(uidlist->data, uidlist_data_offset);
+ end = p + size;
+
+ flags = num;
+ if ((flags & UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER) != 0) {
+ /* link to the file */
+ prev = squat_unpack_num(&p, end);
+
+ if ((prev & 1) != 0) {
+ /* pointer to uidlist */
+ prev = ((prev >> 1) + 0x100) << 1;
+ if (squat_uidlist_get(uidlist, prev, uids) < 0)
+ return -1;
+ } else {
+ prev = offset - (prev >> 1);
+ if (squat_uidlist_get_at_offset(uidlist, prev,
+ 0, uids) < 0)
+ return -1;
+ }
+ uid_list = array_get(uids, &count);
+ next_uid = count == 0 ? 0 : uid_list[count-1] + 1;
+ } else {
+ next_uid = 0;
+ }
+
+ num = base_uid = squat_unpack_num(&p, end);
+ if ((flags & UIDLIST_PACKED_FLAG_BITMASK) == 0)
+ base_uid >>= 1;
+ if (base_uid < next_uid) {
+ squat_uidlist_set_corrupted(uidlist,
+ "broken continued uidlist");
+ return -1;
+ }
+
+ if ((flags & UIDLIST_PACKED_FLAG_BITMASK) != 0) {
+ /* bitmask */
+ size = end - p;
+
+ uidlist_array_append(uids, base_uid++);
+ for (i = 0; i < size; i++) {
+ for (j = 0; j < 8; j++, base_uid++) {
+ if ((p[i] & (1 << j)) != 0)
+ uidlist_array_append(uids, base_uid);
+ }
+ }
+ } else {
+ /* range */
+ for (;;) {
+ if ((num & 1) == 0) {
+ uidlist_array_append(uids, base_uid);
+ } else {
+ /* range */
+ uint32_t seq1 = base_uid;
+ base_uid += squat_unpack_num(&p, end) + 1;
+ uidlist_array_append_range(uids, seq1,
+ base_uid);
+ }
+ if (p == end)
+ break;
+
+ num = squat_unpack_num(&p, end);
+ base_uid += (num >> 1) + 1;
+ }
+ }
+ return 0;
+}
+
+static int
+squat_uidlist_get_offset(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+ uint32_t *offset_r, uint32_t *num_r)
+{
+ const uint8_t *p, *end;
+ unsigned int idx;
+ uint32_t num, skip_bytes, uidlists_offset;
+ size_t max_map_size;
+
+ if (uidlist->fd == -1) {
+ squat_uidlist_set_corrupted(uidlist, "no uidlists");
+ return -1;
+ }
+
+ if (bsearch_insert_pos(&uid_list_idx, uidlist->cur_block_end_indexes,
+ uidlist->cur_block_count,
+ sizeof(uint32_t), uint32_cmp, &idx))
+ idx++;
+ if (unlikely(idx == uidlist->cur_block_count)) {
+ squat_uidlist_set_corrupted(uidlist, "uidlist not found");
+ return -1;
+ }
+ i_assert(uidlist->cur_block_end_indexes != NULL);
+ if (unlikely(idx > 0 &&
+ uidlist->cur_block_end_indexes[idx-1] > uid_list_idx)) {
+ squat_uidlist_set_corrupted(uidlist, "broken block list");
+ return -1;
+ }
+
+ /* make sure everything is mapped */
+ uid_list_idx -= idx == 0 ? 0 : uidlist->cur_block_end_indexes[idx-1];
+ max_map_size = SQUAT_PACK_MAX_SIZE * (1+uid_list_idx);
+ if (uidlist_file_cache_read(uidlist, uidlist->cur_block_offsets[idx],
+ max_map_size) < 0)
+ return -1;
+
+ /* find the uidlist inside the block */
+ i_assert(uidlist->cur_block_offsets != NULL);
+ p = CONST_PTR_OFFSET(uidlist->data, uidlist->cur_block_offsets[idx]);
+ end = CONST_PTR_OFFSET(uidlist->data, uidlist->data_size);
+
+ uidlists_offset = uidlist->cur_block_offsets[idx] -
+ squat_unpack_num(&p, end);
+ for (skip_bytes = 0; uid_list_idx > 0; uid_list_idx--) {
+ num = squat_unpack_num(&p, end);
+ skip_bytes += num >> 2;
+ }
+ *offset_r = uidlists_offset + skip_bytes;
+ *num_r = squat_unpack_num(&p, end);
+
+ if (unlikely(p == end)) {
+ squat_uidlist_set_corrupted(uidlist, "broken file");
+ return -1;
+ }
+ if (unlikely(*offset_r > uidlist->mmap_size &&
+ uidlist->mmap_base != NULL)) {
+ squat_uidlist_set_corrupted(uidlist, "broken offset");
+ return -1;
+ }
+ return 0;
+}
+
+int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+ ARRAY_TYPE(uint32_t) *uids)
+{
+ unsigned int mask;
+ uint32_t uid, offset, num;
+
+ if ((uid_list_idx & 1) != 0) {
+ /* single UID */
+ uid = uid_list_idx >> 1;
+ uidlist_array_append(uids, uid);
+ return 0;
+ } else if (uid_list_idx < (0x100 << 1)) {
+ /* bitmask */
+ for (uid = 0, mask = 2; mask <= 256; mask <<= 1, uid++) {
+ if ((uid_list_idx & mask) != 0)
+ uidlist_array_append(uids, uid);
+ }
+ return 0;
+ }
+
+ uid_list_idx = (uid_list_idx >> 1) - 0x100;
+ if (squat_uidlist_get_offset(uidlist, uid_list_idx, &offset, &num) < 0)
+ return -1;
+ return squat_uidlist_get_at_offset(uidlist, offset, num, uids);
+}
+
+uint32_t squat_uidlist_singleton_last_uid(uint32_t uid_list_idx)
+{
+ unsigned int idx, mask;
+
+ if ((uid_list_idx & 1) != 0) {
+ /* single UID */
+ return uid_list_idx >> 1;
+ } else if (uid_list_idx < (0x100 << 1)) {
+ /* bitmask */
+ if (uid_list_idx == 2) {
+ /* just a quick optimization */
+ return 0;
+ }
+ for (idx = 7, mask = 256; mask > 2; mask >>= 1, idx--) {
+ if ((uid_list_idx & mask) != 0)
+ return idx;
+ }
+ }
+
+ i_unreached();
+ return 0;
+}
+
+int squat_uidlist_get_seqrange(struct squat_uidlist *uidlist,
+ uint32_t uid_list_idx,
+ ARRAY_TYPE(seq_range) *seq_range_arr)
+{
+ ARRAY_TYPE(uint32_t) tmp_uid_arr;
+ struct seq_range range;
+ const uint32_t *tmp_uids;
+ unsigned int i, count;
+ int ret;
+
+ i_array_init(&tmp_uid_arr, 128);
+ ret = squat_uidlist_get(uidlist, uid_list_idx, &tmp_uid_arr);
+ if (ret == 0) {
+ tmp_uids = array_get(&tmp_uid_arr, &count);
+ for (i = 0; i < count; i++) {
+ if ((tmp_uids[i] & UID_LIST_MASK_RANGE) == 0)
+ range.seq1 = range.seq2 = tmp_uids[i];
+ else {
+ range.seq1 = tmp_uids[i] & ~UID_LIST_MASK_RANGE;
+ range.seq2 = tmp_uids[++i];
+ }
+ array_push_back(seq_range_arr, &range);
+ }
+ }
+ array_free(&tmp_uid_arr);
+ return ret;
+}
+
+int squat_uidlist_filter(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+ ARRAY_TYPE(seq_range) *uids)
+{
+ const struct seq_range *parent_range;
+ ARRAY_TYPE(seq_range) dest_uids;
+ ARRAY_TYPE(uint32_t) relative_uids;
+ const uint32_t *rel_range;
+ unsigned int i, rel_count, parent_idx, parent_count, diff, parent_uid;
+ uint32_t prev_seq, seq1, seq2;
+ int ret = 0;
+
+ parent_range = array_get(uids, &parent_count);
+ if (parent_count == 0)
+ return 0;
+
+ i_array_init(&relative_uids, 128);
+ i_array_init(&dest_uids, 128);
+ if (squat_uidlist_get(uidlist, uid_list_idx, &relative_uids) < 0)
+ ret = -1;
+
+ parent_idx = 0;
+ rel_range = array_get(&relative_uids, &rel_count);
+ prev_seq = 0; parent_uid = parent_range[0].seq1;
+ for (i = 0; i < rel_count; i++) {
+ if (unlikely(parent_uid == (uint32_t)-1)) {
+ i_error("broken UID ranges");
+ ret = -1;
+ break;
+ }
+ if ((rel_range[i] & UID_LIST_MASK_RANGE) == 0)
+ seq1 = seq2 = rel_range[i];
+ else {
+ seq1 = (rel_range[i] & ~UID_LIST_MASK_RANGE);
+ seq2 = rel_range[++i];
+ }
+ i_assert(seq1 >= prev_seq);
+ diff = seq1 - prev_seq;
+ while (diff > 0) {
+ if (unlikely(parent_uid == (uint32_t)-1)) {
+ i_error("broken UID ranges");
+ ret = -1;
+ break;
+ }
+
+ for (; parent_idx < parent_count; parent_idx++) {
+ if (parent_range[parent_idx].seq2 <= parent_uid)
+ continue;
+ if (parent_uid < parent_range[parent_idx].seq1)
+ parent_uid = parent_range[parent_idx].seq1;
+ else
+ parent_uid++;
+ break;
+ }
+ diff--;
+ }
+ diff = seq2 - seq1 + 1;
+ while (diff > 0) {
+ if (unlikely(parent_uid == (uint32_t)-1)) {
+ i_error("broken UID ranges");
+ ret = -1;
+ break;
+ }
+ seq_range_array_add(&dest_uids, parent_uid);
+ for (; parent_idx < parent_count; parent_idx++) {
+ if (parent_range[parent_idx].seq2 <= parent_uid)
+ continue;
+ if (parent_uid < parent_range[parent_idx].seq1)
+ parent_uid = parent_range[parent_idx].seq1;
+ else
+ parent_uid++;
+ break;
+ }
+ diff--;
+ }
+
+ prev_seq = seq2 + 1;
+ }
+
+ buffer_set_used_size(uids->arr.buffer, 0);
+ array_append_array(uids, &dest_uids);
+
+ array_free(&relative_uids);
+ array_free(&dest_uids);
+ return ret;
+}
+
+size_t squat_uidlist_mem_used(struct squat_uidlist *uidlist,
+ unsigned int *count_r)
+{
+ *count_r = uidlist->hdr.count;
+ return uidlist->max_size;
+}
diff --git a/src/plugins/fts-squat/squat-uidlist.h b/src/plugins/fts-squat/squat-uidlist.h
new file mode 100644
index 0000000..79ed791
--- /dev/null
+++ b/src/plugins/fts-squat/squat-uidlist.h
@@ -0,0 +1,71 @@
+#ifndef SQUAT_UIDLIST_H
+#define SQUAT_UIDLIST_H
+
+struct squat_trie;
+struct squat_uidlist_build_context;
+struct squat_uidlist_rebuild_context;
+
+struct squat_uidlist_file_header {
+ uint32_t indexid;
+ uint32_t used_file_size;
+ uint32_t block_list_offset;
+ uint32_t count, link_count;
+};
+
+/*
+ uidlist file:
+
+ struct uidlist_header;
+
+ // size includes both prev_offset and uidlist
+ packed (size << 2) | packed_flags; // UIDLIST_PACKED_FLAG_*
+ [packed prev_offset;] // If UIDLIST_PACKED_FLAG_BEGINS_WITH_OFFSET is set
+ if (UIDLIST_PACKED_FLAG_BITMASK) {
+ packed base_uid; // first UID in uidlist
+ uint8_t bitmask[]; // first bit is base_uid+1
+ } else {
+ // FIXME: packed range
+ }
+*/
+
+#define UIDLIST_IS_SINGLETON(idx) \
+ (((idx) & 1) != 0 || (idx) < (0x100 << 1))
+
+struct squat_uidlist *squat_uidlist_init(struct squat_trie *trie);
+void squat_uidlist_deinit(struct squat_uidlist *uidlist);
+
+int squat_uidlist_refresh(struct squat_uidlist *uidlist);
+
+int squat_uidlist_build_init(struct squat_uidlist *uidlist,
+ struct squat_uidlist_build_context **ctx_r);
+uint32_t squat_uidlist_build_add_uid(struct squat_uidlist_build_context *ctx,
+ uint32_t uid_list_idx, uint32_t uid);
+void squat_uidlist_build_flush(struct squat_uidlist_build_context *ctx);
+int squat_uidlist_build_finish(struct squat_uidlist_build_context *ctx);
+void squat_uidlist_build_deinit(struct squat_uidlist_build_context **ctx);
+
+int squat_uidlist_rebuild_init(struct squat_uidlist_build_context *build_ctx,
+ bool compress,
+ struct squat_uidlist_rebuild_context **ctx_r);
+uint32_t squat_uidlist_rebuild_next(struct squat_uidlist_rebuild_context *ctx,
+ const ARRAY_TYPE(uint32_t) *uids);
+uint32_t squat_uidlist_rebuild_nextu(struct squat_uidlist_rebuild_context *ctx,
+ const ARRAY_TYPE(seq_range) *uids);
+int squat_uidlist_rebuild_finish(struct squat_uidlist_rebuild_context *ctx,
+ bool cancel);
+
+int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+ ARRAY_TYPE(uint32_t) *uids);
+uint32_t squat_uidlist_singleton_last_uid(uint32_t uid_list_idx);
+
+int squat_uidlist_get_seqrange(struct squat_uidlist *uidlist,
+ uint32_t uid_list_idx,
+ ARRAY_TYPE(seq_range) *seq_range_arr);
+int squat_uidlist_filter(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+ ARRAY_TYPE(seq_range) *uids);
+
+void squat_uidlist_delete(struct squat_uidlist *uidlist);
+size_t squat_uidlist_mem_used(struct squat_uidlist *uidlist,
+ unsigned int *count_r);
+
+#endif
diff --git a/src/plugins/fts/Makefile.am b/src/plugins/fts/Makefile.am
new file mode 100644
index 0000000..2e7753c
--- /dev/null
+++ b/src/plugins/fts/Makefile.am
@@ -0,0 +1,74 @@
+pkglibexecdir = $(libexecdir)/dovecot
+doveadm_moduledir = $(moduledir)/doveadm
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-fts \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/doveadm
+
+NOPLUGIN_LDFLAGS =
+lib20_doveadm_fts_plugin_la_LDFLAGS = -module -avoid-version
+lib20_fts_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_fts_plugin.la
+
+lib20_fts_plugin_la_LIBADD = ../../lib-fts/libfts.la
+
+lib20_fts_plugin_la_SOURCES = \
+ fts-api.c \
+ fts-build-mail.c \
+ fts-expunge-log.c \
+ fts-indexer.c \
+ fts-parser.c \
+ fts-parser-html.c \
+ fts-parser-script.c \
+ fts-parser-tika.c \
+ fts-plugin.c \
+ fts-search.c \
+ fts-search-args.c \
+ fts-search-serialize.c \
+ fts-storage.c \
+ fts-user.c
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = \
+ fts-api.h \
+ fts-api-private.h \
+ fts-expunge-log.h \
+ fts-indexer.h \
+ fts-parser.h \
+ fts-storage.h \
+ fts-user.h
+
+noinst_HEADERS = \
+ doveadm-fts.h \
+ fts-build-mail.h \
+ fts-plugin.h \
+ fts-search-args.h \
+ fts-search-serialize.h
+
+pkglibexec_PROGRAMS = xml2text
+
+xml2text_SOURCES = xml2text.c fts-parser-html.c
+xml2text_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+xml2text_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS)
+xml2text_DEPENDENCIES = $(module_LTLIBRARIES) $(LIBDOVECOT_DEPS)
+
+pkglibexec_SCRIPTS = decode2text.sh
+EXTRA_DIST = $(pkglibexec_SCRIPTS)
+
+doveadm_module_LTLIBRARIES = \
+ lib20_doveadm_fts_plugin.la
+
+lib20_doveadm_fts_plugin_la_SOURCES = \
+ doveadm-fts.c \
+ doveadm-dump-fts-expunge-log.c
diff --git a/src/plugins/fts/Makefile.in b/src/plugins/fts/Makefile.in
new file mode 100644
index 0000000..624f69f
--- /dev/null
+++ b/src/plugins/fts/Makefile.in
@@ -0,0 +1,1140 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+pkglibexec_PROGRAMS = xml2text$(EXEEXT)
+subdir = src/plugins/fts
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \
+ "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)"
+PROGRAMS = $(pkglibexec_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES)
+lib20_doveadm_fts_plugin_la_LIBADD =
+am_lib20_doveadm_fts_plugin_la_OBJECTS = doveadm-fts.lo \
+ doveadm-dump-fts-expunge-log.lo
+lib20_doveadm_fts_plugin_la_OBJECTS = \
+ $(am_lib20_doveadm_fts_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_doveadm_fts_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_doveadm_fts_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+lib20_fts_plugin_la_DEPENDENCIES = ../../lib-fts/libfts.la
+am_lib20_fts_plugin_la_OBJECTS = fts-api.lo fts-build-mail.lo \
+ fts-expunge-log.lo fts-indexer.lo fts-parser.lo \
+ fts-parser-html.lo fts-parser-script.lo fts-parser-tika.lo \
+ fts-plugin.lo fts-search.lo fts-search-args.lo \
+ fts-search-serialize.lo fts-storage.lo fts-user.lo
+lib20_fts_plugin_la_OBJECTS = $(am_lib20_fts_plugin_la_OBJECTS)
+lib20_fts_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_fts_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_xml2text_OBJECTS = xml2text-xml2text.$(OBJEXT) \
+ xml2text-fts-parser-html.$(OBJEXT)
+xml2text_OBJECTS = $(am_xml2text_OBJECTS)
+am__DEPENDENCIES_1 =
+SCRIPTS = $(pkglibexec_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/doveadm-dump-fts-expunge-log.Plo \
+ ./$(DEPDIR)/doveadm-fts.Plo ./$(DEPDIR)/fts-api.Plo \
+ ./$(DEPDIR)/fts-build-mail.Plo ./$(DEPDIR)/fts-expunge-log.Plo \
+ ./$(DEPDIR)/fts-indexer.Plo ./$(DEPDIR)/fts-parser-html.Plo \
+ ./$(DEPDIR)/fts-parser-script.Plo \
+ ./$(DEPDIR)/fts-parser-tika.Plo ./$(DEPDIR)/fts-parser.Plo \
+ ./$(DEPDIR)/fts-plugin.Plo ./$(DEPDIR)/fts-search-args.Plo \
+ ./$(DEPDIR)/fts-search-serialize.Plo \
+ ./$(DEPDIR)/fts-search.Plo ./$(DEPDIR)/fts-storage.Plo \
+ ./$(DEPDIR)/fts-user.Plo \
+ ./$(DEPDIR)/xml2text-fts-parser-html.Po \
+ ./$(DEPDIR)/xml2text-xml2text.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_doveadm_fts_plugin_la_SOURCES) \
+ $(lib20_fts_plugin_la_SOURCES) $(xml2text_SOURCES)
+DIST_SOURCES = $(lib20_doveadm_fts_plugin_la_SOURCES) \
+ $(lib20_fts_plugin_la_SOURCES) $(xml2text_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+pkglibexecdir = $(libexecdir)/dovecot
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+doveadm_moduledir = $(moduledir)/doveadm
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-fts \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/doveadm
+
+lib20_doveadm_fts_plugin_la_LDFLAGS = -module -avoid-version
+lib20_fts_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_fts_plugin.la
+
+lib20_fts_plugin_la_LIBADD = ../../lib-fts/libfts.la
+lib20_fts_plugin_la_SOURCES = \
+ fts-api.c \
+ fts-build-mail.c \
+ fts-expunge-log.c \
+ fts-indexer.c \
+ fts-parser.c \
+ fts-parser-html.c \
+ fts-parser-script.c \
+ fts-parser-tika.c \
+ fts-plugin.c \
+ fts-search.c \
+ fts-search-args.c \
+ fts-search-serialize.c \
+ fts-storage.c \
+ fts-user.c
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = \
+ fts-api.h \
+ fts-api-private.h \
+ fts-expunge-log.h \
+ fts-indexer.h \
+ fts-parser.h \
+ fts-storage.h \
+ fts-user.h
+
+noinst_HEADERS = \
+ doveadm-fts.h \
+ fts-build-mail.h \
+ fts-plugin.h \
+ fts-search-args.h \
+ fts-search-serialize.h
+
+xml2text_SOURCES = xml2text.c fts-parser-html.c
+xml2text_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+xml2text_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS)
+xml2text_DEPENDENCIES = $(module_LTLIBRARIES) $(LIBDOVECOT_DEPS)
+pkglibexec_SCRIPTS = decode2text.sh
+EXTRA_DIST = $(pkglibexec_SCRIPTS)
+doveadm_module_LTLIBRARIES = \
+ lib20_doveadm_fts_plugin.la
+
+lib20_doveadm_fts_plugin_la_SOURCES = \
+ doveadm-fts.c \
+ doveadm-dump-fts-expunge-log.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fts/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/fts/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files
+
+clean-pkglibexecPROGRAMS:
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \
+ }
+
+uninstall-doveadm_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \
+ done
+
+clean-doveadm_moduleLTLIBRARIES:
+ -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES)
+ @list='$(doveadm_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_doveadm_fts_plugin.la: $(lib20_doveadm_fts_plugin_la_OBJECTS) $(lib20_doveadm_fts_plugin_la_DEPENDENCIES) $(EXTRA_lib20_doveadm_fts_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_doveadm_fts_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib20_doveadm_fts_plugin_la_OBJECTS) $(lib20_doveadm_fts_plugin_la_LIBADD) $(LIBS)
+
+lib20_fts_plugin.la: $(lib20_fts_plugin_la_OBJECTS) $(lib20_fts_plugin_la_DEPENDENCIES) $(EXTRA_lib20_fts_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_fts_plugin_la_LINK) -rpath $(moduledir) $(lib20_fts_plugin_la_OBJECTS) $(lib20_fts_plugin_la_LIBADD) $(LIBS)
+
+xml2text$(EXEEXT): $(xml2text_OBJECTS) $(xml2text_DEPENDENCIES) $(EXTRA_xml2text_DEPENDENCIES)
+ @rm -f xml2text$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(xml2text_OBJECTS) $(xml2text_LDADD) $(LIBS)
+install-pkglibexecSCRIPTS: $(pkglibexec_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_SCRIPTS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_SCRIPTS)'; test -n "$(pkglibexecdir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(pkglibexecdir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-fts-expunge-log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-fts.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-api.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-build-mail.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-expunge-log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-indexer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-parser-html.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-parser-script.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-parser-tika.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-search-args.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-search-serialize.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-search.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-user.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xml2text-fts-parser-html.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xml2text-xml2text.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+xml2text-xml2text.o: xml2text.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xml2text-xml2text.o -MD -MP -MF $(DEPDIR)/xml2text-xml2text.Tpo -c -o xml2text-xml2text.o `test -f 'xml2text.c' || echo '$(srcdir)/'`xml2text.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/xml2text-xml2text.Tpo $(DEPDIR)/xml2text-xml2text.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xml2text.c' object='xml2text-xml2text.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xml2text-xml2text.o `test -f 'xml2text.c' || echo '$(srcdir)/'`xml2text.c
+
+xml2text-xml2text.obj: xml2text.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xml2text-xml2text.obj -MD -MP -MF $(DEPDIR)/xml2text-xml2text.Tpo -c -o xml2text-xml2text.obj `if test -f 'xml2text.c'; then $(CYGPATH_W) 'xml2text.c'; else $(CYGPATH_W) '$(srcdir)/xml2text.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/xml2text-xml2text.Tpo $(DEPDIR)/xml2text-xml2text.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xml2text.c' object='xml2text-xml2text.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xml2text-xml2text.obj `if test -f 'xml2text.c'; then $(CYGPATH_W) 'xml2text.c'; else $(CYGPATH_W) '$(srcdir)/xml2text.c'; fi`
+
+xml2text-fts-parser-html.o: fts-parser-html.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xml2text-fts-parser-html.o -MD -MP -MF $(DEPDIR)/xml2text-fts-parser-html.Tpo -c -o xml2text-fts-parser-html.o `test -f 'fts-parser-html.c' || echo '$(srcdir)/'`fts-parser-html.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/xml2text-fts-parser-html.Tpo $(DEPDIR)/xml2text-fts-parser-html.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fts-parser-html.c' object='xml2text-fts-parser-html.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xml2text-fts-parser-html.o `test -f 'fts-parser-html.c' || echo '$(srcdir)/'`fts-parser-html.c
+
+xml2text-fts-parser-html.obj: fts-parser-html.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xml2text-fts-parser-html.obj -MD -MP -MF $(DEPDIR)/xml2text-fts-parser-html.Tpo -c -o xml2text-fts-parser-html.obj `if test -f 'fts-parser-html.c'; then $(CYGPATH_W) 'fts-parser-html.c'; else $(CYGPATH_W) '$(srcdir)/fts-parser-html.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/xml2text-fts-parser-html.Tpo $(DEPDIR)/xml2text-fts-parser-html.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fts-parser-html.c' object='xml2text-fts-parser-html.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xml2text-fts-parser-html.obj `if test -f 'fts-parser-html.c'; then $(CYGPATH_W) 'fts-parser-html.c'; else $(CYGPATH_W) '$(srcdir)/fts-parser-html.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(SCRIPTS) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-pkglibexecPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/doveadm-dump-fts-expunge-log.Plo
+ -rm -f ./$(DEPDIR)/doveadm-fts.Plo
+ -rm -f ./$(DEPDIR)/fts-api.Plo
+ -rm -f ./$(DEPDIR)/fts-build-mail.Plo
+ -rm -f ./$(DEPDIR)/fts-expunge-log.Plo
+ -rm -f ./$(DEPDIR)/fts-indexer.Plo
+ -rm -f ./$(DEPDIR)/fts-parser-html.Plo
+ -rm -f ./$(DEPDIR)/fts-parser-script.Plo
+ -rm -f ./$(DEPDIR)/fts-parser-tika.Plo
+ -rm -f ./$(DEPDIR)/fts-parser.Plo
+ -rm -f ./$(DEPDIR)/fts-plugin.Plo
+ -rm -f ./$(DEPDIR)/fts-search-args.Plo
+ -rm -f ./$(DEPDIR)/fts-search-serialize.Plo
+ -rm -f ./$(DEPDIR)/fts-search.Plo
+ -rm -f ./$(DEPDIR)/fts-storage.Plo
+ -rm -f ./$(DEPDIR)/fts-user.Plo
+ -rm -f ./$(DEPDIR)/xml2text-fts-parser-html.Po
+ -rm -f ./$(DEPDIR)/xml2text-xml2text.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-doveadm_moduleLTLIBRARIES \
+ install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibexecPROGRAMS install-pkglibexecSCRIPTS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/doveadm-dump-fts-expunge-log.Plo
+ -rm -f ./$(DEPDIR)/doveadm-fts.Plo
+ -rm -f ./$(DEPDIR)/fts-api.Plo
+ -rm -f ./$(DEPDIR)/fts-build-mail.Plo
+ -rm -f ./$(DEPDIR)/fts-expunge-log.Plo
+ -rm -f ./$(DEPDIR)/fts-indexer.Plo
+ -rm -f ./$(DEPDIR)/fts-parser-html.Plo
+ -rm -f ./$(DEPDIR)/fts-parser-script.Plo
+ -rm -f ./$(DEPDIR)/fts-parser-tika.Plo
+ -rm -f ./$(DEPDIR)/fts-parser.Plo
+ -rm -f ./$(DEPDIR)/fts-plugin.Plo
+ -rm -f ./$(DEPDIR)/fts-search-args.Plo
+ -rm -f ./$(DEPDIR)/fts-search-serialize.Plo
+ -rm -f ./$(DEPDIR)/fts-search.Plo
+ -rm -f ./$(DEPDIR)/fts-storage.Plo
+ -rm -f ./$(DEPDIR)/fts-user.Plo
+ -rm -f ./$(DEPDIR)/xml2text-fts-parser-html.Po
+ -rm -f ./$(DEPDIR)/xml2text-xml2text.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \
+ uninstall-pkglibexecPROGRAMS uninstall-pkglibexecSCRIPTS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-pkglibexecPROGRAMS cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-doveadm_moduleLTLIBRARIES install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-pkglibexecPROGRAMS \
+ install-pkglibexecSCRIPTS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \
+ uninstall-pkglibexecPROGRAMS uninstall-pkglibexecSCRIPTS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/fts/decode2text.sh b/src/plugins/fts/decode2text.sh
new file mode 100755
index 0000000..1c881ff
--- /dev/null
+++ b/src/plugins/fts/decode2text.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+
+# Example attachment decoder script. The attachment comes from stdin, and
+# the script is expected to output UTF-8 data to stdout. (If the output isn't
+# UTF-8, everything except valid UTF-8 sequences are dropped from it.)
+
+# The attachment decoding is enabled by setting:
+#
+# plugin {
+# fts_decoder = decode2text
+# }
+# service decode2text {
+# executable = script /usr/local/libexec/dovecot/decode2text.sh
+# user = dovecot
+# unix_listener decode2text {
+# mode = 0666
+# }
+# }
+
+libexec_dir=`dirname $0`
+content_type=$1
+
+# The second parameter is the format's filename extension, which is used when
+# found from a filename of application/octet-stream. You can also add more
+# extensions by giving more parameters.
+formats='application/pdf pdf
+application/x-pdf pdf
+application/msword doc
+application/mspowerpoint ppt
+application/vnd.ms-powerpoint ppt
+application/ms-excel xls
+application/x-msexcel xls
+application/vnd.ms-excel xls
+application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
+application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
+application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
+application/vnd.oasis.opendocument.text odt
+application/vnd.oasis.opendocument.spreadsheet ods
+application/vnd.oasis.opendocument.presentation odp
+'
+
+if [ "$content_type" = "" ]; then
+ echo "$formats"
+ exit 0
+fi
+
+fmt=`echo "$formats" | grep -w "^$content_type" | cut -d ' ' -f 2`
+if [ "$fmt" = "" ]; then
+ echo "Content-Type: $content_type not supported" >&2
+ exit 1
+fi
+
+# most decoders can't handle stdin directly, so write the attachment
+# to a temp file
+path=`mktemp`
+trap "rm -f $path" 0 1 2 3 14 15
+cat > $path
+
+xmlunzip() {
+ name=$1
+
+ tempdir=`mktemp -d`
+ if [ "$tempdir" = "" ]; then
+ exit 1
+ fi
+ trap "rm -rf $path $tempdir" 0 1 2 3 14 15
+ cd $tempdir || exit 1
+ unzip -q "$path" 2>/dev/null || exit 0
+ find . -name "$name" -print0 | xargs -0 cat |
+ $libexec_dir/xml2text
+}
+
+wait_timeout() {
+ childpid=$!
+ trap "kill -9 $childpid; rm -f $path" 1 2 3 14 15
+ wait $childpid
+}
+
+LANG=en_US.UTF-8
+export LANG
+if [ $fmt = "pdf" ]; then
+ /usr/bin/pdftotext $path - 2>/dev/null&
+ wait_timeout 2>/dev/null
+elif [ $fmt = "doc" ]; then
+ (/usr/bin/catdoc $path; true) 2>/dev/null&
+ wait_timeout 2>/dev/null
+elif [ $fmt = "ppt" ]; then
+ (/usr/bin/catppt $path; true) 2>/dev/null&
+ wait_timeout 2>/dev/null
+elif [ $fmt = "xls" ]; then
+ (/usr/bin/xls2csv $path; true) 2>/dev/null&
+ wait_timeout 2>/dev/null
+elif [ $fmt = "odt" -o $fmt = "ods" -o $fmt = "odp" ]; then
+ xmlunzip "content.xml"
+elif [ $fmt = "docx" ]; then
+ xmlunzip "document.xml"
+elif [ $fmt = "xlsx" ]; then
+ xmlunzip "sharedStrings.xml"
+elif [ $fmt = "pptx" ]; then
+ xmlunzip "slide*.xml"
+else
+ echo "Buggy decoder script: $fmt not handled" >&2
+ exit 1
+fi
+exit 0
diff --git a/src/plugins/fts/doveadm-dump-fts-expunge-log.c b/src/plugins/fts/doveadm-dump-fts-expunge-log.c
new file mode 100644
index 0000000..7438bca
--- /dev/null
+++ b/src/plugins/fts/doveadm-dump-fts-expunge-log.c
@@ -0,0 +1,116 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "guid.h"
+#include "doveadm-dump.h"
+#include "doveadm-fts.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+struct fts_expunge_log_record {
+ uint32_t checksum;
+ uint32_t record_size;
+ guid_128_t guid;
+};
+
+static int dump_record(int fd, buffer_t *buf)
+{
+ struct fts_expunge_log_record rec;
+ off_t offset;
+ void *data;
+ const uint32_t *expunges, *uids;
+ ssize_t ret;
+ size_t data_size;
+ unsigned int i, uids_count;
+
+ offset = lseek(fd, 0, SEEK_CUR);
+
+ ret = read(fd, &rec, sizeof(rec));
+ if (ret == 0)
+ return 0;
+
+ if (ret != sizeof(rec))
+ i_fatal("rec read() %d != %d", (int)ret, (int)sizeof(rec));
+
+ if (rec.record_size < sizeof(rec) + sizeof(uint32_t) ||
+ rec.record_size > INT_MAX) {
+ i_fatal("Invalid record_size=%u at offset %"PRIuUOFF_T,
+ rec.record_size, offset);
+ }
+ data_size = rec.record_size - sizeof(rec);
+ buffer_set_used_size(buf, 0);
+ data = buffer_append_space_unsafe(buf, data_size);
+ ret = read(fd, data, data_size);
+ if (ret != (ssize_t)data_size)
+ i_fatal("rec read() %d != %d", (int)ret, (int)data_size);
+
+ printf("#%"PRIuUOFF_T":\n", offset);
+ printf(" checksum = %8x\n", rec.checksum);
+ printf(" size .... = %u\n", rec.record_size);
+ printf(" mailbox . = %s\n", guid_128_to_string(rec.guid));
+
+ expunges = CONST_PTR_OFFSET(data, data_size - sizeof(uint32_t));
+ printf(" expunges = %u\n", *expunges);
+
+ printf(" uids .... = ");
+
+ uids = data;
+ uids_count = (rec.record_size - sizeof(rec) - sizeof(uint32_t)) /
+ sizeof(uint32_t);
+ for (i = 0; i < uids_count; i += 2) {
+ if (i != 0)
+ printf(",");
+ if (uids[i] == uids[i+1])
+ printf("%u", uids[i]);
+ else
+ printf("%u-%u", uids[i], uids[i+1]);
+ }
+ printf("\n");
+ return 1;
+}
+
+static void
+cmd_dump_fts_expunge_log(const char *path, const char *const *args ATTR_UNUSED)
+{
+ buffer_t *buf;
+ int fd, ret;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ i_fatal("open(%s) failed: %m", path);
+
+ buf = buffer_create_dynamic(default_pool, 1024);
+ do {
+ T_BEGIN {
+ ret = dump_record(fd, buf);
+ } T_END;
+ } while (ret > 0);
+ buffer_free(&buf);
+ i_close_fd(&fd);
+}
+
+static bool test_dump_fts_expunge_log(const char *path)
+{
+ const char *p;
+
+ if ((p = strrchr(path, '/')) != NULL)
+ p++;
+ else
+ p = path;
+ return strcmp(p, "dovecot-expunges.log") == 0;
+}
+
+static const struct doveadm_cmd_dump doveadm_cmd_dump_fts_expunge_log = {
+ "fts-expunge-log",
+ test_dump_fts_expunge_log,
+ cmd_dump_fts_expunge_log
+};
+
+void doveadm_dump_fts_expunge_log_init(void)
+{
+ doveadm_dump_register(&doveadm_cmd_dump_fts_expunge_log);
+}
diff --git a/src/plugins/fts/doveadm-fts.c b/src/plugins/fts/doveadm-fts.c
new file mode 100644
index 0000000..1b902a1
--- /dev/null
+++ b/src/plugins/fts/doveadm-fts.c
@@ -0,0 +1,470 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "imap-util.h"
+#include "mail-namespace.h"
+#include "mail-search.h"
+#include "mailbox-list-iter.h"
+#include "fts-tokenizer.h"
+#include "fts-filter.h"
+#include "fts-language.h"
+#include "fts-storage.h"
+#include "fts-search-args.h"
+#include "fts-user.h"
+#include "doveadm-print.h"
+#include "doveadm-mail.h"
+#include "doveadm-mailbox-list-iter.h"
+#include "doveadm-fts.h"
+
+const char *doveadm_fts_plugin_version = DOVECOT_ABI_VERSION;
+
+struct fts_tokenize_cmd_context {
+ struct doveadm_mail_cmd_context ctx;
+ const char *language;
+ const char *tokens;
+};
+
+static int
+cmd_search_box(struct doveadm_mail_cmd_context *ctx,
+ const struct mailbox_info *info)
+{
+ struct mailbox *box;
+ struct fts_backend *backend;
+ struct fts_result result;
+ int ret = 0;
+
+ backend = fts_list_backend(info->ns->list);
+ if (backend == NULL) {
+ i_error("fts not enabled for %s", info->vname);
+ ctx->exit_code = EX_CONFIG;
+ return -1;
+ }
+
+ i_zero(&result);
+ i_array_init(&result.definite_uids, 16);
+ i_array_init(&result.maybe_uids, 16);
+ i_array_init(&result.scores, 16);
+
+ box = mailbox_alloc(info->ns->list, info->vname, 0);
+ if (fts_backend_lookup(backend, box, ctx->search_args->args,
+ FTS_LOOKUP_FLAG_AND_ARGS, &result) < 0) {
+ i_error("fts lookup failed");
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+ ret = -1;
+ } else {
+ printf("%s: ", info->vname);
+ if (array_count(&result.definite_uids) == 0)
+ printf("no results\n");
+ else T_BEGIN {
+ string_t *str = t_str_new(128);
+ imap_write_seq_range(str, &result.definite_uids);
+ printf("%s\n", str_c(str));
+ } T_END;
+ if (array_count(&result.maybe_uids) > 0) T_BEGIN {
+ string_t *str = t_str_new(128);
+ imap_write_seq_range(str, &result.maybe_uids);
+ printf(" - maybe: %s\n", str_c(str));
+ } T_END;
+ fts_backend_lookup_done(backend);
+ }
+ mailbox_free(&box);
+ array_free(&result.definite_uids);
+ array_free(&result.maybe_uids);
+ array_free(&result.scores);
+ return ret;
+}
+
+static int
+cmd_fts_lookup_run(struct doveadm_mail_cmd_context *ctx,
+ struct mail_user *user)
+{
+ const enum mailbox_list_iter_flags iter_flags =
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
+ struct doveadm_mailbox_list_iter *iter;
+ const struct mailbox_info *info;
+ int ret = 0;
+
+ iter = doveadm_mailbox_list_iter_init(ctx, user, ctx->search_args,
+ iter_flags);
+ while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
+ if (cmd_search_box(ctx, info) < 0)
+ ret = -1;
+ } T_END;
+ if (doveadm_mailbox_list_iter_deinit(&iter) < 0)
+ ret = -1;
+ return ret;
+}
+
+static void
+cmd_fts_lookup_init(struct doveadm_mail_cmd_context *ctx,
+ const char *const args[])
+{
+ if (args[0] == NULL)
+ doveadm_mail_help_name("fts lookup");
+
+ ctx->search_args = doveadm_mail_build_search_args(args);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_fts_lookup_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_fts_lookup_run;
+ ctx->v.init = cmd_fts_lookup_init;
+ return ctx;
+}
+
+static int
+cmd_fts_expand_run(struct doveadm_mail_cmd_context *ctx,
+ struct mail_user *user)
+{
+ struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box;
+ struct fts_backend *backend;
+ string_t *str = t_str_new(128);
+
+ backend = fts_list_backend(ns->list);
+ if (backend == NULL) {
+ i_error("fts not enabled for INBOX");
+ ctx->exit_code = EX_CONFIG;
+ return -1;
+ }
+
+ box = mailbox_alloc(ns->list, "INBOX", 0);
+ mail_search_args_init(ctx->search_args, box, FALSE, NULL);
+
+ if (fts_search_args_expand(backend, ctx->search_args) < 0)
+ i_fatal("Couldn't expand search args");
+ mail_search_args_to_cmdline(str, ctx->search_args->args);
+ printf("%s\n", str_c(str));
+ mailbox_free(&box);
+ return 0;
+}
+
+static void
+cmd_fts_expand_init(struct doveadm_mail_cmd_context *ctx,
+ const char *const args[])
+{
+ if (args[0] == NULL)
+ doveadm_mail_help_name("fts expand");
+
+ ctx->search_args = doveadm_mail_build_search_args(args);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_fts_expand_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_fts_expand_run;
+ ctx->v.init = cmd_fts_expand_init;
+ return ctx;
+}
+
+static int
+cmd_fts_tokenize_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct fts_tokenize_cmd_context *ctx =
+ (struct fts_tokenize_cmd_context *)_ctx;
+ struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+ struct fts_backend *backend;
+ struct fts_user_language *user_lang;
+ const struct fts_language *lang = NULL;
+ int ret, ret2;
+ bool final = FALSE;
+
+ backend = fts_list_backend(ns->list);
+ if (backend == NULL) {
+ i_error("fts not enabled for INBOX");
+ _ctx->exit_code = EX_CONFIG;
+ return -1;
+ }
+
+ if (ctx->language == NULL) {
+ struct fts_language_list *lang_list =
+ fts_user_get_language_list(user);
+ enum fts_language_result result;
+ const char *error;
+
+ result = fts_language_detect(lang_list,
+ (const unsigned char *)ctx->tokens, strlen(ctx->tokens),
+ &lang, &error);
+ if (lang == NULL)
+ lang = fts_language_list_get_first(lang_list);
+ switch (result) {
+ case FTS_LANGUAGE_RESULT_SHORT:
+ i_warning("Text too short, can't detect its language - assuming %s", lang->name);
+ break;
+ case FTS_LANGUAGE_RESULT_UNKNOWN:
+ i_warning("Can't detect its language - assuming %s", lang->name);
+ break;
+ case FTS_LANGUAGE_RESULT_OK:
+ break;
+ case FTS_LANGUAGE_RESULT_ERROR:
+ i_error("Language detection library initialization failed: %s", error);
+ _ctx->exit_code = EX_CONFIG;
+ return -1;
+ default:
+ i_unreached();
+ }
+ } else {
+ lang = fts_language_find(ctx->language);
+ if (lang == NULL) {
+ i_error("Unknown language: %s", ctx->language);
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ }
+ user_lang = fts_user_language_find(user, lang);
+ if (user_lang == NULL) {
+ i_error("Language not enabled for user: %s", ctx->language);
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+
+ fts_tokenizer_reset(user_lang->index_tokenizer);
+ for (;;) {
+ const char *token, *error;
+
+ if (!final) {
+ ret = fts_tokenizer_next(user_lang->index_tokenizer,
+ (const unsigned char *)ctx->tokens, strlen(ctx->tokens),
+ &token, &error);
+ } else {
+ ret = fts_tokenizer_final(user_lang->index_tokenizer,
+ &token, &error);
+ }
+ if (ret < 0)
+ break;
+ if (ret > 0 && user_lang->filter != NULL) {
+ ret2 = fts_filter_filter(user_lang->filter, &token, &error);
+ if (ret2 > 0)
+ doveadm_print(token);
+ else if (ret2 < 0)
+ i_error("Couldn't create indexable tokens: %s", error);
+ }
+ if (ret == 0) {
+ if (final)
+ break;
+ final = TRUE;
+ }
+ }
+ return 0;
+}
+
+static void
+cmd_fts_tokenize_init(struct doveadm_mail_cmd_context *_ctx,
+ const char *const args[])
+{
+ struct fts_tokenize_cmd_context *ctx =
+ (struct fts_tokenize_cmd_context *)_ctx;
+
+ if (args[0] == NULL)
+ doveadm_mail_help_name("fts tokenize");
+
+ ctx->tokens = p_strdup(_ctx->pool, t_strarray_join(args, " "));
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW);
+ doveadm_print_header("token", "token", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE);
+}
+
+static bool
+cmd_fts_tokenize_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+ struct fts_tokenize_cmd_context *ctx =
+ (struct fts_tokenize_cmd_context *)_ctx;
+
+ switch (c) {
+ case 'l':
+ ctx->language = p_strdup(_ctx->pool, optarg);
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_fts_tokenize_alloc(void)
+{
+ struct fts_tokenize_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct fts_tokenize_cmd_context);
+ ctx->ctx.v.run = cmd_fts_tokenize_run;
+ ctx->ctx.v.init = cmd_fts_tokenize_init;
+ ctx->ctx.v.parse_arg = cmd_fts_tokenize_parse_arg;
+ ctx->ctx.getopt_args = "l";
+ return &ctx->ctx;
+}
+
+static int
+fts_namespace_find(struct mail_user *user, const char *ns_prefix,
+ struct mail_namespace **ns_r)
+{
+ struct mail_namespace *ns;
+
+ if (ns_prefix == NULL)
+ ns = mail_namespace_find_inbox(user->namespaces);
+ else {
+ ns = mail_namespace_find_prefix(user->namespaces, ns_prefix);
+ if (ns == NULL) {
+ i_error("Namespace prefix not found: %s", ns_prefix);
+ return -1;
+ }
+ }
+
+ if (fts_list_backend(ns->list) == NULL) {
+ i_error("fts not enabled for user's namespace %s",
+ ns_prefix != NULL ? ns_prefix : "INBOX");
+ return -1;
+ }
+ *ns_r = ns;
+ return 0;
+}
+
+static int
+cmd_fts_optimize_run(struct doveadm_mail_cmd_context *ctx,
+ struct mail_user *user)
+{
+ const char *ns_prefix = ctx->args[0];
+ struct mail_namespace *ns;
+ struct fts_backend *backend;
+
+ if (fts_namespace_find(user, ns_prefix, &ns) < 0) {
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ return -1;
+ }
+ backend = fts_list_backend(ns->list);
+ if (fts_backend_optimize(backend) < 0) {
+ i_error("fts optimize failed");
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+cmd_fts_optimize_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[])
+{
+ if (str_array_length(args) > 1)
+ doveadm_mail_help_name("fts optimize");
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_fts_optimize_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_fts_optimize_run;
+ ctx->v.init = cmd_fts_optimize_init;
+ return ctx;
+}
+
+static int
+cmd_fts_rescan_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user)
+{
+ const char *ns_prefix = ctx->args[0];
+ struct mail_namespace *ns;
+ struct fts_backend *backend;
+
+ if (fts_namespace_find(user, ns_prefix, &ns) < 0) {
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ return -1;
+ }
+ backend = fts_list_backend(ns->list);
+ if (fts_backend_rescan(backend) < 0) {
+ i_error("fts rescan failed");
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+cmd_fts_rescan_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[])
+{
+ if (str_array_length(args) > 1)
+ doveadm_mail_help_name("fts rescan");
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_fts_rescan_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_fts_rescan_run;
+ ctx->v.init = cmd_fts_rescan_init;
+ return ctx;
+}
+
+static struct doveadm_cmd_ver2 fts_commands[] = {
+{
+ .name = "fts lookup",
+ .mail_cmd = cmd_fts_lookup_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<search query>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "fts expand",
+ .mail_cmd = cmd_fts_expand_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<search query>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "fts tokenize",
+ .mail_cmd = cmd_fts_tokenize_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<text>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('l', "language", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "text", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "fts optimize",
+ .mail_cmd = cmd_fts_optimize_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[<namespace>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "namespace", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+ .name = "fts rescan",
+ .mail_cmd = cmd_fts_rescan_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[<namespace>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "namespace", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+};
+
+void doveadm_fts_plugin_init(struct module *module ATTR_UNUSED)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(fts_commands); i++)
+ doveadm_cmd_register_ver2(&fts_commands[i]);
+ doveadm_dump_fts_expunge_log_init();
+}
+
+void doveadm_fts_plugin_deinit(void)
+{
+}
diff --git a/src/plugins/fts/doveadm-fts.h b/src/plugins/fts/doveadm-fts.h
new file mode 100644
index 0000000..d4307fe
--- /dev/null
+++ b/src/plugins/fts/doveadm-fts.h
@@ -0,0 +1,11 @@
+#ifndef DOVEADM_FTS_H
+#define DOVEADM_FTS_H
+
+struct module;
+
+void doveadm_dump_fts_expunge_log_init(void);
+
+void doveadm_fts_plugin_init(struct module *module);
+void doveadm_fts_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/fts/fts-api-private.h b/src/plugins/fts/fts-api-private.h
new file mode 100644
index 0000000..a070564
--- /dev/null
+++ b/src/plugins/fts/fts-api-private.h
@@ -0,0 +1,139 @@
+#ifndef FTS_API_PRIVATE_H
+#define FTS_API_PRIVATE_H
+
+#include "unichar.h"
+#include "fts-api.h"
+
+struct mail_user;
+struct mailbox_list;
+
+#define MAILBOX_GUID_HEX_LENGTH (GUID_128_SIZE*2)
+
+struct fts_backend_vfuncs {
+ struct fts_backend *(*alloc)(void);
+ int (*init)(struct fts_backend *backend, const char **error_r);
+ void (*deinit)(struct fts_backend *backend);
+
+ int (*get_last_uid)(struct fts_backend *backend, struct mailbox *box,
+ uint32_t *last_uid_r);
+
+ struct fts_backend_update_context *
+ (*update_init)(struct fts_backend *backend);
+ int (*update_deinit)(struct fts_backend_update_context *ctx);
+
+ void (*update_set_mailbox)(struct fts_backend_update_context *ctx,
+ struct mailbox *box);
+ void (*update_expunge)(struct fts_backend_update_context *ctx,
+ uint32_t uid);
+
+ /* Start a build for specified key */
+ bool (*update_set_build_key)(struct fts_backend_update_context *ctx,
+ const struct fts_backend_build_key *key);
+ /* Finish a build for specified key - guaranteed to be called */
+ void (*update_unset_build_key)(struct fts_backend_update_context *ctx);
+ /* Add data for current build key */
+ int (*update_build_more)(struct fts_backend_update_context *ctx,
+ const unsigned char *data, size_t size);
+
+ int (*refresh)(struct fts_backend *backend);
+ int (*rescan)(struct fts_backend *backend);
+ int (*optimize)(struct fts_backend *backend);
+
+ bool (*can_lookup)(struct fts_backend *backend,
+ const struct mail_search_arg *args);
+ int (*lookup)(struct fts_backend *backend, struct mailbox *box,
+ struct mail_search_arg *args, enum fts_lookup_flags flags,
+ struct fts_result *result);
+ int (*lookup_multi)(struct fts_backend *backend,
+ struct mailbox *const boxes[],
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result);
+ void (*lookup_done)(struct fts_backend *backend);
+};
+
+enum fts_backend_flags {
+ /* Backend supports indexing binary MIME parts */
+ FTS_BACKEND_FLAG_BINARY_MIME_PARTS = 0x01,
+ /* Send built text to backend normalized rather than
+ preserving original case */
+ FTS_BACKEND_FLAG_NORMALIZE_INPUT = 0x02,
+ /* Send only fully indexable words rather than randomly sized blocks */
+ FTS_BACKEND_FLAG_BUILD_FULL_WORDS = 0x04,
+ /* Fuzzy search works */
+ FTS_BACKEND_FLAG_FUZZY_SEARCH = 0x08,
+ /* Tokenize all the input. update_build_more() will be called a single
+ directly indexable token at a time. Searching will modify the search
+ args so that lookup() sees only tokens that can be directly
+ searched. */
+ FTS_BACKEND_FLAG_TOKENIZED_INPUT = 0x10
+};
+
+struct fts_header_filters {
+ pool_t pool;
+ ARRAY_TYPE(const_string) includes;
+ ARRAY_TYPE(const_string) excludes;
+ bool loaded:1;
+ bool exclude_is_default:1;
+};
+
+struct fts_backend {
+ const char *name;
+ enum fts_backend_flags flags;
+
+ struct fts_backend_vfuncs v;
+ struct mail_namespace *ns;
+ struct fts_header_filters header_filters;
+
+ bool updating:1;
+};
+
+struct fts_backend_update_context {
+ struct fts_backend *backend;
+ normalizer_func_t *normalizer;
+
+ struct mailbox *cur_box, *backend_box;
+
+ bool build_key_open:1;
+ bool failed:1;
+};
+
+struct fts_index_header {
+ uint32_t last_indexed_uid;
+
+ /* Checksum of settings. If the settings change, the index should
+ be rebuilt. */
+ uint32_t settings_checksum;
+ uint32_t unused;
+};
+
+void fts_backend_register(const struct fts_backend *backend);
+void fts_backend_unregister(const char *name);
+
+bool fts_backend_default_can_lookup(struct fts_backend *backend,
+ const struct mail_search_arg *args);
+
+void fts_filter_uids(ARRAY_TYPE(seq_range) *definite_dest,
+ const ARRAY_TYPE(seq_range) *definite_filter,
+ ARRAY_TYPE(seq_range) *maybe_dest,
+ const ARRAY_TYPE(seq_range) *maybe_filter);
+
+/* Returns TRUE if ok, FALSE if no fts header */
+bool fts_index_get_header(struct mailbox *box, struct fts_index_header *hdr_r);
+int fts_index_set_header(struct mailbox *box,
+ const struct fts_index_header *hdr);
+int ATTR_NOWARN_UNUSED_RESULT
+fts_index_set_last_uid(struct mailbox *box, uint32_t last_uid);
+int fts_backend_reset_last_uids(struct fts_backend *backend);
+int fts_index_have_compatible_settings(struct mailbox_list *list,
+ uint32_t checksum);
+
+/* Returns TRUE if FTS backend should index the header for optimizing
+ separate lookups */
+bool fts_header_want_indexed(const char *hdr_name);
+/* Returns TRUE if header's values should be considered to have a language. */
+bool fts_header_has_language(const char *hdr_name);
+
+int fts_mailbox_get_guid(struct mailbox *box, const char **guid_r);
+
+#endif
diff --git a/src/plugins/fts/fts-api.c b/src/plugins/fts/fts-api.c
new file mode 100644
index 0000000..a6ea716
--- /dev/null
+++ b/src/plugins/fts/fts-api.c
@@ -0,0 +1,554 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hex-binary.h"
+#include "mail-index.h"
+#include "mail-namespace.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-iter.h"
+#include "mail-search.h"
+#include "fts-api-private.h"
+
+struct event_category event_category_fts = {
+ .name = "fts",
+};
+
+static ARRAY(const struct fts_backend *) backends;
+
+void fts_backend_register(const struct fts_backend *backend)
+{
+ if (!array_is_created(&backends))
+ i_array_init(&backends, 4);
+ array_push_back(&backends, &backend);
+}
+
+void fts_backend_unregister(const char *name)
+{
+ const struct fts_backend *const *be;
+ unsigned int i, count;
+
+ be = array_get(&backends, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(be[i]->name, name) == 0) {
+ array_delete(&backends, i, 1);
+ break;
+ }
+ }
+ if (i == count)
+ i_panic("fts_backend_unregister(%s): unknown backend", name);
+
+ if (count == 1)
+ array_free(&backends);
+}
+
+static const struct fts_backend *
+fts_backend_class_lookup(const char *backend_name)
+{
+ const struct fts_backend *const *be;
+ unsigned int i, count;
+
+ if (array_is_created(&backends)) {
+ be = array_get(&backends, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(be[i]->name, backend_name) == 0)
+ return be[i];
+ }
+ }
+ return NULL;
+}
+
+static void
+fts_header_filters_init(struct fts_backend *backend)
+{
+ struct fts_header_filters *filters = &backend->header_filters;
+ pool_t pool = filters->pool = pool_alloconly_create(
+ MEMPOOL_GROWING"fts_header_filters", 256);
+
+ p_array_init(&filters->includes, pool, 8);
+ p_array_init(&filters->excludes, pool, 8);
+}
+
+static void
+fts_header_filters_deinit(struct fts_backend *backend)
+{
+ pool_unref(&backend->header_filters.pool);
+}
+
+int fts_backend_init(const char *backend_name, struct mail_namespace *ns,
+ const char **error_r, struct fts_backend **backend_r)
+{
+ const struct fts_backend *be;
+ struct fts_backend *backend;
+
+ be = fts_backend_class_lookup(backend_name);
+ if (be == NULL) {
+ *error_r = "Unknown backend";
+ return -1;
+ }
+
+ backend = be->v.alloc();
+ backend->ns = ns;
+ if (backend->v.init(backend, error_r) < 0) {
+ i_free(backend);
+ return -1;
+ }
+
+ fts_header_filters_init(backend);
+ *backend_r = backend;
+ return 0;
+}
+
+void fts_backend_deinit(struct fts_backend **_backend)
+{
+ struct fts_backend *backend = *_backend;
+
+ fts_header_filters_deinit(backend);
+ *_backend = NULL;
+ backend->v.deinit(backend);
+}
+
+int fts_backend_get_last_uid(struct fts_backend *backend, struct mailbox *box,
+ uint32_t *last_uid_r)
+{
+ struct fts_index_header hdr;
+
+ if (box->virtual_vfuncs != NULL) {
+ /* virtual mailboxes themselves don't have any indexes,
+ so catch this call here */
+ if (!fts_index_get_header(box, &hdr))
+ *last_uid_r = 0;
+ else
+ *last_uid_r = hdr.last_indexed_uid;
+ return 0;
+ }
+
+ return backend->v.get_last_uid(backend, box, last_uid_r);
+}
+
+bool fts_backend_is_updating(struct fts_backend *backend)
+{
+ return backend->updating;
+}
+
+struct fts_backend_update_context *
+fts_backend_update_init(struct fts_backend *backend)
+{
+ struct fts_backend_update_context *ctx;
+
+ i_assert(!backend->updating);
+
+ backend->updating = TRUE;
+ ctx = backend->v.update_init(backend);
+ if ((backend->flags & FTS_BACKEND_FLAG_NORMALIZE_INPUT) != 0)
+ ctx->normalizer = backend->ns->user->default_normalizer;
+ return ctx;
+}
+
+static void fts_backend_set_cur_mailbox(struct fts_backend_update_context *ctx)
+{
+ fts_backend_update_unset_build_key(ctx);
+ if (ctx->backend_box != ctx->cur_box) {
+ ctx->backend->v.update_set_mailbox(ctx, ctx->cur_box);
+ ctx->backend_box = ctx->cur_box;
+ }
+}
+
+int fts_backend_update_deinit(struct fts_backend_update_context **_ctx)
+{
+ struct fts_backend_update_context *ctx = *_ctx;
+ struct fts_backend *backend = ctx->backend;
+ int ret;
+
+ *_ctx = NULL;
+
+ ctx->cur_box = NULL;
+ fts_backend_set_cur_mailbox(ctx);
+
+ ret = backend->v.update_deinit(ctx);
+ backend->updating = FALSE;
+ return ret;
+}
+
+void fts_backend_update_set_mailbox(struct fts_backend_update_context *ctx,
+ struct mailbox *box)
+{
+ if (ctx->backend_box != NULL && box != ctx->backend_box) {
+ /* make sure we don't reference the backend box anymore */
+ ctx->backend->v.update_set_mailbox(ctx, NULL);
+ ctx->backend_box = NULL;
+ }
+ ctx->cur_box = box;
+}
+
+void fts_backend_update_expunge(struct fts_backend_update_context *ctx,
+ uint32_t uid)
+{
+ fts_backend_set_cur_mailbox(ctx);
+ ctx->backend->v.update_expunge(ctx, uid);
+}
+
+bool fts_backend_update_set_build_key(struct fts_backend_update_context *ctx,
+ const struct fts_backend_build_key *key)
+{
+ fts_backend_set_cur_mailbox(ctx);
+
+ i_assert(ctx->cur_box != NULL);
+
+ if (!ctx->backend->v.update_set_build_key(ctx, key))
+ return FALSE;
+ ctx->build_key_open = TRUE;
+ return TRUE;
+}
+
+void fts_backend_update_unset_build_key(struct fts_backend_update_context *ctx)
+{
+ if (ctx->build_key_open) {
+ ctx->backend->v.update_unset_build_key(ctx);
+ ctx->build_key_open = FALSE;
+ }
+}
+
+int fts_backend_update_build_more(struct fts_backend_update_context *ctx,
+ const unsigned char *data, size_t size)
+{
+ i_assert(ctx->build_key_open);
+
+ return ctx->backend->v.update_build_more(ctx, data, size);
+}
+
+int fts_backend_refresh(struct fts_backend *backend)
+{
+ return backend->v.refresh(backend);
+}
+
+int fts_backend_reset_last_uids(struct fts_backend *backend)
+{
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ struct mailbox *box;
+ int ret = 0;
+
+ iter = mailbox_list_iter_init(backend->ns->list, "*",
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ if ((info->flags &
+ (MAILBOX_NONEXISTENT | MAILBOX_NOSELECT)) != 0)
+ continue;
+
+ box = mailbox_alloc(info->ns->list, info->vname, 0);
+ if (mailbox_open(box) == 0) {
+ if (fts_index_set_last_uid(box, 0) < 0)
+ ret = -1;
+ }
+ mailbox_free(&box);
+ }
+ if (mailbox_list_iter_deinit(&iter) < 0)
+ ret = -1;
+ return ret;
+}
+
+int fts_backend_rescan(struct fts_backend *backend)
+{
+ struct mailbox *box;
+ bool virtual_storage;
+
+ box = mailbox_alloc(backend->ns->list, "", 0);
+ virtual_storage = box->virtual_vfuncs != NULL;
+ mailbox_free(&box);
+
+ if (virtual_storage) {
+ /* just reset the last-uids for a virtual storage. */
+ return fts_backend_reset_last_uids(backend);
+ }
+
+ return backend->v.rescan == NULL ? 0 :
+ backend->v.rescan(backend);
+}
+
+int fts_backend_optimize(struct fts_backend *backend)
+{
+ return backend->v.optimize == NULL ? 0 :
+ backend->v.optimize(backend);
+}
+
+static void
+fts_merge_maybies(ARRAY_TYPE(seq_range) *dest_maybe,
+ const ARRAY_TYPE(seq_range) *dest_definite,
+ const ARRAY_TYPE(seq_range) *src_maybe,
+ const ARRAY_TYPE(seq_range) *src_definite)
+{
+ ARRAY_TYPE(seq_range) src_unwanted;
+ const struct seq_range *range;
+ struct seq_range new_range;
+ unsigned int i, count;
+ uint32_t seq;
+
+ /* add/leave to dest_maybe if at least one list has maybe,
+ and no lists have none */
+
+ /* create unwanted sequences list from both sources */
+ t_array_init(&src_unwanted, 128);
+ new_range.seq1 = 0; new_range.seq2 = (uint32_t)-1;
+ array_push_back(&src_unwanted, &new_range);
+ seq_range_array_remove_seq_range(&src_unwanted, src_maybe);
+ seq_range_array_remove_seq_range(&src_unwanted, src_definite);
+
+ /* drop unwanted uids */
+ seq_range_array_remove_seq_range(dest_maybe, &src_unwanted);
+
+ /* add uids that are in dest_definite and src_maybe lists */
+ range = array_get(dest_definite, &count);
+ for (i = 0; i < count; i++) {
+ for (seq = range[i].seq1; seq <= range[i].seq2; seq++) {
+ if (seq_range_exists(src_maybe, seq))
+ seq_range_array_add(dest_maybe, seq);
+ }
+ }
+}
+
+void fts_filter_uids(ARRAY_TYPE(seq_range) *definite_dest,
+ const ARRAY_TYPE(seq_range) *definite_filter,
+ ARRAY_TYPE(seq_range) *maybe_dest,
+ const ARRAY_TYPE(seq_range) *maybe_filter)
+{
+ T_BEGIN {
+ fts_merge_maybies(maybe_dest, definite_dest,
+ maybe_filter, definite_filter);
+ } T_END;
+ /* keep only what exists in both lists. the rest is in
+ maybies or not wanted */
+ seq_range_array_intersect(definite_dest, definite_filter);
+}
+
+bool fts_backend_default_can_lookup(struct fts_backend *backend,
+ const struct mail_search_arg *args)
+{
+ for (; args != NULL; args = args->next) {
+ switch (args->type) {
+ case SEARCH_OR:
+ case SEARCH_SUB:
+ case SEARCH_INTHREAD:
+ if (fts_backend_default_can_lookup(backend,
+ args->value.subargs))
+ return TRUE;
+ break;
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ case SEARCH_BODY:
+ case SEARCH_TEXT:
+ if (!args->no_fts)
+ return TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ return FALSE;
+}
+
+bool fts_backend_can_lookup(struct fts_backend *backend,
+ const struct mail_search_arg *args)
+{
+ return backend->v.can_lookup(backend, args);
+}
+
+static int fts_score_map_sort(const struct fts_score_map *m1,
+ const struct fts_score_map *m2)
+{
+ if (m1->uid < m2->uid)
+ return -1;
+ if (m1->uid > m2->uid)
+ return 1;
+ return 0;
+}
+
+int fts_backend_lookup(struct fts_backend *backend, struct mailbox *box,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_result *result)
+{
+ array_clear(&result->definite_uids);
+ array_clear(&result->maybe_uids);
+ array_clear(&result->scores);
+
+ if (backend->v.lookup(backend, box, args, flags, result) < 0)
+ return -1;
+
+ if (!result->scores_sorted && array_is_created(&result->scores)) {
+ array_sort(&result->scores, fts_score_map_sort);
+ result->scores_sorted = TRUE;
+ }
+ return 0;
+}
+
+int fts_backend_lookup_multi(struct fts_backend *backend,
+ struct mailbox *const boxes[],
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result)
+{
+ unsigned int i;
+
+ i_assert(boxes[0] != NULL);
+
+ if (backend->v.lookup_multi != NULL) {
+ if (backend->v.lookup_multi(backend, boxes, args,
+ flags, result) < 0)
+ return -1;
+ if (result->box_results == NULL) {
+ result->box_results = p_new(result->pool,
+ struct fts_result, 1);
+ }
+ return 0;
+ }
+
+ for (i = 0; boxes[i] != NULL; i++) ;
+ result->box_results = p_new(result->pool, struct fts_result, i+1);
+
+ for (i = 0; boxes[i] != NULL; i++) {
+ struct fts_result *box_result = &result->box_results[i];
+
+ p_array_init(&box_result->definite_uids, result->pool, 32);
+ p_array_init(&box_result->maybe_uids, result->pool, 32);
+ p_array_init(&box_result->scores, result->pool, 32);
+ if (backend->v.lookup(backend, boxes[i], args,
+ flags, box_result) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+void fts_backend_lookup_done(struct fts_backend *backend)
+{
+ if (backend->v.lookup_done != NULL)
+ backend->v.lookup_done(backend);
+}
+
+static uint32_t fts_index_get_ext_id(struct mailbox *box)
+{
+ return mail_index_ext_register(box->index, "fts",
+ sizeof(struct fts_index_header),
+ 0, 0);
+}
+
+bool fts_index_get_header(struct mailbox *box, struct fts_index_header *hdr_r)
+{
+ struct mail_index_view *view;
+ const void *data;
+ size_t data_size;
+ bool ret;
+
+ mail_index_refresh(box->index);
+ view = mail_index_view_open(box->index);
+ mail_index_get_header_ext(view, fts_index_get_ext_id(box),
+ &data, &data_size);
+ if (data_size < sizeof(*hdr_r)) {
+ i_zero(hdr_r);
+ ret = FALSE;
+ } else {
+ memcpy(hdr_r, data, sizeof(*hdr_r));
+ ret = TRUE;
+ }
+ mail_index_view_close(&view);
+ return ret;
+}
+
+int fts_index_set_header(struct mailbox *box,
+ const struct fts_index_header *hdr)
+{
+ struct mail_index_transaction *trans;
+ uint32_t ext_id = fts_index_get_ext_id(box);
+
+ trans = mail_index_transaction_begin(box->view, 0);
+ mail_index_update_header_ext(trans, ext_id, 0, hdr, sizeof(*hdr));
+ return mail_index_transaction_commit(&trans);
+}
+
+int fts_index_set_last_uid(struct mailbox *box, uint32_t last_uid)
+{
+ struct fts_index_header hdr;
+
+ (void)fts_index_get_header(box, &hdr);
+ hdr.last_indexed_uid = last_uid;
+ return fts_index_set_header(box, &hdr);
+}
+
+int fts_index_have_compatible_settings(struct mailbox_list *list,
+ uint32_t checksum)
+{
+ struct mail_namespace *ns = mailbox_list_get_namespace(list);
+ struct mailbox *box;
+ struct fts_index_header hdr;
+ const char *vname;
+ size_t len;
+ int ret;
+
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0)
+ vname = "INBOX";
+ else {
+ len = strlen(ns->prefix);
+ if (len > 0 && ns->prefix[len-1] == mail_namespace_get_sep(ns))
+ len--;
+ vname = t_strndup(ns->prefix, len);
+ }
+
+ box = mailbox_alloc(list, vname, 0);
+ if (mailbox_sync(box, (enum mailbox_sync_flags)0) < 0) {
+ i_error("fts: Failed to sync mailbox %s: %s", vname,
+ mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ } else {
+ ret = fts_index_get_header(box, &hdr) &&
+ hdr.settings_checksum == checksum ? 1 : 0;
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static const char *indexed_headers[] = {
+ "From", "To", "Cc", "Bcc", "Subject"
+};
+
+bool fts_header_want_indexed(const char *hdr_name)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(indexed_headers); i++) {
+ if (strcasecmp(hdr_name, indexed_headers[i]) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+bool fts_header_has_language(const char *hdr_name)
+{
+ /* FIXME: should email address headers be detected as different
+ languages? That mainly contains people's names.. */
+ /*if (message_header_is_address(hdr_name))
+ return TRUE;*/
+
+ /* Subject definitely contains language-specific data that can be
+ detected. Comment and Keywords headers also could contain, although
+ just about nobody uses those headers.
+
+ For now we assume that other headers contain non-language specific
+ data that we don't want to filter in special ways. For example
+ it is good to be able to search for Message-IDs. */
+ return strcasecmp(hdr_name, "Subject") == 0 ||
+ strcasecmp(hdr_name, "Comments") == 0 ||
+ strcasecmp(hdr_name, "Keywords") == 0;
+}
+
+int fts_mailbox_get_guid(struct mailbox *box, const char **guid_r)
+{
+ struct mailbox_metadata metadata;
+
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0)
+ return -1;
+
+ *guid_r = guid_128_to_string(metadata.guid);
+ return 0;
+}
diff --git a/src/plugins/fts/fts-api.h b/src/plugins/fts/fts-api.h
new file mode 100644
index 0000000..11a331f
--- /dev/null
+++ b/src/plugins/fts/fts-api.h
@@ -0,0 +1,173 @@
+#ifndef FTS_API_H
+#define FTS_API_H
+
+struct mail;
+struct mailbox;
+struct mail_namespace;
+struct mail_search_arg;
+
+struct fts_backend;
+
+#include "seq-range-array.h"
+
+enum fts_lookup_flags {
+ /* Specifies if the args should be ANDed or ORed together. */
+ FTS_LOOKUP_FLAG_AND_ARGS = 0x01,
+ /* Require exact matching for non-fuzzy search args by returning all
+ such matches as maybe_uids instead of definite_uids */
+ FTS_LOOKUP_FLAG_NO_AUTO_FUZZY = 0x02
+};
+
+enum fts_backend_build_key_type {
+ /* Header */
+ FTS_BACKEND_BUILD_KEY_HDR,
+ /* MIME part header */
+ FTS_BACKEND_BUILD_KEY_MIME_HDR,
+ /* MIME body part */
+ FTS_BACKEND_BUILD_KEY_BODY_PART,
+ /* Binary MIME body part, if backend supports binary data */
+ FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY
+};
+
+struct fts_backend_build_key {
+ uint32_t uid;
+ enum fts_backend_build_key_type type;
+ struct message_part *part;
+
+ /* for _KEY_HDR: */
+ const char *hdr_name;
+
+ /* for _KEY_BODY_PART and _KEY_BODY_PART_BINARY: */
+
+ /* Contains a valid parsed "type/subtype" string. For messages without
+ (valid) Content-Type: header, it's set to "text/plain". */
+ const char *body_content_type;
+ /* Content-Disposition: header without parsing/validation if it exists,
+ otherwise NULL. */
+ const char *body_content_disposition;
+};
+
+struct fts_score_map {
+ uint32_t uid;
+ float score;
+};
+ARRAY_DEFINE_TYPE(fts_score_map, struct fts_score_map);
+
+/* the structure is meant to be implemented by plugins that want to carry
+ some state over from a call to next ones within an fts_search_context
+ session.
+
+ The pointer to this structure is initially granted to be NULL and it
+ remains such unless the plugin itself activates it.
+
+ Any memory management for the pointer and its contents is expected to
+ be performed by the plugin itself, possibly but not necessarily using
+ the result pool propagated to plugin call by struct fts_result.pool and
+ struct fts_multi_result.pool. */
+
+struct fts_search_state;
+
+struct fts_result {
+ pool_t pool;
+ struct fts_search_state *search_state;
+
+ struct mailbox *box;
+
+ ARRAY_TYPE(seq_range) definite_uids;
+ /* The maybe_uids is useful with backends that can only filter out
+ messages, but can't definitively say if the search matched a
+ message. */
+ ARRAY_TYPE(seq_range) maybe_uids;
+ ARRAY_TYPE(fts_score_map) scores;
+ bool scores_sorted;
+};
+
+struct fts_multi_result {
+ pool_t pool;
+ struct fts_search_state *search_state;
+
+ /* box=NULL-terminated array of mailboxes and matching UIDs,
+ all allocated from the given pool. */
+ struct fts_result *box_results;
+};
+
+extern struct event_category event_category_fts;
+
+int fts_backend_init(const char *backend_name, struct mail_namespace *ns,
+ const char **error_r, struct fts_backend **backend_r);
+void fts_backend_deinit(struct fts_backend **backend);
+
+/* Get the last_uid for the mailbox. */
+int fts_backend_get_last_uid(struct fts_backend *backend, struct mailbox *box,
+ uint32_t *last_uid_r);
+
+/* Returns TRUE if there exists an update context. */
+bool fts_backend_is_updating(struct fts_backend *backend);
+
+/* Start an index update. */
+struct fts_backend_update_context *
+fts_backend_update_init(struct fts_backend *backend);
+/* Finish an index update. Returns 0 if ok, -1 if some updates failed.
+ If updates failed, the index is in unspecified state. */
+int fts_backend_update_deinit(struct fts_backend_update_context **ctx);
+
+/* Switch to updating the specified mailbox. box may also be set to NULL to
+ make sure the previous mailbox won't tried to be accessed anymore. */
+void fts_backend_update_set_mailbox(struct fts_backend_update_context *ctx,
+ struct mailbox *box);
+/* Expunge the specified mail. */
+void fts_backend_update_expunge(struct fts_backend_update_context *ctx,
+ uint32_t uid);
+
+/* Switch to building index for specified key. If backend doesn't want to
+ index this key, it can return FALSE and caller will skip to next key. */
+bool fts_backend_update_set_build_key(struct fts_backend_update_context *ctx,
+ const struct fts_backend_build_key *key);
+/* Make sure that if _build_more() is called, we'll assert-crash. */
+void fts_backend_update_unset_build_key(struct fts_backend_update_context *ctx);
+/* Add more content to the index for the currently specified build key.
+ Non-BODY_PART_BINARY data must contain only full valid UTF-8 characters,
+ but it doesn't need to be NUL-terminated. size contains the data size in
+ bytes, not characters. This function may be called many times and the data
+ block sizes may be small. Backend returns 0 if ok, -1 if build should be
+ aborted. */
+int fts_backend_update_build_more(struct fts_backend_update_context *ctx,
+ const unsigned char *data, size_t size);
+
+/* Refresh index to make sure we see latest changes from lookups.
+ Returns 0 if ok, -1 if error. */
+int fts_backend_refresh(struct fts_backend *backend);
+/* Go through the entire index and make sure all mails are indexed,
+ and delete any extra mails in the index. */
+int fts_backend_rescan(struct fts_backend *backend);
+/* Optimize the index. This can be a somewhat heavy operation. */
+int fts_backend_optimize(struct fts_backend *backend);
+
+/* Returns TRUE if fts_backend_lookup() should even be tried for the
+ given args. */
+bool fts_backend_can_lookup(struct fts_backend *backend,
+ const struct mail_search_arg *args);
+/* Do a FTS lookup for the given search args. Backends can support different
+ kinds of search arguments, so match_always=TRUE must be set to all search
+ args that were actually used to produce the search results. The other args
+ are handled by the regular search code. The backends MUST ignore all args
+ that have subargs (SEARCH_OR, SEARCH_SUB), since they are looked up
+ separately.
+
+ The arrays in result must be initialized by caller. */
+int fts_backend_lookup(struct fts_backend *backend, struct mailbox *box,
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_result *result);
+
+/* Search from multiple mailboxes. result->pool must be initialized. */
+int fts_backend_lookup_multi(struct fts_backend *backend,
+ struct mailbox *const boxes[],
+ struct mail_search_arg *args,
+ enum fts_lookup_flags flags,
+ struct fts_multi_result *result);
+/* Called after the lookups are done. The next lookup will be preceded by a
+ refresh. */
+void fts_backend_lookup_done(struct fts_backend *backend);
+
+#endif
diff --git a/src/plugins/fts/fts-build-mail.c b/src/plugins/fts/fts-build-mail.c
new file mode 100644
index 0000000..73d4f4b
--- /dev/null
+++ b/src/plugins/fts/fts-build-mail.c
@@ -0,0 +1,719 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "buffer.h"
+#include "str.h"
+#include "rfc822-parser.h"
+#include "message-address.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+#include "mail-storage.h"
+#include "index-mail.h"
+#include "fts-parser.h"
+#include "fts-user.h"
+#include "fts-language.h"
+#include "fts-tokenizer.h"
+#include "fts-filter.h"
+#include "fts-api-private.h"
+#include "fts-build-mail.h"
+
+/* there are other characters as well, but this doesn't have to be exact */
+#define IS_WORD_WHITESPACE(c) \
+ ((c) == ' ' || (c) == '\t' || (c) == '\n')
+/* if we see a word larger than this, just go ahead and split it from
+ wherever */
+#define MAX_WORD_SIZE 1024
+
+struct fts_mail_build_context {
+ struct mail *mail;
+ struct fts_backend_update_context *update_ctx;
+
+ char *content_type, *content_disposition;
+ struct fts_parser *body_parser;
+
+ buffer_t *word_buf, *pending_input;
+ struct fts_user_language *cur_user_lang;
+};
+
+static int fts_build_data(struct fts_mail_build_context *ctx,
+ const unsigned char *data, size_t size, bool last);
+
+static void fts_build_parse_content_type(struct fts_mail_build_context *ctx,
+ const struct message_header_line *hdr)
+{
+ struct rfc822_parser_context parser;
+ string_t *content_type;
+
+ if (ctx->content_type != NULL)
+ return;
+
+ rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+ rfc822_skip_lwsp(&parser);
+
+ T_BEGIN {
+ content_type = t_str_new(64);
+ (void)rfc822_parse_content_type(&parser, content_type);
+ ctx->content_type = str_lcase(i_strdup(str_c(content_type)));
+ } T_END;
+ rfc822_parser_deinit(&parser);
+}
+
+static void
+fts_build_parse_content_disposition(struct fts_mail_build_context *ctx,
+ const struct message_header_line *hdr)
+{
+ /* just pass it as-is to backend. */
+ i_free(ctx->content_disposition);
+ ctx->content_disposition =
+ i_strndup(hdr->full_value, hdr->full_value_len);
+}
+
+static void fts_parse_mail_header(struct fts_mail_build_context *ctx,
+ const struct message_block *raw_block)
+{
+ const struct message_header_line *hdr = raw_block->hdr;
+
+ if (strcasecmp(hdr->name, "Content-Type") == 0)
+ fts_build_parse_content_type(ctx, hdr);
+ else if (strcasecmp(hdr->name, "Content-Disposition") == 0)
+ fts_build_parse_content_disposition(ctx, hdr);
+}
+
+static int
+fts_build_unstructured_header(struct fts_mail_build_context *ctx,
+ const struct message_header_line *hdr)
+{
+ const unsigned char *data = hdr->full_value;
+ unsigned char *buf = NULL;
+ unsigned int i;
+ int ret;
+
+ /* @UNSAFE: if there are any NULs, replace them with spaces */
+ for (i = 0; i < hdr->full_value_len; i++) {
+ if (hdr->full_value[i] == '\0') {
+ if (buf == NULL) {
+ buf = i_memdup(hdr->full_value,
+ hdr->full_value_len);
+ data = buf;
+ }
+ buf[i] = ' ';
+ }
+ }
+ ret = fts_build_data(ctx, data, hdr->full_value_len, TRUE);
+ i_free(buf);
+ return ret;
+}
+
+static void fts_mail_build_ctx_set_lang(struct fts_mail_build_context *ctx,
+ struct fts_user_language *user_lang)
+{
+ i_assert(user_lang != NULL);
+
+ ctx->cur_user_lang = user_lang;
+ /* reset tokenizer between fields - just to be sure no state
+ leaks between fields (especially if previous indexing had
+ failed) */
+ fts_tokenizer_reset(user_lang->index_tokenizer);
+}
+
+static void
+fts_build_tokenized_hdr_update_lang(struct fts_mail_build_context *ctx,
+ const struct message_header_line *hdr)
+{
+ /* Headers that don't contain any human language will only be
+ translated to lowercase - no stemming or other filtering. There's
+ unfortunately no pefect way of detecting which headers contain
+ human languages, so we check with fts_header_has_language if the
+ header is something that's supposed to containing human text. */
+ if (fts_header_has_language(hdr->name))
+ ctx->cur_user_lang = NULL;
+ else {
+ fts_mail_build_ctx_set_lang(ctx,
+ fts_user_get_data_lang(ctx->update_ctx->backend->ns->user));
+ }
+}
+
+static int fts_build_mail_header(struct fts_mail_build_context *ctx,
+ const struct message_block *block)
+{
+ const struct message_header_line *hdr = block->hdr;
+ struct fts_backend_build_key key;
+ int ret;
+
+ if (hdr->eoh)
+ return 0;
+
+ /* hdr->full_value is always set because we get the block from
+ message_decoder */
+ i_zero(&key);
+ key.uid = ctx->mail->uid;
+ key.type = block->part->physical_pos == 0 ?
+ FTS_BACKEND_BUILD_KEY_HDR : FTS_BACKEND_BUILD_KEY_MIME_HDR;
+ key.part = block->part;
+ key.hdr_name = hdr->name;
+
+ if ((ctx->update_ctx->backend->flags &
+ FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0)
+ fts_build_tokenized_hdr_update_lang(ctx, hdr);
+
+ if (!fts_backend_update_set_build_key(ctx->update_ctx, &key))
+ return 0;
+
+ if (!message_header_is_address(hdr->name)) {
+ /* regular unstructured header */
+ ret = fts_build_unstructured_header(ctx, hdr);
+ } else T_BEGIN {
+ /* message address. normalize it to give better
+ search results. */
+ struct message_address *addr;
+ string_t *str;
+
+ addr = message_address_parse(pool_datastack_create(),
+ hdr->full_value,
+ hdr->full_value_len,
+ UINT_MAX, 0);
+ str = t_str_new(hdr->full_value_len);
+ message_address_write(str, addr);
+
+ ret = fts_build_data(ctx, str_data(str), str_len(str), TRUE);
+ } T_END;
+
+ if ((ctx->update_ctx->backend->flags &
+ FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0) {
+ /* index the header name itself using data-language. */
+ struct fts_user_language *prev_lang = ctx->cur_user_lang;
+
+ fts_mail_build_ctx_set_lang(ctx,
+ fts_user_get_data_lang(ctx->update_ctx->backend->ns->user));
+ key.hdr_name = "";
+ if (fts_backend_update_set_build_key(ctx->update_ctx, &key)) {
+ if (fts_build_data(ctx, (const void *)hdr->name,
+ strlen(hdr->name), TRUE) < 0)
+ ret = -1;
+ }
+ fts_mail_build_ctx_set_lang(ctx, prev_lang);
+ }
+ return ret;
+}
+
+static bool
+fts_build_body_begin(struct fts_mail_build_context *ctx,
+ struct message_part *part, bool *binary_body_r)
+{
+ struct mail_storage *storage;
+ struct fts_parser_context parser_context;
+ struct fts_backend_build_key key;
+
+ i_assert(ctx->body_parser == NULL);
+
+ *binary_body_r = FALSE;
+ i_zero(&key);
+ key.uid = ctx->mail->uid;
+ key.part = part;
+
+ i_zero(&parser_context);
+ parser_context.content_type = ctx->content_type != NULL ?
+ ctx->content_type : "text/plain";
+ if (str_begins(parser_context.content_type, "multipart/")) {
+ /* multiparts are never indexed, only their contents */
+ return FALSE;
+ }
+ storage = mailbox_get_storage(ctx->mail->box);
+ parser_context.user = mail_storage_get_user(storage);
+ parser_context.content_disposition = ctx->content_disposition;
+
+ if (fts_parser_init(&parser_context, &ctx->body_parser)) {
+ /* extract text using the the returned parser */
+ *binary_body_r = TRUE;
+ key.type = FTS_BACKEND_BUILD_KEY_BODY_PART;
+ } else if (str_begins(parser_context.content_type, "text/") ||
+ str_begins(parser_context.content_type, "message/")) {
+ /* text body parts */
+ key.type = FTS_BACKEND_BUILD_KEY_BODY_PART;
+ ctx->body_parser = fts_parser_text_init();
+ } else {
+ /* possibly binary */
+ if ((ctx->update_ctx->backend->flags &
+ FTS_BACKEND_FLAG_BINARY_MIME_PARTS) == 0)
+ return FALSE;
+ *binary_body_r = TRUE;
+ key.type = FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY;
+ }
+ key.body_content_type = parser_context.content_type;
+ key.body_content_disposition = ctx->content_disposition;
+ ctx->cur_user_lang = NULL;
+ if (!fts_backend_update_set_build_key(ctx->update_ctx, &key)) {
+ if (ctx->body_parser != NULL)
+ (void)fts_parser_deinit(&ctx->body_parser, NULL);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+fts_build_add_tokens_with_filter(struct fts_mail_build_context *ctx,
+ const unsigned char *data, size_t size)
+{
+ struct fts_tokenizer *tokenizer = ctx->cur_user_lang->index_tokenizer;
+ struct fts_filter *filter = ctx->cur_user_lang->filter;
+ const char *token, *error;
+ int ret = 1, ret2;
+
+ while (ret > 0) T_BEGIN {
+ ret = ret2 = fts_tokenizer_next(tokenizer, data, size, &token, &error);
+ if (ret2 > 0 && filter != NULL)
+ ret2 = fts_filter_filter(filter, &token, &error);
+ if (ret2 < 0) {
+ mail_set_critical(ctx->mail,
+ "fts: Couldn't create indexable tokens: %s",
+ error);
+ }
+ if (ret2 > 0) {
+ if (fts_backend_update_build_more(ctx->update_ctx,
+ (const void *)token,
+ strlen(token)) < 0) {
+ mail_storage_set_internal_error(ctx->mail->box->storage);
+ ret = -1;
+ }
+ }
+ } T_END;
+ return ret;
+}
+
+static int
+fts_detect_language(struct fts_mail_build_context *ctx,
+ const unsigned char *data, size_t size, bool last,
+ const struct fts_language **lang_r)
+{
+ struct mail_user *user = ctx->update_ctx->backend->ns->user;
+ struct fts_language_list *lang_list = fts_user_get_language_list(user);
+ const struct fts_language *lang;
+ const char *error;
+
+ switch (fts_language_detect(lang_list, data, size, &lang, &error)) {
+ case FTS_LANGUAGE_RESULT_SHORT:
+ /* save the input so far and try again later */
+ buffer_append(ctx->pending_input, data, size);
+ if (last) {
+ /* we've run out of data. use the default language. */
+ *lang_r = fts_language_list_get_first(lang_list);
+ return 1;
+ }
+ return 0;
+ case FTS_LANGUAGE_RESULT_UNKNOWN:
+ /* use the default language */
+ *lang_r = fts_language_list_get_first(lang_list);
+ return 1;
+ case FTS_LANGUAGE_RESULT_OK:
+ *lang_r = lang;
+ return 1;
+ case FTS_LANGUAGE_RESULT_ERROR:
+ /* internal language detection library failure
+ (e.g. invalid config). don't index anything. */
+ mail_set_critical(ctx->mail,
+ "Language detection library initialization failed: %s",
+ error);
+ return -1;
+ default:
+ i_unreached();
+ }
+}
+
+static int
+fts_build_tokenized(struct fts_mail_build_context *ctx,
+ const unsigned char *data, size_t size, bool last)
+{
+ struct mail_user *user = ctx->update_ctx->backend->ns->user;
+ const struct fts_language *lang;
+ int ret;
+
+ if (ctx->cur_user_lang != NULL) {
+ /* we already have a language */
+ } else if ((ret = fts_detect_language(ctx, data, size, last, &lang)) < 0) {
+ return -1;
+ } else if (ret == 0) {
+ /* wait for more data */
+ return 0;
+ } else {
+ fts_mail_build_ctx_set_lang(ctx, fts_user_language_find(user, lang));
+
+ if (ctx->pending_input->used > 0) {
+ if (fts_build_add_tokens_with_filter(ctx,
+ ctx->pending_input->data,
+ ctx->pending_input->used) < 0)
+ return -1;
+ buffer_set_used_size(ctx->pending_input, 0);
+ }
+ }
+ if (fts_build_add_tokens_with_filter(ctx, data, size) < 0)
+ return -1;
+ if (last) {
+ if (fts_build_add_tokens_with_filter(ctx, NULL, 0) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+fts_build_full_words(struct fts_mail_build_context *ctx,
+ const unsigned char *data, size_t size, bool last)
+{
+ size_t i;
+
+ /* we'll need to send only full words to the backend */
+
+ if (ctx->word_buf != NULL && ctx->word_buf->used > 0) {
+ /* continuing previous word */
+ for (i = 0; i < size; i++) {
+ if (IS_WORD_WHITESPACE(data[i]))
+ break;
+ }
+ buffer_append(ctx->word_buf, data, i);
+ data += i;
+ size -= i;
+ if (size == 0 && ctx->word_buf->used < MAX_WORD_SIZE && !last) {
+ /* word is still not finished */
+ return 0;
+ }
+ /* we have a full word, index it */
+ if (fts_backend_update_build_more(ctx->update_ctx,
+ ctx->word_buf->data,
+ ctx->word_buf->used) < 0) {
+ mail_storage_set_internal_error(ctx->mail->box->storage);
+ return -1;
+ }
+ buffer_set_used_size(ctx->word_buf, 0);
+ }
+
+ /* find the boundary for last word */
+ if (last)
+ i = size;
+ else {
+ for (i = size; i > 0; i--) {
+ if (IS_WORD_WHITESPACE(data[i-1]))
+ break;
+ }
+ }
+
+ if (fts_backend_update_build_more(ctx->update_ctx, data, i) < 0) {
+ mail_storage_set_internal_error(ctx->mail->box->storage);
+ return -1;
+ }
+
+ if (i < size) {
+ if (ctx->word_buf == NULL) {
+ ctx->word_buf =
+ buffer_create_dynamic(default_pool, 128);
+ }
+ buffer_append(ctx->word_buf, data + i, size - i);
+ }
+ return 0;
+}
+
+static int fts_build_data(struct fts_mail_build_context *ctx,
+ const unsigned char *data, size_t size, bool last)
+{
+ if ((ctx->update_ctx->backend->flags &
+ FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0) {
+ return fts_build_tokenized(ctx, data, size, last);
+ } else if ((ctx->update_ctx->backend->flags &
+ FTS_BACKEND_FLAG_BUILD_FULL_WORDS) != 0) {
+ return fts_build_full_words(ctx, data, size, last);
+ } else {
+ if (fts_backend_update_build_more(ctx->update_ctx, data, size) < 0) {
+ mail_storage_set_internal_error(ctx->mail->box->storage);
+ return -1;
+ }
+ return 0;
+ }
+}
+
+static int fts_build_body_block(struct fts_mail_build_context *ctx,
+ const struct message_block *block, bool last)
+{
+ i_assert(block->hdr == NULL);
+
+ return fts_build_data(ctx, block->data, block->size, last);
+}
+
+static int fts_body_parser_finish(struct fts_mail_build_context *ctx,
+ const char **retriable_err_msg_r,
+ bool *may_need_retry_r)
+{
+ struct message_block block;
+ const char *retriable_error;
+ int ret = 0;
+ int deinit_ret;
+ *may_need_retry_r = FALSE;
+
+ do {
+ i_zero(&block);
+ fts_parser_more(ctx->body_parser, &block);
+ if (fts_build_body_block(ctx, &block, FALSE) < 0) {
+ ret = -1;
+ break;
+ }
+ } while (block.size > 0);
+
+ deinit_ret = fts_parser_deinit(&ctx->body_parser, &retriable_error);
+ if (ret < 0) {
+ /* indexing already failed - we don't want to retry
+ in any case */
+ return -1;
+ }
+
+ if (deinit_ret == 0) {
+ /* retry the parsing */
+ *may_need_retry_r = TRUE;
+ *retriable_err_msg_r = retriable_error;
+ return -1;
+ }
+ if (deinit_ret < 0) {
+ mail_storage_set_internal_error(ctx->mail->box->storage);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+load_header_filter(const char *key, struct fts_backend *backend,
+ ARRAY_TYPE(const_string) list, bool *matches_all_r)
+{
+ const char *str = mail_user_plugin_getenv(backend->ns->user, key);
+
+ *matches_all_r = FALSE;
+ if (str == NULL || *str == '\0')
+ return;
+
+ char **entries = p_strsplit_spaces(backend->header_filters.pool, str, " ");
+ for (char **entry = entries; *entry != NULL; ++entry) {
+ const char *value = str_lcase(*entry);
+ array_push_back(&list, &value);
+ if (*value == '*') {
+ *matches_all_r = TRUE;
+ break;
+ }
+ }
+ array_sort(&list, i_strcmp_p);
+}
+
+static struct fts_header_filters *
+load_header_filters(struct fts_backend *backend)
+{
+ struct fts_header_filters *filters = &backend->header_filters;
+ if (!filters->loaded) {
+ bool match_all;
+
+ /* match_all return ignored in includes */
+ load_header_filter("fts_header_includes", backend,
+ filters->includes, &match_all);
+
+ load_header_filter("fts_header_excludes", backend,
+ filters->excludes, &match_all);
+ filters->loaded = TRUE;
+ filters->exclude_is_default = match_all;
+ }
+ return filters;
+}
+
+/* This performs comparison between two strings, where the second one can end
+ * with the wildcard '*'. When the match reaches a '*' on the pitem side, zero
+ * (match) is returned regardles of the remaining characters.
+ *
+ * The function obeys the same lexicographic order as i_strcmp_p() and
+ * strcmp(), which is the reason for the casts to unsigned before comparing.
+ */
+static int ATTR_PURE
+header_prefix_cmp(const char *const *pkey, const char *const *pitem)
+{
+ const char *key = *pkey;
+ const char *item = *pitem;
+
+ while (*key == *item && *key != '\0') key++, item++;
+ return item[0] == '*' && item[1] == '\0' ? 0 :
+ (unsigned char)*key - (unsigned char)*item;
+}
+
+static bool
+is_header_indexable(const char *header_name, struct fts_backend *backend)
+{
+ bool indexable;
+ T_BEGIN {
+ struct fts_header_filters *filters = load_header_filters(backend);
+ const char *hdr = t_str_lcase(header_name);
+
+ if (array_bsearch(&filters->includes, &hdr, header_prefix_cmp) != NULL)
+ indexable = TRUE;
+ else if (filters->exclude_is_default ||
+ array_bsearch(&filters->excludes, &hdr, header_prefix_cmp) != NULL)
+ indexable = FALSE;
+ else
+ indexable = TRUE;
+ } T_END;
+ return indexable;
+}
+
+static int
+fts_build_mail_real(struct fts_backend_update_context *update_ctx,
+ struct mail *mail,
+ const char **retriable_err_msg_r,
+ bool *may_need_retry_r)
+{
+ const struct message_parser_settings parser_set = {
+ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE,
+ };
+ struct fts_mail_build_context ctx;
+ struct istream *input;
+ struct message_parser_ctx *parser;
+ struct message_decoder_context *decoder;
+ struct message_block raw_block, block;
+ struct message_part *prev_part, *parts;
+ bool skip_body = FALSE, body_part = FALSE, body_added = FALSE;
+ bool binary_body;
+ const char *error;
+ int ret;
+
+ *may_need_retry_r = FALSE;
+ if (mail_get_stream_because(mail, NULL, NULL, "fts indexing", &input) < 0) {
+ if (mail->expunged)
+ return 0;
+ mail_set_critical(mail, "Failed to read stream: %s",
+ mailbox_get_last_internal_error(mail->box, NULL));
+ return -1;
+ }
+
+ i_zero(&ctx);
+ ctx.update_ctx = update_ctx;
+ ctx.mail = mail;
+ if ((update_ctx->backend->flags & FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0)
+ ctx.pending_input = buffer_create_dynamic(default_pool, 128);
+
+ prev_part = NULL;
+ parser = message_parser_init(pool_datastack_create(), input, &parser_set);
+
+ decoder = message_decoder_init(update_ctx->normalizer, 0);
+ for (;;) {
+ ret = message_parser_parse_next_block(parser, &raw_block);
+ i_assert(ret != 0);
+ if (ret < 0) {
+ if (input->stream_errno == 0)
+ ret = 0;
+ else {
+ mail_set_critical(mail, "read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ }
+ break;
+ }
+
+ if (raw_block.part != prev_part) {
+ /* body part changed. we're now parsing the end of
+ boundary, possibly followed by message epilogue */
+ if (ctx.body_parser != NULL) {
+ if (fts_body_parser_finish(&ctx, retriable_err_msg_r,
+ may_need_retry_r) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+ message_decoder_set_return_binary(decoder, FALSE);
+ fts_backend_update_unset_build_key(update_ctx);
+ prev_part = raw_block.part;
+ i_free_and_null(ctx.content_type);
+ i_free_and_null(ctx.content_disposition);
+
+ if (raw_block.size != 0) {
+ /* multipart. skip until beginning of next
+ part's headers */
+ skip_body = TRUE;
+ }
+ }
+
+ if (raw_block.hdr != NULL) {
+ /* always handle headers */
+ } else if (raw_block.size == 0) {
+ /* end of headers */
+ skip_body = !fts_build_body_begin(&ctx, raw_block.part,
+ &binary_body);
+ if (binary_body)
+ message_decoder_set_return_binary(decoder, TRUE);
+ body_part = TRUE;
+ } else {
+ if (skip_body)
+ continue;
+ }
+
+ if (!message_decoder_decode_next_block(decoder, &raw_block,
+ &block))
+ continue;
+
+ if (block.hdr != NULL) {
+ fts_parse_mail_header(&ctx, &raw_block);
+ if (is_header_indexable(block.hdr->name, update_ctx->backend) &&
+ fts_build_mail_header(&ctx, &block) < 0) {
+ ret = -1;
+ break;
+ }
+ } else if (block.size == 0) {
+ /* end of headers */
+ } else {
+ i_assert(body_part);
+ if (ctx.body_parser != NULL)
+ fts_parser_more(ctx.body_parser, &block);
+ if (fts_build_body_block(&ctx, &block, FALSE) < 0) {
+ ret = -1;
+ break;
+ }
+ body_added = TRUE;
+ }
+ }
+ if (ctx.body_parser != NULL) {
+ if (ret == 0)
+ ret = fts_body_parser_finish(&ctx, retriable_err_msg_r,
+ may_need_retry_r);
+ else
+ (void)fts_parser_deinit(&ctx.body_parser, NULL);
+ }
+ if (ret == 0 && body_part && !skip_body && !body_added) {
+ /* make sure body is added even when it doesn't exist */
+ block.data = NULL; block.size = 0;
+ ret = fts_build_body_block(&ctx, &block, TRUE);
+ }
+ if (message_parser_deinit_from_parts(&parser, &parts, &error) < 0)
+ index_mail_set_message_parts_corrupted(mail, error);
+ message_decoder_deinit(&decoder);
+ i_free(ctx.content_type);
+ i_free(ctx.content_disposition);
+ buffer_free(&ctx.word_buf);
+ buffer_free(&ctx.pending_input);
+ return ret < 0 ? -1 : 1;
+}
+
+int fts_build_mail(struct fts_backend_update_context *update_ctx,
+ struct mail *mail)
+{
+ int ret;
+ /* Number of attempts to be taken if retry is needed */
+ unsigned int attempts = 2;
+ const char *retriable_err_msg;
+ bool may_need_retry;
+
+ T_BEGIN {
+ while ((ret = fts_build_mail_real(update_ctx, mail,
+ &retriable_err_msg,
+ &may_need_retry)) < 0 &&
+ may_need_retry) {
+ if (--attempts == 0) {
+ /* Log this as info instead of as error,
+ because e.g. Tika doesn't differentiate
+ between temporary errors and invalid
+ document input. */
+ i_info("%s - ignoring", retriable_err_msg);
+ ret = 0;
+ break;
+ }
+ }
+ } T_END;
+ return ret;
+}
diff --git a/src/plugins/fts/fts-build-mail.h b/src/plugins/fts/fts-build-mail.h
new file mode 100644
index 0000000..aed4413
--- /dev/null
+++ b/src/plugins/fts/fts-build-mail.h
@@ -0,0 +1,9 @@
+#ifndef FTS_BUILD_MAIL_H
+#define FTS_BUILD_MAIL_H
+
+/* Build indexes for the given mail. Returns 0 on success, -1 on error.
+ The error is set to mail's storage. */
+int fts_build_mail(struct fts_backend_update_context *update_ctx,
+ struct mail *mail);
+
+#endif
diff --git a/src/plugins/fts/fts-expunge-log.c b/src/plugins/fts/fts-expunge-log.c
new file mode 100644
index 0000000..d39ceea
--- /dev/null
+++ b/src/plugins/fts/fts-expunge-log.c
@@ -0,0 +1,617 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "crc32.h"
+#include "hash.h"
+#include "istream.h"
+#include "write-full.h"
+#include "seq-range-array.h"
+#include "mail-storage.h"
+#include "fts-expunge-log.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+struct fts_expunge_log_record {
+ /* CRC32 of this entire record (except this checksum) */
+ uint32_t checksum;
+ /* Size of this entire record */
+ uint32_t record_size;
+
+ /* Mailbox GUID */
+ guid_128_t guid;
+ /* { uid1, uid2 } pairs */
+ /* uint32_t expunge_uid_ranges[]; */
+
+ /* Total number of messages expunged so far in this log */
+ /* uint32_t expunge_count; */
+};
+
+struct fts_expunge_log {
+ char *path;
+
+ int fd;
+ struct stat st;
+};
+
+struct fts_expunge_log_mailbox {
+ guid_128_t guid;
+ ARRAY_TYPE(seq_range) uids;
+ unsigned uids_count;
+};
+
+struct fts_expunge_log_append_ctx {
+ struct fts_expunge_log *log;
+ pool_t pool;
+
+ HASH_TABLE(uint8_t *, struct fts_expunge_log_mailbox *) mailboxes;
+ struct fts_expunge_log_mailbox *prev_mailbox;
+
+ bool failed;
+};
+
+struct fts_expunge_log_read_ctx {
+ struct fts_expunge_log *log;
+
+ struct istream *input;
+ buffer_t buffer;
+ struct fts_expunge_log_read_record read_rec;
+
+ bool failed;
+ bool corrupted;
+ bool unlink;
+};
+
+struct fts_expunge_log *fts_expunge_log_init(const char *path)
+{
+ struct fts_expunge_log *log;
+
+ log = i_new(struct fts_expunge_log, 1);
+ log->path = i_strdup(path);
+ log->fd = -1;
+ return log;
+}
+
+void fts_expunge_log_deinit(struct fts_expunge_log **_log)
+{
+ struct fts_expunge_log *log = *_log;
+
+ *_log = NULL;
+ i_close_fd(&log->fd);
+ i_free(log->path);
+ i_free(log);
+}
+
+static int fts_expunge_log_open(struct fts_expunge_log *log, bool create)
+{
+ int fd;
+
+ i_assert(log->fd == -1);
+
+ /* FIXME: use proper permissions */
+ fd = open(log->path, O_RDWR | O_APPEND | (create ? O_CREAT : 0), 0600);
+ if (fd == -1) {
+ if (errno == ENOENT && !create)
+ return 0;
+
+ i_error("open(%s) failed: %m", log->path);
+ return -1;
+ }
+ if (fstat(fd, &log->st) < 0) {
+ i_error("fstat(%s) failed: %m", log->path);
+ i_close_fd(&fd);
+ return -1;
+ }
+ log->fd = fd;
+ return 1;
+}
+
+static int
+fts_expunge_log_reopen_if_needed(struct fts_expunge_log *log, bool create)
+{
+ struct stat st;
+
+ if (log->fd == -1)
+ return fts_expunge_log_open(log, create);
+
+ if (stat(log->path, &st) == 0) {
+ if (st.st_ino == log->st.st_ino &&
+ CMP_DEV_T(st.st_dev, log->st.st_dev)) {
+ /* same file */
+ return 0;
+ }
+ /* file changed */
+ } else if (errno == ENOENT) {
+ /* recreate the file */
+ } else {
+ i_error("stat(%s) failed: %m", log->path);
+ return -1;
+ }
+ if (close(log->fd) < 0)
+ i_error("close(%s) failed: %m", log->path);
+ log->fd = -1;
+ return fts_expunge_log_open(log, create);
+}
+
+static int
+fts_expunge_log_read_expunge_count(struct fts_expunge_log *log,
+ uint32_t *expunge_count_r)
+{
+ ssize_t ret;
+
+ i_assert(log->fd != -1);
+
+ if (fstat(log->fd, &log->st) < 0) {
+ i_error("fstat(%s) failed: %m", log->path);
+ return -1;
+ }
+ if ((uoff_t)log->st.st_size < sizeof(*expunge_count_r)) {
+ *expunge_count_r = 0;
+ return 0;
+ }
+ /* we'll assume that write()s atomically grow the file size, as
+ O_APPEND almost guarantees. even if not, having a race condition
+ isn't the end of the world. the expunge count is simply read wrong
+ and fts optimize is performed earlier or later than intended. */
+ ret = pread(log->fd, expunge_count_r, sizeof(*expunge_count_r),
+ log->st.st_size - 4);
+ if (ret < 0) {
+ i_error("pread(%s) failed: %m", log->path);
+ return -1;
+ }
+ if (ret != sizeof(*expunge_count_r)) {
+ i_error("pread(%s) read only %d of %d bytes", log->path,
+ (int)ret, (int)sizeof(*expunge_count_r));
+ return -1;
+ }
+ return 0;
+}
+
+struct fts_expunge_log_append_ctx *
+fts_expunge_log_append_begin(struct fts_expunge_log *log)
+{
+ struct fts_expunge_log_append_ctx *ctx;
+ pool_t pool;
+
+ pool = pool_alloconly_create("fts expunge log append", 1024);
+ ctx = p_new(pool, struct fts_expunge_log_append_ctx, 1);
+ ctx->log = log;
+ ctx->pool = pool;
+ hash_table_create(&ctx->mailboxes, pool, 0, guid_128_hash, guid_128_cmp);
+
+ if (log != NULL && fts_expunge_log_reopen_if_needed(log, TRUE) < 0)
+ ctx->failed = TRUE;
+ return ctx;
+}
+
+static struct fts_expunge_log_mailbox *
+fts_expunge_log_mailbox_alloc(struct fts_expunge_log_append_ctx *ctx,
+ const guid_128_t mailbox_guid)
+{
+ uint8_t *guid_p;
+ struct fts_expunge_log_mailbox *mailbox;
+
+ mailbox = p_new(ctx->pool, struct fts_expunge_log_mailbox, 1);
+ guid_128_copy(mailbox->guid, mailbox_guid);
+ p_array_init(&mailbox->uids, ctx->pool, 16);
+
+ guid_p = mailbox->guid;
+ hash_table_insert(ctx->mailboxes, guid_p, mailbox);
+ return mailbox;
+}
+
+static struct fts_expunge_log_mailbox *
+fts_expunge_log_append_mailbox(struct fts_expunge_log_append_ctx *ctx,
+ const guid_128_t mailbox_guid)
+{
+ const uint8_t *guid_p = mailbox_guid;
+ struct fts_expunge_log_mailbox *mailbox;
+
+ if (ctx->prev_mailbox != NULL &&
+ guid_128_equals(mailbox_guid, ctx->prev_mailbox->guid))
+ mailbox = ctx->prev_mailbox;
+ else {
+ mailbox = hash_table_lookup(ctx->mailboxes, guid_p);
+ if (mailbox == NULL)
+ mailbox = fts_expunge_log_mailbox_alloc(ctx, mailbox_guid);
+ ctx->prev_mailbox = mailbox;
+ }
+ return mailbox;
+}
+void fts_expunge_log_append_next(struct fts_expunge_log_append_ctx *ctx,
+ const guid_128_t mailbox_guid,
+ uint32_t uid)
+{
+ struct fts_expunge_log_mailbox *mailbox;
+
+ mailbox = fts_expunge_log_append_mailbox(ctx, mailbox_guid);
+ if (!seq_range_array_add(&mailbox->uids, uid))
+ mailbox->uids_count++;
+}
+void fts_expunge_log_append_range(struct fts_expunge_log_append_ctx *ctx,
+ const guid_128_t mailbox_guid,
+ const struct seq_range *uids)
+{
+ struct fts_expunge_log_mailbox *mailbox;
+
+ mailbox = fts_expunge_log_append_mailbox(ctx, mailbox_guid);
+ mailbox->uids_count += seq_range_array_add_range_count(&mailbox->uids,
+ uids->seq1, uids->seq2);
+ /* To be honest, an unbacked log doesn't need to maintain the uids_count,
+ but we don't know here if we're supporting an unbacked log or not, so we
+ have to maintain the value, just in case.
+ At the moment, the only caller of this function is for unbacked logs. */
+}
+void fts_expunge_log_append_record(struct fts_expunge_log_append_ctx *ctx,
+ const struct fts_expunge_log_read_record *record)
+{
+ const struct seq_range *range;
+ /* FIXME: Optimise with a merge */
+ array_foreach(&record->uids, range)
+ fts_expunge_log_append_range(ctx, record->mailbox_guid, range);
+}
+static void fts_expunge_log_append_mailbox_record(struct fts_expunge_log_append_ctx *ctx,
+ struct fts_expunge_log_mailbox *mailbox)
+{
+ const struct seq_range *range;
+ /* FIXME: Optimise with a merge */
+ array_foreach(&mailbox->uids, range)
+ fts_expunge_log_append_range(ctx, mailbox->guid, range);
+}
+
+static void
+fts_expunge_log_export(struct fts_expunge_log_append_ctx *ctx,
+ uint32_t expunge_count, buffer_t *output)
+{
+ struct hash_iterate_context *iter;
+ uint8_t *guid_p;
+ struct fts_expunge_log_mailbox *mailbox;
+ struct fts_expunge_log_record *rec;
+ size_t rec_offset;
+
+ iter = hash_table_iterate_init(ctx->mailboxes);
+ while (hash_table_iterate(iter, ctx->mailboxes, &guid_p, &mailbox)) {
+ rec_offset = output->used;
+ rec = buffer_append_space_unsafe(output, sizeof(*rec));
+ memcpy(rec->guid, mailbox->guid, sizeof(rec->guid));
+
+ /* uint32_t expunge_uid_ranges[]; */
+ buffer_append(output, array_front(&mailbox->uids),
+ array_count(&mailbox->uids) *
+ sizeof(struct seq_range));
+ /* uint32_t expunge_count; */
+ expunge_count += mailbox->uids_count;
+ buffer_append(output, &expunge_count, sizeof(expunge_count));
+
+ /* update the header now that we know the record contents */
+ rec = buffer_get_space_unsafe(output, rec_offset,
+ output->used - rec_offset);
+ rec->record_size = output->used - rec_offset;
+ rec->checksum = crc32_data(&rec->record_size,
+ rec->record_size -
+ sizeof(rec->checksum));
+ }
+ hash_table_iterate_deinit(&iter);
+}
+
+static int
+fts_expunge_log_write(struct fts_expunge_log_append_ctx *ctx)
+{
+ struct fts_expunge_log *log = ctx->log;
+ buffer_t *buf;
+ uint32_t expunge_count, *e;
+ int ret;
+
+ /* Unbacked expunge logs cannot be written, by definition */
+ i_assert(log != NULL);
+
+ /* try to append to the latest file */
+ if (fts_expunge_log_reopen_if_needed(log, TRUE) < 0)
+ return -1;
+
+ if (fts_expunge_log_read_expunge_count(log, &expunge_count) < 0)
+ return -1;
+
+ buf = buffer_create_dynamic(default_pool, 1024);
+ fts_expunge_log_export(ctx, expunge_count, buf);
+ /* the file was opened with O_APPEND, so this write() should be
+ appended atomically without any need for locking. */
+ for (;;) {
+ if (write_full(log->fd, buf->data, buf->used) < 0) {
+ i_error("write(%s) failed: %m", log->path);
+ if (ftruncate(log->fd, log->st.st_size) < 0)
+ i_error("ftruncate(%s) failed: %m", log->path);
+ }
+ if ((ret = fts_expunge_log_reopen_if_needed(log, TRUE)) <= 0)
+ break;
+ /* the log was unlinked, so we'll need to write again to
+ the new file. the expunge_count needs to be reset to zero
+ from here. */
+ e = buffer_get_space_unsafe(buf, buf->used - sizeof(uint32_t),
+ sizeof(uint32_t));
+ i_assert(*e > expunge_count);
+ *e -= expunge_count;
+ expunge_count = 0;
+ }
+ buffer_free(&buf);
+
+ if (ret == 0) {
+ /* finish by closing the log. this forces NFS to flush the
+ changes to disk without our having to explicitly play with
+ fsync() */
+ if (close(log->fd) < 0) {
+ /* FIXME: we should ftruncate() in case there
+ were partial writes.. */
+ i_error("close(%s) failed: %m", log->path);
+ ret = -1;
+ }
+ log->fd = -1;
+ }
+ return ret;
+}
+
+static int fts_expunge_log_append_finalize(struct fts_expunge_log_append_ctx **_ctx,
+ bool commit)
+{
+ struct fts_expunge_log_append_ctx *ctx = *_ctx;
+ int ret = ctx->failed ? -1 : 0;
+
+ *_ctx = NULL;
+ if (commit && ret == 0)
+ ret = fts_expunge_log_write(ctx);
+
+ hash_table_destroy(&ctx->mailboxes);
+ pool_unref(&ctx->pool);
+ return ret;
+}
+
+int fts_expunge_log_uid_count(struct fts_expunge_log *log,
+ unsigned int *expunges_r)
+{
+ int ret;
+
+ if ((ret = fts_expunge_log_reopen_if_needed(log, FALSE)) <= 0) {
+ *expunges_r = 0;
+ return ret;
+ }
+
+ return fts_expunge_log_read_expunge_count(log, expunges_r);
+}
+
+int fts_expunge_log_append_commit(struct fts_expunge_log_append_ctx **_ctx)
+{
+ return fts_expunge_log_append_finalize(_ctx, TRUE);
+}
+
+int fts_expunge_log_append_abort(struct fts_expunge_log_append_ctx **_ctx)
+{
+ return fts_expunge_log_append_finalize(_ctx, FALSE);
+}
+
+struct fts_expunge_log_read_ctx *
+fts_expunge_log_read_begin(struct fts_expunge_log *log)
+{
+ struct fts_expunge_log_read_ctx *ctx;
+
+ ctx = i_new(struct fts_expunge_log_read_ctx, 1);
+ ctx->log = log;
+ if (fts_expunge_log_reopen_if_needed(log, FALSE) < 0)
+ ctx->failed = TRUE;
+ else if (log->fd != -1)
+ ctx->input = i_stream_create_fd(log->fd, SIZE_MAX);
+ ctx->unlink = TRUE;
+ return ctx;
+}
+
+static bool
+fts_expunge_log_record_size_is_valid(const struct fts_expunge_log_record *rec,
+ unsigned int *uids_size_r)
+{
+ if (rec->record_size < sizeof(*rec) + sizeof(uint32_t)*3)
+ return FALSE;
+ *uids_size_r = rec->record_size - sizeof(*rec) - sizeof(uint32_t);
+ return *uids_size_r % sizeof(uint32_t)*2 == 0;
+}
+
+static void
+fts_expunge_log_read_failure(struct fts_expunge_log_read_ctx *ctx,
+ unsigned int wanted_size)
+{
+ size_t size;
+
+ if (ctx->input->stream_errno != 0) {
+ ctx->failed = TRUE;
+ i_error("read(%s) failed: %s", ctx->log->path,
+ i_stream_get_error(ctx->input));
+ } else {
+ size = i_stream_get_data_size(ctx->input);
+ ctx->corrupted = TRUE;
+ i_error("Corrupted fts expunge log %s: "
+ "Unexpected EOF (read %zu / %u bytes)",
+ ctx->log->path, size, wanted_size);
+ }
+}
+
+const struct fts_expunge_log_read_record *
+fts_expunge_log_read_next(struct fts_expunge_log_read_ctx *ctx)
+{
+ const unsigned char *data;
+ const struct fts_expunge_log_record *rec;
+ unsigned int uids_size;
+ size_t size;
+ uint32_t checksum;
+
+ if (ctx->input == NULL)
+ return NULL;
+
+ /* initial read to try to get the record */
+ (void)i_stream_read_bytes(ctx->input, &data, &size, IO_BLOCK_SIZE);
+ if (size == 0 && ctx->input->stream_errno == 0) {
+ /* expected EOF - mark the file as read by unlinking it */
+ if (ctx->unlink)
+ i_unlink_if_exists(ctx->log->path);
+
+ /* try reading again, in case something new was written */
+ i_stream_sync(ctx->input);
+ (void)i_stream_read_bytes(ctx->input, &data, &size,
+ IO_BLOCK_SIZE);
+ }
+ if (size < sizeof(*rec)) {
+ if (size == 0 && ctx->input->stream_errno == 0) {
+ /* expected EOF */
+ return NULL;
+ }
+ fts_expunge_log_read_failure(ctx, sizeof(*rec));
+ return NULL;
+ }
+ rec = (const void *)data;
+
+ if (!fts_expunge_log_record_size_is_valid(rec, &uids_size)) {
+ ctx->corrupted = TRUE;
+ i_error("Corrupted fts expunge log %s: "
+ "Invalid record size: %u",
+ ctx->log->path, rec->record_size);
+ return NULL;
+ }
+
+ /* read the entire record */
+ while (size < rec->record_size) {
+ if (i_stream_read_bytes(ctx->input, &data, &size, rec->record_size) < 0) {
+ fts_expunge_log_read_failure(ctx, rec->record_size);
+ return NULL;
+ }
+ rec = (const void *)data;
+ }
+
+ /* verify that the record checksum is valid */
+ checksum = crc32_data(&rec->record_size,
+ rec->record_size - sizeof(rec->checksum));
+ if (checksum != rec->checksum) {
+ ctx->corrupted = TRUE;
+ i_error("Corrupted fts expunge log %s: "
+ "Record checksum mismatch: %u != %u",
+ ctx->log->path, checksum, rec->checksum);
+ return NULL;
+ }
+
+ memcpy(ctx->read_rec.mailbox_guid, rec->guid,
+ sizeof(ctx->read_rec.mailbox_guid));
+ /* create the UIDs array by pointing it directly into input
+ stream's buffer */
+ buffer_create_from_const_data(&ctx->buffer, rec + 1, uids_size);
+ array_create_from_buffer(&ctx->read_rec.uids, &ctx->buffer,
+ sizeof(struct seq_range));
+
+ i_stream_skip(ctx->input, rec->record_size);
+ return &ctx->read_rec;
+}
+
+int fts_expunge_log_read_end(struct fts_expunge_log_read_ctx **_ctx)
+{
+ struct fts_expunge_log_read_ctx *ctx = *_ctx;
+ int ret = ctx->failed ? -1 : (ctx->corrupted ? 0 : 1);
+
+ *_ctx = NULL;
+
+ if (ctx->corrupted) {
+ if (ctx->unlink)
+ i_unlink_if_exists(ctx->log->path);
+ }
+
+ i_stream_unref(&ctx->input);
+ i_free(ctx);
+ return ret;
+}
+
+int fts_expunge_log_flatten(const char *path,
+ struct fts_expunge_log_append_ctx **flattened_r)
+{
+ struct fts_expunge_log *read;
+ struct fts_expunge_log_read_ctx *read_ctx;
+ const struct fts_expunge_log_read_record *record;
+ struct fts_expunge_log_append_ctx *append;
+ int ret;
+
+ i_assert(path != NULL && flattened_r != NULL);
+ read = fts_expunge_log_init(path);
+
+ read_ctx = fts_expunge_log_read_begin(read);
+ read_ctx->unlink = FALSE;
+
+ append = fts_expunge_log_append_begin(NULL);
+ while((record = fts_expunge_log_read_next(read_ctx)) != NULL) {
+ fts_expunge_log_append_record(append, record);
+ }
+
+ if ((ret = fts_expunge_log_read_end(&read_ctx)) > 0)
+ *flattened_r = append;
+ fts_expunge_log_deinit(&read);
+
+ return ret;
+}
+bool fts_expunge_log_contains(const struct fts_expunge_log_append_ctx *ctx,
+ const guid_128_t mailbox_guid, uint32_t uid)
+{
+ const struct fts_expunge_log_mailbox *mailbox;
+ const uint8_t *guid_p = mailbox_guid;
+
+ mailbox = hash_table_lookup(ctx->mailboxes, guid_p);
+ if (mailbox == NULL)
+ return FALSE;
+ return seq_range_exists(&mailbox->uids, uid);
+}
+int fts_expunge_log_append_remove(struct fts_expunge_log_append_ctx *from,
+ const struct fts_expunge_log_read_record *record)
+{
+ const uint8_t *guid_p = record->mailbox_guid;
+ struct fts_expunge_log_mailbox *mailbox = hash_table_lookup(from->mailboxes, guid_p);
+ if (mailbox == NULL)
+ return 0; /* may only remove things that exist */
+
+ mailbox->uids_count -= seq_range_array_remove_seq_range(&mailbox->uids, &record->uids);
+ return 1;
+}
+int fts_expunge_log_subtract(struct fts_expunge_log_append_ctx *from,
+ struct fts_expunge_log *subtract)
+{
+ unsigned int failures = 0;
+ struct fts_expunge_log_read_ctx *read_ctx = fts_expunge_log_read_begin(subtract);
+ read_ctx->unlink = FALSE;
+
+ const struct fts_expunge_log_read_record *record;
+ while ((record = fts_expunge_log_read_next(read_ctx)) != NULL) {
+ if (fts_expunge_log_append_remove(from, record) <= 0)
+ failures++;
+ }
+ if (failures > 0)
+ i_warning("fts: Expunge log subtract ignored %u nonexistent mailbox GUIDs",
+ failures);
+ return fts_expunge_log_read_end(&read_ctx);
+}
+/* It could be argued that somehow adding a log (file) to the append context
+ and then calling the _write() helper would be easier. But then there's the
+ _commit() vs. _abort() cleanup that would need to be addressed. Just creating
+ a copy is simpler. */
+int fts_expunge_log_flat_write(const struct fts_expunge_log_append_ctx *read_log,
+ const char *path)
+{
+ int ret;
+ struct fts_expunge_log *nlog = fts_expunge_log_init(path);
+ struct fts_expunge_log_append_ctx *nappend = fts_expunge_log_append_begin(nlog);
+
+ struct hash_iterate_context *iter;
+ uint8_t *guid_p;
+ struct fts_expunge_log_mailbox *mailbox;
+
+ iter = hash_table_iterate_init(read_log->mailboxes);
+ while (hash_table_iterate(iter, read_log->mailboxes, &guid_p, &mailbox))
+ fts_expunge_log_append_mailbox_record(nappend, mailbox);
+
+ hash_table_iterate_deinit(&iter);
+ ret = fts_expunge_log_append_commit(&nappend);
+ fts_expunge_log_deinit(&nlog);
+
+ return ret;
+}
diff --git a/src/plugins/fts/fts-expunge-log.h b/src/plugins/fts/fts-expunge-log.h
new file mode 100644
index 0000000..cc15f29
--- /dev/null
+++ b/src/plugins/fts/fts-expunge-log.h
@@ -0,0 +1,58 @@
+#ifndef FTS_EXPUNGE_LOG
+#define FTS_EXPUNGE_LOG
+
+#include "seq-range-array.h"
+#include "guid.h"
+
+struct fts_expunge_log_read_record {
+ guid_128_t mailbox_guid;
+ ARRAY_TYPE(seq_range) uids;
+};
+
+struct fts_expunge_log *fts_expunge_log_init(const char *path);
+void fts_expunge_log_deinit(struct fts_expunge_log **log);
+
+struct fts_expunge_log_append_ctx *
+fts_expunge_log_append_begin(struct fts_expunge_log *log);
+void fts_expunge_log_append_next(struct fts_expunge_log_append_ctx *ctx,
+ const guid_128_t mailbox_guid,
+ uint32_t uid);
+void fts_expunge_log_append_range(struct fts_expunge_log_append_ctx *ctx,
+ const guid_128_t mailbox_guid,
+ const struct seq_range *uids);
+void fts_expunge_log_append_record(struct fts_expunge_log_append_ctx *ctx,
+ const struct fts_expunge_log_read_record *record);
+/* In-memory flattened structures may have records removed from them,
+ file-backed ones may not. Non-existence of UIDs is not an error,
+ non-existence of mailbox GUID causes an error return of 0. */
+int fts_expunge_log_append_remove(struct fts_expunge_log_append_ctx *ctx,
+ const struct fts_expunge_log_read_record *record);
+int fts_expunge_log_append_commit(struct fts_expunge_log_append_ctx **ctx);
+/* Do not commit non-backed structures, abort them after use. */
+int fts_expunge_log_append_abort(struct fts_expunge_log_append_ctx **ctx);
+
+int fts_expunge_log_uid_count(struct fts_expunge_log *log,
+ unsigned int *expunges_r);
+
+struct fts_expunge_log_read_ctx *
+fts_expunge_log_read_begin(struct fts_expunge_log *log);
+const struct fts_expunge_log_read_record *
+fts_expunge_log_read_next(struct fts_expunge_log_read_ctx *ctx);
+/* Returns 1 if all ok, 0 if there was corruption, -1 if I/O error.
+ If end() is called before reading all records, the log isn't unlinked. */
+int fts_expunge_log_read_end(struct fts_expunge_log_read_ctx **ctx);
+
+/* Read an entire log file, and flatten it into one hash of arrays.
+ The struct it returns cannot be written, as it has no backing store */
+int fts_expunge_log_flatten(const char *path,
+ struct fts_expunge_log_append_ctx **flattened_r);
+bool fts_expunge_log_contains(const struct fts_expunge_log_append_ctx *ctx,
+ const guid_128_t mailbox_guid, uint32_t uid);
+/* Modify in-place a flattened log. If non-existent mailbox GUIDs are
+ encountered, a warning will be logged. */
+int fts_expunge_log_subtract(struct fts_expunge_log_append_ctx *from,
+ struct fts_expunge_log *subtract);
+/* Write a modified flattened log as a new file. */
+int fts_expunge_log_flat_write(const struct fts_expunge_log_append_ctx *flattened,
+ const char *path);
+#endif
diff --git a/src/plugins/fts/fts-indexer.c b/src/plugins/fts/fts-indexer.c
new file mode 100644
index 0000000..aca23c9
--- /dev/null
+++ b/src/plugins/fts/fts-indexer.c
@@ -0,0 +1,300 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "connection.h"
+#include "write-full.h"
+#include "istream.h"
+#include "ostream.h"
+#include "strescape.h"
+#include "time-util.h"
+#include "settings-parser.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "fts-api.h"
+#include "fts-indexer.h"
+
+#define INDEXER_NOTIFY_INTERVAL_SECS 10
+#define INDEXER_SOCKET_NAME "indexer"
+#define INDEXER_WAIT_MSECS 250
+
+struct fts_indexer_context {
+ struct connection conn;
+
+ struct mailbox *box;
+ struct ioloop *ioloop;
+
+ struct timeval search_start_time, last_notify;
+ unsigned int percentage;
+ struct connection_list *connection_list;
+
+ bool notified:1;
+ bool failed:1;
+ bool completed:1;
+};
+
+static void fts_indexer_notify(struct fts_indexer_context *ctx)
+{
+ unsigned long long elapsed_msecs, est_total_msecs;
+ unsigned int eta_secs;
+
+ if (ioloop_time - ctx->last_notify.tv_sec < INDEXER_NOTIFY_INTERVAL_SECS)
+ return;
+ ctx->last_notify = ioloop_timeval;
+
+ if (ctx->box->storage->callbacks.notify_ok == NULL ||
+ ctx->percentage == 0)
+ return;
+
+ elapsed_msecs = timeval_diff_msecs(&ioloop_timeval,
+ &ctx->search_start_time);
+ est_total_msecs = elapsed_msecs * 100 / ctx->percentage;
+ eta_secs = (est_total_msecs - elapsed_msecs) / 1000;
+
+ T_BEGIN {
+ const char *text;
+
+ text = t_strdup_printf("Indexed %d%% of the mailbox, "
+ "ETA %d:%02d", ctx->percentage,
+ eta_secs/60, eta_secs%60);
+ ctx->box->storage->callbacks.
+ notify_ok(ctx->box, text,
+ ctx->box->storage->callback_context);
+ ctx->notified = TRUE;
+ } T_END;
+}
+
+static int fts_indexer_more_int(struct fts_indexer_context *ctx)
+{
+ struct ioloop *prev_ioloop = current_ioloop;
+ struct timeout *to;
+
+ if (ctx->failed)
+ return -1;
+ if (ctx->completed)
+ return 1;
+
+ /* wait for a while for the reply. FIXME: once search API supports
+ asynchronous waits, get rid of this wait and use the mail IO loop */
+ io_loop_set_current(ctx->ioloop);
+ to = timeout_add_short(INDEXER_WAIT_MSECS, io_loop_stop, ctx->ioloop);
+ io_loop_run(ctx->ioloop);
+ timeout_remove(&to);
+ io_loop_set_current(prev_ioloop);
+
+ if (ctx->failed)
+ return -1;
+ if (ctx->completed)
+ return 1;
+ return 0;
+}
+
+int fts_indexer_more(struct fts_indexer_context *ctx)
+{
+ int ret;
+
+ if ((ret = fts_indexer_more_int(ctx)) < 0) {
+ /* If failed is already set, the code has had a chance to
+ * set an internal error already, i.e. MAIL_ERROR_INUSE. */
+ if (!ctx->failed)
+ mail_storage_set_internal_error(ctx->box->storage);
+ ctx->failed = TRUE;
+ return -1;
+ }
+
+ if (ret == 0)
+ fts_indexer_notify(ctx);
+
+ return ret;
+}
+
+static void fts_indexer_destroy(struct connection *conn)
+{
+ struct fts_indexer_context *ctx =
+ container_of(conn, struct fts_indexer_context, conn);
+ connection_deinit(conn);
+ if (!ctx->completed)
+ ctx->failed = TRUE;
+ ctx->completed = TRUE;
+}
+
+int fts_indexer_deinit(struct fts_indexer_context **_ctx)
+{
+ struct fts_indexer_context *ctx = *_ctx;
+ i_assert(ctx != NULL);
+ *_ctx = NULL;
+ if (!ctx->completed)
+ ctx->failed = TRUE;
+ int ret = ctx->failed ? -1 : 0;
+ if (ctx->notified) {
+ /* we notified at least once */
+ ctx->box->storage->callbacks.
+ notify_ok(ctx->box, "Mailbox indexing finished",
+ ctx->box->storage->callback_context);
+ }
+ connection_list_deinit(&ctx->connection_list);
+ io_loop_set_current(ctx->ioloop);
+ io_loop_destroy(&ctx->ioloop);
+ i_free(ctx);
+ return ret;
+}
+
+static int
+fts_indexer_input_args(struct connection *conn, const char *const *args)
+{
+ struct fts_indexer_context *ctx =
+ container_of(conn, struct fts_indexer_context, conn);
+ int percentage;
+ if (args[1] == NULL) {
+ e_error(conn->event, "indexer sent invalid reply");
+ return -1;
+ }
+ if (strcmp(args[0], "1") != 0) {
+ e_error(conn->event, "indexer sent invalid reply");
+ return -1;
+ }
+ if (strcmp(args[1], "OK") == 0)
+ return 1;
+ if (str_to_int(args[1], &percentage) < 0) {
+ e_error(conn->event, "indexer sent invalid progress: %s", args[1]);
+ ctx->failed = TRUE;
+ return -1;
+ }
+ if (percentage < 0) {
+ e_error(ctx->box->event, "indexer failed to index mailbox");
+ ctx->failed = TRUE;
+ return -1;
+ }
+ ctx->percentage = percentage;
+ if (ctx->percentage == 100)
+ ctx->completed = TRUE;
+ return 1;
+}
+
+static void fts_indexer_client_connected(struct connection *conn, bool success)
+{
+ struct fts_indexer_context *ctx =
+ container_of(conn, struct fts_indexer_context, conn);
+ if (!success) {
+ ctx->completed = TRUE;
+ ctx->failed = TRUE;
+ return;
+ }
+ ctx->failed = ctx->completed = FALSE;
+ const char *cmd = t_strdup_printf("PREPEND\t1\t%s\t%s\t0\t%s\n",
+ str_tabescape(ctx->box->storage->user->username),
+ str_tabescape(ctx->box->vname),
+ str_tabescape(ctx->box->storage->user->session_id));
+ o_stream_nsend_str(conn->output, cmd);
+}
+
+static void fts_indexer_idle_timeout(struct connection *conn)
+{
+ struct fts_indexer_context *ctx =
+ container_of(conn, struct fts_indexer_context, conn);
+ mail_storage_set_error(ctx->box->storage, MAIL_ERROR_INUSE,
+ "Timeout while waiting for indexing to finish");
+ ctx->failed = TRUE;
+ connection_disconnect(conn);
+}
+
+static const struct connection_settings indexer_client_set =
+{
+ .service_name_in = "indexer",
+ .service_name_out = "indexer",
+ .major_version = 1,
+ .minor_version = 0,
+ .client_connect_timeout_msecs = 2000,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = IO_BLOCK_SIZE,
+ .client = TRUE,
+};
+
+static const struct connection_vfuncs indexer_client_vfuncs =
+{
+ .destroy = fts_indexer_destroy,
+ .client_connected = fts_indexer_client_connected,
+ .input_args = fts_indexer_input_args,
+ .idle_timeout = fts_indexer_idle_timeout,
+};
+
+int fts_indexer_init(struct fts_backend *backend, struct mailbox *box,
+ struct fts_indexer_context **ctx_r)
+{
+ struct ioloop *prev_ioloop = current_ioloop;
+ struct fts_indexer_context *ctx;
+ struct mailbox_status status;
+ uint32_t last_uid, seq1, seq2;
+ const char *path, *value, *error;
+ unsigned int timeout_secs = 0;
+ int ret;
+
+ value = mail_user_plugin_getenv(box->storage->user, "fts_index_timeout");
+ if (value != NULL) {
+ if (settings_get_time(value, &timeout_secs, &error) < 0) {
+ e_error(box->storage->user->event,
+ "Invalid fts_index_timeout setting: %s",
+ error);
+ return -1;
+ }
+ }
+
+ if (fts_backend_get_last_uid(backend, box, &last_uid) < 0)
+ return -1;
+
+ mailbox_get_open_status(box, STATUS_UIDNEXT, &status);
+ if (status.uidnext == last_uid+1) {
+ /* everything is already indexed */
+ return 0;
+ }
+
+ mailbox_get_seq_range(box, last_uid+1, (uint32_t)-1, &seq1, &seq2);
+ if (seq1 == 0) {
+ /* no new messages (last messages in mailbox were expunged) */
+ return 0;
+ }
+
+ path = t_strconcat(box->storage->user->set->base_dir,
+ "/"INDEXER_SOCKET_NAME, NULL);
+
+ ctx = i_new(struct fts_indexer_context, 1);
+ ctx->box = box;
+ ctx->search_start_time = ioloop_timeval;
+ ctx->conn.event_parent = box->event;
+ ctx->ioloop = io_loop_create();
+ ctx->connection_list = connection_list_init(&indexer_client_set,
+ &indexer_client_vfuncs);
+ ctx->conn.input_idle_timeout_secs = timeout_secs;
+ connection_init_client_unix(ctx->connection_list, &ctx->conn,
+ path);
+ ret = connection_client_connect(&ctx->conn);
+ io_loop_set_current(prev_ioloop);
+ *ctx_r = ctx;
+ return ctx->failed || ret < 0 ? -1 : 1;
+}
+
+#define INDEXER_HANDSHAKE "1\t0\tindexer\tindexer\n"
+
+int fts_indexer_cmd(struct mail_user *user, const char *cmd,
+ const char **path_r)
+{
+ const char *path;
+ int fd;
+
+ path = t_strconcat(user->set->base_dir,
+ "/"INDEXER_SOCKET_NAME, NULL);
+ fd = net_connect_unix_with_retries(path, 1000);
+ if (fd == -1) {
+ i_error("net_connect_unix(%s) failed: %m", path);
+ return -1;
+ }
+
+ cmd = t_strconcat(INDEXER_HANDSHAKE, cmd, NULL);
+ if (write_full(fd, cmd, strlen(cmd)) < 0) {
+ i_error("write(%s) failed: %m", path);
+ i_close_fd(&fd);
+ return -1;
+ }
+ *path_r = path;
+ return fd;
+}
diff --git a/src/plugins/fts/fts-indexer.h b/src/plugins/fts/fts-indexer.h
new file mode 100644
index 0000000..7ccbc7e
--- /dev/null
+++ b/src/plugins/fts/fts-indexer.h
@@ -0,0 +1,22 @@
+#ifndef FTS_BUILD_H
+#define FTS_BUILD_H
+
+struct fts_backend;
+struct fts_indexer_context;
+
+/* Initialize indexing the given mailbox via indexer service. Returns 1 if
+ indexing started, 0 if there was no need to index or -1 if error. */
+int fts_indexer_init(struct fts_backend *backend, struct mailbox *box,
+ struct fts_indexer_context **ctx_r);
+/* Returns 0 if ok, -1 if error. */
+int fts_indexer_deinit(struct fts_indexer_context **ctx);
+
+/* Build more. Returns 1 if finished, 0 if this function needs to be called
+ again, -1 if error. */
+int fts_indexer_more(struct fts_indexer_context *ctx);
+
+/* Returns fd, which you can either read from or close. */
+int fts_indexer_cmd(struct mail_user *user, const char *cmd,
+ const char **path_r);
+
+#endif
diff --git a/src/plugins/fts/fts-parser-html.c b/src/plugins/fts/fts-parser-html.c
new file mode 100644
index 0000000..aa2078d
--- /dev/null
+++ b/src/plugins/fts/fts-parser-html.c
@@ -0,0 +1,64 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "message-parser.h"
+#include "mail-html2text.h"
+#include "fts-parser.h"
+
+struct html_fts_parser {
+ struct fts_parser parser;
+ struct mail_html2text *html2text;
+ buffer_t *output;
+};
+
+static struct fts_parser *
+fts_parser_html_try_init(struct fts_parser_context *parser_context)
+{
+ struct html_fts_parser *parser;
+
+ if (!mail_html2text_content_type_match(parser_context->content_type))
+ return NULL;
+
+ parser = i_new(struct html_fts_parser, 1);
+ parser->parser.v = fts_parser_html;
+ parser->html2text = mail_html2text_init(0);
+ parser->output = buffer_create_dynamic(default_pool, 4096);
+ return &parser->parser;
+}
+
+static void fts_parser_html_more(struct fts_parser *_parser,
+ struct message_block *block)
+{
+ struct html_fts_parser *parser = (struct html_fts_parser *)_parser;
+
+ if (block->size == 0) {
+ /* finished */
+ return;
+ }
+
+ buffer_set_used_size(parser->output, 0);
+ mail_html2text_more(parser->html2text, block->data, block->size,
+ parser->output);
+
+ block->data = parser->output->data;
+ block->size = parser->output->used;
+}
+
+static int fts_parser_html_deinit(struct fts_parser *_parser,
+ const char **retriable_err_msg_r ATTR_UNUSED)
+{
+ struct html_fts_parser *parser = (struct html_fts_parser *)_parser;
+
+ mail_html2text_deinit(&parser->html2text);
+ buffer_free(&parser->output);
+ i_free(parser);
+ return 1;
+}
+
+struct fts_parser_vfuncs fts_parser_html = {
+ fts_parser_html_try_init,
+ fts_parser_html_more,
+ fts_parser_html_deinit,
+ NULL
+};
diff --git a/src/plugins/fts/fts-parser-script.c b/src/plugins/fts/fts-parser-script.c
new file mode 100644
index 0000000..eefbe07
--- /dev/null
+++ b/src/plugins/fts/fts-parser-script.c
@@ -0,0 +1,277 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "net.h"
+#include "istream.h"
+#include "write-full.h"
+#include "module-context.h"
+#include "rfc822-parser.h"
+#include "rfc2231-parser.h"
+#include "message-parser.h"
+#include "mail-user.h"
+#include "fts-parser.h"
+
+#define SCRIPT_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, fts_parser_script_user_module)
+
+#define SCRIPT_HANDSHAKE "VERSION\tscript\t4\t0\nalarm=10\nnoreply\n"
+
+struct content {
+ const char *content_type;
+ const char *const *extensions;
+};
+
+struct fts_parser_script_user {
+ union mail_user_module_context module_ctx;
+
+ ARRAY(struct content) content;
+};
+
+struct script_fts_parser {
+ struct fts_parser parser;
+
+ int fd;
+ char *path;
+
+ unsigned char outbuf[IO_BLOCK_SIZE];
+ bool failed;
+ bool shutdown;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(fts_parser_script_user_module,
+ &mail_user_module_register);
+
+static int script_connect(struct mail_user *user, const char **path_r)
+{
+ const char *path;
+ int fd;
+
+ path = mail_user_plugin_getenv(user, "fts_decoder");
+ if (path == NULL)
+ return -1;
+
+ if (*path != '/')
+ path = t_strconcat(user->set->base_dir, "/", path, NULL);
+ fd = net_connect_unix_with_retries(path, 1000);
+ if (fd == -1)
+ i_error("net_connect_unix(%s) failed: %m", path);
+ else
+ net_set_nonblock(fd, FALSE);
+ *path_r = path;
+ return fd;
+}
+
+static int script_contents_read(struct mail_user *user)
+{
+ struct fts_parser_script_user *suser = SCRIPT_USER_CONTEXT(user);
+ const char *path, *cmd, *line;
+ char **args;
+ struct istream *input;
+ struct content *content;
+ bool eof_seen = FALSE;
+ int fd, ret = 0;
+ i_assert(suser != NULL);
+
+ fd = script_connect(user, &path);
+ if (fd == -1)
+ return -1;
+
+ cmd = t_strdup_printf(SCRIPT_HANDSHAKE"\n");
+ if (write_full(fd, cmd, strlen(cmd)) < 0) {
+ i_error("write(%s) failed: %m", path);
+ i_close_fd(&fd);
+ return -1;
+ }
+ input = i_stream_create_fd_autoclose(&fd, 1024);
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ /* <content-type> <extension> [<extension> ...] */
+ args = p_strsplit_spaces(user->pool, line, " ");
+ if (args[0] == NULL) {
+ eof_seen = TRUE;
+ break;
+ }
+ if (args[0][0] == '\0' || args[1] == NULL) {
+ i_error("parser script sent invalid input: %s", line);
+ continue;
+ }
+
+ content = array_append_space(&suser->content);
+ content->content_type = str_lcase(args[0]);
+ content->extensions = (const void *)(args+1);
+ }
+ if (input->stream_errno != 0) {
+ i_error("parser script read(%s) failed: %s", path,
+ i_stream_get_error(input));
+ ret = -1;
+ } else if (!eof_seen) {
+ if (input->v_offset == 0)
+ i_error("parser script didn't send any data");
+ else
+ i_error("parser script didn't send empty EOF line");
+ }
+ i_stream_destroy(&input);
+ return ret;
+}
+
+static bool script_support_content(struct mail_user *user,
+ const char **content_type,
+ const char *filename)
+{
+ struct fts_parser_script_user *suser = SCRIPT_USER_CONTEXT(user);
+ const struct content *content;
+ const char *extension;
+
+ if (suser == NULL) {
+ suser = p_new(user->pool, struct fts_parser_script_user, 1);
+ p_array_init(&suser->content, user->pool, 32);
+ MODULE_CONTEXT_SET(user, fts_parser_script_user_module, suser);
+ }
+ if (array_count(&suser->content) == 0) {
+ if (script_contents_read(user) < 0)
+ return FALSE;
+ }
+
+ if (strcmp(*content_type, "application/octet-stream") == 0) {
+ if (filename == NULL)
+ return FALSE;
+ extension = strrchr(filename, '.');
+ if (extension == NULL)
+ return FALSE;
+ extension = extension + 1;
+
+ array_foreach(&suser->content, content) {
+ if (content->extensions != NULL &&
+ str_array_icase_find(content->extensions, extension)) {
+ *content_type = content->content_type;
+ return TRUE;
+ }
+ }
+ } else {
+ array_foreach(&suser->content, content) {
+ if (strcmp(content->content_type, *content_type) == 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void parse_content_disposition(const char *content_disposition,
+ const char **filename_r)
+{
+ struct rfc822_parser_context parser;
+ const char *const *results, *filename2;
+ string_t *str;
+
+ *filename_r = NULL;
+
+ if (content_disposition == NULL)
+ return;
+
+ rfc822_parser_init(&parser, (const unsigned char *)content_disposition,
+ strlen(content_disposition), NULL);
+ rfc822_skip_lwsp(&parser);
+
+ /* type; param; param; .. */
+ str = t_str_new(32);
+ if (rfc822_parse_mime_token(&parser, str) < 0) {
+ rfc822_parser_deinit(&parser);
+ return;
+ }
+
+ rfc2231_parse(&parser, &results);
+ filename2 = NULL;
+ for (; *results != NULL; results += 2) {
+ if (strcasecmp(results[0], "filename") == 0) {
+ *filename_r = results[1];
+ break;
+ }
+ if (strcasecmp(results[0], "filename*") == 0)
+ filename2 = results[1];
+ }
+ if (*filename_r == NULL) {
+ /* RFC 2231 style non-ascii filename. we don't really care
+ much about the filename actually, just about its extension */
+ *filename_r = filename2;
+ }
+ rfc822_parser_deinit(&parser);
+}
+
+static struct fts_parser *
+fts_parser_script_try_init(struct fts_parser_context *parser_context)
+{
+ struct script_fts_parser *parser;
+ const char *filename, *path, *cmd;
+ int fd;
+
+ parse_content_disposition(parser_context->content_disposition, &filename);
+ if (!script_support_content(parser_context->user, &parser_context->content_type, filename))
+ return NULL;
+
+ fd = script_connect(parser_context->user, &path);
+ if (fd == -1)
+ return NULL;
+ cmd = t_strdup_printf(SCRIPT_HANDSHAKE"%s\n\n", parser_context->content_type);
+ if (write_full(fd, cmd, strlen(cmd)) < 0) {
+ i_error("write(%s) failed: %m", path);
+ i_close_fd(&fd);
+ return NULL;
+ }
+
+ parser = i_new(struct script_fts_parser, 1);
+ parser->parser.v = fts_parser_script;
+ parser->path = i_strdup(path);
+ parser->fd = fd;
+ return &parser->parser;
+}
+
+static void fts_parser_script_more(struct fts_parser *_parser,
+ struct message_block *block)
+{
+ struct script_fts_parser *parser = (struct script_fts_parser *)_parser;
+ ssize_t ret;
+
+ if (block->size > 0) {
+ /* first we'll send everything to the script */
+ if (!parser->failed &&
+ write_full(parser->fd, block->data, block->size) < 0) {
+ i_error("write(%s) failed: %m", parser->path);
+ parser->failed = TRUE;
+ }
+ block->size = 0;
+ } else {
+ if (!parser->shutdown) {
+ if (shutdown(parser->fd, SHUT_WR) < 0)
+ i_error("shutdown(%s) failed: %m", parser->path);
+ parser->shutdown = TRUE;
+ }
+ /* read the result from the script */
+ ret = read(parser->fd, parser->outbuf, sizeof(parser->outbuf));
+ if (ret < 0)
+ i_error("read(%s) failed: %m", parser->path);
+ else {
+ block->data = parser->outbuf;
+ block->size = ret;
+ }
+ }
+}
+
+static int fts_parser_script_deinit(struct fts_parser *_parser,
+ const char **retriable_err_msg_r ATTR_UNUSED)
+{
+ struct script_fts_parser *parser = (struct script_fts_parser *)_parser;
+ int ret = parser->failed ? -1 : 1;
+
+ if (close(parser->fd) < 0)
+ i_error("close(%s) failed: %m", parser->path);
+ i_free(parser->path);
+ i_free(parser);
+ return ret;
+}
+
+struct fts_parser_vfuncs fts_parser_script = {
+ fts_parser_script_try_init,
+ fts_parser_script_more,
+ fts_parser_script_deinit,
+ NULL
+};
diff --git a/src/plugins/fts/fts-parser-tika.c b/src/plugins/fts/fts-parser-tika.c
new file mode 100644
index 0000000..bb6379c
--- /dev/null
+++ b/src/plugins/fts/fts-parser-tika.c
@@ -0,0 +1,278 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "module-context.h"
+#include "iostream-ssl.h"
+#include "http-url.h"
+#include "http-client.h"
+#include "message-parser.h"
+#include "mail-user.h"
+#include "fts-parser.h"
+
+#define TIKA_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, fts_parser_tika_user_module)
+
+struct fts_parser_tika_user {
+ union mail_user_module_context module_ctx;
+ struct http_url *http_url;
+};
+
+struct tika_fts_parser {
+ struct fts_parser parser;
+ struct mail_user *user;
+ struct http_client_request *http_req;
+
+ struct ioloop *ioloop;
+ struct io *io;
+ struct istream *payload;
+
+ bool failed;
+};
+
+static struct http_client *tika_http_client = NULL;
+static MODULE_CONTEXT_DEFINE_INIT(fts_parser_tika_user_module,
+ &mail_user_module_register);
+
+static int
+tika_get_http_client_url(struct mail_user *user, struct http_url **http_url_r)
+{
+ struct fts_parser_tika_user *tuser = TIKA_USER_CONTEXT(user);
+ struct http_client_settings http_set;
+ struct ssl_iostream_settings ssl_set;
+ const char *url, *error;
+
+ url = mail_user_plugin_getenv(user, "fts_tika");
+ if (url == NULL) {
+ /* fts_tika disabled */
+ return -1;
+ }
+
+ if (tuser != NULL) {
+ *http_url_r = tuser->http_url;
+ return *http_url_r == NULL ? -1 : 0;
+ }
+
+ tuser = p_new(user->pool, struct fts_parser_tika_user, 1);
+ MODULE_CONTEXT_SET(user, fts_parser_tika_user_module, tuser);
+
+ if (http_url_parse(url, NULL, 0, user->pool,
+ &tuser->http_url, &error) < 0) {
+ i_error("fts_tika: Failed to parse HTTP url %s: %s", url, error);
+ return -1;
+ }
+
+ if (tika_http_client == NULL) {
+ mail_user_init_ssl_client_settings(user, &ssl_set);
+
+ i_zero(&http_set);
+ http_set.max_idle_time_msecs = 100;
+ http_set.max_parallel_connections = 1;
+ http_set.max_pipelined_requests = 1;
+ http_set.max_redirects = 1;
+ http_set.max_attempts = 3;
+ http_set.connect_timeout_msecs = 5*1000;
+ http_set.request_timeout_msecs = 60*1000;
+ http_set.ssl = &ssl_set;
+ http_set.debug = user->mail_debug;
+ http_set.event_parent = user->event;
+
+ /* FIXME: We should initialize a shared client instead. However,
+ this is currently not possible due to an obscure bug
+ in the blocking HTTP payload API, which causes
+ conflicts with other HTTP applications like FTS Solr.
+ Using a private client will provide a quick fix for
+ now. */
+ tika_http_client = http_client_init_private(&http_set);
+ }
+ *http_url_r = tuser->http_url;
+ return 0;
+}
+
+static void
+fts_tika_parser_response(const struct http_response *response,
+ struct tika_fts_parser *parser)
+{
+ i_assert(parser->payload == NULL);
+
+ switch (response->status) {
+ case 200:
+ /* read response */
+ if (response->payload == NULL)
+ parser->payload = i_stream_create_from_data("", 0);
+ else {
+ i_stream_ref(response->payload);
+ parser->payload = response->payload;
+ }
+ break;
+ case 204: /* empty response */
+ case 415: /* Unsupported Media Type */
+ case 422: /* Unprocessable Entity */
+ e_debug(parser->user->event, "fts_tika: PUT %s failed: %s",
+ mail_user_plugin_getenv(parser->user, "fts_tika"),
+ http_response_get_message(response));
+ parser->payload = i_stream_create_from_data("", 0);
+ break;
+ default:
+ if (response->status / 100 == 5) {
+ /* Server Error - the problem could be anything (in Tika or
+ HTTP server or proxy) and might be retriable, but Tika has
+ trouble processing some documents and throws up this error
+ every time for those documents. */
+ parser->parser.may_need_retry = TRUE;
+ i_free(parser->parser.retriable_error_msg);
+ parser->parser.retriable_error_msg =
+ i_strdup_printf("fts_tika: PUT %s failed: %s",
+ mail_user_plugin_getenv(parser->user, "fts_tika"),
+ http_response_get_message(response));
+ parser->payload = i_stream_create_from_data("", 0);
+ } else {
+ i_error("fts_tika: PUT %s failed: %s",
+ mail_user_plugin_getenv(parser->user, "fts_tika"),
+ http_response_get_message(response));
+ parser->failed = TRUE;
+ }
+ break;
+ }
+ parser->http_req = NULL;
+ io_loop_stop(current_ioloop);
+}
+
+static struct fts_parser *
+fts_parser_tika_try_init(struct fts_parser_context *parser_context)
+{
+ struct tika_fts_parser *parser;
+ struct http_url *http_url;
+ struct http_client_request *http_req;
+
+ if (tika_get_http_client_url(parser_context->user, &http_url) < 0)
+ return NULL;
+ if (http_url->path == NULL)
+ http_url->path = "/";
+
+ parser = i_new(struct tika_fts_parser, 1);
+ parser->parser.v = fts_parser_tika;
+ parser->user = parser_context->user;
+
+ http_req = http_client_request(tika_http_client, "PUT",
+ http_url->host.name,
+ t_strconcat(http_url->path, http_url->enc_query, NULL),
+ fts_tika_parser_response, parser);
+ http_client_request_set_port(http_req, http_url->port);
+ http_client_request_set_ssl(http_req, http_url->have_ssl);
+ if (parser_context->content_type != NULL)
+ http_client_request_add_header(http_req, "Content-Type",
+ parser_context->content_type);
+ if (parser_context->content_disposition != NULL)
+ http_client_request_add_header(http_req, "Content-Disposition",
+ parser_context->content_disposition);
+ http_client_request_add_header(http_req, "Accept", "text/plain");
+
+ parser->http_req = http_req;
+ return &parser->parser;
+}
+
+static void fts_parser_tika_more(struct fts_parser *_parser,
+ struct message_block *block)
+{
+ struct tika_fts_parser *parser = (struct tika_fts_parser *)_parser;
+ struct ioloop *prev_ioloop = current_ioloop;
+ const unsigned char *data;
+ size_t size;
+ ssize_t ret;
+
+ if (block->size > 0) {
+ /* first we'll send everything to Tika */
+ if (!parser->failed &&
+ http_client_request_send_payload(&parser->http_req,
+ block->data,
+ block->size) < 0)
+ parser->failed = TRUE;
+ block->size = 0;
+ return;
+ }
+
+ if (parser->payload == NULL) {
+ /* read the result from Tika */
+ if (!parser->failed &&
+ http_client_request_finish_payload(&parser->http_req) < 0)
+ parser->failed = TRUE;
+ if (!parser->failed && parser->payload == NULL)
+ http_client_wait(tika_http_client);
+ if (parser->failed)
+ return;
+ i_assert(parser->payload != NULL);
+ }
+ /* continue returning data from Tika. we'll create a new ioloop just
+ for reading this one payload. */
+ while ((ret = i_stream_read_more(parser->payload, &data, &size)) == 0) {
+ if (parser->failed)
+ break;
+ /* wait for more input from Tika */
+ if (parser->ioloop == NULL) {
+ parser->ioloop = io_loop_create();
+ parser->io = io_add_istream(parser->payload, io_loop_stop,
+ current_ioloop);
+ } else {
+ io_loop_set_current(parser->ioloop);
+ }
+ io_loop_run(current_ioloop);
+ }
+ /* switch back to original ioloop. */
+ io_loop_set_current(prev_ioloop);
+
+ if (parser->failed)
+ ;
+ else if (size > 0) {
+ i_assert(ret > 0);
+ block->data = data;
+ block->size = size;
+ i_stream_skip(parser->payload, size);
+ } else {
+ /* finished */
+ i_assert(ret == -1);
+ if (parser->payload->stream_errno != 0) {
+ i_error("read(%s) failed: %s",
+ i_stream_get_name(parser->payload),
+ i_stream_get_error(parser->payload));
+ parser->failed = TRUE;
+ }
+ }
+}
+
+static int fts_parser_tika_deinit(struct fts_parser *_parser, const char **retriable_err_msg_r)
+{
+ struct tika_fts_parser *parser = (struct tika_fts_parser *)_parser;
+ int ret = _parser->may_need_retry ? 0: (parser->failed ? -1 : 1);
+
+ i_assert(ret != 0 || _parser->retriable_error_msg != NULL);
+ if (retriable_err_msg_r != NULL)
+ *retriable_err_msg_r = t_strdup(_parser->retriable_error_msg);
+ i_free(_parser->retriable_error_msg);
+
+ /* remove io before unrefing payload - otherwise lib-http adds another
+ timeout to ioloop unnecessarily */
+ i_stream_unref(&parser->payload);
+ io_remove(&parser->io);
+ http_client_request_abort(&parser->http_req);
+ if (parser->ioloop != NULL) {
+ io_loop_set_current(parser->ioloop);
+ io_loop_destroy(&parser->ioloop);
+ }
+ i_free(parser);
+ return ret;
+}
+
+static void fts_parser_tika_unload(void)
+{
+ if (tika_http_client != NULL)
+ http_client_deinit(&tika_http_client);
+}
+
+struct fts_parser_vfuncs fts_parser_tika = {
+ fts_parser_tika_try_init,
+ fts_parser_tika_more,
+ fts_parser_tika_deinit,
+ fts_parser_tika_unload
+};
diff --git a/src/plugins/fts/fts-parser.c b/src/plugins/fts/fts-parser.c
new file mode 100644
index 0000000..c0eac80
--- /dev/null
+++ b/src/plugins/fts/fts-parser.c
@@ -0,0 +1,127 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "unichar.h"
+#include "message-parser.h"
+#include "fts-parser.h"
+
+static const struct fts_parser_vfuncs *parsers[] = {
+ &fts_parser_html,
+ &fts_parser_script,
+ &fts_parser_tika
+};
+
+static const char *plaintext_content_types[] = {
+ "text/plain",
+ "message/delivery-status",
+ "message/disposition-notification",
+ "application/pgp-signature",
+ NULL
+};
+
+bool fts_parser_init(struct fts_parser_context *parser_context,
+ struct fts_parser **parser_r)
+{
+ unsigned int i;
+ i_assert(parser_context->user != NULL);
+ i_assert(parser_context->content_type != NULL);
+
+ if (str_array_find(plaintext_content_types, parser_context->content_type)) {
+ /* we probably don't want/need to allow parsers to handle
+ plaintext? */
+ return FALSE;
+ }
+
+ for (i = 0; i < N_ELEMENTS(parsers); i++) {
+ *parser_r = parsers[i]->try_init(parser_context);
+ if (*parser_r != NULL)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+struct fts_parser *fts_parser_text_init(void)
+{
+ return i_new(struct fts_parser, 1);
+}
+
+static bool data_has_nuls(const unsigned char *data, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ if (data[i] == '\0')
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void replace_nul_bytes(buffer_t *buf)
+{
+ unsigned char *data;
+ size_t i, size;
+
+ data = buffer_get_modifiable_data(buf, &size);
+ for (i = 0; i < size; i++) {
+ if (data[i] == '\0')
+ data[i] = ' ';
+ }
+}
+
+void fts_parser_more(struct fts_parser *parser, struct message_block *block)
+{
+ if (parser->v.more != NULL)
+ parser->v.more(parser, block);
+
+ if (!uni_utf8_data_is_valid(block->data, block->size) ||
+ data_has_nuls(block->data, block->size)) {
+ /* output isn't valid UTF-8. make it. */
+ if (parser->utf8_output == NULL) {
+ parser->utf8_output =
+ buffer_create_dynamic(default_pool, 4096);
+ } else {
+ buffer_set_used_size(parser->utf8_output, 0);
+ }
+ if (uni_utf8_get_valid_data(block->data, block->size,
+ parser->utf8_output)) {
+ /* valid UTF-8, but there were NULs */
+ buffer_append(parser->utf8_output, block->data,
+ block->size);
+ }
+ replace_nul_bytes(parser->utf8_output);
+ block->data = parser->utf8_output->data;
+ block->size = parser->utf8_output->used;
+ }
+}
+
+int fts_parser_deinit(struct fts_parser **_parser, const char **retriable_err_msg_r)
+{
+ struct fts_parser *parser = *_parser;
+ int ret = 1;
+
+ *_parser = NULL;
+
+ buffer_free(&parser->utf8_output);
+ if (parser->v.deinit != NULL) {
+ const char *error = NULL;
+ ret = parser->v.deinit(parser, &error);
+ if (ret == 0) {
+ i_assert(error != NULL);
+ if (retriable_err_msg_r != NULL)
+ *retriable_err_msg_r = error;
+ }
+ } else
+ i_free(parser);
+ return ret;
+}
+
+void fts_parsers_unload(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(parsers); i++) {
+ if (parsers[i]->unload != NULL)
+ parsers[i]->unload();
+ }
+}
diff --git a/src/plugins/fts/fts-parser.h b/src/plugins/fts/fts-parser.h
new file mode 100644
index 0000000..0eb716e
--- /dev/null
+++ b/src/plugins/fts/fts-parser.h
@@ -0,0 +1,48 @@
+#ifndef FTS_PARSER_H
+#define FTS_PARSER_H
+
+struct message_block;
+struct mail_user;
+
+struct fts_parser_context {
+ /* Can't be NULL */
+ struct mail_user *user;
+ /* Can't be NULL */
+ const char *content_type;
+ const char *content_disposition;
+};
+
+struct fts_parser_vfuncs {
+ struct fts_parser *(*try_init)(struct fts_parser_context *parser_context);
+ void (*more)(struct fts_parser *parser, struct message_block *block);
+ int (*deinit)(struct fts_parser *parser, const char **retriable_err_msg_r);
+ void (*unload)(void);
+};
+
+struct fts_parser {
+ struct fts_parser_vfuncs v;
+ buffer_t *utf8_output;
+ bool may_need_retry;
+ char *retriable_error_msg;
+};
+
+extern struct fts_parser_vfuncs fts_parser_html;
+extern struct fts_parser_vfuncs fts_parser_script;
+extern struct fts_parser_vfuncs fts_parser_tika;
+
+bool fts_parser_init(struct fts_parser_context *parser_context,
+ struct fts_parser **parser_r);
+struct fts_parser *fts_parser_text_init(void);
+
+/* The parser is initially called with message body blocks. Once message is
+ finished, it's still called with incoming size=0 while the parser increases
+ it to non-zero. */
+void fts_parser_more(struct fts_parser *parser, struct message_block *block);
+/* Returns 1 if ok, 0 if the parsing should be retried, -1 if error.
+ If 0 is returned, the retriable_err_msg_r is set, which should be logged
+ as error if no retrying is performed. */
+int fts_parser_deinit(struct fts_parser **parser, const char **retriable_err_msg_r);
+
+void fts_parsers_unload(void);
+
+#endif
diff --git a/src/plugins/fts/fts-plugin.c b/src/plugins/fts/fts-plugin.c
new file mode 100644
index 0000000..1902cb6
--- /dev/null
+++ b/src/plugins/fts/fts-plugin.c
@@ -0,0 +1,33 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage-hooks.h"
+#include "fts-filter.h"
+#include "fts-tokenizer.h"
+#include "fts-parser.h"
+#include "fts-storage.h"
+#include "fts-user.h"
+#include "fts-plugin.h"
+#include "fts-library.h"
+
+const char *fts_plugin_version = DOVECOT_ABI_VERSION;
+
+static struct mail_storage_hooks fts_mail_storage_hooks = {
+ .mail_namespaces_added = fts_mail_namespaces_added,
+ .mailbox_list_created = fts_mailbox_list_created,
+ .mailbox_allocated = fts_mailbox_allocated,
+ .mail_allocated = fts_mail_allocated
+};
+
+void fts_plugin_init(struct module *module)
+{
+ fts_library_init();
+ mail_storage_hooks_add(module, &fts_mail_storage_hooks);
+}
+
+void fts_plugin_deinit(void)
+{
+ fts_library_deinit();
+ fts_parsers_unload();
+ mail_storage_hooks_remove(&fts_mail_storage_hooks);
+}
diff --git a/src/plugins/fts/fts-plugin.h b/src/plugins/fts/fts-plugin.h
new file mode 100644
index 0000000..aeec68c
--- /dev/null
+++ b/src/plugins/fts/fts-plugin.h
@@ -0,0 +1,7 @@
+#ifndef FTS_PLUGIN_H
+#define FTS_PLUGIN_H
+
+void fts_plugin_init(struct module *module);
+void fts_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/fts/fts-search-args.c b/src/plugins/fts/fts-search-args.c
new file mode 100644
index 0000000..b58b238
--- /dev/null
+++ b/src/plugins/fts/fts-search-args.c
@@ -0,0 +1,258 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-namespace.h"
+#include "mail-search.h"
+#include "fts-api-private.h"
+#include "fts-tokenizer.h"
+#include "fts-filter.h"
+#include "fts-user.h"
+#include "fts-search-args.h"
+
+static void strings_deduplicate(ARRAY_TYPE(const_string) *arr)
+{
+ const char *const *strings;
+ unsigned int i, count;
+
+ strings = array_get(arr, &count);
+ for (i = 1; i < count; ) {
+ if (strcmp(strings[i-1], strings[i]) == 0) {
+ array_delete(arr, i, 1);
+ strings = array_get(arr, &count);
+ } else {
+ i++;
+ }
+ }
+}
+
+static struct mail_search_arg *
+fts_search_arg_create_or(const struct mail_search_arg *orig_arg, pool_t pool,
+ const ARRAY_TYPE(const_string) *tokens)
+{
+ struct mail_search_arg *arg, *or_arg, **argp;
+ const char *token;
+
+ /* create the OR arg first as the parent */
+ or_arg = p_new(pool, struct mail_search_arg, 1);
+ or_arg->type = SEARCH_OR;
+
+ /* now create all the child args for the OR */
+ argp = &or_arg->value.subargs;
+ array_foreach_elem(tokens, token) {
+ arg = p_new(pool, struct mail_search_arg, 1);
+ *arg = *orig_arg;
+ arg->match_not = FALSE; /* we copied this to the root OR */
+ arg->next = NULL;
+ arg->value.str = p_strdup(pool, token);
+
+ *argp = arg;
+ argp = &arg->next;
+ }
+ return or_arg;
+}
+
+static int
+fts_backend_dovecot_expand_tokens(struct fts_filter *filter,
+ pool_t pool,
+ struct mail_search_arg *parent_arg,
+ const struct mail_search_arg *orig_arg,
+ const char *orig_token, const char *token,
+ const char **error_r)
+{
+ struct mail_search_arg *arg;
+ ARRAY_TYPE(const_string) tokens;
+ const char *token2, *error;
+ int ret;
+
+ t_array_init(&tokens, 4);
+ /* first add the word exactly as it without any tokenization */
+ array_push_back(&tokens, &orig_token);
+ /* then add it tokenized, but without filtering */
+ array_push_back(&tokens, &token);
+
+ /* add the word filtered */
+ if (filter != NULL) {
+ token2 = t_strdup(token);
+ ret = fts_filter_filter(filter, &token2, &error);
+ if (ret > 0) {
+ token2 = t_strdup(token2);
+ array_push_back(&tokens, &token2);
+ } else if (ret < 0) {
+ *error_r = t_strdup_printf("Couldn't filter search token: %s", error);
+ return -1;
+ } else {
+ /* The filter dropped the token, which means it was
+ never even indexed. Ignore this word entirely in the
+ search query. */
+ return 0;
+ }
+ }
+ array_sort(&tokens, i_strcmp_p);
+ strings_deduplicate(&tokens);
+
+ arg = fts_search_arg_create_or(orig_arg, pool, &tokens);
+ arg->next = parent_arg->value.subargs;
+ parent_arg->value.subargs = arg;
+ return 0;
+}
+
+static int
+fts_backend_dovecot_tokenize_lang(struct fts_user_language *user_lang,
+ pool_t pool, struct mail_search_arg *or_arg,
+ struct mail_search_arg *orig_arg,
+ const char *orig_token, const char **error_r)
+{
+ size_t orig_token_len = strlen(orig_token);
+ struct mail_search_arg *and_arg, *orig_or_args = or_arg->value.subargs;
+ const char *token, *error;
+ int ret;
+
+ /* we want all the tokens found from the string to be found, so create
+ a parent AND and place all the filtered token alternatives under
+ it */
+ and_arg = p_new(pool, struct mail_search_arg, 1);
+ and_arg->type = SEARCH_SUB;
+ and_arg->next = orig_or_args;
+ or_arg->value.subargs = and_arg;
+
+ /* reset tokenizer between search args in case there's any state left
+ from some previous failure */
+ fts_tokenizer_reset(user_lang->search_tokenizer);
+ while ((ret = fts_tokenizer_next(user_lang->search_tokenizer,
+ (const void *)orig_token,
+ orig_token_len, &token, &error)) > 0) {
+ if (fts_backend_dovecot_expand_tokens(user_lang->filter, pool,
+ and_arg, orig_arg, orig_token,
+ token, error_r) < 0)
+ return -1;
+ }
+ while (ret >= 0 &&
+ (ret = fts_tokenizer_final(user_lang->search_tokenizer, &token, &error)) > 0) {
+ if (fts_backend_dovecot_expand_tokens(user_lang->filter, pool,
+ and_arg, orig_arg, orig_token,
+ token, error_r) < 0)
+ return -1;
+ }
+ if (ret < 0) {
+ *error_r = t_strdup_printf("Couldn't tokenize search args: %s", error);
+ return -1;
+ }
+ if (and_arg->value.subargs == NULL) {
+ /* nothing was actually expanded, remove the empty and_arg */
+ or_arg->value.subargs = orig_or_args;
+ }
+ return 0;
+}
+
+static int fts_search_arg_expand(struct fts_backend *backend, pool_t pool,
+ struct mail_search_arg **argp)
+{
+ const ARRAY_TYPE(fts_user_language) *languages;
+ struct fts_user_language *lang;
+ struct mail_search_arg *or_arg, *orig_arg = *argp;
+ const char *error, *orig_token = orig_arg->value.str;
+
+ if (((*argp)->type == SEARCH_HEADER ||
+ (*argp)->type == SEARCH_HEADER_ADDRESS ||
+ (*argp)->type == SEARCH_HEADER_COMPRESS_LWSP) &&
+ !fts_header_has_language((*argp)->hdr_field_name)) {
+ /* use only the data-language */
+ languages = fts_user_get_data_languages(backend->ns->user);
+ } else {
+ languages = fts_user_get_all_languages(backend->ns->user);
+ }
+
+ /* OR together all the different expansions for different languages.
+ it's enough for one of them to match. */
+ or_arg = p_new(pool, struct mail_search_arg, 1);
+ or_arg->type = SEARCH_OR;
+ or_arg->match_not = orig_arg->match_not;
+ or_arg->next = orig_arg->next;
+
+ array_foreach_elem(languages, lang) {
+ if (fts_backend_dovecot_tokenize_lang(lang, pool, or_arg,
+ orig_arg, orig_token, &error) < 0) {
+ i_error("fts: %s", error);
+ return -1;
+ }
+ }
+
+ if (or_arg->value.subargs == NULL) {
+ /* we couldn't parse any tokens from the input */
+ or_arg->type = SEARCH_ALL;
+ or_arg->match_not = !or_arg->match_not;
+ }
+ *argp = or_arg;
+ return 0;
+}
+
+static int
+fts_search_args_expand_tree(struct fts_backend *backend, pool_t pool,
+ struct mail_search_arg **argp)
+{
+ int ret;
+
+ for (; *argp != NULL; argp = &(*argp)->next) {
+ switch ((*argp)->type) {
+ case SEARCH_OR:
+ case SEARCH_SUB:
+ case SEARCH_INTHREAD:
+ if (fts_search_args_expand_tree(backend, pool,
+ &(*argp)->value.subargs) < 0)
+ return -1;
+ break;
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ if ((*argp)->value.str[0] == '\0') {
+ /* we're testing for the existence of
+ the header */
+ break;
+ }
+ /* fall through */
+ case SEARCH_BODY:
+ case SEARCH_TEXT:
+ T_BEGIN {
+ ret = fts_search_arg_expand(backend, pool, argp);
+ } T_END;
+ if (ret < 0)
+ return -1;
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+int fts_search_args_expand(struct fts_backend *backend,
+ struct mail_search_args *args)
+{
+ struct mail_search_arg *args_dup, *orig_args = args->args;
+
+ /* don't keep re-expanding every time the search args are used.
+ this is especially important to avoid an assert-crash in
+ index_search_result_update_flags(). */
+ if (args->fts_expanded)
+ return 0;
+ args->fts_expanded = TRUE;
+
+ /* duplicate the args, so if expansion fails we haven't changed
+ anything */
+ args_dup = mail_search_arg_dup(args->pool, args->args);
+
+ if (fts_search_args_expand_tree(backend, args->pool, &args_dup) < 0)
+ return -1;
+
+ /* we'll need to re-simplify the args if we changed anything */
+ args->simplified = FALSE;
+ args->args = args_dup;
+ mail_search_args_simplify(args);
+
+ /* duplicated args aren't initialized */
+ i_assert(args->init_refcount > 0);
+ mail_search_arg_init(args, args_dup);
+ mail_search_arg_deinit(orig_args);
+ return 0;
+}
diff --git a/src/plugins/fts/fts-search-args.h b/src/plugins/fts/fts-search-args.h
new file mode 100644
index 0000000..9fb8923
--- /dev/null
+++ b/src/plugins/fts/fts-search-args.h
@@ -0,0 +1,7 @@
+#ifndef FTS_SEARCH_ARGS_H
+#define FTS_SEARCH_ARGS_H
+
+int fts_search_args_expand(struct fts_backend *backend,
+ struct mail_search_args *args);
+
+#endif
diff --git a/src/plugins/fts/fts-search-serialize.c b/src/plugins/fts/fts-search-serialize.c
new file mode 100644
index 0000000..e30d4ce
--- /dev/null
+++ b/src/plugins/fts/fts-search-serialize.c
@@ -0,0 +1,99 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "mail-search.h"
+#include "fts-search-serialize.h"
+
+#define HAVE_SUBARGS(arg) \
+ ((arg)->type == SEARCH_SUB || (arg)->type == SEARCH_OR)
+
+void fts_search_serialize(buffer_t *buf, const struct mail_search_arg *args)
+{
+ char chr;
+
+ for (; args != NULL; args = args->next) {
+ chr = (args->match_always ? 1 : 0) |
+ (args->nonmatch_always ? 2 : 0);
+ buffer_append_c(buf, chr);
+
+ if (HAVE_SUBARGS(args))
+ fts_search_serialize(buf, args->value.subargs);
+ }
+}
+
+static void fts_search_deserialize_idx(struct mail_search_arg *args,
+ const buffer_t *buf, unsigned int *idx)
+{
+ const char *data = buf->data;
+
+ for (; args != NULL; args = args->next) {
+ i_assert(*idx < buf->used);
+
+ args->match_always = (data[*idx] & 1) != 0;
+ args->nonmatch_always = (data[*idx] & 2) != 0;
+ args->result = args->match_always ? 1 :
+ (args->nonmatch_always ? 0 : -1);
+ *idx += 1;
+
+ if (HAVE_SUBARGS(args)) {
+ fts_search_deserialize_idx(args->value.subargs,
+ buf, idx);
+ }
+ }
+}
+
+void fts_search_deserialize(struct mail_search_arg *args,
+ const buffer_t *buf)
+{
+ unsigned int idx = 0;
+
+ fts_search_deserialize_idx(args, buf, &idx);
+ i_assert(idx == buf->used);
+}
+
+static void
+fts_search_deserialize_add_idx(struct mail_search_arg *args,
+ const buffer_t *buf, unsigned int *idx,
+ bool matches)
+{
+ const char *data = buf->data;
+
+ for (; args != NULL; args = args->next) {
+ i_assert(*idx < buf->used);
+
+ if (data[*idx] != 0) {
+ if (matches) {
+ args->match_always = TRUE;
+ args->result = 1;
+ } else {
+ args->nonmatch_always = TRUE;
+ args->result = 0;
+ }
+ }
+ *idx += 1;
+
+ if (HAVE_SUBARGS(args)) {
+ fts_search_deserialize_add_idx(args->value.subargs,
+ buf, idx, matches);
+ }
+ }
+}
+
+void fts_search_deserialize_add_matches(struct mail_search_arg *args,
+ const buffer_t *buf)
+{
+ unsigned int idx = 0;
+
+ fts_search_deserialize_add_idx(args, buf, &idx, TRUE);
+ i_assert(idx == buf->used);
+}
+
+void fts_search_deserialize_add_nonmatches(struct mail_search_arg *args,
+ const buffer_t *buf)
+{
+ unsigned int idx = 0;
+
+ fts_search_deserialize_add_idx(args, buf, &idx, FALSE);
+ i_assert(idx == buf->used);
+}
diff --git a/src/plugins/fts/fts-search-serialize.h b/src/plugins/fts/fts-search-serialize.h
new file mode 100644
index 0000000..c1a7d88
--- /dev/null
+++ b/src/plugins/fts/fts-search-serialize.h
@@ -0,0 +1,16 @@
+#ifndef FTS_SEARCH_SERIALIZE_H
+#define FTS_SEARCH_SERIALIZE_H
+
+/* serialize [non]match_always fields (clearing buffer) */
+void fts_search_serialize(buffer_t *buf, const struct mail_search_arg *args);
+/* add/remove [non]match_always fields in search args */
+void fts_search_deserialize(struct mail_search_arg *args,
+ const buffer_t *buf);
+/* add match_always=TRUE fields to search args */
+void fts_search_deserialize_add_matches(struct mail_search_arg *args,
+ const buffer_t *buf);
+/* add nonmatch_always=TRUE fields to search args */
+void fts_search_deserialize_add_nonmatches(struct mail_search_arg *args,
+ const buffer_t *buf);
+
+#endif
diff --git a/src/plugins/fts/fts-search.c b/src/plugins/fts/fts-search.c
new file mode 100644
index 0000000..895ea59
--- /dev/null
+++ b/src/plugins/fts/fts-search.c
@@ -0,0 +1,385 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "seq-range-array.h"
+#include "mail-search.h"
+#include "fts-api-private.h"
+#include "fts-search-args.h"
+#include "fts-search-serialize.h"
+#include "fts-storage.h"
+#include "hash.h"
+
+static void
+uid_range_to_seqs(struct fts_search_context *fctx,
+ const ARRAY_TYPE(seq_range) *uid_range,
+ ARRAY_TYPE(seq_range) *seq_range)
+{
+ const struct seq_range *range;
+ unsigned int i, count;
+ uint32_t seq1, seq2;
+
+ range = array_get(uid_range, &count);
+ if (!array_is_created(seq_range))
+ p_array_init(seq_range, fctx->result_pool, count);
+ for (i = 0; i < count; i++) {
+ if (range[i].seq1 > range[i].seq2)
+ continue;
+ mailbox_get_seq_range(fctx->box, range[i].seq1, range[i].seq2,
+ &seq1, &seq2);
+ if (seq1 != 0)
+ seq_range_array_add_range(seq_range, seq1, seq2);
+ }
+}
+
+static int fts_search_lookup_level_single(struct fts_search_context *fctx,
+ struct mail_search_arg *args,
+ bool and_args)
+{
+ enum fts_lookup_flags flags = fctx->flags |
+ (and_args ? FTS_LOOKUP_FLAG_AND_ARGS : 0);
+ struct fts_search_level *level;
+ struct fts_result result;
+
+ i_zero(&result);
+ result.search_state = fctx->search_state;
+ result.pool = fctx->result_pool;
+ p_array_init(&result.definite_uids, fctx->result_pool, 32);
+ p_array_init(&result.maybe_uids, fctx->result_pool, 32);
+ p_array_init(&result.scores, fctx->result_pool, 32);
+
+ mail_search_args_reset(args, TRUE);
+ if (fts_backend_lookup(fctx->backend, fctx->box, args, flags,
+ &result) < 0)
+ return -1;
+
+ fctx->search_state = result.search_state;
+ level = array_append_space(&fctx->levels);
+ level->args_matches = buffer_create_dynamic(fctx->result_pool, 16);
+ fts_search_serialize(level->args_matches, args);
+
+ uid_range_to_seqs(fctx, &result.definite_uids, &level->definite_seqs);
+ uid_range_to_seqs(fctx, &result.maybe_uids, &level->maybe_seqs);
+ level->score_map = result.scores;
+ return 0;
+}
+
+static void
+level_scores_add_vuids(struct mailbox *box,
+ struct fts_search_level *level, struct fts_result *br)
+{
+ const struct fts_score_map *scores;
+ unsigned int i, count;
+ ARRAY_TYPE(seq_range) backend_uids;
+ ARRAY_TYPE(uint32_t) vuids_arr;
+ const uint32_t *vuids;
+ struct fts_score_map *score;
+
+ scores = array_get(&br->scores, &count);
+ t_array_init(&vuids_arr, count);
+ t_array_init(&backend_uids, 64);
+ for (i = 0; i < count; i++)
+ seq_range_array_add(&backend_uids, scores[i].uid);
+ box->virtual_vfuncs->get_virtual_uid_map(box, br->box,
+ &backend_uids, &vuids_arr);
+
+ i_assert(array_count(&vuids_arr) == array_count(&br->scores));
+ vuids = array_get(&vuids_arr, &count);
+ for (i = 0; i < count; i++) {
+ score = array_append_space(&level->score_map);
+ score->uid = vuids[i];
+ score->score = scores[i].score;
+ }
+}
+
+static int
+mailbox_cmp_fts_backend(struct mailbox *const *m1, struct mailbox *const *m2)
+{
+ struct fts_backend *b1, *b2;
+
+ b1 = fts_mailbox_backend(*m1);
+ b2 = fts_mailbox_backend(*m2);
+ if (b1 < b2)
+ return -1;
+ if (b1 > b2)
+ return 1;
+ return 0;
+}
+
+static int
+multi_add_lookup_result(struct fts_search_context *fctx,
+ struct fts_search_level *level,
+ struct mail_search_arg *args,
+ struct fts_multi_result *result)
+{
+ ARRAY_TYPE(seq_range) vuids;
+ size_t orig_size;
+ unsigned int i;
+
+ orig_size = level->args_matches->used;
+ fts_search_serialize(level->args_matches, args);
+ if (orig_size > 0) {
+ if (level->args_matches->used != orig_size * 2 ||
+ memcmp(level->args_matches->data,
+ CONST_PTR_OFFSET(level->args_matches->data,
+ orig_size), orig_size) != 0)
+ i_panic("incompatible fts backends for namespaces");
+ buffer_set_used_size(level->args_matches, orig_size);
+ }
+
+ t_array_init(&vuids, 64);
+ for (i = 0; result->box_results[i].box != NULL; i++) {
+ struct fts_result *br = &result->box_results[i];
+
+ array_clear(&vuids);
+ if (array_is_created(&br->definite_uids)) {
+ fctx->box->virtual_vfuncs->get_virtual_uids(fctx->box,
+ br->box, &br->definite_uids, &vuids);
+ }
+ uid_range_to_seqs(fctx, &vuids, &level->definite_seqs);
+
+ array_clear(&vuids);
+ if (array_is_created(&br->maybe_uids)) {
+ fctx->box->virtual_vfuncs->get_virtual_uids(fctx->box,
+ br->box, &br->maybe_uids, &vuids);
+ }
+ uid_range_to_seqs(fctx, &vuids, &level->maybe_seqs);
+
+ if (array_is_created(&br->scores))
+ level_scores_add_vuids(fctx->box, level, br);
+ }
+ return 0;
+}
+
+static int fts_search_lookup_level_multi(struct fts_search_context *fctx,
+ struct mail_search_arg *args,
+ bool and_args)
+{
+ enum fts_lookup_flags flags = fctx->flags |
+ (and_args ? FTS_LOOKUP_FLAG_AND_ARGS : 0);
+ ARRAY_TYPE(mailboxes) mailboxes_arr, tmp_mailboxes;
+ struct mailbox *const *mailboxes;
+ struct fts_backend *backend;
+ struct fts_search_level *level;
+ struct fts_multi_result result;
+ unsigned int i, j, mailbox_count;
+
+ p_array_init(&mailboxes_arr, fctx->result_pool, 8);
+ fctx->box->virtual_vfuncs->get_virtual_backend_boxes(fctx->box,
+ &mailboxes_arr, TRUE);
+ array_sort(&mailboxes_arr, mailbox_cmp_fts_backend);
+
+ i_zero(&result);
+ result.search_state = fctx->search_state;
+ result.pool = fctx->result_pool;
+
+ level = array_append_space(&fctx->levels);
+ level->args_matches = buffer_create_dynamic(fctx->result_pool, 16);
+ p_array_init(&level->score_map, fctx->result_pool, 1);
+
+ mailboxes = array_get(&mailboxes_arr, &mailbox_count);
+ t_array_init(&tmp_mailboxes, mailbox_count);
+ for (i = 0; i < mailbox_count; i = j) {
+ array_clear(&tmp_mailboxes);
+ array_push_back(&tmp_mailboxes, &mailboxes[i]);
+
+ backend = fts_mailbox_backend(mailboxes[i]);
+ for (j = i + 1; j < mailbox_count; j++) {
+ if (fts_mailbox_backend(mailboxes[j]) != backend)
+ break;
+ array_push_back(&tmp_mailboxes, &mailboxes[j]);
+ }
+ array_append_zero(&tmp_mailboxes);
+
+ mail_search_args_reset(args, TRUE);
+ if (fts_backend_lookup_multi(backend,
+ array_front(&tmp_mailboxes),
+ args, flags, &result) < 0)
+ return -1;
+
+ if (multi_add_lookup_result(fctx, level, args, &result) < 0)
+ return -1;
+ }
+ fctx->search_state = result.search_state;
+ return 0;
+}
+
+static int fts_search_lookup_level(struct fts_search_context *fctx,
+ struct mail_search_arg *args,
+ bool and_args)
+{
+ int ret;
+
+ T_BEGIN {
+ ret = !fctx->virtual_mailbox ?
+ fts_search_lookup_level_single(fctx, args, and_args) :
+ fts_search_lookup_level_multi(fctx, args, and_args);
+ } T_END;
+ if (ret < 0)
+ return -1;
+
+ for (; args != NULL; args = args->next) {
+ if (args->type != SEARCH_OR && args->type != SEARCH_SUB)
+ continue;
+
+ if (fts_search_lookup_level(fctx, args->value.subargs,
+ args->type == SEARCH_SUB) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void
+fts_search_merge_scores_and(ARRAY_TYPE(fts_score_map) *dest,
+ const ARRAY_TYPE(fts_score_map) *src)
+{
+ struct fts_score_map *dest_map;
+ const struct fts_score_map *src_map;
+ unsigned int desti, srci, dest_count, src_count;
+
+ dest_map = array_get_modifiable(dest, &dest_count);
+ src_map = array_get(src, &src_count);
+
+ /* arg_scores are summed to current scores. we could drop UIDs that
+ don't exist in both, but that's just extra work so don't bother */
+ for (desti = srci = 0; desti < dest_count && srci < src_count;) {
+ if (dest_map[desti].uid < src_map[srci].uid)
+ desti++;
+ else if (dest_map[desti].uid > src_map[srci].uid)
+ srci++;
+ else {
+ if (dest_map[desti].score < src_map[srci].score)
+ dest_map[desti].score = src_map[srci].score;
+ desti++; srci++;
+ }
+ }
+}
+
+static void
+fts_search_merge_scores_or(ARRAY_TYPE(fts_score_map) *dest,
+ const ARRAY_TYPE(fts_score_map) *src)
+{
+ ARRAY_TYPE(fts_score_map) src2;
+ const struct fts_score_map *src_map, *src2_map;
+ unsigned int srci, src2i, src_count, src2_count;
+
+ t_array_init(&src2, array_count(dest));
+ array_append_array(&src2, dest);
+ array_clear(dest);
+
+ src_map = array_get(src, &src_count);
+ src2_map = array_get(&src2, &src2_count);
+
+ /* add any missing UIDs to current scores. if any existing UIDs have
+ lower scores than in arg_scores, increase them. */
+ for (srci = src2i = 0; srci < src_count || src2i < src2_count;) {
+ if (src2i == src2_count ||
+ src_map[srci].uid < src2_map[src2i].uid) {
+ array_push_back(dest, &src_map[srci]);
+ srci++;
+ } else if (srci == src_count ||
+ src_map[srci].uid > src2_map[src2i].uid) {
+ array_push_back(dest, &src2_map[src2i]);
+ src2i++;
+ } else {
+ i_assert(src_map[srci].uid == src2_map[src2i].uid);
+ if (src_map[srci].score > src2_map[src2i].score)
+ array_push_back(dest, &src_map[srci]);
+ else
+ array_push_back(dest, &src2_map[src2i]);
+ srci++; src2i++;
+ }
+ }
+}
+
+static void
+fts_search_merge_scores_level(struct fts_search_context *fctx,
+ struct mail_search_arg *args, unsigned int *idx,
+ bool and_args, ARRAY_TYPE(fts_score_map) *scores)
+{
+ const struct fts_search_level *level;
+ ARRAY_TYPE(fts_score_map) arg_scores;
+
+ i_assert(array_count(scores) == 0);
+
+ /*
+ The (simplified) args can look like:
+
+ A and B and (C or D) and (E or F) and ...
+ A or B or (C and D) or (E and F) or ...
+
+ The A op B part's scores are in level->scores. The child args'
+ scores are in the sub levels' scores.
+ */
+
+ level = array_idx(&fctx->levels, *idx);
+ array_append_array(scores, &level->score_map);
+
+ t_array_init(&arg_scores, 64);
+ for (; args != NULL; args = args->next) {
+ if (args->type != SEARCH_OR && args->type != SEARCH_SUB)
+ continue;
+
+ *idx += 1;
+ array_clear(&arg_scores);
+ fts_search_merge_scores_level(fctx, args->value.subargs, idx,
+ args->type == SEARCH_OR,
+ &arg_scores);
+
+ if (and_args)
+ fts_search_merge_scores_and(scores, &arg_scores);
+ else
+ fts_search_merge_scores_or(scores, &arg_scores);
+ }
+}
+
+static void fts_search_merge_scores(struct fts_search_context *fctx)
+{
+ unsigned int idx = 0;
+
+ fts_search_merge_scores_level(fctx, fctx->args->args, &idx,
+ TRUE, &fctx->scores->score_map);
+}
+
+static void fts_search_try_lookup(struct fts_search_context *fctx)
+{
+ uint32_t last_uid, seq1, seq2;
+
+ i_assert(array_count(&fctx->levels) == 0);
+ i_assert(fctx->args->simplified);
+
+ if (fts_backend_refresh(fctx->backend) < 0)
+ return;
+ if (fts_backend_get_last_uid(fctx->backend, fctx->box, &last_uid) < 0)
+ return;
+ mailbox_get_seq_range(fctx->box, last_uid+1, (uint32_t)-1,
+ &seq1, &seq2);
+ fctx->first_unindexed_seq = seq1 != 0 ? seq1 : (uint32_t)-1;
+
+ if (fctx->virtual_mailbox) {
+ hash_table_clear(fctx->last_indexed_virtual_uids, TRUE);
+ fctx->next_unindexed_seq = fctx->first_unindexed_seq;
+ }
+
+ if ((fctx->backend->flags & FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0) {
+ if (fts_search_args_expand(fctx->backend, fctx->args) < 0)
+ return;
+ }
+ fts_search_serialize(fctx->orig_matches, fctx->args->args);
+
+ if (fts_search_lookup_level(fctx, fctx->args->args, TRUE) == 0) {
+ fctx->fts_lookup_success = TRUE;
+ fts_search_merge_scores(fctx);
+ }
+
+ fts_search_deserialize(fctx->args->args, fctx->orig_matches);
+ fts_backend_lookup_done(fctx->backend);
+}
+
+void fts_search_lookup(struct fts_search_context *fctx)
+{
+ struct event_reason *reason = event_reason_begin("fts:lookup");
+ fts_search_try_lookup(fctx);
+ event_reason_end(&reason);
+}
diff --git a/src/plugins/fts/fts-storage.c b/src/plugins/fts/fts-storage.c
new file mode 100644
index 0000000..101d52a
--- /dev/null
+++ b/src/plugins/fts/fts-storage.c
@@ -0,0 +1,981 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "str.h"
+#include "strescape.h"
+#include "write-full.h"
+#include "mail-search-build.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "fts-api-private.h"
+#include "fts-tokenizer.h"
+#include "fts-indexer.h"
+#include "fts-build-mail.h"
+#include "fts-search-serialize.h"
+#include "fts-plugin.h"
+#include "fts-user.h"
+#include "fts-storage.h"
+#include "hash.h"
+
+
+#define FTS_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, fts_storage_module)
+#define FTS_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, fts_storage_module)
+#define FTS_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, fts_mail_module)
+#define FTS_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, fts_mailbox_list_module)
+#define FTS_LIST_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, fts_mailbox_list_module)
+
+#define INDEXER_SOCKET_NAME "indexer"
+#define INDEXER_HANDSHAKE "VERSION\tindexer\t1\t0\n"
+
+struct fts_mailbox_list {
+ union mailbox_list_module_context module_ctx;
+ struct fts_backend *backend;
+
+ const char *backend_name;
+ struct fts_backend_update_context *update_ctx;
+ unsigned int update_ctx_refcount;
+
+ bool failed:1;
+};
+
+struct fts_mailbox {
+ union mailbox_module_context module_ctx;
+ struct fts_backend_update_context *sync_update_ctx;
+ bool fts_mailbox_excluded;
+};
+
+struct fts_transaction_context {
+ union mailbox_transaction_module_context module_ctx;
+
+ struct fts_scores *scores;
+ uint32_t next_index_seq;
+ uint32_t highest_virtual_uid;
+ unsigned int precache_extra_count;
+
+ bool indexing:1;
+ bool precached:1;
+ bool mails_saved:1;
+ const char *failure_reason;
+};
+
+struct fts_mail {
+ union mail_module_context module_ctx;
+ char score[30];
+
+ bool virtual_mail:1;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(fts_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(fts_mail_module, &mail_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(fts_mailbox_list_module,
+ &mailbox_list_module_register);
+
+static int fts_mailbox_get_last_cached_seq(struct mailbox *box, uint32_t *seq_r)
+{
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(box->list);
+ uint32_t seq1, seq2, last_uid;
+
+ if (fts_backend_get_last_uid(flist->backend, box, &last_uid) < 0) {
+ mail_storage_set_internal_error(box->storage);
+ return -1;
+ }
+
+ if (last_uid == 0)
+ *seq_r = 0;
+ else {
+ mailbox_get_seq_range(box, 1, last_uid, &seq1, &seq2);
+ *seq_r = seq2;
+ }
+ return 0;
+}
+
+static int
+fts_mailbox_get_status(struct mailbox *box, enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(box);
+ uint32_t seq;
+
+ if (fbox->module_ctx.super.get_status(box, items, status_r) < 0)
+ return -1;
+
+ if ((items & STATUS_LAST_CACHED_SEQ) != 0) {
+ if (fts_mailbox_get_last_cached_seq(box, &seq) < 0)
+ return -1;
+
+ /* Always use the FTS's last_cached_seq. This is because we
+ don't want to reindex all mails to FTS if .cache file is
+ deleted. */
+ status_r->last_cached_seq = seq;
+ }
+ return 0;
+}
+
+
+static void fts_scores_unref(struct fts_scores **_scores)
+{
+ struct fts_scores *scores = *_scores;
+
+ *_scores = NULL;
+ if (--scores->refcount == 0) {
+ array_free(&scores->score_map);
+ i_free(scores);
+ }
+}
+
+static void fts_try_build_init(struct mail_search_context *ctx,
+ struct fts_search_context *fctx)
+{
+ int ret;
+
+ i_assert(!fts_backend_is_updating(fctx->backend));
+
+ ret = fts_indexer_init(fctx->backend, ctx->transaction->box,
+ &fctx->indexer_ctx);
+ if (ret < 0)
+ return;
+
+ if (ret == 0) {
+ /* the index was up to date */
+ fts_search_lookup(fctx);
+ } else {
+ /* hide "searching" notifications while building index */
+ ctx->progress_hidden = TRUE;
+ }
+}
+
+static bool fts_want_build_args(const struct mail_search_arg *args)
+{
+ /* we want to update index only when searching from message body.
+ it's not worth the wait for searching only from headers, which
+ could be in cache file already */
+ for (; args != NULL; args = args->next) {
+ switch (args->type) {
+ case SEARCH_OR:
+ case SEARCH_SUB:
+ case SEARCH_INTHREAD:
+ if (fts_want_build_args(args->value.subargs))
+ return TRUE;
+ break;
+ case SEARCH_BODY:
+ case SEARCH_TEXT:
+ if (!args->no_fts)
+ return TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static bool fts_args_have_fuzzy(const struct mail_search_arg *args)
+{
+ for (; args != NULL; args = args->next) {
+ if (args->fuzzy)
+ return TRUE;
+ switch (args->type) {
+ case SEARCH_OR:
+ case SEARCH_SUB:
+ case SEARCH_INTHREAD:
+ if (fts_args_have_fuzzy(args->value.subargs))
+ return TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static enum fts_enforced fts_enforced_parse(const char *str)
+{
+ if (str == NULL || strcmp(str, "no") == 0)
+ return FTS_ENFORCED_NO;
+ else if (strcmp(str, "body") == 0)
+ return FTS_ENFORCED_BODY;
+ else
+ return FTS_ENFORCED_YES;
+}
+
+static struct mail_search_context *
+fts_mailbox_search_init(struct mailbox_transaction_context *t,
+ struct mail_search_args *args,
+ const enum mail_sort_type *sort_program,
+ enum mail_fetch_field wanted_fields,
+ struct mailbox_header_lookup_ctx *wanted_headers)
+{
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(t);
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(t->box);
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(t->box->list);
+ struct mail_search_context *ctx;
+ struct fts_search_context *fctx;
+
+ ctx = fbox->module_ctx.super.search_init(t, args, sort_program,
+ wanted_fields, wanted_headers);
+
+ if (!fts_backend_can_lookup(flist->backend, args->args))
+ return ctx;
+
+ fctx = i_new(struct fts_search_context, 1);
+ fctx->box = t->box;
+ fctx->backend = flist->backend;
+ fctx->t = t;
+ fctx->args = args;
+ fctx->result_pool = pool_alloconly_create("fts results", 1024*64);
+ fctx->orig_matches = buffer_create_dynamic(default_pool, 64);
+ fctx->virtual_mailbox = t->box->virtual_vfuncs != NULL;
+ if (fctx->virtual_mailbox) {
+ hash_table_create(&fctx->last_indexed_virtual_uids,
+ default_pool, 0, str_hash, strcmp);
+ }
+ fctx->enforced = fts_enforced_parse(
+ mail_user_plugin_getenv(t->box->storage->user, "fts_enforced"));
+ i_array_init(&fctx->levels, 8);
+ fctx->scores = i_new(struct fts_scores, 1);
+ fctx->scores->refcount = 1;
+ i_array_init(&fctx->scores->score_map, 64);
+ MODULE_CONTEXT_SET(ctx, fts_storage_module, fctx);
+
+ /* FIXME: we'll assume that all the args are fuzzy. not good,
+ but would require much more work to fix it. */
+ if (!fts_args_have_fuzzy(args->args) &&
+ mail_user_plugin_getenv_bool(t->box->storage->user,
+ "fts_no_autofuzzy"))
+ fctx->flags |= FTS_LOOKUP_FLAG_NO_AUTO_FUZZY;
+ /* transaction contains the last search's scores. they can be
+ queried later with mail_get_special() */
+ if (ft->scores != NULL)
+ fts_scores_unref(&ft->scores);
+ ft->scores = fctx->scores;
+ ft->scores->refcount++;
+
+ if (fctx->enforced == FTS_ENFORCED_YES ||
+ fts_want_build_args(args->args))
+ fts_try_build_init(ctx, fctx);
+ else
+ fts_search_lookup(fctx);
+ return ctx;
+}
+
+static bool fts_mailbox_build_continue(struct mail_search_context *ctx)
+{
+ struct fts_search_context *fctx = FTS_CONTEXT_REQUIRE(ctx);
+ int ret;
+
+ ret = fts_indexer_more(fctx->indexer_ctx);
+ if (ret == 0)
+ return FALSE;
+
+ /* indexing finished */
+ ctx->progress_hidden = FALSE;
+ if (fts_indexer_deinit(&fctx->indexer_ctx) < 0)
+ ret = -1;
+ if (ret > 0)
+ fts_search_lookup(fctx);
+ if (ret < 0) {
+ /* if indexing timed out, it probably means that
+ the mailbox is still being indexed, but it's a large
+ mailbox and it takes a while. in this situation
+ we'll simply abort the search.
+
+ if indexing failed for any other reason, just
+ fallback to searching the slow way. */
+ fctx->indexing_timed_out =
+ mailbox_get_last_mail_error(fctx->box) == MAIL_ERROR_INUSE;
+ }
+ return TRUE;
+}
+
+static bool
+fts_mailbox_search_next_nonblock(struct mail_search_context *ctx,
+ struct mail **mail_r, bool *tryagain_r)
+{
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box);
+ struct fts_search_context *fctx = FTS_CONTEXT(ctx);
+
+ if (fctx != NULL && fctx->indexer_ctx != NULL) {
+ /* this command is still building the indexes */
+ if (!fts_mailbox_build_continue(ctx)) {
+ *tryagain_r = TRUE;
+ return FALSE;
+ }
+ if (fctx->indexing_timed_out) {
+ *tryagain_r = FALSE;
+ return FALSE;
+ }
+ }
+ if (fctx != NULL && !fctx->fts_lookup_success &&
+ fctx->enforced != FTS_ENFORCED_NO)
+ return FALSE;
+
+ return fbox->module_ctx.super.
+ search_next_nonblock(ctx, mail_r, tryagain_r);
+}
+
+static void
+fts_search_apply_results_level(struct mail_search_context *ctx,
+ struct mail_search_arg *args, unsigned int *idx)
+{
+ struct fts_search_context *fctx = FTS_CONTEXT_REQUIRE(ctx);
+ const struct fts_search_level *level;
+
+ level = array_idx(&fctx->levels, *idx);
+
+ if (array_is_created(&level->definite_seqs) &&
+ seq_range_exists(&level->definite_seqs, ctx->seq))
+ fts_search_deserialize_add_matches(args, level->args_matches);
+ else if (!array_is_created(&level->maybe_seqs) ||
+ !seq_range_exists(&level->maybe_seqs, ctx->seq))
+ fts_search_deserialize_add_nonmatches(args, level->args_matches);
+
+ for (; args != NULL; args = args->next) {
+ if (args->type != SEARCH_OR && args->type != SEARCH_SUB)
+ continue;
+
+ *idx += 1;
+ fts_search_apply_results_level(ctx, args->value.subargs, idx);
+ }
+}
+
+static bool fts_mailbox_search_next_update_seq(struct mail_search_context *ctx)
+{
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box);
+ struct fts_search_context *fctx = FTS_CONTEXT(ctx);
+ unsigned int idx;
+
+ if (fctx == NULL || !fctx->fts_lookup_success) {
+ /* fts lookup not done for this search */
+ if (fctx != NULL && fctx->indexing_timed_out)
+ return FALSE;
+ return fbox->module_ctx.super.search_next_update_seq(ctx);
+ }
+
+ /* restore original [non]matches */
+ fts_search_deserialize(ctx->args->args, fctx->orig_matches);
+
+ if (!fbox->module_ctx.super.search_next_update_seq(ctx))
+ return FALSE;
+
+ if (ctx->seq >= fctx->first_unindexed_seq) {
+ /* we've not indexed this far */
+ return TRUE;
+ }
+
+ /* apply [non]matches based on the FTS lookup results */
+ idx = 0;
+ fts_search_apply_results_level(ctx, ctx->args->args, &idx);
+ return TRUE;
+}
+
+static int fts_mailbox_search_deinit(struct mail_search_context *ctx)
+{
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box);
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(ctx->transaction);
+ struct fts_search_context *fctx = FTS_CONTEXT(ctx);
+ int ret = 0;
+
+ if (fctx != NULL) {
+ if (fctx->virtual_mailbox)
+ hash_table_destroy(&fctx->last_indexed_virtual_uids);
+ if (fctx->indexer_ctx != NULL) {
+ if (fts_indexer_deinit(&fctx->indexer_ctx) < 0)
+ ft->failure_reason = "FTS indexing failed";
+ }
+ if (fctx->indexing_timed_out)
+ ret = -1;
+ else if (!fctx->fts_lookup_success &&
+ fctx->enforced != FTS_ENFORCED_NO) {
+ /* FTS lookup failed and we didn't want to fallback to
+ opening all the mails and searching manually */
+ mail_storage_set_internal_error(ctx->transaction->box->storage);
+ ret = -1;
+ }
+
+ buffer_free(&fctx->orig_matches);
+ array_free(&fctx->levels);
+ pool_unref(&fctx->result_pool);
+ fts_scores_unref(&fctx->scores);
+ i_free(fctx);
+ }
+ if (fbox->module_ctx.super.search_deinit(ctx) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int fts_score_cmp(const uint32_t *uid, const struct fts_score_map *score)
+{
+ return *uid < score->uid ? -1 :
+ (*uid > score->uid ? 1 : 0);
+}
+
+static int fts_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
+ const char **value_r)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct fts_mail *fmail = FTS_MAIL_CONTEXT(mail);
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(_mail->transaction);
+ const struct fts_score_map *scores;
+
+ if (field != MAIL_FETCH_SEARCH_RELEVANCY || ft->scores == NULL)
+ scores = NULL;
+ else {
+ scores = array_bsearch(&ft->scores->score_map, &_mail->uid,
+ fts_score_cmp);
+ }
+ if (scores != NULL) {
+ i_assert(scores->uid == _mail->uid);
+ (void)i_snprintf(fmail->score, sizeof(fmail->score),
+ "%f", scores->score);
+
+ *value_r = fmail->score;
+ return 0;
+ }
+
+ return fmail->module_ctx.super.get_special(_mail, field, value_r);
+}
+
+static int
+fts_mail_precache_range(struct mailbox_transaction_context *trans,
+ struct fts_backend_update_context *update_ctx,
+ uint32_t seq1, uint32_t seq2, unsigned int *extra_count)
+{
+ struct mail_search_args *search_args;
+ struct mail_search_context *ctx;
+ struct mail *mail;
+ int ret = 0;
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_seqset(search_args, seq1, seq2);
+ ctx = mailbox_search_init(trans, search_args, NULL,
+ MAIL_FETCH_STREAM_HEADER |
+ MAIL_FETCH_STREAM_BODY, NULL);
+ mail_search_args_unref(&search_args);
+
+ while (mailbox_search_next(ctx, &mail)) {
+ if (fts_build_mail(update_ctx, mail) < 0) {
+ ret = -1;
+ break;
+ }
+ if (mail_precache(mail) < 0) {
+ ret = -1;
+ break;
+ }
+ *extra_count += 1;
+ }
+ if (mailbox_search_deinit(&ctx) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int fts_mail_precache_init(struct mail *_mail)
+{
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(_mail->transaction);
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(_mail->box->list);
+ uint32_t last_seq;
+
+ if (fts_mailbox_get_last_cached_seq(_mail->box, &last_seq) < 0) {
+ ft->failure_reason = "Failed to lookup last indexed FTS mail";
+ return -1;
+ }
+
+ ft->precached = TRUE;
+ ft->next_index_seq = last_seq + 1;
+ if (flist->update_ctx == NULL)
+ flist->update_ctx = fts_backend_update_init(flist->backend);
+ flist->update_ctx_refcount++;
+ return 0;
+}
+
+static int fts_mail_index(struct mail *_mail)
+{
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(_mail->transaction);
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(_mail->box->list);
+ struct mail_private *pmail = (struct mail_private *)_mail;
+
+ if (ft->failure_reason != NULL)
+ return -1;
+
+ if (!ft->precached) {
+ if (fts_mail_precache_init(_mail) < 0)
+ return -1;
+ }
+ if (pmail->vmail != NULL) {
+ /* Indexing via virtual mailbox: Index all the mails in this
+ same real mailbox. */
+ uint32_t msgs_count =
+ mail_index_view_get_messages_count(_mail->box->view);
+
+ fts_backend_update_set_mailbox(flist->update_ctx, _mail->box);
+ if (ft->next_index_seq > msgs_count) {
+ /* everything indexed already */
+ return 0;
+ } else if (fts_mail_precache_range(_mail->transaction,
+ flist->update_ctx,
+ ft->next_index_seq,
+ msgs_count,
+ &ft->precache_extra_count) < 0) {
+ return -1;
+ } else {
+ ft->next_index_seq = msgs_count+1;
+ return 0;
+ }
+ }
+
+ if (ft->next_index_seq < _mail->seq) {
+ /* we'll first need to index all the missing mails up to the
+ current one. */
+ fts_backend_update_set_mailbox(flist->update_ctx, _mail->box);
+ if (fts_mail_precache_range(_mail->transaction,
+ flist->update_ctx,
+ ft->next_index_seq,
+ _mail->seq-1,
+ &ft->precache_extra_count) < 0)
+ return -1;
+ ft->next_index_seq = _mail->seq;
+ }
+
+ if (ft->next_index_seq == _mail->seq) {
+ fts_backend_update_set_mailbox(flist->update_ctx, _mail->box);
+ if (fts_build_mail(flist->update_ctx, _mail) < 0)
+ return -1;
+ ft->next_index_seq = _mail->seq + 1;
+ }
+ return 0;
+}
+
+static int fts_mail_precache(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct fts_mail *fmail = FTS_MAIL_CONTEXT(mail);
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(_mail->transaction);
+ int ret = 0;
+
+ fmail->module_ctx.super.precache(_mail);
+ if (fmail->virtual_mail) {
+ if (ft->highest_virtual_uid < _mail->uid)
+ ft->highest_virtual_uid = _mail->uid;
+ } else if (!ft->indexing) T_BEGIN {
+ /* avoid recursing here from fts_mail_precache_range() */
+ struct event_reason *reason =
+ event_reason_begin("fts:index");
+ ft->indexing = TRUE;
+ ret = fts_mail_index(_mail);
+ i_assert(ft->indexing);
+ ft->indexing = FALSE;
+ event_reason_end(&reason);
+ } T_END;
+ return ret;
+}
+
+void fts_mail_allocated(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ struct fts_mailbox *fbox = FTS_CONTEXT(_mail->box);
+ struct fts_mail *fmail;
+
+ if (fbox == NULL)
+ return;
+
+ fmail = p_new(mail->pool, struct fts_mail, 1);
+ fmail->module_ctx.super = *v;
+ mail->vlast = &fmail->module_ctx.super;
+ fmail->virtual_mail = _mail->box->virtual_vfuncs != NULL;
+
+ v->get_special = fts_mail_get_special;
+ v->precache = fts_mail_precache;
+ MODULE_CONTEXT_SET(mail, fts_mail_module, fmail);
+}
+
+static struct mailbox_transaction_context *
+fts_transaction_begin(struct mailbox *box,
+ enum mailbox_transaction_flags flags,
+ const char *reason)
+{
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(box);
+ struct mailbox_transaction_context *t;
+ struct fts_transaction_context *ft;
+
+ ft = i_new(struct fts_transaction_context, 1);
+
+ t = fbox->module_ctx.super.transaction_begin(box, flags, reason);
+ MODULE_CONTEXT_SET(t, fts_storage_module, ft);
+ return t;
+}
+
+static int fts_transaction_end(struct mailbox_transaction_context *t, const char **error_r)
+{
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(t);
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(t->box->list);
+ int ret = 0;
+
+ if (ft->failure_reason != NULL) {
+ *error_r = t_strdup(ft->failure_reason);
+ ret = -1;
+ }
+
+ struct event_reason *reason = event_reason_begin("fts:index");
+ if (ft->precached) {
+ i_assert(flist->update_ctx_refcount > 0);
+ if (--flist->update_ctx_refcount == 0) {
+ if (fts_backend_update_deinit(&flist->update_ctx) < 0) {
+ ret = -1;
+ *error_r = "backend deinit";
+ }
+ }
+ } else if (ft->highest_virtual_uid > 0) {
+ if (fts_index_set_last_uid(t->box, ft->highest_virtual_uid) < 0) {
+ ret = -1;
+ *error_r = "index last uid setting";
+ }
+ }
+ if (ft->scores != NULL)
+ fts_scores_unref(&ft->scores);
+ if (ft->precache_extra_count > 0) {
+ if (ret < 0) {
+ i_error("fts: Failed after indexing %u extra mails internally in %s: %s",
+ ft->precache_extra_count, t->box->vname, *error_r);
+ } else {
+ i_info("fts: Indexed %u extra mails internally in %s",
+ ft->precache_extra_count, t->box->vname);
+ }
+ }
+ event_reason_end(&reason);
+ i_free(ft);
+ return ret;
+}
+
+static void fts_transaction_rollback(struct mailbox_transaction_context *t)
+{
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(t->box);
+ const char *error;
+
+ (void)fts_transaction_end(t, &error);
+ fbox->module_ctx.super.transaction_rollback(t);
+}
+
+static void fts_queue_index(struct mailbox *box)
+{
+ struct mail_user *user = box->storage->user;
+ string_t *str = t_str_new(256);
+ const char *path, *value;
+ unsigned int max_recent_msgs;
+ int fd;
+
+ path = t_strconcat(user->set->base_dir, "/"INDEXER_SOCKET_NAME, NULL);
+ fd = net_connect_unix(path);
+ if (fd == -1) {
+ i_error("net_connect_unix(%s) failed: %m", path);
+ return;
+ }
+
+ value = mail_user_plugin_getenv(user, "fts_autoindex_max_recent_msgs");
+ if (value == NULL || str_to_uint(value, &max_recent_msgs) < 0)
+ max_recent_msgs = 0;
+
+ str_append(str, INDEXER_HANDSHAKE);
+ str_append(str, "APPEND\t0\t");
+ str_append_tabescaped(str, user->username);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, box->vname);
+ str_printfa(str, "\t%u", max_recent_msgs);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, box->storage->user->session_id);
+ str_append_c(str, '\n');
+ if (write_full(fd, str_data(str), str_len(str)) < 0)
+ i_error("write(%s) failed: %m", path);
+ i_close_fd(&fd);
+}
+
+static int
+fts_transaction_commit(struct mailbox_transaction_context *t,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(t);
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(t->box);
+ struct mailbox *box = t->box;
+ bool autoindex;
+ int ret = 0;
+ const char *error;
+
+ autoindex = ft->mails_saved && !fbox->fts_mailbox_excluded &&
+ mail_user_plugin_getenv_bool(box->storage->user,
+ "fts_autoindex");
+
+ if (fts_transaction_end(t, &error) < 0) {
+ mail_storage_set_error(t->box->storage, MAIL_ERROR_TEMP,
+ t_strdup_printf("FTS transaction commit failed: %s",
+ error));
+ ret = -1;
+ }
+ if (fbox->module_ctx.super.transaction_commit(t, changes_r) < 0)
+ ret = -1;
+ if (ret < 0)
+ return -1;
+
+ if (autoindex)
+ fts_queue_index(box);
+ return 0;
+}
+
+static void fts_mailbox_sync_notify(struct mailbox *box, uint32_t uid,
+ enum mailbox_sync_type sync_type)
+{
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(box->list);
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(box);
+
+ if (fbox->module_ctx.super.sync_notify != NULL)
+ fbox->module_ctx.super.sync_notify(box, uid, sync_type);
+
+ if (sync_type != MAILBOX_SYNC_TYPE_EXPUNGE) {
+ if (uid == 0 && fbox->sync_update_ctx != NULL) {
+ /* this sync is finished */
+ (void)fts_backend_update_deinit(&fbox->sync_update_ctx);
+ }
+ return;
+ }
+
+ if (fbox->sync_update_ctx == NULL) {
+ if (fts_backend_is_updating(flist->backend)) {
+ /* FIXME: maildir workaround - we could get here
+ because we're building an index, which doesn't find
+ some mail and starts syncing the mailbox.. */
+ return;
+ }
+ fbox->sync_update_ctx = fts_backend_update_init(flist->backend);
+ fts_backend_update_set_mailbox(fbox->sync_update_ctx, box);
+ }
+ fts_backend_update_expunge(fbox->sync_update_ctx, uid);
+}
+
+static int fts_sync_deinit(struct mailbox_sync_context *ctx,
+ struct mailbox_sync_status *status_r)
+{
+ struct mailbox *box = ctx->box;
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(box);
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT(box->list);
+ bool optimize;
+ int ret = 0;
+
+ optimize = (ctx->flags & (MAILBOX_SYNC_FLAG_FORCE_RESYNC |
+ MAILBOX_SYNC_FLAG_OPTIMIZE)) != 0;
+ if (fbox->module_ctx.super.sync_deinit(ctx, status_r) < 0)
+ return -1;
+ ctx = NULL;
+
+ if (optimize) {
+ i_assert(flist != NULL);
+ if (fts_backend_optimize(flist->backend) < 0) {
+ mailbox_set_critical(box, "FTS optimize failed");
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+static int fts_save_finish(struct mail_save_context *ctx)
+{
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(ctx->transaction);
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box);
+
+ if (fbox->module_ctx.super.save_finish(ctx) < 0)
+ return -1;
+ ft->mails_saved = TRUE;
+ return 0;
+}
+
+static int fts_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(ctx->transaction);
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box);
+
+ if (fbox->module_ctx.super.copy(ctx, mail) < 0)
+ return -1;
+ ft->mails_saved = TRUE;
+ return 0;
+}
+
+static void fts_mailbox_virtual_match_mail(struct mail_search_context *ctx,
+ struct mail *mail)
+{
+ struct fts_search_context *fctx = FTS_CONTEXT(ctx);
+ unsigned int idx, be_last_uid;
+
+ if (fctx == NULL || !fctx->fts_lookup_success || !fctx->virtual_mailbox ||
+ ctx->seq < fctx->first_unindexed_seq)
+ return;
+ /* Table of last indexed UID per backend mailbox */
+ HASH_TABLE_TYPE(virtual_last_indexed) hash_tbl =
+ fctx->last_indexed_virtual_uids;
+
+ struct mail *backend_mail;
+ if (mail->box->mail_vfuncs->get_backend_mail(mail, &backend_mail) < 0)
+ return;
+ const char *box_name = backend_mail->box->vname;
+ /* Get the last indexed UID in the backend mailbox */
+ void *uid_value =
+ hash_table_lookup(fctx->last_indexed_virtual_uids, box_name);
+ if (uid_value == NULL) {
+ /* This backend's last indexed uid is not yet inserted to the table */
+ struct fts_mailbox_list *flist =
+ FTS_LIST_CONTEXT(backend_mail->box->list);
+ if (flist == NULL || flist->failed ||
+ mailbox_open(backend_mail->box) < 0 ||
+ fts_backend_get_last_uid(flist->backend, backend_mail->box,
+ &be_last_uid) < 0) {
+ be_last_uid = 0;
+ } else {
+ const char *vname_copy =
+ p_strdup(fctx->result_pool, backend_mail->box->vname);
+ hash_table_insert(hash_tbl, vname_copy,
+ POINTER_CAST(be_last_uid + 1));
+ }
+ } else {
+ be_last_uid = POINTER_CAST_TO(uid_value, uint32_t) - 1;
+ }
+ if (backend_mail->uid <= be_last_uid) {
+ /* Mail was already indexed in the backend mailbox.
+ Apply [non]matches based on the FTS lookup results */
+ struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(ctx->transaction);
+
+ if (fctx->next_unindexed_seq == mail->seq) {
+ fctx->next_unindexed_seq++;
+ ft->highest_virtual_uid = mail->uid;
+ }
+ idx = 0;
+ fts_search_apply_results_level(ctx, ctx->args->args, &idx);
+ } else {
+ fctx->virtual_seen_unindexed_gaps = TRUE;
+ }
+}
+
+static int fts_mailbox_search_next_match_mail(struct mail_search_context *ctx,
+ struct mail *mail)
+{
+ struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box);
+
+ fts_mailbox_virtual_match_mail(ctx, mail);
+ return fbox->module_ctx.super.search_next_match_mail(ctx, mail);
+}
+
+void fts_mailbox_allocated(struct mailbox *box)
+{
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT(box->list);
+ struct mailbox_vfuncs *v = box->vlast;
+ struct fts_mailbox *fbox;
+
+ if (flist == NULL || flist->failed)
+ return;
+
+ fbox = p_new(box->pool, struct fts_mailbox, 1);
+ fbox->module_ctx.super = *v;
+ box->vlast = &fbox->module_ctx.super;
+ fbox->fts_mailbox_excluded = fts_user_autoindex_exclude(box);
+
+ v->get_status = fts_mailbox_get_status;
+ v->search_init = fts_mailbox_search_init;
+ v->search_next_nonblock = fts_mailbox_search_next_nonblock;
+ v->search_next_update_seq = fts_mailbox_search_next_update_seq;
+ v->search_deinit = fts_mailbox_search_deinit;
+ v->transaction_begin = fts_transaction_begin;
+ v->transaction_rollback = fts_transaction_rollback;
+ v->transaction_commit = fts_transaction_commit;
+ v->sync_notify = fts_mailbox_sync_notify;
+ v->sync_deinit = fts_sync_deinit;
+ v->save_finish = fts_save_finish;
+ v->copy = fts_copy;
+ v->search_next_match_mail = fts_mailbox_search_next_match_mail;
+
+ MODULE_CONTEXT_SET(box, fts_storage_module, fbox);
+}
+
+static void fts_mailbox_list_deinit(struct mailbox_list *list)
+{
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(list);
+
+ if (flist->backend != NULL)
+ fts_backend_deinit(&flist->backend);
+ flist->module_ctx.super.deinit(list);
+}
+
+static int
+fts_init_namespace(struct fts_mailbox_list *flist, struct mail_namespace *ns,
+ const char **error_r)
+{
+ struct fts_backend *backend;
+ if (fts_backend_init(flist->backend_name, ns, error_r, &backend) < 0) {
+ flist->failed = TRUE;
+ return -1;
+ }
+ flist->backend = backend;
+ if ((flist->backend->flags & FTS_BACKEND_FLAG_FUZZY_SEARCH) != 0)
+ ns->user->fuzzy_search = TRUE;
+ return 0;
+}
+
+void fts_mail_namespaces_added(struct mail_namespace *ns)
+{
+ while(ns != NULL) {
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT(ns->list);
+ const char *error;
+
+ if (flist != NULL && !flist->failed && flist->backend == NULL &&
+ fts_init_namespace(flist, ns, &error) < 0) {
+ i_error("fts: Failed to initialize backend '%s': %s",
+ flist->backend_name, error);
+ }
+ ns = ns->next;
+ }
+}
+
+void
+fts_mailbox_list_created(struct mailbox_list *list)
+{
+ const char *name = mail_user_plugin_getenv(list->ns->user, "fts");
+ const char *path;
+
+ if (name == NULL || name[0] == '\0') {
+ e_debug(list->ns->user->event,
+ "fts: No fts setting - plugin disabled");
+ return;
+ }
+
+ if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_INDEX, &path)) {
+ e_debug(list->ns->user->event,
+ "fts: Indexes disabled for namespace '%s'",
+ list->ns->prefix);
+ return;
+ }
+
+ struct fts_mailbox_list *flist;
+ struct mailbox_list_vfuncs *v = list->vlast;
+
+ flist = p_new(list->pool, struct fts_mailbox_list, 1);
+ flist->module_ctx.super = *v;
+ flist->backend_name = name;
+ list->vlast = &flist->module_ctx.super;
+ v->deinit = fts_mailbox_list_deinit;
+ MODULE_CONTEXT_SET(list, fts_mailbox_list_module, flist);
+}
+
+struct fts_backend *fts_mailbox_backend(struct mailbox *box)
+{
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(box->list);
+
+ return flist->backend;
+}
+
+struct fts_backend *fts_list_backend(struct mailbox_list *list)
+{
+ struct fts_mailbox_list *flist = FTS_LIST_CONTEXT(list);
+
+ return flist == NULL ? NULL : flist->backend;
+}
diff --git a/src/plugins/fts/fts-storage.h b/src/plugins/fts/fts-storage.h
new file mode 100644
index 0000000..ea28ed2
--- /dev/null
+++ b/src/plugins/fts/fts-storage.h
@@ -0,0 +1,70 @@
+#ifndef FTS_STORAGE_H
+#define FTS_STORAGE_H
+
+#include "mail-storage-private.h"
+#include "fts-api.h"
+
+enum fts_enforced {
+ FTS_ENFORCED_NO,
+ FTS_ENFORCED_YES,
+ FTS_ENFORCED_BODY,
+};
+
+struct fts_scores {
+ int refcount;
+ ARRAY_TYPE(fts_score_map) score_map;
+};
+
+struct fts_search_level {
+ ARRAY_TYPE(seq_range) definite_seqs, maybe_seqs;
+ buffer_t *args_matches;
+ ARRAY_TYPE(fts_score_map) score_map;
+};
+
+HASH_TABLE_DEFINE_TYPE(virtual_last_indexed, const char *, void *);
+
+struct fts_search_context {
+ union mail_search_module_context module_ctx;
+
+ struct fts_backend *backend;
+ struct mailbox *box;
+ struct mailbox_transaction_context *t;
+ struct mail_search_args *args;
+ enum fts_lookup_flags flags;
+ enum fts_enforced enforced;
+
+ pool_t result_pool;
+ ARRAY(struct fts_search_level) levels;
+ buffer_t *orig_matches;
+
+ uint32_t first_unindexed_seq;
+ uint32_t next_unindexed_seq;
+ HASH_TABLE_TYPE(virtual_last_indexed) last_indexed_virtual_uids;
+
+ /* final scores, combined from all levels */
+ struct fts_scores *scores;
+
+ struct fts_indexer_context *indexer_ctx;
+ struct fts_search_state *search_state;
+
+ bool virtual_mailbox:1;
+ bool fts_lookup_success:1;
+ bool indexing_timed_out:1;
+ bool virtual_seen_unindexed_gaps:1;
+};
+
+/* Figure out if we want to use full text search indexes and update
+ backends in fctx accordingly. */
+void fts_search_analyze(struct fts_search_context *fctx);
+/* Perform the actual index lookup and update definite_uids and maybe_uids. */
+void fts_search_lookup(struct fts_search_context *fctx);
+/* Returns FTS backend for the given mailbox (assumes it has one). */
+struct fts_backend *fts_mailbox_backend(struct mailbox *box);
+/* Returns FTS backend for the given mailbox list, or NULL if it has none. */
+struct fts_backend *fts_list_backend(struct mailbox_list *list);
+
+void fts_mail_allocated(struct mail *mail);
+void fts_mail_namespaces_added(struct mail_namespace *ns);
+void fts_mailbox_allocated(struct mailbox *box);
+void fts_mailbox_list_created(struct mailbox_list *list);
+#endif
diff --git a/src/plugins/fts/fts-user.c b/src/plugins/fts/fts-user.c
new file mode 100644
index 0000000..3c813cd
--- /dev/null
+++ b/src/plugins/fts/fts-user.c
@@ -0,0 +1,423 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "module-context.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "mailbox-match-plugin.h"
+#include "fts-language.h"
+#include "fts-filter.h"
+#include "fts-tokenizer.h"
+#include "fts-user.h"
+
+#define FTS_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, fts_user_module)
+
+struct fts_user {
+ union mail_user_module_context module_ctx;
+ int refcount;
+
+ struct fts_language_list *lang_list;
+ struct fts_user_language *data_lang;
+ ARRAY_TYPE(fts_user_language) languages, data_languages;
+
+ struct mailbox_match_plugin *autoindex_exclude;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(fts_user_module,
+ &mail_user_module_register);
+
+static const char *const *str_keyvalues_to_array(const char *str)
+{
+ const char *key, *value, *const *keyvalues;
+ ARRAY_TYPE(const_string) arr;
+ unsigned int i;
+
+ if (str == NULL)
+ return NULL;
+
+ t_array_init(&arr, 8);
+ keyvalues = t_strsplit_spaces(str, " ");
+ for (i = 0; keyvalues[i] != NULL; i++) {
+ value = strchr(keyvalues[i], '=');
+ if (value != NULL)
+ key = t_strdup_until(keyvalues[i], value++);
+ else {
+ key = keyvalues[i];
+ value = "";
+ }
+ array_push_back(&arr, &key);
+ array_push_back(&arr, &value);
+ }
+ array_append_zero(&arr);
+ return array_front(&arr);
+}
+
+static int
+fts_user_init_languages(struct mail_user *user, struct fts_user *fuser,
+ const char **error_r)
+{
+ const char *languages, *unknown;
+ const char *lang_config[3] = {NULL, NULL, NULL};
+
+ languages = mail_user_plugin_getenv(user, "fts_languages");
+ if (languages == NULL) {
+ *error_r = "fts_languages setting is missing";
+ return -1;
+ }
+
+ lang_config[1] = mail_user_plugin_getenv(user, "fts_language_config");
+ if (lang_config[1] != NULL)
+ lang_config[0] = "fts_language_config";
+ if (fts_language_list_init(lang_config, &fuser->lang_list, error_r) < 0)
+ return -1;
+
+ if (!fts_language_list_add_names(fuser->lang_list, languages, &unknown)) {
+ *error_r = t_strdup_printf(
+ "fts_languages: Unknown language '%s'", unknown);
+ return -1;
+ }
+ if (array_count(fts_language_list_get_all(fuser->lang_list)) == 0) {
+ *error_r = "fts_languages setting is empty";
+ return -1;
+ }
+ return 0;
+}
+
+static int
+fts_user_create_filters(struct mail_user *user, const struct fts_language *lang,
+ struct fts_filter **filter_r, const char **error_r)
+{
+ const struct fts_filter *filter_class;
+ struct fts_filter *filter = NULL, *parent = NULL;
+ const char *filters_key, *const *filters, *filter_set_name;
+ const char *str, *error, *set_key;
+ unsigned int i;
+ int ret = 0;
+
+ /* try to get the language-specific filters first */
+ filters_key = t_strconcat("fts_filters_", lang->name, NULL);
+ str = mail_user_plugin_getenv(user, filters_key);
+ if (str == NULL) {
+ /* fallback to global filters */
+ filters_key = "fts_filters";
+ str = mail_user_plugin_getenv(user, filters_key);
+ if (str == NULL) {
+ /* No filters */
+ *filter_r = NULL;
+ return 0;
+ }
+ }
+
+ filters = t_strsplit_spaces(str, " ");
+ for (i = 0; filters[i] != NULL; i++) {
+ filter_class = fts_filter_find(filters[i]);
+ if (filter_class == NULL) {
+ *error_r = t_strdup_printf("%s: Unknown filter '%s'",
+ filters_key, filters[i]);
+ ret = -1;
+ break;
+ }
+
+ /* try the language-specific setting first */
+ filter_set_name = t_str_replace(filters[i], '-', '_');
+ set_key = t_strdup_printf("fts_filter_%s_%s",
+ lang->name, filter_set_name);
+ str = mail_user_plugin_getenv(user, set_key);
+ if (str == NULL) {
+ set_key = t_strdup_printf("fts_filter_%s", filter_set_name);
+ str = mail_user_plugin_getenv(user, set_key);
+ }
+
+ if (fts_filter_create(filter_class, parent, lang,
+ str_keyvalues_to_array(str),
+ &filter, &error) < 0) {
+ *error_r = t_strdup_printf("%s: %s", set_key, error);
+ ret = -1;
+ break;
+ }
+ if (parent != NULL)
+ fts_filter_unref(&parent);
+ parent = filter;
+ }
+ if (ret < 0) {
+ if (parent != NULL)
+ fts_filter_unref(&parent);
+ return -1;
+ }
+ *filter_r = filter;
+ return 0;
+}
+
+static int
+fts_user_create_tokenizer(struct mail_user *user,
+ const struct fts_language *lang,
+ struct fts_tokenizer **tokenizer_r, bool search,
+ const char **error_r)
+{
+ const struct fts_tokenizer *tokenizer_class;
+ struct fts_tokenizer *tokenizer = NULL, *parent = NULL;
+ const char *tokenizers_key, *const *tokenizers, *tokenizer_set_name;
+ const char *str, *error, *set_key;
+ unsigned int i;
+ int ret = 0;
+
+ tokenizers_key = t_strconcat("fts_tokenizers_", lang->name, NULL);
+ str = mail_user_plugin_getenv(user, tokenizers_key);
+ if (str == NULL) {
+ str = mail_user_plugin_getenv(user, "fts_tokenizers");
+ if (str == NULL) {
+ *error_r = t_strdup_printf("%s or fts_tokenizers setting must exist", tokenizers_key);
+ return -1;
+ }
+ tokenizers_key = "fts_tokenizers";
+ }
+
+ tokenizers = t_strsplit_spaces(str, " ");
+
+ for (i = 0; tokenizers[i] != NULL; i++) {
+ tokenizer_class = fts_tokenizer_find(tokenizers[i]);
+ if (tokenizer_class == NULL) {
+ *error_r = t_strdup_printf("%s: Unknown tokenizer '%s'",
+ tokenizers_key, tokenizers[i]);
+ ret = -1;
+ break;
+ }
+
+ tokenizer_set_name = t_str_replace(tokenizers[i], '-', '_');
+ set_key = t_strdup_printf("fts_tokenizer_%s_%s", tokenizer_set_name, lang->name);
+ str = mail_user_plugin_getenv(user, set_key);
+ if (str == NULL) {
+ set_key = t_strdup_printf("fts_tokenizer_%s", tokenizer_set_name);
+ str = mail_user_plugin_getenv(user, set_key);
+ }
+
+ /* tell the tokenizers that we're tokenizing a search string
+ (instead of tokenizing indexed data) */
+ if (search)
+ str = t_strconcat("search=yes ", str, NULL);
+
+ if (fts_tokenizer_create(tokenizer_class, parent,
+ str_keyvalues_to_array(str),
+ &tokenizer, &error) < 0) {
+ *error_r = t_strdup_printf("%s: %s", set_key, error);
+ ret = -1;
+ break;
+ }
+ if (parent != NULL)
+ fts_tokenizer_unref(&parent);
+ parent = tokenizer;
+ }
+ if (ret < 0) {
+ if (parent != NULL)
+ fts_tokenizer_unref(&parent);
+ return -1;
+ }
+ *tokenizer_r = tokenizer;
+ return 0;
+}
+
+static int
+fts_user_language_init_tokenizers(struct mail_user *user,
+ struct fts_user_language *user_lang,
+ const char **error_r)
+{
+ if (fts_user_create_tokenizer(user, user_lang->lang,
+ &user_lang->index_tokenizer, FALSE,
+ error_r) < 0)
+ return -1;
+
+ if (fts_user_create_tokenizer(user, user_lang->lang,
+ &user_lang->search_tokenizer, TRUE,
+ error_r) < 0)
+ return -1;
+ return 0;
+}
+
+struct fts_user_language *
+fts_user_language_find(struct mail_user *user,
+ const struct fts_language *lang)
+{
+ struct fts_user_language *user_lang;
+ struct fts_user *fuser = FTS_USER_CONTEXT(user);
+
+ i_assert(fuser != NULL);
+ array_foreach_elem(&fuser->languages, user_lang) {
+ if (strcmp(user_lang->lang->name, lang->name) == 0)
+ return user_lang;
+ }
+ return NULL;
+}
+
+static int fts_user_language_create(struct mail_user *user,
+ struct fts_user *fuser,
+ const struct fts_language *lang,
+ const char **error_r)
+{
+ struct fts_user_language *user_lang;
+
+ user_lang = p_new(user->pool, struct fts_user_language, 1);
+ user_lang->lang = lang;
+ array_push_back(&fuser->languages, &user_lang);
+
+ if (fts_user_language_init_tokenizers(user, user_lang, error_r) < 0)
+ return -1;
+ if (fts_user_create_filters(user, lang, &user_lang->filter, error_r) < 0)
+ return -1;
+ return 0;
+}
+
+static int fts_user_languages_fill_all(struct mail_user *user,
+ struct fts_user *fuser,
+ const char **error_r)
+{
+ const struct fts_language *lang;
+
+ array_foreach_elem(fts_language_list_get_all(fuser->lang_list), lang) {
+ if (fts_user_language_create(user, fuser, lang, error_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+fts_user_init_data_language(struct mail_user *user, struct fts_user *fuser,
+ const char **error_r)
+{
+ struct fts_user_language *user_lang;
+ const char *error;
+
+ user_lang = p_new(user->pool, struct fts_user_language, 1);
+ user_lang->lang = &fts_language_data;
+
+ if (fts_user_language_init_tokenizers(user, user_lang, error_r) < 0)
+ return -1;
+
+ if (fts_filter_create(fts_filter_lowercase, NULL, user_lang->lang, NULL,
+ &user_lang->filter, &error) < 0)
+ i_unreached();
+ i_assert(user_lang->filter != NULL);
+
+ p_array_init(&fuser->data_languages, user->pool, 1);
+ array_push_back(&fuser->data_languages, &user_lang);
+ array_push_back(&fuser->languages, &user_lang);
+
+ fuser->data_lang = user_lang;
+ return 0;
+}
+
+struct fts_language_list *fts_user_get_language_list(struct mail_user *user)
+{
+ struct fts_user *fuser = FTS_USER_CONTEXT(user);
+
+ i_assert(fuser != NULL);
+ return fuser->lang_list;
+}
+
+const ARRAY_TYPE(fts_user_language) *
+fts_user_get_all_languages(struct mail_user *user)
+{
+ struct fts_user *fuser = FTS_USER_CONTEXT(user);
+
+ i_assert(fuser != NULL);
+ return &fuser->languages;
+}
+
+const ARRAY_TYPE(fts_user_language) *
+fts_user_get_data_languages(struct mail_user *user)
+{
+ struct fts_user *fuser = FTS_USER_CONTEXT(user);
+
+ i_assert(fuser != NULL);
+ return &fuser->data_languages;
+}
+
+struct fts_user_language *fts_user_get_data_lang(struct mail_user *user)
+{
+ struct fts_user *fuser = FTS_USER_CONTEXT(user);
+
+ i_assert(fuser != NULL);
+ return fuser->data_lang;
+}
+
+bool fts_user_autoindex_exclude(struct mailbox *box)
+{
+ struct fts_user *fuser = FTS_USER_CONTEXT(box->storage->user);
+
+ return mailbox_match_plugin_exclude(fuser->autoindex_exclude, box);
+}
+
+static void fts_user_language_free(struct fts_user_language *user_lang)
+{
+ if (user_lang->filter != NULL)
+ fts_filter_unref(&user_lang->filter);
+ if (user_lang->index_tokenizer != NULL)
+ fts_tokenizer_unref(&user_lang->index_tokenizer);
+ if (user_lang->search_tokenizer != NULL)
+ fts_tokenizer_unref(&user_lang->search_tokenizer);
+}
+
+static void fts_user_free(struct fts_user *fuser)
+{
+ struct fts_user_language *user_lang;
+
+ if (fuser->lang_list != NULL)
+ fts_language_list_deinit(&fuser->lang_list);
+
+ if (array_is_created(&fuser->languages)) {
+ array_foreach_elem(&fuser->languages, user_lang)
+ fts_user_language_free(user_lang);
+ }
+ mailbox_match_plugin_deinit(&fuser->autoindex_exclude);
+}
+
+static int
+fts_mail_user_init_libfts(struct mail_user *user, struct fts_user *fuser,
+ const char **error_r)
+{
+ p_array_init(&fuser->languages, user->pool, 4);
+
+ if (fts_user_init_languages(user, fuser, error_r) < 0 ||
+ fts_user_init_data_language(user, fuser, error_r) < 0)
+ return -1;
+ if (fts_user_languages_fill_all(user, fuser, error_r) < 0)
+ return -1;
+ return 0;
+}
+
+int fts_mail_user_init(struct mail_user *user, bool initialize_libfts,
+ const char **error_r)
+{
+ struct fts_user *fuser = FTS_USER_CONTEXT(user);
+
+ if (fuser != NULL) {
+ /* multiple fts plugins are loaded */
+ fuser->refcount++;
+ return 0;
+ }
+
+ fuser = p_new(user->pool, struct fts_user, 1);
+ fuser->refcount = 1;
+ if (initialize_libfts) {
+ if (fts_mail_user_init_libfts(user, fuser, error_r) < 0) {
+ fts_user_free(fuser);
+ return -1;
+ }
+ }
+ fuser->autoindex_exclude =
+ mailbox_match_plugin_init(user, "fts_autoindex_exclude");
+
+ MODULE_CONTEXT_SET(user, fts_user_module, fuser);
+ return 0;
+}
+
+void fts_mail_user_deinit(struct mail_user *user)
+{
+ struct fts_user *fuser = FTS_USER_CONTEXT(user);
+
+ if (fuser != NULL) {
+ i_assert(fuser->refcount > 0);
+ if (--fuser->refcount == 0)
+ fts_user_free(fuser);
+ }
+}
diff --git a/src/plugins/fts/fts-user.h b/src/plugins/fts/fts-user.h
new file mode 100644
index 0000000..043f4e1
--- /dev/null
+++ b/src/plugins/fts/fts-user.h
@@ -0,0 +1,27 @@
+#ifndef FTS_USER_H
+#define FTS_USER_H
+
+struct fts_user_language {
+ const struct fts_language *lang;
+ struct fts_filter *filter;
+ struct fts_tokenizer *index_tokenizer, *search_tokenizer;
+};
+ARRAY_DEFINE_TYPE(fts_user_language, struct fts_user_language *);
+
+struct fts_user_language *
+fts_user_language_find(struct mail_user *user,
+ const struct fts_language *lang);
+struct fts_language_list *fts_user_get_language_list(struct mail_user *user);
+const ARRAY_TYPE(fts_user_language) *
+fts_user_get_all_languages(struct mail_user *user);
+struct fts_user_language *fts_user_get_data_lang(struct mail_user *user);
+const ARRAY_TYPE(fts_user_language) *
+fts_user_get_data_languages(struct mail_user *user);
+
+bool fts_user_autoindex_exclude(struct mailbox *box);
+
+int fts_mail_user_init(struct mail_user *user, bool initialize_libfts,
+ const char **error_r);
+void fts_mail_user_deinit(struct mail_user *user);
+
+#endif
diff --git a/src/plugins/fts/xml2text.c b/src/plugins/fts/xml2text.c
new file mode 100644
index 0000000..f3c573c
--- /dev/null
+++ b/src/plugins/fts/xml2text.c
@@ -0,0 +1,44 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "message-parser.h"
+#include "fts-parser.h"
+
+#include <unistd.h>
+
+int main(void)
+{
+ struct fts_parser *parser;
+ unsigned char buf[IO_BLOCK_SIZE];
+ struct message_block block;
+ ssize_t ret;
+ struct fts_parser_context parser_context = {.content_type = "text/html"};
+
+ lib_init();
+
+ parser = fts_parser_html.try_init(&parser_context);
+ i_assert(parser != NULL);
+
+ i_zero(&block);
+ while ((ret = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
+ block.data = buf;
+ block.size = ret;
+ parser->v.more(parser, &block);
+ if (write(STDOUT_FILENO, block.data, block.size) < 0)
+ i_fatal("write(stdout) failed: %m");
+ }
+ if (ret < 0)
+ i_fatal("read(stdin) failed: %m");
+
+ for (;;) {
+ block.size = 0;
+ parser->v.more(parser, &block);
+ if (block.size == 0)
+ break;
+ if (write(STDOUT_FILENO, block.data, block.size) < 0)
+ i_fatal("write(stdout) failed: %m");
+ }
+
+ lib_deinit();
+ return 0;
+}
diff --git a/src/plugins/imap-acl/Makefile.am b/src/plugins/imap-acl/Makefile.am
new file mode 100644
index 0000000..c683974
--- /dev/null
+++ b/src/plugins/imap-acl/Makefile.am
@@ -0,0 +1,31 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/imapc \
+ -I$(top_srcdir)/src/imap \
+ -I$(top_srcdir)/src/plugins/acl \
+ -I$(top_srcdir)/src/lib-imap-client \
+ -I$(top_srcdir)/src/lib-ssl-iostream
+
+imap_moduledir = $(moduledir)
+
+NOPLUGIN_LDFLAGS =
+lib02_imap_acl_plugin_la_LDFLAGS = -module -avoid-version
+
+imap_module_LTLIBRARIES = \
+ lib02_imap_acl_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+lib02_imap_acl_plugin_la_LIBADD = \
+ ../acl/lib01_acl_plugin.la
+endif
+
+lib02_imap_acl_plugin_la_SOURCES = \
+ imap-acl-plugin.c
+
+noinst_HEADERS = \
+ imap-acl-plugin.h
diff --git a/src/plugins/imap-acl/Makefile.in b/src/plugins/imap-acl/Makefile.in
new file mode 100644
index 0000000..9f284af
--- /dev/null
+++ b/src/plugins/imap-acl/Makefile.in
@@ -0,0 +1,831 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/imap-acl
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(imap_moduledir)"
+LTLIBRARIES = $(imap_module_LTLIBRARIES)
+@DOVECOT_PLUGIN_DEPS_TRUE@lib02_imap_acl_plugin_la_DEPENDENCIES = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../acl/lib01_acl_plugin.la
+am_lib02_imap_acl_plugin_la_OBJECTS = imap-acl-plugin.lo
+lib02_imap_acl_plugin_la_OBJECTS = \
+ $(am_lib02_imap_acl_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib02_imap_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib02_imap_acl_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/imap-acl-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib02_imap_acl_plugin_la_SOURCES)
+DIST_SOURCES = $(lib02_imap_acl_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/imapc \
+ -I$(top_srcdir)/src/imap \
+ -I$(top_srcdir)/src/plugins/acl \
+ -I$(top_srcdir)/src/lib-imap-client \
+ -I$(top_srcdir)/src/lib-ssl-iostream
+
+imap_moduledir = $(moduledir)
+lib02_imap_acl_plugin_la_LDFLAGS = -module -avoid-version
+imap_module_LTLIBRARIES = \
+ lib02_imap_acl_plugin.la
+
+@DOVECOT_PLUGIN_DEPS_TRUE@lib02_imap_acl_plugin_la_LIBADD = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../acl/lib01_acl_plugin.la
+
+lib02_imap_acl_plugin_la_SOURCES = \
+ imap-acl-plugin.c
+
+noinst_HEADERS = \
+ imap-acl-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/imap-acl/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/imap-acl/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-imap_moduleLTLIBRARIES: $(imap_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(imap_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(imap_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(imap_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(imap_moduledir)"; \
+ }
+
+uninstall-imap_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(imap_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(imap_moduledir)/$$f"; \
+ done
+
+clean-imap_moduleLTLIBRARIES:
+ -test -z "$(imap_module_LTLIBRARIES)" || rm -f $(imap_module_LTLIBRARIES)
+ @list='$(imap_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib02_imap_acl_plugin.la: $(lib02_imap_acl_plugin_la_OBJECTS) $(lib02_imap_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib02_imap_acl_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib02_imap_acl_plugin_la_LINK) -rpath $(imap_moduledir) $(lib02_imap_acl_plugin_la_OBJECTS) $(lib02_imap_acl_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-acl-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(imap_moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imap-acl-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-imap_moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/imap-acl-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-imap_moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-imap_moduleLTLIBRARIES install-info install-info-am \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-imap_moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/imap-acl/imap-acl-plugin.c b/src/plugins/imap-acl/imap-acl-plugin.c
new file mode 100644
index 0000000..5d1c3be
--- /dev/null
+++ b/src/plugins/imap-acl/imap-acl-plugin.c
@@ -0,0 +1,1128 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "imap-quote.h"
+#include "imap-resp-code.h"
+#include "imap-commands.h"
+#include "imapc-client.h"
+#include "imapc-client-private.h"
+#include "imapc-settings.h"
+#include "imapc-storage.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+#include "mail-storage-private.h"
+#include "module-context.h"
+#include "acl-api.h"
+#include "acl-storage.h"
+#include "acl-plugin.h"
+#include "imap-acl-plugin.h"
+
+
+#define ERROR_NOT_ADMIN "["IMAP_RESP_CODE_NOPERM"] " \
+ "You lack administrator privileges on this mailbox."
+
+#define IMAP_ACL_ANYONE "anyone"
+#define IMAP_ACL_AUTHENTICATED "authenticated"
+#define IMAP_ACL_OWNER "owner"
+#define IMAP_ACL_GROUP_PREFIX "$"
+#define IMAP_ACL_GROUP_OVERRIDE_PREFIX "!$"
+#define IMAP_ACL_GLOBAL_PREFIX "#"
+
+#define IMAP_ACL_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, imap_acl_storage_module)
+#define IMAP_ACL_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, imap_acl_storage_module)
+
+struct imap_acl_letter_map {
+ char letter;
+ const char *name;
+};
+
+static const struct imap_acl_letter_map imap_acl_letter_map[] = {
+ { 'l', MAIL_ACL_LOOKUP },
+ { 'r', MAIL_ACL_READ },
+ { 'w', MAIL_ACL_WRITE },
+ { 's', MAIL_ACL_WRITE_SEEN },
+ { 't', MAIL_ACL_WRITE_DELETED },
+ { 'i', MAIL_ACL_INSERT },
+ { 'p', MAIL_ACL_POST },
+ { 'e', MAIL_ACL_EXPUNGE },
+ { 'k', MAIL_ACL_CREATE },
+ { 'x', MAIL_ACL_DELETE },
+ { 'a', MAIL_ACL_ADMIN },
+ { '\0', NULL }
+};
+
+struct imap_acl_storage {
+ union mail_storage_module_context module_ctx;
+ struct imapc_acl_context *iacl_ctx;
+};
+
+struct imap_acl_storage_module imap_acl_storage_module =
+ MODULE_CONTEXT_INIT(&mail_storage_module_register);
+
+const char *imap_acl_plugin_version = DOVECOT_ABI_VERSION;
+
+static struct module *imap_acl_module;
+static imap_client_created_func_t *next_hook_client_created;
+
+enum imap_acl_cmd {
+ IMAP_ACL_CMD_MYRIGHTS = 0,
+ IMAP_ACL_CMD_GETACL,
+ IMAP_ACL_CMD_SETACL,
+ IMAP_ACL_CMD_DELETEACL,
+};
+
+const char *imapc_acl_cmd_names[] = {
+ "MYRIGHTS", "GETACL", "SETACL", "DELETEACL"
+};
+
+struct imapc_acl_context {
+ struct imapc_client *client;
+ enum imap_acl_cmd proxy_cmd;
+ struct mail_storage *storage;
+ struct imapc_mailbox *expected_box;
+ string_t *reply;
+};
+
+static int
+acl_mailbox_open_as_admin(struct client_command_context *cmd,
+ struct mailbox *box, const char *name)
+{
+ enum mailbox_existence existence = MAILBOX_EXISTENCE_NONE;
+ int ret;
+
+ if (ACL_USER_CONTEXT(cmd->client->user) == NULL) {
+ client_send_command_error(cmd, "ACLs disabled.");
+ return 0;
+ }
+
+ if (mailbox_exists(box, TRUE, &existence) == 0 &&
+ existence == MAILBOX_EXISTENCE_SELECT) {
+ ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN);
+ if (ret > 0)
+ return ret;
+ }
+
+ /* mailbox doesn't exist / not an administrator. */
+ if (existence != MAILBOX_EXISTENCE_SELECT ||
+ acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP) <= 0) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO ["IMAP_RESP_CODE_NONEXISTENT"] "
+ MAIL_ERRSTR_MAILBOX_NOT_FOUND, name));
+ } else {
+ client_send_tagline(cmd, "NO "ERROR_NOT_ADMIN);
+ }
+ return 0;
+}
+
+static const struct imap_acl_letter_map *
+imap_acl_letter_map_find(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
+ if (strcmp(imap_acl_letter_map[i].name, name) == 0)
+ return &imap_acl_letter_map[i];
+ }
+ return NULL;
+}
+
+static void
+imap_acl_write_rights_list(string_t *dest, const char *const *rights)
+{
+ const struct imap_acl_letter_map *map;
+ unsigned int i;
+ size_t orig_len = str_len(dest);
+ bool append_c = FALSE, append_d = FALSE;
+
+ for (i = 0; rights[i] != NULL; i++) {
+ /* write only letters */
+ map = imap_acl_letter_map_find(rights[i]);
+ if (map != NULL) {
+ str_append_c(dest, map->letter);
+ if (map->letter == 'k' || map->letter == 'x')
+ append_c = TRUE;
+ if (map->letter == 't' || map->letter == 'e')
+ append_d = TRUE;
+ }
+ }
+ if (append_c)
+ str_append_c(dest, 'c');
+ if (append_d)
+ str_append_c(dest, 'd');
+ if (orig_len == str_len(dest))
+ str_append(dest, "\"\"");
+}
+
+static void
+imap_acl_write_right(string_t *dest, string_t *tmp,
+ const struct acl_rights *right, bool neg)
+{
+ const char *const *rights = neg ? right->neg_rights : right->rights;
+
+ str_truncate(tmp, 0);
+ if (neg) str_append_c(tmp,'-');
+ if (right->global)
+ str_append(tmp, IMAP_ACL_GLOBAL_PREFIX);
+ switch (right->id_type) {
+ case ACL_ID_ANYONE:
+ str_append(tmp, IMAP_ACL_ANYONE);
+ break;
+ case ACL_ID_AUTHENTICATED:
+ str_append(tmp, IMAP_ACL_AUTHENTICATED);
+ break;
+ case ACL_ID_OWNER:
+ str_append(tmp, IMAP_ACL_OWNER);
+ break;
+ case ACL_ID_USER:
+ str_append(tmp, right->identifier);
+ break;
+ case ACL_ID_GROUP:
+ str_append(tmp, IMAP_ACL_GROUP_PREFIX);
+ str_append(tmp, right->identifier);
+ break;
+ case ACL_ID_GROUP_OVERRIDE:
+ str_append(tmp, IMAP_ACL_GROUP_OVERRIDE_PREFIX);
+ str_append(tmp, right->identifier);
+ break;
+ case ACL_ID_TYPE_COUNT:
+ i_unreached();
+ }
+
+ imap_append_astring(dest, str_c(tmp));
+ str_append_c(dest, ' ');
+ imap_acl_write_rights_list(dest, rights);
+}
+
+static bool
+acl_rights_is_owner(struct acl_backend *backend,
+ const struct acl_rights *rights)
+{
+ switch (rights->id_type) {
+ case ACL_ID_OWNER:
+ return TRUE;
+ case ACL_ID_USER:
+ return acl_backend_user_name_equals(backend,
+ rights->identifier);
+ default:
+ return FALSE;
+ }
+}
+
+static bool have_positive_owner_rights(struct acl_backend *backend,
+ struct acl_object *aclobj)
+{
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+ bool ret = FALSE;
+
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ if (acl_rights_is_owner(backend, &rights)) {
+ if (rights.rights != NULL) {
+ ret = TRUE;
+ break;
+ }
+ }
+ }
+ (void)acl_object_list_deinit(&iter);
+ return ret;
+}
+
+static int
+imap_acl_write_aclobj(string_t *dest, struct acl_backend *backend,
+ struct acl_object *aclobj, bool convert_owner,
+ bool add_default)
+{
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+ string_t *tmp;
+ const char *username;
+ size_t orig_len = str_len(dest);
+ bool seen_owner = FALSE, seen_positive_owner = FALSE;
+ int ret;
+
+ username = acl_backend_get_acl_username(backend);
+ if (username == NULL)
+ convert_owner = FALSE;
+
+ tmp = t_str_new(128);
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ if (acl_rights_is_owner(backend, &rights)) {
+ if (rights.id_type == ACL_ID_OWNER && convert_owner) {
+ rights.id_type = ACL_ID_USER;
+ rights.identifier = username;
+ }
+ if (seen_owner && convert_owner) {
+ /* oops, we have both owner and user=myself.
+ can't do the conversion, so try again. */
+ str_truncate(dest, orig_len);
+ return imap_acl_write_aclobj(dest, backend,
+ aclobj, FALSE,
+ add_default);
+ }
+ seen_owner = TRUE;
+ if (rights.rights != NULL)
+ seen_positive_owner = TRUE;
+ }
+
+ if (rights.rights != NULL) {
+ str_append_c(dest, ' ');
+ imap_acl_write_right(dest, tmp, &rights, FALSE);
+ }
+ if (rights.neg_rights != NULL) {
+ str_append_c(dest, ' ');
+ imap_acl_write_right(dest, tmp, &rights, TRUE);
+ }
+ }
+ ret = acl_object_list_deinit(&iter);
+
+ if (!seen_positive_owner && username != NULL && add_default) {
+ /* no positive owner rights returned, write default ACLs */
+ i_zero(&rights);
+ if (!convert_owner) {
+ rights.id_type = ACL_ID_OWNER;
+ } else {
+ rights.id_type = ACL_ID_USER;
+ rights.identifier = username;
+ }
+ rights.rights = acl_object_get_default_rights(aclobj);
+ if (rights.rights != NULL) {
+ str_append_c(dest, ' ');
+ imap_acl_write_right(dest, tmp, &rights, FALSE);
+ }
+ }
+ return ret;
+}
+
+static const char *
+imapc_acl_get_mailbox_error(struct imapc_mailbox *mbox)
+{
+ enum mail_error err;
+ const char *error = mailbox_get_last_error(&mbox->box, &err);
+ const char *resp_code;
+ string_t *str = t_str_new(128);
+
+ if (imapc_mail_error_to_resp_text_code(err, &resp_code))
+ str_printfa(str, "[%s] ", resp_code);
+ str_append(str, error);
+
+ return str_c(str);
+}
+
+static void
+imapc_acl_myrights_untagged_cb(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ struct imap_acl_storage *iacl_storage =
+ IMAP_ACL_CONTEXT_REQUIRE(&client->_storage->storage);
+ struct imapc_acl_context *ctx = iacl_storage->iacl_ctx;
+ const char *value;
+
+ if (!imap_arg_get_astring(&reply->args[0], &value) ||
+ ctx->expected_box == NULL)
+ return;
+
+ /* Untagged reply was not meant for this mailbox */
+ if (!imapc_mailbox_name_equals(ctx->expected_box, value))
+ return;
+
+ /* copy rights from reply to string
+ <args[0](mailbox)> <args[1](rights)> */
+ if (imap_arg_get_astring(&reply->args[1], &value)) {
+ str_append(ctx->reply, value);
+ } else {
+ /* Rights could not been parsed mark this
+ failed and clear the prepared reply. */
+ str_truncate(ctx->reply, 0);
+ }
+ /* Just handle one untagged reply. */
+ ctx->expected_box = NULL;
+}
+
+static void
+imapc_acl_getacl_untagged_cb(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ struct imap_acl_storage *iacl_storage =
+ IMAP_ACL_CONTEXT_REQUIRE(&client->_storage->storage);
+ struct imapc_acl_context *ctx = iacl_storage->iacl_ctx;
+ const char *key, *value;
+ unsigned int i;
+
+ if (!imap_arg_get_astring(&reply->args[0], &value) ||
+ ctx->expected_box == NULL)
+ return;
+
+ /* Untagged reply was not meant for this mailbox */
+ if (!imapc_mailbox_name_equals(ctx->expected_box, value))
+ return;
+
+ /* Parse key:value pairs of user:right and append them
+ to the prepared reply. */
+ for (i = 1; reply->args[i].type != IMAP_ARG_EOL; i += 2) {
+ if (imap_arg_get_astring(&reply->args[i], &key) &&
+ imap_arg_get_astring(&reply->args[i+1], &value)) {
+ str_append(iacl_storage->iacl_ctx->reply, key);
+ str_append_c(iacl_storage->iacl_ctx->reply, ' ');
+ str_append(iacl_storage->iacl_ctx->reply, value);
+ str_append_c(iacl_storage->iacl_ctx->reply, ' ');
+ } else {
+ /* Rights could not been parsed clear prepared reply. */
+ str_truncate(ctx->reply, 0);
+ break;
+ }
+ }
+ /* Just handle one untagged reply. */
+ ctx->expected_box = NULL;
+}
+
+static struct imapc_acl_context *
+imap_acl_cmd_context_alloc(struct imapc_mailbox *mbox)
+{
+ struct imapc_acl_context *iacl_ctx =
+ p_new(mbox->box.storage->pool, struct imapc_acl_context, 1);
+ iacl_ctx->reply = str_new(mbox->box.storage->pool, 128);
+ return iacl_ctx;
+}
+
+static void imap_acl_cmd_context_init(struct imapc_acl_context *iacl_ctx,
+ struct imapc_mailbox *mbox,
+ enum imap_acl_cmd proxy_cmd)
+{
+ iacl_ctx->client = mbox->storage->client->client;
+ iacl_ctx->proxy_cmd = proxy_cmd;
+ iacl_ctx->expected_box = mbox;
+ str_truncate(iacl_ctx->reply, 0);
+}
+
+static struct imapc_acl_context *
+imap_acl_cmd_context_register(struct imapc_mailbox *mbox, enum imap_acl_cmd proxy_cmd)
+{
+ struct mailbox *box = &mbox->box;
+ struct imap_acl_storage *iacl_storage = IMAP_ACL_CONTEXT(box->storage);
+
+ if (iacl_storage == NULL) {
+ iacl_storage = p_new(box->storage->pool, struct imap_acl_storage, 1);
+ MODULE_CONTEXT_SET(box->storage, imap_acl_storage_module, iacl_storage);
+ iacl_storage->iacl_ctx = imap_acl_cmd_context_alloc(mbox);
+ }
+
+ imap_acl_cmd_context_init(iacl_storage->iacl_ctx, mbox, proxy_cmd);
+
+ return iacl_storage->iacl_ctx;
+}
+
+static const char *imap_acl_get_mailbox_name(const struct mail_namespace *ns,
+ const char *mailbox)
+{
+ /* Strip namespace prefix from mailbox name or append "INBOX" if
+ mailbox is "" and mailbox is in shared namespace. */
+
+ if (ns->prefix_len == 0)
+ return mailbox;
+
+ i_assert(ns->prefix_len >= 1);
+
+ if ((mailbox[ns->prefix_len-1] == '\0' ||
+ mailbox[ns->prefix_len] == '\0') &&
+ strncmp(mailbox, ns->prefix, ns->prefix_len-1) == 0 &&
+ ns->type == MAIL_NAMESPACE_TYPE_SHARED) {
+ /* Given mailbox name does not contain an actual mailbox name
+ but just the namespace prefix so default to "INBOX". */
+ return "INBOX";
+ }
+
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
+ strcasecmp(mailbox, "INBOX") == 0) {
+ /* For user INBOX always use INBOX and ignore namespace
+ prefix. */
+ return "INBOX";
+ }
+
+ i_assert(strncmp(mailbox, ns->prefix, ns->prefix_len-1) == 0);
+ return mailbox+ns->prefix_len;
+}
+
+static const char *
+imapc_acl_prepare_cmd(string_t *reply_r, const char *mailbox,
+ const struct mail_namespace *ns, const char *cmd_args,
+ const enum imap_acl_cmd proxy_cmd)
+{
+ string_t *proxy_cmd_str = t_str_new(128);
+ /* Prepare proxy_cmd and untagged replies */
+ switch (proxy_cmd) {
+ case IMAP_ACL_CMD_MYRIGHTS:
+ /* Prepare client untagged reply. */
+ str_append(reply_r, "* MYRIGHTS ");
+ imap_append_astring(reply_r, mailbox);
+ str_append_c(reply_r, ' ');
+
+ str_append(proxy_cmd_str, "MYRIGHTS ");
+ /* Strip namespace prefix. */
+ imap_append_astring(proxy_cmd_str,
+ imap_acl_get_mailbox_name(ns, mailbox));
+ break;
+ case IMAP_ACL_CMD_GETACL:
+ /* Prepare client untagged reply. */
+ str_append(reply_r, "* ACL ");
+ imap_append_astring(reply_r, mailbox);
+ str_append_c(reply_r, ' ');
+
+ str_append(proxy_cmd_str, "GETACL ");
+ imap_append_astring(proxy_cmd_str,
+ imap_acl_get_mailbox_name(ns, mailbox));
+ break;
+ case IMAP_ACL_CMD_SETACL:
+ /* No contents in untagged replies for SETACL */
+ str_append(proxy_cmd_str, "SETACL ");
+ imap_append_astring(proxy_cmd_str,
+ imap_acl_get_mailbox_name(ns, mailbox));
+
+ str_append_c(proxy_cmd_str, ' ');
+ str_append(proxy_cmd_str, cmd_args);
+ break;
+ case IMAP_ACL_CMD_DELETEACL:
+ /* No contents in untagged replies for DELETEACL */
+ str_append(proxy_cmd_str, "DELETEACL ");
+ imap_append_astring(proxy_cmd_str,
+ imap_acl_get_mailbox_name(ns, mailbox));
+
+ str_append_c(proxy_cmd_str, ' ');
+ str_append(proxy_cmd_str, cmd_args);
+ break;
+ default:
+ i_unreached();
+ }
+ return str_c(proxy_cmd_str);
+}
+
+static struct imapc_command *
+imapc_acl_simple_context_init(struct imapc_simple_context *ctx,
+ struct imapc_mailbox *mbox)
+{
+ imapc_simple_context_init(ctx, mbox->storage->client);
+ return imapc_client_cmd(mbox->storage->client->client,
+ imapc_simple_callback, ctx);
+}
+
+static void imapc_acl_send_client_reply(struct imapc_acl_context *iacl_ctx,
+ struct client_command_context *orig_cmd,
+ const char *success_tagged_reply)
+{
+ if (str_len(iacl_ctx->reply) == 0)
+ client_send_tagline(orig_cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+ else {
+ client_send_line(orig_cmd->client, str_c(iacl_ctx->reply));
+ client_send_tagline(orig_cmd, success_tagged_reply);
+ }
+}
+
+static bool imap_acl_proxy_cmd(struct mailbox *box,
+ const char *mailbox,
+ const char *cmd_args,
+ const struct mail_namespace *ns,
+ struct client_command_context *orig_cmd,
+ const enum imap_acl_cmd proxy_cmd)
+{
+ struct imapc_acl_context *iacl_ctx;
+ struct imapc_simple_context ctx;
+ struct imapc_command *imapc_cmd;
+ const char *proxy_cmd_str;
+
+ if (strcmp(box->storage->name, "imapc") != 0) {
+ /* Storage is not "imapc". */
+ return FALSE;
+ }
+
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+ if (!IMAPC_HAS_FEATURE(mbox->storage, IMAPC_FEATURE_ACL)) {
+ /* Storage is "imapc" but no proxying of ACL commands should
+ be done. */
+ return FALSE;
+ }
+
+ iacl_ctx = imap_acl_cmd_context_register(mbox, proxy_cmd);
+
+ /* Register callbacks for untagged replies */
+ imapc_storage_client_register_untagged(mbox->storage->client, "ACL",
+ imapc_acl_getacl_untagged_cb);
+ imapc_storage_client_register_untagged(mbox->storage->client, "MYRIGHTS",
+ imapc_acl_myrights_untagged_cb);
+
+ imapc_cmd = imapc_acl_simple_context_init(&ctx, mbox);
+
+ /* Prepare untagged replies and return proxy_cmd */
+ proxy_cmd_str = imapc_acl_prepare_cmd(iacl_ctx->reply, mailbox,
+ ns, cmd_args, proxy_cmd);
+
+ imapc_command_send(imapc_cmd, proxy_cmd_str);
+ imapc_simple_run(&ctx, &imapc_cmd);
+
+ if (ctx.ret != 0) {
+ /* If the remote replied BAD or NO send NO. */
+ client_send_tagline(orig_cmd,
+ t_strdup_printf("NO %s", imapc_acl_get_mailbox_error(mbox)));
+ } else {
+ /* Command was OK on remote backend, send untagged reply from
+ ctx.str and tagged reply. */
+ switch (iacl_ctx->proxy_cmd) {
+ case IMAP_ACL_CMD_DELETEACL:
+ client_send_tagline(orig_cmd, "OK Deleteacl complete.");
+ break;
+ case IMAP_ACL_CMD_GETACL:
+ imapc_acl_send_client_reply(iacl_ctx,
+ orig_cmd,
+ "OK Getacl complete.");
+ break;
+ case IMAP_ACL_CMD_MYRIGHTS:
+ imapc_acl_send_client_reply(iacl_ctx,
+ orig_cmd,
+ "OK Myrights complete.");
+ break;
+ case IMAP_ACL_CMD_SETACL:
+ client_send_tagline(orig_cmd, "OK Setacl complete.");
+ break;
+ default:
+ i_unreached();
+ }
+ }
+
+ /* Unregister callbacks for untagged replies */
+ imapc_storage_client_unregister_untagged(mbox->storage->client, "MYRIGHTS");
+ imapc_storage_client_unregister_untagged(mbox->storage->client, "ACL");
+ return TRUE;
+}
+
+static void imap_acl_cmd_getacl(struct mailbox *box, struct mail_namespace *ns,
+ const char *mailbox,
+ struct client_command_context *cmd)
+{
+ struct acl_backend *backend;
+ string_t *str;
+ int ret;
+
+ if (acl_mailbox_open_as_admin(cmd, box, mailbox) <= 0)
+ return;
+
+ backend = acl_mailbox_list_get_backend(ns->list);
+
+ str = t_str_new(128);
+ str_append(str, "* ACL ");
+ imap_append_astring(str, mailbox);
+
+ ret = imap_acl_write_aclobj(str, backend,
+ acl_mailbox_get_aclobj(box), TRUE,
+ ns->type == MAIL_NAMESPACE_TYPE_PRIVATE);
+ if (ret > -1) {
+ client_send_line(cmd->client, str_c(str));
+ client_send_tagline(cmd, "OK Getacl completed.");
+ } else {
+ client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+ }
+}
+
+static bool cmd_getacl(struct client_command_context *cmd)
+{
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ const char *mailbox, *orig_mailbox;
+
+ if (!client_read_string_args(cmd, 1, &mailbox))
+ return FALSE;
+ orig_mailbox = mailbox;
+
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ box = mailbox_alloc(ns->list, mailbox,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
+ /* If the location is remote and imapc_feature acl is enabled, proxy the
+ command to the configured imapc location. */
+ if (!imap_acl_proxy_cmd(box, orig_mailbox, NULL, ns, cmd, IMAP_ACL_CMD_GETACL))
+ imap_acl_cmd_getacl(box, ns, orig_mailbox, cmd);
+ mailbox_free(&box);
+ return TRUE;
+}
+
+static void imap_acl_cmd_myrights(struct mailbox *box, const char *mailbox,
+ struct client_command_context *cmd)
+{
+ const char *const *rights;
+ string_t *str = t_str_new(128);
+
+ if (acl_object_get_my_rights(acl_mailbox_get_aclobj(box),
+ pool_datastack_create(), &rights) < 0) {
+ client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+ return;
+ }
+
+ /* Post right alone doesn't give permissions to see if the mailbox
+ exists or not. Only mail deliveries care about that. */
+ if (*rights == NULL ||
+ (strcmp(*rights, MAIL_ACL_POST) == 0 && rights[1] == NULL)) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO ["IMAP_RESP_CODE_NONEXISTENT"] "
+ MAIL_ERRSTR_MAILBOX_NOT_FOUND, mailbox));
+ return;
+ }
+
+ str_append(str, "* MYRIGHTS ");
+ imap_append_astring(str, mailbox);
+ str_append_c(str, ' ');
+ imap_acl_write_rights_list(str, rights);
+
+ client_send_line(cmd->client, str_c(str));
+ client_send_tagline(cmd, "OK Myrights completed.");
+}
+
+static bool cmd_myrights(struct client_command_context *cmd)
+{
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ const char *mailbox, *orig_mailbox;
+
+ if (!client_read_string_args(cmd, 1, &mailbox))
+ return FALSE;
+ orig_mailbox = mailbox;
+
+ if (ACL_USER_CONTEXT(cmd->client->user) == NULL) {
+ client_send_command_error(cmd, "ACLs disabled.");
+ return TRUE;
+ }
+
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ box = mailbox_alloc(ns->list, mailbox,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
+
+ /* If the location is remote and imapc_feature acl is enabled, proxy the
+ command to the configured imapc location. */
+ if (!imap_acl_proxy_cmd(box, orig_mailbox, NULL, ns,
+ cmd, IMAP_ACL_CMD_MYRIGHTS))
+ imap_acl_cmd_myrights(box, orig_mailbox, cmd);
+ mailbox_free(&box);
+ return TRUE;
+}
+
+static bool cmd_listrights(struct client_command_context *cmd)
+{
+ struct mailbox *box;
+ struct mail_namespace *ns;
+ const char *mailbox, *orig_mailbox, *identifier;
+ string_t *str;
+
+ if (!client_read_string_args(cmd, 2, &mailbox, &identifier))
+ return FALSE;
+ orig_mailbox = mailbox;
+
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ box = mailbox_alloc(ns->list, mailbox,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
+
+ str = t_str_new(128);
+ str_append(str, "* LISTRIGHTS ");
+ imap_append_astring(str, orig_mailbox);
+ str_append_c(str, ' ');
+ imap_append_astring(str, identifier);
+ str_append_c(str, ' ');
+ str_append(str, "\"\" l r w s t p i e k x a c d");
+
+ client_send_line(cmd->client, str_c(str));
+ client_send_tagline(cmd, "OK Listrights completed.");
+ mailbox_free(&box);
+ return TRUE;
+}
+
+static int
+imap_acl_letters_parse(const char *letters, const char *const **rights_r,
+ const char **client_error_r)
+{
+ static const char *acl_k = MAIL_ACL_CREATE;
+ static const char *acl_x = MAIL_ACL_DELETE;
+ static const char *acl_e = MAIL_ACL_EXPUNGE;
+ static const char *acl_t = MAIL_ACL_WRITE_DELETED;
+ ARRAY_TYPE(const_string) rights;
+ unsigned int i;
+
+ t_array_init(&rights, 64);
+ for (; *letters != '\0'; letters++) {
+ for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
+ if (imap_acl_letter_map[i].letter == *letters) {
+ array_push_back(&rights,
+ &imap_acl_letter_map[i].name);
+ break;
+ }
+ }
+ if (imap_acl_letter_map[i].name == NULL) {
+ /* Handling of obsolete rights as virtual
+ rights according to RFC 4314 */
+ switch (*letters) {
+ case 'c':
+ array_push_back(&rights, &acl_k);
+ array_push_back(&rights, &acl_x);
+ break;
+ case 'd':
+ array_push_back(&rights, &acl_e);
+ array_push_back(&rights, &acl_t);
+ break;
+ default:
+ *client_error_r = t_strdup_printf(
+ "Invalid ACL right: %c", *letters);
+ return -1;
+ }
+ }
+ }
+ array_append_zero(&rights);
+ *rights_r = array_front(&rights);
+ return 0;
+}
+
+static bool acl_anyone_allow(struct mail_user *user)
+{
+ const char *env;
+
+ env = mail_user_plugin_getenv(user, "acl_anyone");
+ return env != NULL && strcmp(env, "allow") == 0;
+}
+
+static int
+imap_acl_identifier_parse(struct client_command_context *cmd,
+ const char *id, struct acl_rights *rights,
+ bool check_anyone, const char **client_error_r)
+{
+ struct mail_user *user = cmd->client->user;
+
+ if (str_begins(id, IMAP_ACL_GLOBAL_PREFIX)) {
+ *client_error_r = t_strdup_printf("Global ACLs can't be modified: %s",
+ id);
+ return -1;
+ }
+
+ if (strcmp(id, IMAP_ACL_ANYONE) == 0) {
+ if (check_anyone && !acl_anyone_allow(user)) {
+ *client_error_r = "'anyone' identifier is disallowed";
+ return -1;
+ }
+ rights->id_type = ACL_ID_ANYONE;
+ } else if (strcmp(id, IMAP_ACL_AUTHENTICATED) == 0) {
+ if (check_anyone && !acl_anyone_allow(user)) {
+ *client_error_r = "'authenticated' identifier is disallowed";
+ return -1;
+ }
+ rights->id_type = ACL_ID_AUTHENTICATED;
+ } else if (strcmp(id, IMAP_ACL_OWNER) == 0)
+ rights->id_type = ACL_ID_OWNER;
+ else if (str_begins(id, IMAP_ACL_GROUP_PREFIX)) {
+ rights->id_type = ACL_ID_GROUP;
+ rights->identifier = id + strlen(IMAP_ACL_GROUP_PREFIX);
+ } else if (str_begins(id, IMAP_ACL_GROUP_OVERRIDE_PREFIX)) {
+ rights->id_type = ACL_ID_GROUP_OVERRIDE;
+ rights->identifier = id +
+ strlen(IMAP_ACL_GROUP_OVERRIDE_PREFIX);
+ } else {
+ rights->id_type = ACL_ID_USER;
+ rights->identifier = id;
+ }
+ return 0;
+}
+
+static void imap_acl_update_ensure_keep_admins(struct acl_backend *backend,
+ struct acl_object *aclobj,
+ struct acl_rights_update *update)
+{
+ static const char *acl_admin = MAIL_ACL_ADMIN;
+ const char *const *rights = update->rights.rights;
+ const char *const *default_rights;
+ ARRAY_TYPE(const_string) new_rights;
+ unsigned int i;
+
+ t_array_init(&new_rights, 64);
+ for (i = 0; rights[i] != NULL; i++) {
+ if (strcmp(rights[i], MAIL_ACL_ADMIN) == 0)
+ break;
+ array_push_back(&new_rights, &rights[i]);
+ }
+
+ switch (update->modify_mode) {
+ case ACL_MODIFY_MODE_ADD:
+ if (have_positive_owner_rights(backend, aclobj))
+ return;
+
+ /* adding initial rights for a user. we need to add
+ the defaults also. don't worry about duplicates. */
+ for (; rights[i] != NULL; i++)
+ array_push_back(&new_rights, &rights[i]);
+ default_rights = acl_object_get_default_rights(aclobj);
+ for (i = 0; default_rights[i] != NULL; i++)
+ array_push_back(&new_rights, &default_rights[i]);
+ break;
+ case ACL_MODIFY_MODE_REMOVE:
+ if (rights[i] == NULL)
+ return;
+
+ /* skip over the ADMIN removal and add the rest */
+ for (i++; rights[i] != NULL; i++)
+ array_push_back(&new_rights, &rights[i]);
+ break;
+ case ACL_MODIFY_MODE_REPLACE:
+ if (rights[i] != NULL)
+ return;
+
+ /* add the missing ADMIN right */
+ array_push_back(&new_rights, &acl_admin);
+ break;
+ default:
+ return;
+ }
+ array_append_zero(&new_rights);
+ update->rights.rights = array_front(&new_rights);
+}
+
+static int
+cmd_acl_mailbox_update(struct mailbox *box,
+ const struct acl_rights_update *update,
+ const char **client_error_r)
+{
+ struct mailbox_transaction_context *t;
+ int ret;
+
+ if (mailbox_open(box) < 0) {
+ *client_error_r = mailbox_get_last_error(box, NULL);
+ return -1;
+ }
+
+ t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL,
+ __func__);
+ ret = acl_mailbox_update_acl(t, update);
+ if (mailbox_transaction_commit(&t) < 0)
+ ret = -1;
+ *client_error_r = MAIL_ERRSTR_CRITICAL_MSG;
+ return ret;
+}
+
+static void imap_acl_cmd_setacl(struct mailbox *box, struct mail_namespace *ns,
+ const char *mailbox, const char *identifier,
+ const char *rights,
+ struct client_command_context *cmd)
+{
+ struct acl_backend *backend;
+ struct acl_object *aclobj;
+ struct acl_rights_update update;
+ struct acl_rights *r;
+ const char *client_error;
+ bool negative = FALSE;
+
+ i_zero(&update);
+ if (*identifier == '-') {
+ negative = TRUE;
+ identifier++;
+ }
+
+ switch (*rights) {
+ case '-':
+ update.modify_mode = ACL_MODIFY_MODE_REMOVE;
+ rights++;
+ break;
+ case '+':
+ update.modify_mode = ACL_MODIFY_MODE_ADD;
+ rights++;
+ break;
+ default:
+ update.modify_mode = ACL_MODIFY_MODE_REPLACE;
+ break;
+ }
+
+ if (imap_acl_identifier_parse(cmd, identifier, &update.rights,
+ TRUE, &client_error) < 0) {
+ client_send_command_error(cmd, client_error);
+ return;
+ }
+ if (imap_acl_letters_parse(rights, &update.rights.rights, &client_error) < 0) {
+ client_send_command_error(cmd, client_error);
+ return;
+ }
+ r = &update.rights;
+
+ if (acl_mailbox_open_as_admin(cmd, box, mailbox) <= 0)
+ return;
+
+ backend = acl_mailbox_list_get_backend(ns->list);
+ if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC &&
+ r->id_type == ACL_ID_OWNER) {
+ client_send_tagline(cmd, "NO Public namespaces have no owner");
+ return;
+ }
+
+ aclobj = acl_mailbox_get_aclobj(box);
+ if (negative) {
+ update.neg_modify_mode = update.modify_mode;
+ update.modify_mode = ACL_MODIFY_MODE_REMOVE;
+ update.rights.neg_rights = update.rights.rights;
+ update.rights.rights = NULL;
+ } else if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE &&
+ r->rights != NULL &&
+ ((r->id_type == ACL_ID_USER &&
+ acl_backend_user_name_equals(backend, r->identifier)) ||
+ (r->id_type == ACL_ID_OWNER &&
+ strcmp(acl_backend_get_acl_username(backend),
+ ns->user->username) == 0))) {
+ /* make sure client doesn't (accidentally) remove admin
+ privileges from its own mailboxes */
+ imap_acl_update_ensure_keep_admins(backend, aclobj, &update);
+ }
+
+ if (cmd_acl_mailbox_update(box, &update, &client_error) < 0)
+ client_send_tagline(cmd, t_strdup_printf("NO %s", client_error));
+ else
+ client_send_tagline(cmd, "OK Setacl complete.");
+}
+
+static bool cmd_setacl(struct client_command_context *cmd)
+{
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ const char *mailbox, *orig_mailbox, *identifier, *rights;
+ string_t *proxy_cmd_args = t_str_new(64);
+
+ if (!client_read_string_args(cmd, 3, &mailbox, &identifier, &rights))
+ return FALSE;
+ orig_mailbox = mailbox;
+
+ if (*identifier == '\0') {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ /* Keep original identifer for proxy_cmd_args */
+ imap_append_astring(proxy_cmd_args, identifier);
+ str_append_c(proxy_cmd_args, ' ');
+ /* Append original rights for proxy_cmd_args */
+ imap_append_astring(proxy_cmd_args, rights);
+
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ box = mailbox_alloc(ns->list, mailbox,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
+ /* If the location is remote and imapc_feature acl is enabled, proxy the
+ command to the configured imapc location. */
+ if (!imap_acl_proxy_cmd(box, orig_mailbox, str_c(proxy_cmd_args),
+ ns, cmd, IMAP_ACL_CMD_SETACL))
+ imap_acl_cmd_setacl(box, ns, orig_mailbox, identifier, rights, cmd);
+ mailbox_free(&box);
+ return TRUE;
+}
+
+static void imap_acl_cmd_deleteacl(struct mailbox *box, const char *mailbox,
+ const char *identifier,
+ struct client_command_context *cmd)
+{
+ struct acl_rights_update update;
+ const char *client_error;
+
+ i_zero(&update);
+ if (*identifier != '-')
+ update.modify_mode = ACL_MODIFY_MODE_CLEAR;
+ else {
+ update.neg_modify_mode = ACL_MODIFY_MODE_CLEAR;
+ identifier++;
+ }
+
+ if (imap_acl_identifier_parse(cmd, identifier, &update.rights,
+ FALSE, &client_error) < 0) {
+ client_send_command_error(cmd, client_error);
+ return;
+ }
+
+ if (acl_mailbox_open_as_admin(cmd, box, mailbox) <= 0)
+ return;
+
+ if (cmd_acl_mailbox_update(box, &update, &client_error) < 0)
+ client_send_tagline(cmd, t_strdup_printf("NO %s", client_error));
+ else
+ client_send_tagline(cmd, "OK Deleteacl complete.");
+}
+
+static bool cmd_deleteacl(struct client_command_context *cmd)
+{
+ struct mailbox *box;
+ struct mail_namespace *ns;
+ const char *mailbox, *orig_mailbox, *identifier;
+ string_t *proxy_cmd_args = t_str_new(64);
+
+ if (!client_read_string_args(cmd, 2, &mailbox, &identifier))
+ return FALSE;
+ orig_mailbox = mailbox;
+
+ if (*identifier == '\0') {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ /* Escaped identifer for proxy_cmd_args */
+ imap_append_astring(proxy_cmd_args, identifier);
+
+ box = mailbox_alloc(ns->list, mailbox,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
+
+ /* If the location is remote and imapc_feature acl is enabled, proxy the
+ command to the configured imapc location. */
+ if (!imap_acl_proxy_cmd(box, orig_mailbox, str_c(proxy_cmd_args),
+ ns, cmd, IMAP_ACL_CMD_DELETEACL))
+ imap_acl_cmd_deleteacl(box, orig_mailbox, identifier, cmd);
+ mailbox_free(&box);
+ return TRUE;
+}
+
+static void imap_acl_client_created(struct client **client)
+{
+ if (mail_user_is_plugin_loaded((*client)->user, imap_acl_module)) {
+ client_add_capability(*client, "ACL");
+ client_add_capability(*client, "RIGHTS=texk");
+ }
+
+ if (next_hook_client_created != NULL)
+ next_hook_client_created(client);
+}
+
+void imap_acl_plugin_init(struct module *module)
+{
+ command_register("LISTRIGHTS", cmd_listrights, 0);
+ command_register("GETACL", cmd_getacl, 0);
+ command_register("MYRIGHTS", cmd_myrights, 0);
+ command_register("SETACL", cmd_setacl, 0);
+ command_register("DELETEACL", cmd_deleteacl, 0);
+
+ imap_acl_module = module;
+ next_hook_client_created =
+ imap_client_created_hook_set(imap_acl_client_created);
+}
+
+void imap_acl_plugin_deinit(void)
+{
+ command_unregister("GETACL");
+ command_unregister("MYRIGHTS");
+ command_unregister("SETACL");
+ command_unregister("DELETEACL");
+ command_unregister("LISTRIGHTS");
+
+ imap_client_created_hook_set(next_hook_client_created);
+}
+
+const char *imap_acl_plugin_dependencies[] = { "acl", NULL };
+const char imap_acl_plugin_binary_dependency[] = "imap";
diff --git a/src/plugins/imap-acl/imap-acl-plugin.h b/src/plugins/imap-acl/imap-acl-plugin.h
new file mode 100644
index 0000000..117e8fb
--- /dev/null
+++ b/src/plugins/imap-acl/imap-acl-plugin.h
@@ -0,0 +1,12 @@
+#ifndef IMAP_ACL_PLUGIN_H
+#define IMAP_ACL_PLUGIN_H
+
+extern const char *imap_acl_plugin_dependencies[];
+extern const char imap_acl_plugin_binary_dependency[];
+
+extern MODULE_CONTEXT_DEFINE(imap_acl_storage_module, &mail_storage_module_register);
+
+void imap_acl_plugin_init(struct module *module);
+void imap_acl_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/imap-old-stats/Makefile.am b/src/plugins/imap-old-stats/Makefile.am
new file mode 100644
index 0000000..ed07ed5
--- /dev/null
+++ b/src/plugins/imap-old-stats/Makefile.am
@@ -0,0 +1,28 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-old-stats \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/imap \
+ -I$(top_srcdir)/src/plugins/old-stats
+
+imap_moduledir = $(moduledir)
+
+NOPLUGIN_LDFLAGS =
+lib95_imap_old_stats_plugin_la_LDFLAGS = -module -avoid-version
+
+imap_module_LTLIBRARIES = \
+ lib95_imap_old_stats_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+lib95_imap_old_stats_plugin_la_LIBADD = \
+ ../old-stats/lib90_old_stats_plugin.la
+endif
+
+lib95_imap_old_stats_plugin_la_SOURCES = \
+ imap-stats-plugin.c
+
+noinst_HEADERS = \
+ imap-stats-plugin.h
diff --git a/src/plugins/imap-old-stats/Makefile.in b/src/plugins/imap-old-stats/Makefile.in
new file mode 100644
index 0000000..458b4a8
--- /dev/null
+++ b/src/plugins/imap-old-stats/Makefile.in
@@ -0,0 +1,827 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/imap-old-stats
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(imap_moduledir)"
+LTLIBRARIES = $(imap_module_LTLIBRARIES)
+@DOVECOT_PLUGIN_DEPS_TRUE@lib95_imap_old_stats_plugin_la_DEPENDENCIES = ../old-stats/lib90_old_stats_plugin.la
+am_lib95_imap_old_stats_plugin_la_OBJECTS = imap-stats-plugin.lo
+lib95_imap_old_stats_plugin_la_OBJECTS = \
+ $(am_lib95_imap_old_stats_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib95_imap_old_stats_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib95_imap_old_stats_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/imap-stats-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib95_imap_old_stats_plugin_la_SOURCES)
+DIST_SOURCES = $(lib95_imap_old_stats_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-old-stats \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/imap \
+ -I$(top_srcdir)/src/plugins/old-stats
+
+imap_moduledir = $(moduledir)
+lib95_imap_old_stats_plugin_la_LDFLAGS = -module -avoid-version
+imap_module_LTLIBRARIES = \
+ lib95_imap_old_stats_plugin.la
+
+@DOVECOT_PLUGIN_DEPS_TRUE@lib95_imap_old_stats_plugin_la_LIBADD = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../old-stats/lib90_old_stats_plugin.la
+
+lib95_imap_old_stats_plugin_la_SOURCES = \
+ imap-stats-plugin.c
+
+noinst_HEADERS = \
+ imap-stats-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/imap-old-stats/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/imap-old-stats/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-imap_moduleLTLIBRARIES: $(imap_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(imap_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(imap_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(imap_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(imap_moduledir)"; \
+ }
+
+uninstall-imap_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(imap_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(imap_moduledir)/$$f"; \
+ done
+
+clean-imap_moduleLTLIBRARIES:
+ -test -z "$(imap_module_LTLIBRARIES)" || rm -f $(imap_module_LTLIBRARIES)
+ @list='$(imap_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib95_imap_old_stats_plugin.la: $(lib95_imap_old_stats_plugin_la_OBJECTS) $(lib95_imap_old_stats_plugin_la_DEPENDENCIES) $(EXTRA_lib95_imap_old_stats_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib95_imap_old_stats_plugin_la_LINK) -rpath $(imap_moduledir) $(lib95_imap_old_stats_plugin_la_OBJECTS) $(lib95_imap_old_stats_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-stats-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(imap_moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imap-stats-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-imap_moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/imap-stats-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-imap_moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-imap_moduleLTLIBRARIES install-info install-info-am \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-imap_moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/imap-old-stats/imap-stats-plugin.c b/src/plugins/imap-old-stats/imap-stats-plugin.c
new file mode 100644
index 0000000..6c91aa7
--- /dev/null
+++ b/src/plugins/imap-old-stats/imap-stats-plugin.c
@@ -0,0 +1,128 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "base64.h"
+#include "str.h"
+#include "imap-commands.h"
+#include "stats.h"
+#include "stats-plugin.h"
+#include "stats-connection.h"
+#include "imap-stats-plugin.h"
+
+#define IMAP_STATS_IMAP_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, imap_stats_imap_module)
+
+struct stats_client_command {
+ union imap_module_context module_ctx;
+
+ unsigned int id;
+ bool continued;
+ struct stats *stats, *pre_stats;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(imap_stats_imap_module,
+ &imap_module_register);
+
+const char *imap_stats_plugin_version = DOVECOT_ABI_VERSION;
+
+static void stats_command_pre(struct client_command_context *cmd)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT(cmd->client->user);
+ struct stats_client_command *scmd;
+ static unsigned int stats_cmd_id_counter = 0;
+
+ if (suser == NULL || !suser->track_commands)
+ return;
+
+ if (strcasecmp(cmd->name, "IDLE") == 0) {
+ /* IDLE can run forever and waste stats process's memory while
+ waiting for it to timeout. don't send them. */
+ return;
+ }
+
+ scmd = IMAP_STATS_IMAP_CONTEXT(cmd);
+ if (scmd == NULL) {
+ scmd = p_new(cmd->pool, struct stats_client_command, 1);
+ scmd->id = ++stats_cmd_id_counter;
+ scmd->stats = stats_alloc(cmd->pool);
+ scmd->pre_stats = stats_alloc(cmd->pool);
+ MODULE_CONTEXT_SET(cmd, imap_stats_imap_module, scmd);
+ }
+
+ mail_user_stats_fill(cmd->client->user, scmd->pre_stats);
+}
+
+static void stats_command_post(struct client_command_context *cmd)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT(cmd->client->user);
+ struct stats_client_command *scmd = IMAP_STATS_IMAP_CONTEXT(cmd);
+ struct stats *new_stats, *diff_stats;
+ const char *error;
+ size_t args_pos = 0, args_len = 0;
+ string_t *str;
+ buffer_t *buf;
+
+ if (suser == NULL || scmd == NULL)
+ return;
+
+ new_stats = stats_alloc(pool_datastack_create());
+ diff_stats = stats_alloc(pool_datastack_create());
+
+ mail_user_stats_fill(cmd->client->user, new_stats);
+ if (!stats_diff(scmd->pre_stats, new_stats, diff_stats, &error))
+ i_error("stats: command stats shrank: %s", error);
+ stats_add(scmd->stats, diff_stats);
+
+ str = t_str_new(128);
+ str_append(str, "UPDATE-CMD\t");
+ str_append(str, suser->stats_session_id);
+
+ str_printfa(str, "\t%u\t", scmd->id);
+ if (cmd->state == CLIENT_COMMAND_STATE_DONE)
+ str_append_c(str, 'd');
+ if (scmd->continued)
+ str_append_c(str, 'c');
+ else {
+ str_append_c(str, '\t');
+ str_append(str, cmd->name);
+ str_append_c(str, '\t');
+ args_pos = str_len(str);
+ if (cmd->args != NULL)
+ str_append(str, cmd->args);
+ args_len = str_len(str) - args_pos;
+ scmd->continued = TRUE;
+ }
+
+ buf = t_buffer_create(128);
+ stats_export(buf, scmd->stats);
+ str_append_c(str, '\t');
+ base64_encode(buf->data, buf->used, str);
+
+ str_append_c(str, '\n');
+
+ if (str_len(str) > PIPE_BUF) {
+ /* truncate the args so it fits */
+ size_t delete_count = str_len(str) - PIPE_BUF;
+
+ i_assert(args_pos != 0);
+ if (delete_count > args_len)
+ delete_count = args_len;
+ str_delete(str, args_pos + args_len - delete_count,
+ delete_count);
+ }
+
+ stats_connection_send(suser->stats_conn, str);
+}
+
+void imap_old_stats_plugin_init(struct module *module ATTR_UNUSED)
+{
+ command_hook_register(stats_command_pre, stats_command_post);
+}
+
+void imap_old_stats_plugin_deinit(void)
+{
+ command_hook_unregister(stats_command_pre, stats_command_post);
+}
+
+const char *imap_old_stats_plugin_dependencies[] = { "old_stats", NULL };
+const char imap_old_stats_plugin_binary_dependency[] = "imap";
diff --git a/src/plugins/imap-old-stats/imap-stats-plugin.h b/src/plugins/imap-old-stats/imap-stats-plugin.h
new file mode 100644
index 0000000..2a95b53
--- /dev/null
+++ b/src/plugins/imap-old-stats/imap-stats-plugin.h
@@ -0,0 +1,12 @@
+#ifndef IMAP_STATS_PLUGIN_H
+#define IMAP_STATS_PLUGIN_H
+
+struct module;
+
+extern const char *imap_stats_plugin_dependencies[];
+extern const char imap_stats_plugin_binary_dependency[];
+
+void imap_old_stats_plugin_init(struct module *module);
+void imap_old_stats_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/imap-quota/Makefile.am b/src/plugins/imap-quota/Makefile.am
new file mode 100644
index 0000000..7c19c02
--- /dev/null
+++ b/src/plugins/imap-quota/Makefile.am
@@ -0,0 +1,27 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/imap \
+ -I$(top_srcdir)/src/plugins/quota
+
+imap_moduledir = $(moduledir)
+
+NOPLUGIN_LDFLAGS =
+lib11_imap_quota_plugin_la_LDFLAGS = -module -avoid-version
+
+imap_module_LTLIBRARIES = \
+ lib11_imap_quota_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+lib11_imap_quota_plugin_la_LIBADD = \
+ ../quota/lib10_quota_plugin.la
+endif
+
+lib11_imap_quota_plugin_la_SOURCES = \
+ imap-quota-plugin.c
+
+noinst_HEADERS = \
+ imap-quota-plugin.h
diff --git a/src/plugins/imap-quota/Makefile.in b/src/plugins/imap-quota/Makefile.in
new file mode 100644
index 0000000..122d1df
--- /dev/null
+++ b/src/plugins/imap-quota/Makefile.in
@@ -0,0 +1,827 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/imap-quota
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(imap_moduledir)"
+LTLIBRARIES = $(imap_module_LTLIBRARIES)
+@DOVECOT_PLUGIN_DEPS_TRUE@lib11_imap_quota_plugin_la_DEPENDENCIES = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../quota/lib10_quota_plugin.la
+am_lib11_imap_quota_plugin_la_OBJECTS = imap-quota-plugin.lo
+lib11_imap_quota_plugin_la_OBJECTS = \
+ $(am_lib11_imap_quota_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib11_imap_quota_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib11_imap_quota_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/imap-quota-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib11_imap_quota_plugin_la_SOURCES)
+DIST_SOURCES = $(lib11_imap_quota_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/imap \
+ -I$(top_srcdir)/src/plugins/quota
+
+imap_moduledir = $(moduledir)
+lib11_imap_quota_plugin_la_LDFLAGS = -module -avoid-version
+imap_module_LTLIBRARIES = \
+ lib11_imap_quota_plugin.la
+
+@DOVECOT_PLUGIN_DEPS_TRUE@lib11_imap_quota_plugin_la_LIBADD = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../quota/lib10_quota_plugin.la
+
+lib11_imap_quota_plugin_la_SOURCES = \
+ imap-quota-plugin.c
+
+noinst_HEADERS = \
+ imap-quota-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/imap-quota/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/imap-quota/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-imap_moduleLTLIBRARIES: $(imap_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(imap_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(imap_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(imap_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(imap_moduledir)"; \
+ }
+
+uninstall-imap_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(imap_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(imap_moduledir)/$$f"; \
+ done
+
+clean-imap_moduleLTLIBRARIES:
+ -test -z "$(imap_module_LTLIBRARIES)" || rm -f $(imap_module_LTLIBRARIES)
+ @list='$(imap_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib11_imap_quota_plugin.la: $(lib11_imap_quota_plugin_la_OBJECTS) $(lib11_imap_quota_plugin_la_DEPENDENCIES) $(EXTRA_lib11_imap_quota_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib11_imap_quota_plugin_la_LINK) -rpath $(imap_moduledir) $(lib11_imap_quota_plugin_la_OBJECTS) $(lib11_imap_quota_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-quota-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(imap_moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imap-quota-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-imap_moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/imap-quota-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-imap_moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-imap_moduleLTLIBRARIES install-info install-info-am \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-imap_moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/imap-quota/imap-quota-plugin.c b/src/plugins/imap-quota/imap-quota-plugin.c
new file mode 100644
index 0000000..7863419
--- /dev/null
+++ b/src/plugins/imap-quota/imap-quota-plugin.c
@@ -0,0 +1,266 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "ostream.h"
+#include "imap-quote.h"
+#include "mail-namespace.h"
+#include "imap-commands.h"
+#include "quota.h"
+#include "quota-plugin.h"
+#include "imap-quota-plugin.h"
+
+
+#define QUOTA_USER_SEPARATOR ':'
+
+const char *imap_quota_plugin_version = DOVECOT_ABI_VERSION;
+
+static struct module *imap_quota_module;
+static imap_client_created_func_t *next_hook_client_created;
+
+static const char *
+imap_quota_root_get_name(struct mail_user *user, struct mail_user *owner,
+ struct quota_root *root)
+{
+ const char *name;
+
+ name = quota_root_get_name(root);
+ if (user == owner || owner == NULL)
+ return name;
+ return t_strdup_printf("%s%c%s", owner->username,
+ QUOTA_USER_SEPARATOR, name);
+}
+
+static int
+quota_reply_write(string_t *str, struct mail_user *user,
+ struct mail_user *owner, struct quota_root *root)
+{
+ const char *name, *const *list, *error;
+ unsigned int i;
+ uint64_t value, limit;
+ size_t prefix_len, orig_len = str_len(str);
+ enum quota_get_result ret = QUOTA_GET_RESULT_UNLIMITED;
+
+ str_append(str, "* QUOTA ");
+ name = imap_quota_root_get_name(user, owner, root);
+ imap_append_astring(str, name);
+
+ str_append(str, " (");
+ prefix_len = str_len(str);
+ list = quota_root_get_resources(root);
+ for (i = 0; *list != NULL; list++) {
+ ret = quota_get_resource(root, "", *list, &value, &limit, &error);
+ if (ret == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ i_error("Failed to get quota resource %s: %s",
+ *list, error);
+ break;
+ }
+ if (ret == QUOTA_GET_RESULT_LIMITED) {
+ if (i > 0)
+ str_append_c(str, ' ');
+ str_printfa(str, "%s %"PRIu64" %"PRIu64, *list,
+ value, limit);
+ i++;
+ }
+ }
+ if (str_len(str) == prefix_len) {
+ /* this quota root doesn't have any quota actually enabled. */
+ str_truncate(str, orig_len);
+ } else {
+ str_append(str, ")\r\n");
+ }
+ return ret == QUOTA_GET_RESULT_INTERNAL_ERROR ? -1 : 0;
+}
+
+static bool cmd_getquotaroot(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct quota_user *quser = QUOTA_USER_CONTEXT(client->user);
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ struct quota_root_iter *iter;
+ struct quota_root *root;
+ const char *mailbox, *orig_mailbox, *name;
+ string_t *quotaroot_reply, *quota_reply;
+ int ret;
+
+ /* <mailbox> */
+ if (!client_read_string_args(cmd, 1, &mailbox))
+ return FALSE;
+ orig_mailbox = mailbox;
+
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ if (quser == NULL) {
+ client_send_tagline(cmd, "OK No quota.");
+ return TRUE;
+ }
+ if (ns->owner != NULL && ns->owner != client->user) {
+ client_send_tagline(cmd, "NO Not showing other users' quota.");
+ return TRUE;
+ }
+
+ box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY);
+
+ /* build QUOTAROOT reply and QUOTA reply for all quota roots */
+ quotaroot_reply = t_str_new(128);
+ quota_reply = t_str_new(256);
+ str_append(quotaroot_reply, "* QUOTAROOT ");
+ imap_append_astring(quotaroot_reply, orig_mailbox);
+
+ ret = 0;
+ iter = quota_root_iter_init(box);
+ while ((root = quota_root_iter_next(iter)) != NULL) {
+ if (quota_root_is_hidden(root))
+ continue;
+ str_append_c(quotaroot_reply, ' ');
+ name = imap_quota_root_get_name(client->user, ns->owner, root);
+ imap_append_astring(quotaroot_reply, name);
+
+ if (quota_reply_write(quota_reply, client->user, ns->owner, root) < 0)
+ ret = -1;
+ }
+ quota_root_iter_deinit(&iter);
+ mailbox_free(&box);
+
+ /* send replies */
+ if (ret < 0)
+ client_send_tagline(cmd, "NO Internal quota calculation error.");
+ else if (str_len(quota_reply) == 0)
+ client_send_tagline(cmd, "OK No quota.");
+ else {
+ client_send_line(client, str_c(quotaroot_reply));
+ o_stream_nsend(client->output, str_data(quota_reply),
+ str_len(quota_reply));
+ client_send_tagline(cmd, "OK Getquotaroot completed.");
+ }
+ return TRUE;
+}
+
+static bool
+parse_quota_root(struct mail_user *user, const char *root_name,
+ struct mail_user **owner_r, struct quota_root **root_r)
+{
+ const char *p;
+
+ *owner_r = user;
+ *root_r = quota_root_lookup(user, root_name);
+ if (*root_r != NULL || !user->admin)
+ return *root_r != NULL;
+
+ /* we're an admin. see if there's a quota root for another user. */
+ p = strchr(root_name, QUOTA_USER_SEPARATOR);
+ if (p != NULL) {
+ *owner_r = mail_user_find(user, t_strdup_until(root_name, p));
+ *root_r = *owner_r == NULL ? NULL :
+ quota_root_lookup(*owner_r, p + 1);
+ }
+ return *root_r != NULL;
+}
+
+static bool cmd_getquota(struct client_command_context *cmd)
+{
+ struct mail_user *owner;
+ struct quota_root *root;
+ const char *root_name;
+ string_t *quota_reply;
+
+ /* <quota root> */
+ if (!client_read_string_args(cmd, 1, &root_name))
+ return FALSE;
+
+ if (!parse_quota_root(cmd->client->user, root_name, &owner, &root)) {
+ client_send_tagline(cmd, "NO Quota root doesn't exist.");
+ return TRUE;
+ }
+
+ quota_reply = t_str_new(128);
+ if (quota_reply_write(quota_reply, cmd->client->user, owner, root) < 0)
+ client_send_tagline(cmd, "NO Internal quota calculation error.");
+ else {
+ o_stream_nsend(cmd->client->output, str_data(quota_reply),
+ str_len(quota_reply));
+ client_send_tagline(cmd, "OK Getquota completed.");
+ }
+ return TRUE;
+}
+
+static bool cmd_setquota(struct client_command_context *cmd)
+{
+ struct quota_root *root;
+ struct mail_user *owner;
+ const struct imap_arg *args, *list_args;
+ const char *root_name, *name, *value_str, *client_error;
+ uint64_t value;
+
+ /* <quota root> <resource limits> */
+ if (!client_read_args(cmd, 2, 0, &args))
+ return FALSE;
+
+ if (!imap_arg_get_astring(&args[0], &root_name) ||
+ !imap_arg_get_list(&args[1], &list_args)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ if (!cmd->client->user->admin) {
+ client_send_tagline(cmd, "NO Quota can be changed only by admin.");
+ return TRUE;
+ }
+
+ if (!parse_quota_root(cmd->client->user, root_name, &owner, &root)) {
+ client_send_tagline(cmd, "NO Quota root doesn't exist.");
+ return TRUE;
+ }
+
+ for (; !IMAP_ARG_IS_EOL(list_args); list_args += 2) {
+ if (!imap_arg_get_atom(&list_args[0], &name) ||
+ !imap_arg_get_atom(&list_args[1], &value_str) ||
+ str_to_uint64(value_str, &value) < 0) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ if (quota_set_resource(root, name, value, &client_error) < 0) {
+ client_send_command_error(cmd, client_error);
+ return TRUE;
+ }
+ }
+
+ client_send_tagline(cmd, "OK Setquota completed.");
+ return TRUE;
+}
+
+static void imap_quota_client_created(struct client **client)
+{
+ if (mail_user_is_plugin_loaded((*client)->user, imap_quota_module))
+ client_add_capability(*client, "QUOTA");
+
+ if (next_hook_client_created != NULL)
+ next_hook_client_created(client);
+}
+
+void imap_quota_plugin_init(struct module *module)
+{
+ command_register("GETQUOTAROOT", cmd_getquotaroot, 0);
+ command_register("GETQUOTA", cmd_getquota, 0);
+ command_register("SETQUOTA", cmd_setquota, 0);
+
+ imap_quota_module = module;
+ next_hook_client_created =
+ imap_client_created_hook_set(imap_quota_client_created);
+}
+
+void imap_quota_plugin_deinit(void)
+{
+ command_unregister("GETQUOTAROOT");
+ command_unregister("GETQUOTA");
+ command_unregister("SETQUOTA");
+
+ imap_client_created_hook_set(next_hook_client_created);
+}
+
+const char *imap_quota_plugin_dependencies[] = { "quota", NULL };
+const char imap_quota_plugin_binary_dependency[] = "imap";
diff --git a/src/plugins/imap-quota/imap-quota-plugin.h b/src/plugins/imap-quota/imap-quota-plugin.h
new file mode 100644
index 0000000..1e55d10
--- /dev/null
+++ b/src/plugins/imap-quota/imap-quota-plugin.h
@@ -0,0 +1,12 @@
+#ifndef IMAP_QUOTA_PLUGIN_H
+#define IMAP_QUOTA_PLUGIN_H
+
+struct module;
+
+extern const char *imap_quota_plugin_dependencies[];
+extern const char imap_quota_plugin_binary_dependency[];
+
+void imap_quota_plugin_init(struct module *module);
+void imap_quota_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/imap-zlib/Makefile.am b/src/plugins/imap-zlib/Makefile.am
new file mode 100644
index 0000000..fa4f2f1
--- /dev/null
+++ b/src/plugins/imap-zlib/Makefile.am
@@ -0,0 +1,25 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-compression \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/imap
+
+imap_moduledir = $(moduledir)
+
+NOPLUGIN_LDFLAGS =
+lib30_imap_zlib_plugin_la_LDFLAGS = -module -avoid-version
+
+imap_module_LTLIBRARIES = \
+ lib30_imap_zlib_plugin.la
+
+lib30_imap_zlib_plugin_la_LIBADD = \
+ ../../lib-compression/libcompression.la
+
+lib30_imap_zlib_plugin_la_SOURCES = \
+ imap-zlib-plugin.c
+
+noinst_HEADERS = \
+ imap-zlib-plugin.h
diff --git a/src/plugins/imap-zlib/Makefile.in b/src/plugins/imap-zlib/Makefile.in
new file mode 100644
index 0000000..2a91e20
--- /dev/null
+++ b/src/plugins/imap-zlib/Makefile.in
@@ -0,0 +1,827 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/imap-zlib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(imap_moduledir)"
+LTLIBRARIES = $(imap_module_LTLIBRARIES)
+lib30_imap_zlib_plugin_la_DEPENDENCIES = \
+ ../../lib-compression/libcompression.la
+am_lib30_imap_zlib_plugin_la_OBJECTS = imap-zlib-plugin.lo
+lib30_imap_zlib_plugin_la_OBJECTS = \
+ $(am_lib30_imap_zlib_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib30_imap_zlib_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib30_imap_zlib_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/imap-zlib-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib30_imap_zlib_plugin_la_SOURCES)
+DIST_SOURCES = $(lib30_imap_zlib_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-compression \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/imap
+
+imap_moduledir = $(moduledir)
+lib30_imap_zlib_plugin_la_LDFLAGS = -module -avoid-version
+imap_module_LTLIBRARIES = \
+ lib30_imap_zlib_plugin.la
+
+lib30_imap_zlib_plugin_la_LIBADD = \
+ ../../lib-compression/libcompression.la
+
+lib30_imap_zlib_plugin_la_SOURCES = \
+ imap-zlib-plugin.c
+
+noinst_HEADERS = \
+ imap-zlib-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/imap-zlib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/imap-zlib/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-imap_moduleLTLIBRARIES: $(imap_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(imap_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(imap_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(imap_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(imap_moduledir)"; \
+ }
+
+uninstall-imap_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(imap_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(imap_moduledir)/$$f"; \
+ done
+
+clean-imap_moduleLTLIBRARIES:
+ -test -z "$(imap_module_LTLIBRARIES)" || rm -f $(imap_module_LTLIBRARIES)
+ @list='$(imap_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib30_imap_zlib_plugin.la: $(lib30_imap_zlib_plugin_la_OBJECTS) $(lib30_imap_zlib_plugin_la_DEPENDENCIES) $(EXTRA_lib30_imap_zlib_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib30_imap_zlib_plugin_la_LINK) -rpath $(imap_moduledir) $(lib30_imap_zlib_plugin_la_OBJECTS) $(lib30_imap_zlib_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-zlib-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(imap_moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imap-zlib-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-imap_moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/imap-zlib-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-imap_moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-imap_moduleLTLIBRARIES install-info install-info-am \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-imap_moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/imap-zlib/imap-zlib-plugin.c b/src/plugins/imap-zlib/imap-zlib-plugin.c
new file mode 100644
index 0000000..df5508b
--- /dev/null
+++ b/src/plugins/imap-zlib/imap-zlib-plugin.c
@@ -0,0 +1,184 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "module-context.h"
+#include "imap-commands.h"
+#include "compression.h"
+#include "imap-zlib-plugin.h"
+
+
+#define IMAP_COMPRESS_DEFAULT_LEVEL 6
+
+#define IMAP_ZLIB_IMAP_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, imap_zlib_imap_module)
+
+struct zlib_client {
+ union imap_module_context module_ctx;
+
+ int (*next_state_export)(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r);
+ const struct compression_handler *handler;
+};
+
+const char *imap_zlib_plugin_version = DOVECOT_ABI_VERSION;
+
+static struct module *imap_zlib_module;
+static imap_client_created_func_t *next_hook_client_created;
+
+static MODULE_CONTEXT_DEFINE_INIT(imap_zlib_imap_module,
+ &imap_module_register);
+
+static void client_skip_line(struct client *client)
+{
+ const unsigned char *data;
+ size_t data_size;
+
+ data = i_stream_get_data(client->input, &data_size);
+ i_assert(data_size > 0);
+ if (data[0] == '\n')
+ i_stream_skip(client->input, 1);
+ else if (data[0] == '\r' && data_size > 1 && data[1] == '\n')
+ i_stream_skip(client->input, 2);
+ else
+ i_unreached();
+ client->input_skip_line = FALSE;
+}
+
+static void client_update_imap_parser_streams(struct client *client)
+{
+ struct client_command_context *cmd;
+
+ if (client->free_parser != NULL) {
+ imap_parser_set_streams(client->free_parser,
+ client->input, client->output);
+ }
+
+ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+ imap_parser_set_streams(cmd->parser,
+ client->input, client->output);
+ }
+}
+
+static bool cmd_compress(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client);
+ const struct compression_handler *handler;
+ const struct imap_arg *args;
+ struct istream *old_input;
+ struct ostream *old_output;
+ const char *mechanism, *value;
+ int level;
+ int ret;
+
+ /* <mechanism> */
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!imap_arg_get_atom(args, &mechanism) ||
+ !IMAP_ARG_IS_EOL(&args[1])) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+ if (zclient->handler != NULL) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO [COMPRESSIONACTIVE] COMPRESSION=%s already enabled.",
+ t_str_ucase(zclient->handler->name)));
+ return TRUE;
+ }
+
+ ret = compression_lookup_handler(t_str_lcase(mechanism), &handler);
+ if (ret <= 0) {
+ const char * tagline =
+ t_strdup_printf("NO %s compression mechanism",
+ ret == 0 ? "Unsupported" : "Unknown");
+ client_send_tagline(cmd, tagline);
+ return TRUE;
+ }
+
+ client_skip_line(client);
+ client_send_tagline(cmd, "OK Begin compression.");
+
+ const char *setting = t_strdup_printf("imap_compress_%s_level",
+ handler->name);
+ value = mail_user_plugin_getenv(client->user, setting);
+ if (value == NULL) {
+ level = handler->get_default_level();
+ } else if (str_to_int(value, &level) < 0 ||
+ level < handler->get_min_level() ||
+ level > handler->get_max_level()) {
+ i_error("%s: Level must be between %d..%d",
+ setting,
+ handler->get_min_level(),
+ handler->get_max_level());
+ level = handler->get_default_level();
+ }
+ old_input = client->input;
+ old_output = client->output;
+ client->input = handler->create_istream(old_input);
+ client->output = handler->create_ostream(old_output, level);
+ /* preserve output offset so that the bytes out counter in logout
+ message doesn't get reset here */
+ client->output->offset = old_output->offset;
+ i_stream_unref(&old_input);
+ o_stream_unref(&old_output);
+
+ client_update_imap_parser_streams(client);
+ zclient->handler = handler;
+ return TRUE;
+}
+
+static int
+imap_zlib_state_export(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r)
+{
+ struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client);
+
+ if (zclient->handler != NULL && internal) {
+ *error_r = "COMPRESS enabled";
+ return 0;
+ }
+ return zclient->next_state_export(client, internal, dest, error_r);
+}
+
+static void imap_zlib_client_created(struct client **clientp)
+{
+ struct client *client = *clientp;
+ struct zlib_client *zclient;
+ const struct compression_handler *handler;
+
+ if (mail_user_is_plugin_loaded(client->user, imap_zlib_module) &&
+ compression_lookup_handler("deflate", &handler) > 0) {
+ zclient = p_new(client->pool, struct zlib_client, 1);
+ MODULE_CONTEXT_SET(client, imap_zlib_imap_module, zclient);
+
+ zclient->next_state_export = (*clientp)->v.state_export;
+ (*clientp)->v.state_export = imap_zlib_state_export;
+
+ client_add_capability(*clientp, "COMPRESS=DEFLATE");
+ }
+
+ if (next_hook_client_created != NULL)
+ next_hook_client_created(clientp);
+}
+
+void imap_zlib_plugin_init(struct module *module)
+{
+ command_register("COMPRESS", cmd_compress, 0);
+
+ imap_zlib_module = module;
+ next_hook_client_created =
+ imap_client_created_hook_set(imap_zlib_client_created);
+}
+
+void imap_zlib_plugin_deinit(void)
+{
+ command_unregister("COMPRESS");
+
+ imap_client_created_hook_set(next_hook_client_created);
+}
+
+const char imap_zlib_plugin_binary_dependency[] = "imap";
diff --git a/src/plugins/imap-zlib/imap-zlib-plugin.h b/src/plugins/imap-zlib/imap-zlib-plugin.h
new file mode 100644
index 0000000..251bb23
--- /dev/null
+++ b/src/plugins/imap-zlib/imap-zlib-plugin.h
@@ -0,0 +1,12 @@
+#ifndef IMAP_ZLIB_PLUGIN_H
+#define IMAP_ZLIB_PLUGIN_H
+
+struct module;
+
+extern const char *imap_zlib_plugin_dependencies[];
+extern const char imap_zlib_plugin_binary_dependency[];
+
+void imap_zlib_plugin_init(struct module *module);
+void imap_zlib_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/last-login/Makefile.am b/src/plugins/last-login/Makefile.am
new file mode 100644
index 0000000..cdedf43
--- /dev/null
+++ b/src/plugins/last-login/Makefile.am
@@ -0,0 +1,19 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+NOPLUGIN_LDFLAGS =
+lib10_last_login_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib10_last_login_plugin.la
+
+lib10_last_login_plugin_la_SOURCES = \
+ last-login-plugin.c
+
+noinst_HEADERS = \
+ last-login-plugin.h
diff --git a/src/plugins/last-login/Makefile.in b/src/plugins/last-login/Makefile.in
new file mode 100644
index 0000000..2e20c77
--- /dev/null
+++ b/src/plugins/last-login/Makefile.in
@@ -0,0 +1,822 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/last-login
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib10_last_login_plugin_la_LIBADD =
+am_lib10_last_login_plugin_la_OBJECTS = last-login-plugin.lo
+lib10_last_login_plugin_la_OBJECTS = \
+ $(am_lib10_last_login_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib10_last_login_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib10_last_login_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/last-login-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib10_last_login_plugin_la_SOURCES)
+DIST_SOURCES = $(lib10_last_login_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+lib10_last_login_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib10_last_login_plugin.la
+
+lib10_last_login_plugin_la_SOURCES = \
+ last-login-plugin.c
+
+noinst_HEADERS = \
+ last-login-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/last-login/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/last-login/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib10_last_login_plugin.la: $(lib10_last_login_plugin_la_OBJECTS) $(lib10_last_login_plugin_la_DEPENDENCIES) $(EXTRA_lib10_last_login_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib10_last_login_plugin_la_LINK) -rpath $(moduledir) $(lib10_last_login_plugin_la_OBJECTS) $(lib10_last_login_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/last-login-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/last-login-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/last-login-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/last-login/last-login-plugin.c b/src/plugins/last-login/last-login-plugin.c
new file mode 100644
index 0000000..1f580b9
--- /dev/null
+++ b/src/plugins/last-login/last-login-plugin.c
@@ -0,0 +1,157 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "dict.h"
+#include "mail-user.h"
+#include "mail-namespace.h"
+#include "mail-storage-private.h"
+#include "mail-storage-hooks.h"
+#include "last-login-plugin.h"
+
+#define LAST_LOGIN_USER_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, last_login_user_module)
+
+#define LAST_LOGIN_DEFAULT_KEY_PREFIX "last-login/"
+
+struct last_login_user {
+ union mail_user_module_context module_ctx;
+ struct dict *dict;
+ struct timeout *to;
+};
+
+const char *last_login_plugin_version = DOVECOT_ABI_VERSION;
+
+static MODULE_CONTEXT_DEFINE_INIT(last_login_user_module,
+ &mail_user_module_register);
+
+static void last_login_dict_deinit(struct mail_user *user)
+{
+ struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user);
+
+ if (luser->dict != NULL) {
+ dict_wait(luser->dict);
+ dict_deinit(&luser->dict);
+ }
+ /* remove timeout after dict_wait(), which may trigger
+ last_login_dict_commit() */
+ timeout_remove(&luser->to);
+}
+
+static void last_login_user_deinit(struct mail_user *user)
+{
+ struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user);
+
+ last_login_dict_deinit(user);
+ luser->module_ctx.super.deinit(user);
+}
+
+static void
+last_login_dict_commit(const struct dict_commit_result *result,
+ struct mail_user *user)
+{
+ struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user);
+
+ switch(result->ret) {
+ case DICT_COMMIT_RET_OK:
+ case DICT_COMMIT_RET_NOTFOUND:
+ break;
+ case DICT_COMMIT_RET_FAILED:
+ i_error("last_login_dict: Failed to write value: %s",
+ result->error);
+ break;
+ case DICT_COMMIT_RET_WRITE_UNCERTAIN:
+ i_error("last_login_dict: Write was unconfirmed (timeout or disconnect): %s",
+ result->error);
+ break;
+ }
+
+ /* don't deinit the dict immediately here, lib-dict will just crash */
+ luser->to = timeout_add(0, last_login_dict_deinit, user);
+}
+
+static void last_login_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct last_login_user *luser;
+ struct dict *dict;
+ struct dict_settings set;
+ struct dict_transaction_context *trans;
+ const char *dict_value, *key_name, *precision, *error;
+
+ if (user->autocreated) {
+ /* we want to handle only logged in users,
+ not lda's raw user or accessed shared users */
+ return;
+ }
+ if (user->session_restored) {
+ /* This is IMAP unhibernation, not a real login. */
+ return;
+ }
+
+ dict_value = mail_user_plugin_getenv(user, "last_login_dict");
+ if (dict_value == NULL || dict_value[0] == '\0')
+ return;
+
+ i_zero(&set);
+ set.base_dir = user->set->base_dir;
+ set.event_parent = user->event;
+ if (dict_init(dict_value, &set, &dict, &error) < 0) {
+ i_error("last_login_dict: dict_init(%s) failed: %s",
+ dict_value, error);
+ return;
+ }
+
+ luser = p_new(user->pool, struct last_login_user, 1);
+ luser->module_ctx.super = *v;
+ user->vlast = &luser->module_ctx.super;
+ v->deinit = last_login_user_deinit;
+
+ luser->dict = dict;
+ MODULE_CONTEXT_SET(user, last_login_user_module, luser);
+
+ key_name = mail_user_plugin_getenv(user, "last_login_key");
+ if (key_name == NULL) {
+ key_name = t_strdup_printf(LAST_LOGIN_DEFAULT_KEY_PREFIX"%s",
+ user->username);
+ }
+ key_name = t_strconcat(DICT_PATH_SHARED, key_name, NULL);
+
+ precision = mail_user_plugin_getenv(user, "last_login_precision");
+
+ const struct dict_op_settings *dset = mail_user_get_dict_op_settings(user);
+ trans = dict_transaction_begin(dict, dset);
+ if (precision == NULL || strcmp(precision, "s") == 0)
+ dict_set(trans, key_name, dec2str(ioloop_time));
+ else if (strcmp(precision, "ms") == 0) {
+ dict_set(trans, key_name, t_strdup_printf(
+ "%ld%03u", (long)ioloop_timeval.tv_sec,
+ (unsigned int)(ioloop_timeval.tv_usec/1000)));
+ } else if (strcmp(precision, "us") == 0) {
+ dict_set(trans, key_name, t_strdup_printf(
+ "%ld%06u", (long)ioloop_timeval.tv_sec,
+ (unsigned int)ioloop_timeval.tv_usec));
+ } else if (strcmp(precision, "ns") == 0) {
+ dict_set(trans, key_name, t_strdup_printf(
+ "%ld%06u000", (long)ioloop_timeval.tv_sec,
+ (unsigned int)ioloop_timeval.tv_usec));
+ } else {
+ i_error("last_login_dict: Invalid last_login_precision '%s'", precision);
+ }
+ dict_transaction_no_slowness_warning(trans);
+ dict_transaction_commit_async(&trans, last_login_dict_commit, user);
+}
+
+static struct mail_storage_hooks last_login_mail_storage_hooks = {
+ .mail_user_created = last_login_mail_user_created
+};
+
+void last_login_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &last_login_mail_storage_hooks);
+}
+
+void last_login_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&last_login_mail_storage_hooks);
+}
diff --git a/src/plugins/last-login/last-login-plugin.h b/src/plugins/last-login/last-login-plugin.h
new file mode 100644
index 0000000..481a148
--- /dev/null
+++ b/src/plugins/last-login/last-login-plugin.h
@@ -0,0 +1,7 @@
+#ifndef LAST_LOGIN_PLUGIN_H
+#define LAST_LOGIN_PLUGIN_H
+
+void last_login_plugin_init(struct module *module);
+void last_login_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/lazy-expunge/Makefile.am b/src/plugins/lazy-expunge/Makefile.am
new file mode 100644
index 0000000..8147b64
--- /dev/null
+++ b/src/plugins/lazy-expunge/Makefile.am
@@ -0,0 +1,21 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/maildir \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/plugins/quota
+
+NOPLUGIN_LDFLAGS =
+lib02_lazy_expunge_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib02_lazy_expunge_plugin.la
+
+lib02_lazy_expunge_plugin_la_SOURCES = \
+ lazy-expunge-plugin.c
+
+noinst_HEADERS = \
+ lazy-expunge-plugin.h
diff --git a/src/plugins/lazy-expunge/Makefile.in b/src/plugins/lazy-expunge/Makefile.in
new file mode 100644
index 0000000..b8c31b3
--- /dev/null
+++ b/src/plugins/lazy-expunge/Makefile.in
@@ -0,0 +1,824 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/lazy-expunge
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib02_lazy_expunge_plugin_la_LIBADD =
+am_lib02_lazy_expunge_plugin_la_OBJECTS = lazy-expunge-plugin.lo
+lib02_lazy_expunge_plugin_la_OBJECTS = \
+ $(am_lib02_lazy_expunge_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib02_lazy_expunge_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib02_lazy_expunge_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/lazy-expunge-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib02_lazy_expunge_plugin_la_SOURCES)
+DIST_SOURCES = $(lib02_lazy_expunge_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/maildir \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/plugins/quota
+
+lib02_lazy_expunge_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib02_lazy_expunge_plugin.la
+
+lib02_lazy_expunge_plugin_la_SOURCES = \
+ lazy-expunge-plugin.c
+
+noinst_HEADERS = \
+ lazy-expunge-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/lazy-expunge/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/lazy-expunge/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib02_lazy_expunge_plugin.la: $(lib02_lazy_expunge_plugin_la_OBJECTS) $(lib02_lazy_expunge_plugin_la_DEPENDENCIES) $(EXTRA_lib02_lazy_expunge_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib02_lazy_expunge_plugin_la_LINK) -rpath $(moduledir) $(lib02_lazy_expunge_plugin_la_OBJECTS) $(lib02_lazy_expunge_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lazy-expunge-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/lazy-expunge-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/lazy-expunge-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/lazy-expunge/lazy-expunge-plugin.c b/src/plugins/lazy-expunge/lazy-expunge-plugin.c
new file mode 100644
index 0000000..ca10839
--- /dev/null
+++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.c
@@ -0,0 +1,654 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "hash.h"
+#include "seq-range-array.h"
+#include "mkdir-parents.h"
+#include "mail-storage-private.h"
+#include "mail-search-build.h"
+#include "mailbox-list-private.h"
+#include "mailbox-match-plugin.h"
+#include "mail-namespace.h"
+#include "lazy-expunge-plugin.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <time.h>
+
+#define LAZY_EXPUNGE_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, lazy_expunge_mail_storage_module)
+#define LAZY_EXPUNGE_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, lazy_expunge_mail_storage_module)
+#define LAZY_EXPUNGE_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, lazy_expunge_mailbox_list_module)
+#define LAZY_EXPUNGE_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, lazy_expunge_mail_user_module)
+#define LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, lazy_expunge_mail_user_module)
+#define LAZY_EXPUNGE_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, lazy_expunge_mail_module)
+#define LAZY_EXPUNGE_MAIL_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, lazy_expunge_mail_module)
+
+struct lazy_expunge_mail {
+ union mail_module_context module_ctx;
+ bool moving;
+ bool recursing;
+};
+
+struct lazy_expunge_mail_user {
+ union mail_user_module_context module_ctx;
+
+ struct mail_namespace *lazy_ns;
+ struct mailbox_match_plugin *excludes;
+ const char *lazy_mailbox_vname;
+ const char *env;
+ bool copy_only_last_instance;
+};
+
+struct lazy_expunge_mailbox_list {
+ union mailbox_list_module_context module_ctx;
+
+ bool allow_rename:1;
+ bool internal_namespace:1;
+};
+
+struct lazy_expunge_transaction {
+ union mailbox_transaction_module_context module_ctx;
+
+ struct mailbox *dest_box;
+ struct mailbox_transaction_context *dest_trans;
+
+ pool_t pool;
+ HASH_TABLE(const char *, void *) guids;
+
+ char *delayed_errstr;
+ char *delayed_internal_errstr;
+ enum mail_error delayed_error;
+
+ bool copy_only_last_instance;
+};
+
+const char *lazy_expunge_plugin_version = DOVECOT_ABI_VERSION;
+
+static MODULE_CONTEXT_DEFINE_INIT(lazy_expunge_mail_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(lazy_expunge_mail_module,
+ &mail_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(lazy_expunge_mailbox_list_module,
+ &mailbox_list_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(lazy_expunge_mail_user_module,
+ &mail_user_module_register);
+
+static const char *
+get_dest_vname(struct mailbox_list *list, struct mailbox *src_box)
+{
+ struct lazy_expunge_mail_user *luser =
+ LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(list->ns->user);
+ const char *name;
+ char src_sep, dest_sep;
+
+ if (luser->lazy_mailbox_vname != NULL)
+ return luser->lazy_mailbox_vname;
+
+ /* use the (canonical / unaliased) storage name */
+ name = src_box->name;
+ /* replace hierarchy separators with destination virtual separator */
+ src_sep = mailbox_list_get_hierarchy_sep(src_box->list);
+ dest_sep = mail_namespace_get_sep(list->ns);
+ if (src_sep != dest_sep) {
+ string_t *str = t_str_new(128);
+ unsigned int i;
+
+ for (i = 0; name[i] != '\0'; i++) {
+ if (name[i] == src_sep)
+ str_append_c(str, dest_sep);
+ else
+ str_append_c(str, name[i]);
+ }
+ name = str_c(str);
+ }
+ /* add expunge namespace prefix. the name is now a proper vname */
+ return t_strconcat(list->ns->prefix, name, NULL);
+}
+
+static struct mailbox *
+mailbox_open_or_create(struct mailbox_list *list, struct mailbox *src_box,
+ const char **error_r)
+{
+ struct mailbox *box;
+ enum mail_error error;
+ const char *name;
+
+ name = get_dest_vname(list, src_box);
+
+ box = mailbox_alloc(list, name, MAILBOX_FLAG_NO_INDEX_FILES |
+ MAILBOX_FLAG_SAVEONLY | MAILBOX_FLAG_IGNORE_ACLS);
+ if (mailbox_open(box) == 0) {
+ *error_r = NULL;
+ return box;
+ }
+
+ *error_r = mailbox_get_last_internal_error(box, &error);
+ if (error != MAIL_ERROR_NOTFOUND) {
+ *error_r = t_strdup_printf("Failed to open mailbox %s: %s",
+ name, *error_r);
+ mailbox_free(&box);
+ return NULL;
+ }
+
+ /* try creating and re-opening it. */
+ if (mailbox_create(box, NULL, FALSE) < 0 &&
+ mailbox_get_last_mail_error(box) != MAIL_ERROR_EXISTS) {
+ *error_r = t_strdup_printf("Failed to create mailbox %s: %s", name,
+ mailbox_get_last_internal_error(box, NULL));
+ mailbox_free(&box);
+ return NULL;
+ }
+ if (mailbox_open(box) < 0) {
+ *error_r = t_strdup_printf("Failed to open created mailbox %s: %s", name,
+ mailbox_get_last_internal_error(box, NULL));
+ mailbox_free(&box);
+ return NULL;
+ }
+ return box;
+}
+
+static unsigned int
+lazy_expunge_count_in_transaction(struct lazy_expunge_transaction *lt,
+ const char *guid)
+{
+ void *refcountp;
+ unsigned int refcount;
+
+ if (lt->pool == NULL) {
+ lt->pool = pool_alloconly_create("lazy expunge transaction",
+ 1024);
+ hash_table_create(&lt->guids, lt->pool, 0, str_hash, strcmp);
+ }
+
+ refcountp = hash_table_lookup(lt->guids, guid);
+ refcount = POINTER_CAST_TO(refcountp, unsigned int) + 1;
+ refcountp = POINTER_CAST(refcount);
+ if (refcount == 1) {
+ guid = p_strdup(lt->pool, guid);
+ hash_table_insert(lt->guids, guid, refcountp);
+ } else {
+ hash_table_update(lt->guids, guid, refcountp);
+ }
+ return refcount-1;
+}
+
+static int lazy_expunge_mail_is_last_instance(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct lazy_expunge_transaction *lt =
+ LAZY_EXPUNGE_CONTEXT_REQUIRE(_mail->transaction);
+ const char *value, *errstr;
+ unsigned long refcount;
+ enum mail_error error;
+
+ /* mail is reused by the search query, so the next mail_prefetch() on
+ it will try to prefetch the refcount */
+ mail->wanted_fields |= MAIL_FETCH_REFCOUNT;
+
+ if (mail_get_special(_mail, MAIL_FETCH_REFCOUNT, &value) < 0) {
+ errstr = mailbox_get_last_internal_error(_mail->box, &error);
+ if (error == MAIL_ERROR_EXPUNGED) {
+ /* already expunged - just ignore it */
+ return 0;
+ }
+ mail_set_critical(_mail,
+ "lazy_expunge: Couldn't lookup message's refcount: %s",
+ errstr);
+ return -1;
+ }
+ if (*value == '\0') {
+ /* refcounts not supported by backend. assume all mails are
+ the last instance. */
+ return 1;
+ }
+ if (str_to_ulong(value, &refcount) < 0)
+ i_panic("Invalid mail refcount number: %s", value);
+ if (refcount > 1) {
+ /* this probably isn't the last instance of the mail, but
+ it's possible that the same mail was copied to this mailbox
+ multiple times and we're deleting more than one instance
+ within this transaction. in those cases each expunge will
+ see the same refcount, so we need to adjust the refcount
+ by tracking the expunged message's refcount IDs. */
+ if (mail_get_special(_mail, MAIL_FETCH_REFCOUNT_ID, &value) < 0) {
+ errstr = mailbox_get_last_internal_error(_mail->box, &error);
+ if (error == MAIL_ERROR_EXPUNGED) {
+ /* already expunged - just ignore it */
+ return 0;
+ }
+ mail_set_critical(_mail,
+ "lazy_expunge: Couldn't lookup message's refcount ID: %s", errstr);
+ return -1;
+ }
+ if (*value == '\0') {
+ /* refcount IDs not supported by backend, but refcounts
+ are? not with our current backends. */
+ mail_set_critical(_mail,
+ "lazy_expunge: Message unexpectedly has no refcount ID");
+ return -1;
+ }
+ refcount -= lazy_expunge_count_in_transaction(lt, value);
+ }
+ return refcount <= 1 ? 1 : 0;
+}
+
+static bool lazy_expunge_is_internal_mailbox(struct mailbox *box)
+{
+ struct mail_namespace *ns = box->list->ns;
+ struct lazy_expunge_mail_user *luser =
+ LAZY_EXPUNGE_USER_CONTEXT(ns->user);
+ struct lazy_expunge_mailbox_list *llist =
+ LAZY_EXPUNGE_LIST_CONTEXT(box->list);
+
+ if (luser == NULL || llist == NULL) {
+ /* lazy_expunge not enabled at all */
+ return FALSE;
+ }
+ if (llist->internal_namespace) {
+ /* lazy-expunge namespace */
+ return TRUE;
+ }
+ if (luser->lazy_mailbox_vname != NULL &&
+ strcmp(luser->lazy_mailbox_vname, box->vname) == 0) {
+ /* lazy-expunge mailbox */
+ return TRUE;
+ }
+ if (mailbox_match_plugin_exclude(luser->excludes, box)) {
+ /* Mailbox explicitly excluded by configuration */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void lazy_expunge_set_error(struct lazy_expunge_transaction *lt,
+ struct mail_storage *storage)
+{
+ const char *errstr;
+ enum mail_error error;
+
+ errstr = mail_storage_get_last_error(storage, &error);
+ if (error == MAIL_ERROR_EXPUNGED) {
+ /* expunging failed because the mail was already expunged.
+ we don't want to fail because of that. */
+ return;
+ }
+
+ if (lt->delayed_error != MAIL_ERROR_NONE)
+ return;
+ lt->delayed_error = error;
+ lt->delayed_errstr = i_strdup(errstr);
+ lt->delayed_internal_errstr =
+ i_strdup(mail_storage_get_last_internal_error(storage, NULL));
+}
+
+static void lazy_expunge_mail_expunge_move(struct mail *_mail)
+{
+ struct mail_namespace *ns = _mail->box->list->ns;
+ struct lazy_expunge_mail_user *luser =
+ LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(ns->user);
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct lazy_expunge_mail *mmail =
+ LAZY_EXPUNGE_MAIL_CONTEXT_REQUIRE(mail);
+ struct lazy_expunge_transaction *lt =
+ LAZY_EXPUNGE_CONTEXT_REQUIRE(_mail->transaction);
+ struct mail_save_context *save_ctx;
+ const char *error;
+
+ if (lt->dest_box == NULL) {
+ lt->dest_box = mailbox_open_or_create(luser->lazy_ns->list,
+ _mail->box, &error);
+ if (lt->dest_box == NULL) {
+ mail_set_critical(_mail,
+ "lazy_expunge: Couldn't open expunge mailbox: "
+ "%s", error);
+ lazy_expunge_set_error(lt, _mail->box->storage);
+ return;
+ }
+ if (mailbox_sync(lt->dest_box, 0) < 0) {
+ mail_set_critical(_mail,
+ "lazy_expunge: Couldn't sync expunge mailbox");
+ lazy_expunge_set_error(lt, lt->dest_box->storage);
+ mailbox_free(&lt->dest_box);
+ return;
+ }
+
+ lt->dest_trans = mailbox_transaction_begin(lt->dest_box,
+ MAILBOX_TRANSACTION_FLAG_EXTERNAL,
+ __func__);
+ }
+
+ save_ctx = mailbox_save_alloc(lt->dest_trans);
+ mailbox_save_copy_flags(save_ctx, _mail);
+ save_ctx->data.flags &= ENUM_NEGATE(MAIL_DELETED);
+
+ mmail->recursing = TRUE;
+ if (mailbox_move(&save_ctx, _mail) < 0 && !_mail->expunged)
+ lazy_expunge_set_error(lt, lt->dest_box->storage);
+ mmail->recursing = FALSE;
+}
+
+static void lazy_expunge_mail_expunge(struct mail *_mail)
+{
+ struct lazy_expunge_transaction *lt =
+ LAZY_EXPUNGE_CONTEXT_REQUIRE(_mail->transaction);
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct lazy_expunge_mail *mmail =
+ LAZY_EXPUNGE_MAIL_CONTEXT_REQUIRE(mail);
+ struct mail *real_mail;
+ bool moving = mmail->moving;
+ int ret;
+
+ if (lt->delayed_error != MAIL_ERROR_NONE)
+ return;
+ if (mmail->recursing) {
+ mmail->module_ctx.super.expunge(_mail);
+ return;
+ }
+
+ /* Clear this in case the mail is used for non-move later on. */
+ mmail->moving = FALSE;
+
+ /* don't copy the mail if we're expunging from lazy_expunge
+ namespace (even if it's via a virtual mailbox) */
+ if (mail_get_backend_mail(_mail, &real_mail) < 0) {
+ lazy_expunge_set_error(lt, _mail->box->storage);
+ return;
+ }
+ if (lazy_expunge_is_internal_mailbox(real_mail->box)) {
+ mmail->module_ctx.super.expunge(_mail);
+ return;
+ }
+
+ struct event_reason *reason =
+ event_reason_begin("lazy_expunge:expunge");
+ if (!lt->copy_only_last_instance)
+ ret = 1;
+ else {
+ /* we want to copy only the last instance of the mail to
+ lazy_expunge namespace. other instances will be expunged
+ immediately. */
+ if (moving)
+ ret = 0;
+ else {
+ ret = lazy_expunge_mail_is_last_instance(_mail);
+ if (ret < 0)
+ lazy_expunge_set_error(lt, _mail->box->storage);
+ }
+ }
+ if (ret > 0)
+ lazy_expunge_mail_expunge_move(_mail);
+ event_reason_end(&reason);
+
+ if (ret == 0) {
+ /* Not the last instance of the mail - expunge it normally.
+ Since this is a normal expunge, do it without the
+ reason_code. */
+ mmail->module_ctx.super.expunge(_mail);
+ }
+}
+
+static int lazy_expunge_copy(struct mail_save_context *ctx, struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mailbox_module_context *mbox =
+ LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx->transaction->box);
+ struct lazy_expunge_mail *mmail = LAZY_EXPUNGE_MAIL_CONTEXT(mail);
+
+ if (mmail != NULL)
+ mmail->moving = ctx->moving;
+ return mbox->super.copy(ctx, _mail);
+}
+
+static struct mailbox_transaction_context *
+lazy_expunge_transaction_begin(struct mailbox *box,
+ enum mailbox_transaction_flags flags,
+ const char *reason)
+{
+ struct lazy_expunge_mail_user *luser =
+ LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(box->list->ns->user);
+ union mailbox_module_context *mbox = LAZY_EXPUNGE_CONTEXT_REQUIRE(box);
+ struct mailbox_transaction_context *t;
+ struct lazy_expunge_transaction *lt;
+
+ t = mbox->super.transaction_begin(box, flags, reason);
+ lt = i_new(struct lazy_expunge_transaction, 1);
+ lt->copy_only_last_instance = luser->copy_only_last_instance;
+
+ MODULE_CONTEXT_SET(t, lazy_expunge_mail_storage_module, lt);
+ return t;
+}
+
+static void lazy_expunge_transaction_free(struct lazy_expunge_transaction *lt)
+{
+ if (lt->dest_trans != NULL)
+ mailbox_transaction_rollback(&lt->dest_trans);
+ if (lt->dest_box != NULL)
+ mailbox_free(&lt->dest_box);
+ hash_table_destroy(&lt->guids);
+ pool_unref(&lt->pool);
+ i_free(lt->delayed_errstr);
+ i_free(lt->delayed_internal_errstr);
+ i_free(lt);
+}
+
+static int
+lazy_expunge_transaction_commit(struct mailbox_transaction_context *ctx,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ union mailbox_module_context *mbox = LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx->box);
+ struct lazy_expunge_transaction *lt = LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx);
+ int ret;
+
+ if (lt->dest_trans != NULL && lt->delayed_error == MAIL_ERROR_NONE) {
+ if (mailbox_transaction_commit(&lt->dest_trans) < 0) {
+ lazy_expunge_set_error(lt, ctx->box->storage);
+ }
+ }
+
+ if (lt->delayed_error == MAIL_ERROR_NONE)
+ ret = mbox->super.transaction_commit(ctx, changes_r);
+ else if (lt->delayed_error != MAIL_ERROR_TEMP) {
+ mail_storage_set_error(ctx->box->storage, lt->delayed_error,
+ lt->delayed_errstr);
+ mbox->super.transaction_rollback(ctx);
+ ret = -1;
+ } else {
+ mailbox_set_critical(ctx->box,
+ "Lazy-expunge transaction failed: %s",
+ lt->delayed_internal_errstr);
+ mbox->super.transaction_rollback(ctx);
+ ret = -1;
+ }
+ lazy_expunge_transaction_free(lt);
+ return ret;
+}
+
+static void
+lazy_expunge_transaction_rollback(struct mailbox_transaction_context *ctx)
+{
+ union mailbox_module_context *mbox = LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx->box);
+ struct lazy_expunge_transaction *lt = LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx);
+
+ mbox->super.transaction_rollback(ctx);
+ lazy_expunge_transaction_free(lt);
+}
+
+static void lazy_expunge_mail_allocated(struct mail *_mail)
+{
+ struct lazy_expunge_transaction *lt =
+ LAZY_EXPUNGE_CONTEXT(_mail->transaction);
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ struct lazy_expunge_mail *mmail;
+
+ if (lt == NULL)
+ return;
+
+ mmail = p_new(mail->pool, struct lazy_expunge_mail, 1);
+ mmail->module_ctx.super = *v;
+ mail->vlast = &mmail->module_ctx.super;
+
+ v->expunge = lazy_expunge_mail_expunge;
+ MODULE_CONTEXT_SET(mail, lazy_expunge_mail_module, mmail);
+}
+
+static int
+lazy_expunge_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ union mailbox_module_context *lbox = LAZY_EXPUNGE_CONTEXT_REQUIRE(src);
+ struct lazy_expunge_mailbox_list *src_llist =
+ LAZY_EXPUNGE_LIST_CONTEXT(src->list);
+ struct lazy_expunge_mailbox_list *dest_llist =
+ LAZY_EXPUNGE_LIST_CONTEXT(dest->list);
+
+ i_assert(src_llist != NULL && dest_llist != NULL);
+
+ if (!src_llist->allow_rename &&
+ (src_llist->internal_namespace ||
+ dest_llist->internal_namespace)) {
+ mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename mailboxes to/from expunge namespace.");
+ return -1;
+ }
+ return lbox->super.rename_box(src, dest);
+}
+
+static void lazy_expunge_mailbox_allocated(struct mailbox *box)
+{
+ struct lazy_expunge_mailbox_list *llist =
+ LAZY_EXPUNGE_LIST_CONTEXT(box->list);
+ union mailbox_module_context *mbox;
+ struct mailbox_vfuncs *v = box->vlast;
+
+ if (llist == NULL || (box->flags & MAILBOX_FLAG_DELETE_UNSAFE) != 0)
+ return;
+
+ mbox = p_new(box->pool, union mailbox_module_context, 1);
+ mbox->super = *v;
+ box->vlast = &mbox->super;
+ MODULE_CONTEXT_SET_SELF(box, lazy_expunge_mail_storage_module, mbox);
+
+ if (!lazy_expunge_is_internal_mailbox(box)) {
+ v->copy = lazy_expunge_copy;
+ v->transaction_begin = lazy_expunge_transaction_begin;
+ v->transaction_commit = lazy_expunge_transaction_commit;
+ v->transaction_rollback = lazy_expunge_transaction_rollback;
+ v->rename_box = lazy_expunge_mailbox_rename;
+ } else if (llist->internal_namespace) {
+ v->rename_box = lazy_expunge_mailbox_rename;
+ } else {
+ /* internal mailbox in a non-internal namespace -
+ don't add any unnecessary restrictions to it. if it's not
+ wanted, just use the ACL plugin. */
+ }
+}
+
+static void lazy_expunge_mailbox_list_created(struct mailbox_list *list)
+{
+ struct lazy_expunge_mail_user *luser =
+ LAZY_EXPUNGE_USER_CONTEXT(list->ns->user);
+ struct lazy_expunge_mailbox_list *llist;
+
+ if (luser == NULL)
+ return;
+
+ /* if this is one of our internal namespaces, mark it as such before
+ quota plugin sees it */
+ if (strcmp(list->ns->prefix, luser->env) == 0)
+ list->ns->flags |= NAMESPACE_FLAG_NOQUOTA;
+
+ if (list->ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) {
+ llist = p_new(list->pool, struct lazy_expunge_mailbox_list, 1);
+ MODULE_CONTEXT_SET(list, lazy_expunge_mailbox_list_module,
+ llist);
+ }
+}
+
+static void
+lazy_expunge_mail_namespaces_created(struct mail_namespace *namespaces)
+{
+ struct lazy_expunge_mail_user *luser =
+ LAZY_EXPUNGE_USER_CONTEXT(namespaces->user);
+ struct lazy_expunge_mailbox_list *llist;
+
+ if (luser == NULL)
+ return;
+
+ luser->lazy_ns = mail_namespace_find_prefix(namespaces, luser->env);
+ if (luser->lazy_ns != NULL) {
+ /* we don't want to override this namespace's expunge operation. */
+ llist = LAZY_EXPUNGE_LIST_CONTEXT(luser->lazy_ns->list);
+ i_assert(llist != NULL);
+ llist->internal_namespace = TRUE;
+ } else {
+ /* store the the expunged mails to the specified mailbox. */
+ luser->lazy_ns = mail_namespace_find(namespaces, luser->env);
+ luser->lazy_mailbox_vname = luser->env;
+ }
+ mail_namespace_ref(luser->lazy_ns);
+}
+
+static void lazy_expunge_user_deinit(struct mail_user *user)
+{
+ struct lazy_expunge_mail_user *luser = LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(user);
+
+ /* mail_namespaces_created hook isn't necessarily ever called */
+ if (luser->lazy_ns != NULL)
+ mail_namespace_unref(&luser->lazy_ns);
+ mailbox_match_plugin_deinit(&luser->excludes);
+
+ luser->module_ctx.super.deinit(user);
+}
+
+static void lazy_expunge_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct lazy_expunge_mail_user *luser;
+ const char *env;
+
+ env = mail_user_plugin_getenv(user, "lazy_expunge");
+ if (env != NULL && env[0] != '\0') {
+ luser = p_new(user->pool, struct lazy_expunge_mail_user, 1);
+ luser->module_ctx.super = *v;
+ user->vlast = &luser->module_ctx.super;
+ v->deinit = lazy_expunge_user_deinit;
+ luser->env = env;
+ luser->copy_only_last_instance =
+ mail_user_plugin_getenv_bool(user, "lazy_expunge_only_last_instance");
+ luser->excludes = mailbox_match_plugin_init(user, "lazy_expunge_exclude");
+
+ MODULE_CONTEXT_SET(user, lazy_expunge_mail_user_module, luser);
+ } else {
+ e_debug(user->event, "lazy_expunge: No lazy_expunge setting - "
+ "plugin disabled");
+ }
+}
+
+static struct mail_storage_hooks lazy_expunge_mail_storage_hooks = {
+ .mail_user_created = lazy_expunge_mail_user_created,
+ .mail_namespaces_created = lazy_expunge_mail_namespaces_created,
+ .mailbox_list_created = lazy_expunge_mailbox_list_created,
+ .mailbox_allocated = lazy_expunge_mailbox_allocated,
+ .mail_allocated = lazy_expunge_mail_allocated
+};
+
+void lazy_expunge_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &lazy_expunge_mail_storage_hooks);
+}
+
+void lazy_expunge_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&lazy_expunge_mail_storage_hooks);
+}
diff --git a/src/plugins/lazy-expunge/lazy-expunge-plugin.h b/src/plugins/lazy-expunge/lazy-expunge-plugin.h
new file mode 100644
index 0000000..88ee53d
--- /dev/null
+++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.h
@@ -0,0 +1,7 @@
+#ifndef LAZY_EXPUNGE_PLUGIN_H
+#define LAZY_EXPUNGE_PLUGIN_H
+
+void lazy_expunge_plugin_init(struct module *module);
+void lazy_expunge_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/listescape/Makefile.am b/src/plugins/listescape/Makefile.am
new file mode 100644
index 0000000..fa614a3
--- /dev/null
+++ b/src/plugins/listescape/Makefile.am
@@ -0,0 +1,18 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+NOPLUGIN_LDFLAGS =
+lib20_listescape_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_listescape_plugin.la
+
+lib20_listescape_plugin_la_SOURCES = \
+ listescape-plugin.c
+
+noinst_HEADERS = \
+ listescape-plugin.h
diff --git a/src/plugins/listescape/Makefile.in b/src/plugins/listescape/Makefile.in
new file mode 100644
index 0000000..4e420e1
--- /dev/null
+++ b/src/plugins/listescape/Makefile.in
@@ -0,0 +1,821 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/listescape
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib20_listescape_plugin_la_LIBADD =
+am_lib20_listescape_plugin_la_OBJECTS = listescape-plugin.lo
+lib20_listescape_plugin_la_OBJECTS = \
+ $(am_lib20_listescape_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_listescape_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_listescape_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/listescape-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_listescape_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_listescape_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+lib20_listescape_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_listescape_plugin.la
+
+lib20_listescape_plugin_la_SOURCES = \
+ listescape-plugin.c
+
+noinst_HEADERS = \
+ listescape-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/listescape/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/listescape/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_listescape_plugin.la: $(lib20_listescape_plugin_la_OBJECTS) $(lib20_listescape_plugin_la_DEPENDENCIES) $(EXTRA_lib20_listescape_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_listescape_plugin_la_LINK) -rpath $(moduledir) $(lib20_listescape_plugin_la_OBJECTS) $(lib20_listescape_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listescape-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/listescape-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/listescape-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/listescape/listescape-plugin.c b/src/plugins/listescape/listescape-plugin.c
new file mode 100644
index 0000000..89fb1f9
--- /dev/null
+++ b/src/plugins/listescape/listescape-plugin.c
@@ -0,0 +1,36 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage-hooks.h"
+#include "mailbox-list-private.h"
+#include "listescape-plugin.h"
+
+#define DEFAULT_ESCAPE_CHAR '\\'
+
+const char *listescape_plugin_version = DOVECOT_ABI_VERSION;
+
+static void listescape_mailbox_list_created(struct mailbox_list *list)
+{
+ const char *env;
+
+ if (list->set.storage_name_escape_char == '\0') {
+ env = mail_user_plugin_getenv(list->ns->user, "listescape_char");
+ list->set.storage_name_escape_char =
+ env != NULL && *env != '\0' ?
+ env[0] : DEFAULT_ESCAPE_CHAR;
+ }
+}
+
+static struct mail_storage_hooks listescape_mail_storage_hooks = {
+ .mailbox_list_created = listescape_mailbox_list_created
+};
+
+void listescape_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &listescape_mail_storage_hooks);
+}
+
+void listescape_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&listescape_mail_storage_hooks);
+}
diff --git a/src/plugins/listescape/listescape-plugin.h b/src/plugins/listescape/listescape-plugin.h
new file mode 100644
index 0000000..f8ff4fc
--- /dev/null
+++ b/src/plugins/listescape/listescape-plugin.h
@@ -0,0 +1,7 @@
+#ifndef LISTESCAPE_PLUGIN_H
+#define LISTESCAPE_PLUGIN_H
+
+void listescape_plugin_init(struct module *module);
+void listescape_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/mail-crypt/Makefile.am b/src/plugins/mail-crypt/Makefile.am
new file mode 100644
index 0000000..942dc87
--- /dev/null
+++ b/src/plugins/mail-crypt/Makefile.am
@@ -0,0 +1,116 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-dcrypt \
+ -I$(top_srcdir)/src/lib-fs \
+ -I$(top_srcdir)/src/doveadm \
+ -I$(top_srcdir)/src/plugins/acl
+
+if SSL_VERSION_GE_102
+test_options =
+else !SSL_VERSION_GE_102
+test_options = NOUNDEF=1
+endif !SSL_VERSION_GE_102
+
+doveadm_moduledir = $(moduledir)/doveadm
+
+NOPLUGIN_LDFLAGS =
+
+module_LTLIBRARIES = \
+ lib10_mail_crypt_plugin.la \
+ lib05_mail_crypt_acl_plugin.la \
+ libfs_crypt.la \
+ libfs_mail_crypt.la
+
+doveadm_module_LTLIBRARIES = \
+ libdoveadm_mail_crypt_plugin.la
+
+lib10_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version
+lib10_mail_crypt_plugin_la_LIBADD = \
+ $(LIBDCRYPT_LIBS) \
+ $(LIBDOVECOT)
+
+lib05_mail_crypt_acl_plugin_la_LDFLAGS = -module -avoid-version
+if DOVECOT_PLUGIN_DEPS
+lib05_mail_crypt_acl_plugin_la_LIBADD = \
+ $(LIBDCRYPT_LIBS) \
+ lib10_mail_crypt_plugin.la
+endif
+
+lib10_mail_crypt_plugin_la_SOURCES = \
+ mail-crypt-global-key.c \
+ mail-crypt-userenv.c \
+ mail-crypt-key.c \
+ mail-crypt-plugin.c
+
+lib05_mail_crypt_acl_plugin_la_SOURCES = \
+ mail-crypt-acl-plugin.c
+
+libfs_crypt_la_SOURCES = fs-crypt.c \
+ mail-crypt-global-key.c \
+ mail-crypt-pluginenv.c \
+ fs-crypt-settings.c
+
+libfs_crypt_la_LIBADD = $(LIBDOVECOT)
+libfs_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libfs_crypt_la_LDFLAGS = -module -avoid-version
+
+libfs_mail_crypt_la_SOURCES = fs-mail-crypt.c \
+ mail-crypt-global-key.c \
+ mail-crypt-userenv.c
+libfs_mail_crypt_la_LIBADD = $(LIBDOVECOT)
+libfs_mail_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libfs_mail_crypt_la_LDFLAGS = -module -avoid-version
+
+libdoveadm_mail_crypt_plugin_la_SOURCES = \
+ doveadm-mail-crypt.c
+libdoveadm_mail_crypt_plugin_la_LIBADD = $(LIBDOVECOT)
+libdoveadm_mail_crypt_plugin_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libdoveadm_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version
+
+test_programs = \
+ test-mail-global-key \
+ test-mail-key
+
+test_mail_global_key_SOURCES = \
+ test-mail-global-key.c \
+ fs-crypt-settings.c \
+ mail-crypt-global-key.c
+test_mail_global_key_LDADD = $(LIBDOVECOT)
+test_mail_global_key_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+test_mail_global_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS)
+test_mail_global_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\"
+
+test_mail_key_SOURCES = \
+ test-mail-key.c \
+ mail-crypt-key.c \
+ mail-crypt-global-key.c \
+ mail-crypt-userenv.c
+
+test_mail_key_LDADD = $(LIBDOVECOT_STORAGE) $(LIBDOVECOT)
+test_mail_key_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(LIBDOVECOT_STORAGE_DEPS)
+test_mail_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS)
+test_mail_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\"
+
+EXTRA_DIST = fs-crypt-common.c
+
+noinst_HEADERS = \
+ mail-crypt-plugin.h \
+ mail-crypt-common.h \
+ mail-crypt-global-key.h \
+ mail-crypt-key.h \
+ fs-crypt-settings.h
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+noinst_PROGRAMS = $(test_programs)
diff --git a/src/plugins/mail-crypt/Makefile.in b/src/plugins/mail-crypt/Makefile.in
new file mode 100644
index 0000000..c815616
--- /dev/null
+++ b/src/plugins/mail-crypt/Makefile.in
@@ -0,0 +1,1200 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/plugins/mail-crypt
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-mail-global-key$(EXEEXT) test-mail-key$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(doveadm_moduledir)" \
+ "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES)
+@DOVECOT_PLUGIN_DEPS_TRUE@lib05_mail_crypt_acl_plugin_la_DEPENDENCIES = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ lib10_mail_crypt_plugin.la
+am_lib05_mail_crypt_acl_plugin_la_OBJECTS = mail-crypt-acl-plugin.lo
+lib05_mail_crypt_acl_plugin_la_OBJECTS = \
+ $(am_lib05_mail_crypt_acl_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib05_mail_crypt_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib05_mail_crypt_acl_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+am__DEPENDENCIES_1 =
+lib10_mail_crypt_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_lib10_mail_crypt_plugin_la_OBJECTS = mail-crypt-global-key.lo \
+ mail-crypt-userenv.lo mail-crypt-key.lo mail-crypt-plugin.lo
+lib10_mail_crypt_plugin_la_OBJECTS = \
+ $(am_lib10_mail_crypt_plugin_la_OBJECTS)
+lib10_mail_crypt_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib10_mail_crypt_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_libdoveadm_mail_crypt_plugin_la_OBJECTS = doveadm-mail-crypt.lo
+libdoveadm_mail_crypt_plugin_la_OBJECTS = \
+ $(am_libdoveadm_mail_crypt_plugin_la_OBJECTS)
+libdoveadm_mail_crypt_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(libdoveadm_mail_crypt_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+am_libfs_crypt_la_OBJECTS = fs-crypt.lo mail-crypt-global-key.lo \
+ mail-crypt-pluginenv.lo fs-crypt-settings.lo
+libfs_crypt_la_OBJECTS = $(am_libfs_crypt_la_OBJECTS)
+libfs_crypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libfs_crypt_la_LDFLAGS) $(LDFLAGS) -o \
+ $@
+am_libfs_mail_crypt_la_OBJECTS = fs-mail-crypt.lo \
+ mail-crypt-global-key.lo mail-crypt-userenv.lo
+libfs_mail_crypt_la_OBJECTS = $(am_libfs_mail_crypt_la_OBJECTS)
+libfs_mail_crypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libfs_mail_crypt_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_test_mail_global_key_OBJECTS = \
+ test_mail_global_key-test-mail-global-key.$(OBJEXT) \
+ test_mail_global_key-fs-crypt-settings.$(OBJEXT) \
+ test_mail_global_key-mail-crypt-global-key.$(OBJEXT)
+test_mail_global_key_OBJECTS = $(am_test_mail_global_key_OBJECTS)
+test_mail_global_key_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(test_mail_global_key_CFLAGS) $(CFLAGS) \
+ $(test_mail_global_key_LDFLAGS) $(LDFLAGS) -o $@
+am_test_mail_key_OBJECTS = test_mail_key-test-mail-key.$(OBJEXT) \
+ test_mail_key-mail-crypt-key.$(OBJEXT) \
+ test_mail_key-mail-crypt-global-key.$(OBJEXT) \
+ test_mail_key-mail-crypt-userenv.$(OBJEXT)
+test_mail_key_OBJECTS = $(am_test_mail_key_OBJECTS)
+test_mail_key_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_mail_key_CFLAGS) \
+ $(CFLAGS) $(test_mail_key_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/doveadm-mail-crypt.Plo \
+ ./$(DEPDIR)/fs-crypt-settings.Plo ./$(DEPDIR)/fs-crypt.Plo \
+ ./$(DEPDIR)/fs-mail-crypt.Plo \
+ ./$(DEPDIR)/mail-crypt-acl-plugin.Plo \
+ ./$(DEPDIR)/mail-crypt-global-key.Plo \
+ ./$(DEPDIR)/mail-crypt-key.Plo \
+ ./$(DEPDIR)/mail-crypt-plugin.Plo \
+ ./$(DEPDIR)/mail-crypt-pluginenv.Plo \
+ ./$(DEPDIR)/mail-crypt-userenv.Plo \
+ ./$(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po \
+ ./$(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po \
+ ./$(DEPDIR)/test_mail_global_key-test-mail-global-key.Po \
+ ./$(DEPDIR)/test_mail_key-mail-crypt-global-key.Po \
+ ./$(DEPDIR)/test_mail_key-mail-crypt-key.Po \
+ ./$(DEPDIR)/test_mail_key-mail-crypt-userenv.Po \
+ ./$(DEPDIR)/test_mail_key-test-mail-key.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib05_mail_crypt_acl_plugin_la_SOURCES) \
+ $(lib10_mail_crypt_plugin_la_SOURCES) \
+ $(libdoveadm_mail_crypt_plugin_la_SOURCES) \
+ $(libfs_crypt_la_SOURCES) $(libfs_mail_crypt_la_SOURCES) \
+ $(test_mail_global_key_SOURCES) $(test_mail_key_SOURCES)
+DIST_SOURCES = $(lib05_mail_crypt_acl_plugin_la_SOURCES) \
+ $(lib10_mail_crypt_plugin_la_SOURCES) \
+ $(libdoveadm_mail_crypt_plugin_la_SOURCES) \
+ $(libfs_crypt_la_SOURCES) $(libfs_mail_crypt_la_SOURCES) \
+ $(test_mail_global_key_SOURCES) $(test_mail_key_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-dcrypt \
+ -I$(top_srcdir)/src/lib-fs \
+ -I$(top_srcdir)/src/doveadm \
+ -I$(top_srcdir)/src/plugins/acl
+
+@SSL_VERSION_GE_102_FALSE@test_options = NOUNDEF=1
+@SSL_VERSION_GE_102_TRUE@test_options =
+doveadm_moduledir = $(moduledir)/doveadm
+module_LTLIBRARIES = \
+ lib10_mail_crypt_plugin.la \
+ lib05_mail_crypt_acl_plugin.la \
+ libfs_crypt.la \
+ libfs_mail_crypt.la
+
+doveadm_module_LTLIBRARIES = \
+ libdoveadm_mail_crypt_plugin.la
+
+lib10_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version
+lib10_mail_crypt_plugin_la_LIBADD = \
+ $(LIBDCRYPT_LIBS) \
+ $(LIBDOVECOT)
+
+lib05_mail_crypt_acl_plugin_la_LDFLAGS = -module -avoid-version
+@DOVECOT_PLUGIN_DEPS_TRUE@lib05_mail_crypt_acl_plugin_la_LIBADD = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ $(LIBDCRYPT_LIBS) \
+@DOVECOT_PLUGIN_DEPS_TRUE@ lib10_mail_crypt_plugin.la
+
+lib10_mail_crypt_plugin_la_SOURCES = \
+ mail-crypt-global-key.c \
+ mail-crypt-userenv.c \
+ mail-crypt-key.c \
+ mail-crypt-plugin.c
+
+lib05_mail_crypt_acl_plugin_la_SOURCES = \
+ mail-crypt-acl-plugin.c
+
+libfs_crypt_la_SOURCES = fs-crypt.c \
+ mail-crypt-global-key.c \
+ mail-crypt-pluginenv.c \
+ fs-crypt-settings.c
+
+libfs_crypt_la_LIBADD = $(LIBDOVECOT)
+libfs_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libfs_crypt_la_LDFLAGS = -module -avoid-version
+libfs_mail_crypt_la_SOURCES = fs-mail-crypt.c \
+ mail-crypt-global-key.c \
+ mail-crypt-userenv.c
+
+libfs_mail_crypt_la_LIBADD = $(LIBDOVECOT)
+libfs_mail_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libfs_mail_crypt_la_LDFLAGS = -module -avoid-version
+libdoveadm_mail_crypt_plugin_la_SOURCES = \
+ doveadm-mail-crypt.c
+
+libdoveadm_mail_crypt_plugin_la_LIBADD = $(LIBDOVECOT)
+libdoveadm_mail_crypt_plugin_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libdoveadm_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version
+test_programs = \
+ test-mail-global-key \
+ test-mail-key
+
+test_mail_global_key_SOURCES = \
+ test-mail-global-key.c \
+ fs-crypt-settings.c \
+ mail-crypt-global-key.c
+
+test_mail_global_key_LDADD = $(LIBDOVECOT)
+test_mail_global_key_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+test_mail_global_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS)
+test_mail_global_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\"
+test_mail_key_SOURCES = \
+ test-mail-key.c \
+ mail-crypt-key.c \
+ mail-crypt-global-key.c \
+ mail-crypt-userenv.c
+
+test_mail_key_LDADD = $(LIBDOVECOT_STORAGE) $(LIBDOVECOT)
+test_mail_key_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(LIBDOVECOT_STORAGE_DEPS)
+test_mail_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS)
+test_mail_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\"
+EXTRA_DIST = fs-crypt-common.c
+noinst_HEADERS = \
+ mail-crypt-plugin.h \
+ mail-crypt-common.h \
+ mail-crypt-global-key.h \
+ mail-crypt-key.h \
+ fs-crypt-settings.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/mail-crypt/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/mail-crypt/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \
+ }
+
+uninstall-doveadm_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \
+ done
+
+clean-doveadm_moduleLTLIBRARIES:
+ -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES)
+ @list='$(doveadm_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib05_mail_crypt_acl_plugin.la: $(lib05_mail_crypt_acl_plugin_la_OBJECTS) $(lib05_mail_crypt_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib05_mail_crypt_acl_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib05_mail_crypt_acl_plugin_la_LINK) -rpath $(moduledir) $(lib05_mail_crypt_acl_plugin_la_OBJECTS) $(lib05_mail_crypt_acl_plugin_la_LIBADD) $(LIBS)
+
+lib10_mail_crypt_plugin.la: $(lib10_mail_crypt_plugin_la_OBJECTS) $(lib10_mail_crypt_plugin_la_DEPENDENCIES) $(EXTRA_lib10_mail_crypt_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib10_mail_crypt_plugin_la_LINK) -rpath $(moduledir) $(lib10_mail_crypt_plugin_la_OBJECTS) $(lib10_mail_crypt_plugin_la_LIBADD) $(LIBS)
+
+libdoveadm_mail_crypt_plugin.la: $(libdoveadm_mail_crypt_plugin_la_OBJECTS) $(libdoveadm_mail_crypt_plugin_la_DEPENDENCIES) $(EXTRA_libdoveadm_mail_crypt_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libdoveadm_mail_crypt_plugin_la_LINK) -rpath $(doveadm_moduledir) $(libdoveadm_mail_crypt_plugin_la_OBJECTS) $(libdoveadm_mail_crypt_plugin_la_LIBADD) $(LIBS)
+
+libfs_crypt.la: $(libfs_crypt_la_OBJECTS) $(libfs_crypt_la_DEPENDENCIES) $(EXTRA_libfs_crypt_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libfs_crypt_la_LINK) -rpath $(moduledir) $(libfs_crypt_la_OBJECTS) $(libfs_crypt_la_LIBADD) $(LIBS)
+
+libfs_mail_crypt.la: $(libfs_mail_crypt_la_OBJECTS) $(libfs_mail_crypt_la_DEPENDENCIES) $(EXTRA_libfs_mail_crypt_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libfs_mail_crypt_la_LINK) -rpath $(moduledir) $(libfs_mail_crypt_la_OBJECTS) $(libfs_mail_crypt_la_LIBADD) $(LIBS)
+
+test-mail-global-key$(EXEEXT): $(test_mail_global_key_OBJECTS) $(test_mail_global_key_DEPENDENCIES) $(EXTRA_test_mail_global_key_DEPENDENCIES)
+ @rm -f test-mail-global-key$(EXEEXT)
+ $(AM_V_CCLD)$(test_mail_global_key_LINK) $(test_mail_global_key_OBJECTS) $(test_mail_global_key_LDADD) $(LIBS)
+
+test-mail-key$(EXEEXT): $(test_mail_key_OBJECTS) $(test_mail_key_DEPENDENCIES) $(EXTRA_test_mail_key_DEPENDENCIES)
+ @rm -f test-mail-key$(EXEEXT)
+ $(AM_V_CCLD)$(test_mail_key_LINK) $(test_mail_key_OBJECTS) $(test_mail_key_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-crypt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fs-crypt-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fs-crypt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fs-mail-crypt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-acl-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-global-key.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-key.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-pluginenv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-userenv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_global_key-test-mail-global-key.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_key-mail-crypt-global-key.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_key-mail-crypt-key.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_key-mail-crypt-userenv.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_key-test-mail-key.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+test_mail_global_key-test-mail-global-key.o: test-mail-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-test-mail-global-key.o -MD -MP -MF $(DEPDIR)/test_mail_global_key-test-mail-global-key.Tpo -c -o test_mail_global_key-test-mail-global-key.o `test -f 'test-mail-global-key.c' || echo '$(srcdir)/'`test-mail-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-test-mail-global-key.Tpo $(DEPDIR)/test_mail_global_key-test-mail-global-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mail-global-key.c' object='test_mail_global_key-test-mail-global-key.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-test-mail-global-key.o `test -f 'test-mail-global-key.c' || echo '$(srcdir)/'`test-mail-global-key.c
+
+test_mail_global_key-test-mail-global-key.obj: test-mail-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-test-mail-global-key.obj -MD -MP -MF $(DEPDIR)/test_mail_global_key-test-mail-global-key.Tpo -c -o test_mail_global_key-test-mail-global-key.obj `if test -f 'test-mail-global-key.c'; then $(CYGPATH_W) 'test-mail-global-key.c'; else $(CYGPATH_W) '$(srcdir)/test-mail-global-key.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-test-mail-global-key.Tpo $(DEPDIR)/test_mail_global_key-test-mail-global-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mail-global-key.c' object='test_mail_global_key-test-mail-global-key.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-test-mail-global-key.obj `if test -f 'test-mail-global-key.c'; then $(CYGPATH_W) 'test-mail-global-key.c'; else $(CYGPATH_W) '$(srcdir)/test-mail-global-key.c'; fi`
+
+test_mail_global_key-fs-crypt-settings.o: fs-crypt-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-fs-crypt-settings.o -MD -MP -MF $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Tpo -c -o test_mail_global_key-fs-crypt-settings.o `test -f 'fs-crypt-settings.c' || echo '$(srcdir)/'`fs-crypt-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Tpo $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fs-crypt-settings.c' object='test_mail_global_key-fs-crypt-settings.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-fs-crypt-settings.o `test -f 'fs-crypt-settings.c' || echo '$(srcdir)/'`fs-crypt-settings.c
+
+test_mail_global_key-fs-crypt-settings.obj: fs-crypt-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-fs-crypt-settings.obj -MD -MP -MF $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Tpo -c -o test_mail_global_key-fs-crypt-settings.obj `if test -f 'fs-crypt-settings.c'; then $(CYGPATH_W) 'fs-crypt-settings.c'; else $(CYGPATH_W) '$(srcdir)/fs-crypt-settings.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Tpo $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fs-crypt-settings.c' object='test_mail_global_key-fs-crypt-settings.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-fs-crypt-settings.obj `if test -f 'fs-crypt-settings.c'; then $(CYGPATH_W) 'fs-crypt-settings.c'; else $(CYGPATH_W) '$(srcdir)/fs-crypt-settings.c'; fi`
+
+test_mail_global_key-mail-crypt-global-key.o: mail-crypt-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-mail-crypt-global-key.o -MD -MP -MF $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Tpo -c -o test_mail_global_key-mail-crypt-global-key.o `test -f 'mail-crypt-global-key.c' || echo '$(srcdir)/'`mail-crypt-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Tpo $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-global-key.c' object='test_mail_global_key-mail-crypt-global-key.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-mail-crypt-global-key.o `test -f 'mail-crypt-global-key.c' || echo '$(srcdir)/'`mail-crypt-global-key.c
+
+test_mail_global_key-mail-crypt-global-key.obj: mail-crypt-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-mail-crypt-global-key.obj -MD -MP -MF $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Tpo -c -o test_mail_global_key-mail-crypt-global-key.obj `if test -f 'mail-crypt-global-key.c'; then $(CYGPATH_W) 'mail-crypt-global-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-global-key.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Tpo $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-global-key.c' object='test_mail_global_key-mail-crypt-global-key.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-mail-crypt-global-key.obj `if test -f 'mail-crypt-global-key.c'; then $(CYGPATH_W) 'mail-crypt-global-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-global-key.c'; fi`
+
+test_mail_key-test-mail-key.o: test-mail-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-test-mail-key.o -MD -MP -MF $(DEPDIR)/test_mail_key-test-mail-key.Tpo -c -o test_mail_key-test-mail-key.o `test -f 'test-mail-key.c' || echo '$(srcdir)/'`test-mail-key.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-test-mail-key.Tpo $(DEPDIR)/test_mail_key-test-mail-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mail-key.c' object='test_mail_key-test-mail-key.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-test-mail-key.o `test -f 'test-mail-key.c' || echo '$(srcdir)/'`test-mail-key.c
+
+test_mail_key-test-mail-key.obj: test-mail-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-test-mail-key.obj -MD -MP -MF $(DEPDIR)/test_mail_key-test-mail-key.Tpo -c -o test_mail_key-test-mail-key.obj `if test -f 'test-mail-key.c'; then $(CYGPATH_W) 'test-mail-key.c'; else $(CYGPATH_W) '$(srcdir)/test-mail-key.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-test-mail-key.Tpo $(DEPDIR)/test_mail_key-test-mail-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mail-key.c' object='test_mail_key-test-mail-key.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-test-mail-key.obj `if test -f 'test-mail-key.c'; then $(CYGPATH_W) 'test-mail-key.c'; else $(CYGPATH_W) '$(srcdir)/test-mail-key.c'; fi`
+
+test_mail_key-mail-crypt-key.o: mail-crypt-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-key.o -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-key.Tpo -c -o test_mail_key-mail-crypt-key.o `test -f 'mail-crypt-key.c' || echo '$(srcdir)/'`mail-crypt-key.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-key.Tpo $(DEPDIR)/test_mail_key-mail-crypt-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-key.c' object='test_mail_key-mail-crypt-key.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-key.o `test -f 'mail-crypt-key.c' || echo '$(srcdir)/'`mail-crypt-key.c
+
+test_mail_key-mail-crypt-key.obj: mail-crypt-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-key.obj -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-key.Tpo -c -o test_mail_key-mail-crypt-key.obj `if test -f 'mail-crypt-key.c'; then $(CYGPATH_W) 'mail-crypt-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-key.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-key.Tpo $(DEPDIR)/test_mail_key-mail-crypt-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-key.c' object='test_mail_key-mail-crypt-key.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-key.obj `if test -f 'mail-crypt-key.c'; then $(CYGPATH_W) 'mail-crypt-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-key.c'; fi`
+
+test_mail_key-mail-crypt-global-key.o: mail-crypt-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-global-key.o -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-global-key.Tpo -c -o test_mail_key-mail-crypt-global-key.o `test -f 'mail-crypt-global-key.c' || echo '$(srcdir)/'`mail-crypt-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-global-key.Tpo $(DEPDIR)/test_mail_key-mail-crypt-global-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-global-key.c' object='test_mail_key-mail-crypt-global-key.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-global-key.o `test -f 'mail-crypt-global-key.c' || echo '$(srcdir)/'`mail-crypt-global-key.c
+
+test_mail_key-mail-crypt-global-key.obj: mail-crypt-global-key.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-global-key.obj -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-global-key.Tpo -c -o test_mail_key-mail-crypt-global-key.obj `if test -f 'mail-crypt-global-key.c'; then $(CYGPATH_W) 'mail-crypt-global-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-global-key.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-global-key.Tpo $(DEPDIR)/test_mail_key-mail-crypt-global-key.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-global-key.c' object='test_mail_key-mail-crypt-global-key.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-global-key.obj `if test -f 'mail-crypt-global-key.c'; then $(CYGPATH_W) 'mail-crypt-global-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-global-key.c'; fi`
+
+test_mail_key-mail-crypt-userenv.o: mail-crypt-userenv.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-userenv.o -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-userenv.Tpo -c -o test_mail_key-mail-crypt-userenv.o `test -f 'mail-crypt-userenv.c' || echo '$(srcdir)/'`mail-crypt-userenv.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-userenv.Tpo $(DEPDIR)/test_mail_key-mail-crypt-userenv.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-userenv.c' object='test_mail_key-mail-crypt-userenv.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-userenv.o `test -f 'mail-crypt-userenv.c' || echo '$(srcdir)/'`mail-crypt-userenv.c
+
+test_mail_key-mail-crypt-userenv.obj: mail-crypt-userenv.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-userenv.obj -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-userenv.Tpo -c -o test_mail_key-mail-crypt-userenv.obj `if test -f 'mail-crypt-userenv.c'; then $(CYGPATH_W) 'mail-crypt-userenv.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-userenv.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-userenv.Tpo $(DEPDIR)/test_mail_key-mail-crypt-userenv.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-userenv.c' object='test_mail_key-mail-crypt-userenv.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-userenv.obj `if test -f 'mail-crypt-userenv.c'; then $(CYGPATH_W) 'mail-crypt-userenv.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-userenv.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/doveadm-mail-crypt.Plo
+ -rm -f ./$(DEPDIR)/fs-crypt-settings.Plo
+ -rm -f ./$(DEPDIR)/fs-crypt.Plo
+ -rm -f ./$(DEPDIR)/fs-mail-crypt.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-acl-plugin.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-global-key.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-key.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-plugin.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-pluginenv.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-userenv.Plo
+ -rm -f ./$(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po
+ -rm -f ./$(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po
+ -rm -f ./$(DEPDIR)/test_mail_global_key-test-mail-global-key.Po
+ -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-global-key.Po
+ -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-key.Po
+ -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-userenv.Po
+ -rm -f ./$(DEPDIR)/test_mail_key-test-mail-key.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-doveadm_moduleLTLIBRARIES \
+ install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/doveadm-mail-crypt.Plo
+ -rm -f ./$(DEPDIR)/fs-crypt-settings.Plo
+ -rm -f ./$(DEPDIR)/fs-crypt.Plo
+ -rm -f ./$(DEPDIR)/fs-mail-crypt.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-acl-plugin.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-global-key.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-key.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-plugin.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-pluginenv.Plo
+ -rm -f ./$(DEPDIR)/mail-crypt-userenv.Plo
+ -rm -f ./$(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po
+ -rm -f ./$(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po
+ -rm -f ./$(DEPDIR)/test_mail_global_key-test-mail-global-key.Po
+ -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-global-key.Po
+ -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-key.Po
+ -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-userenv.Po
+ -rm -f ./$(DEPDIR)/test_mail_key-test-mail-key.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-doveadm_moduleLTLIBRARIES \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am \
+ install-doveadm_moduleLTLIBRARIES install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/mail-crypt/doveadm-mail-crypt.c b/src/plugins/mail-crypt/doveadm-mail-crypt.c
new file mode 100644
index 0000000..a4322ed
--- /dev/null
+++ b/src/plugins/mail-crypt/doveadm-mail-crypt.c
@@ -0,0 +1,1048 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "askpass.h"
+#include "doveadm-mail.h"
+#include "getopt.h"
+#include "array.h"
+#include "str.h"
+#include "buffer.h"
+#include "ioloop.h"
+#include "ioloop-private.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-storage-settings.h"
+#include "mailbox-attribute.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "mailbox-list-iter.h"
+#include "doveadm-print.h"
+#include "hex-binary.h"
+
+#define DOVEADM_MCP_SUCCESS "\xE2\x9C\x93" /* emits a utf-8 CHECK MARK (U+2713) */
+#define DOVEADM_MCP_FAIL "x"
+#define DOVEADM_MCP_USERKEY "<userkey>"
+
+struct generated_key {
+ const char *name;
+ const char *id;
+ const char *error;
+ struct mailbox *box;
+ bool success:1;
+ bool active:1;
+};
+
+ARRAY_DEFINE_TYPE(generated_keys, struct generated_key);
+
+struct mcp_cmd_context {
+ struct doveadm_mail_cmd_context ctx;
+
+ const char *old_password;
+ const char *new_password;
+
+ unsigned int matched_keys;
+
+ bool userkey_only:1;
+ bool recrypt_box_keys:1;
+ bool force:1;
+ bool ask_old_password:1;
+ bool ask_new_password:1;
+ bool clear_password:1;
+};
+
+struct mcp_key_iter_ctx {
+ pool_t pool;
+ ARRAY_TYPE(generated_keys) keys;
+};
+
+void doveadm_mail_crypt_plugin_init(struct module *mod ATTR_UNUSED);
+void doveadm_mail_crypt_plugin_deinit(void);
+
+static int
+mcp_user_create(struct mail_user *user, const char *dest_username,
+ struct mail_user **dest_user_r,
+ struct mail_storage_service_user **dest_service_user_r,
+ const char **error_r)
+{
+ const struct mail_storage_service_input *old_input;
+ struct mail_storage_service_input input;
+ struct mail_storage_service_ctx *service_ctx;
+ struct ioloop_context *cur_ioloop_ctx;
+
+ int ret;
+
+ i_assert(user->_service_user != NULL);
+ service_ctx = mail_storage_service_user_get_service_ctx(user->_service_user);
+ old_input = mail_storage_service_user_get_input(user->_service_user);
+
+ if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL)
+ io_loop_context_deactivate(cur_ioloop_ctx);
+
+ i_zero(&input);
+ input.module = old_input->module;
+ input.service = old_input->service;
+ input.username = dest_username;
+ input.session_id_prefix = user->session_id;
+ input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS |
+ MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT;
+
+ ret = mail_storage_service_lookup_next(service_ctx, &input,
+ dest_service_user_r,
+ dest_user_r, error_r);
+
+ if (ret == 0)
+ *error_r = "User not found";
+
+ return ret;
+}
+
+static int
+mcp_update_shared_key(struct mailbox_transaction_context *t,
+ struct mail_user *user, const char *target_uid,
+ struct dcrypt_private_key *key, const char **error_r)
+{
+ const char *error;
+ struct mail_user *dest_user;
+ struct mail_storage_service_user *dest_service_user;
+ struct ioloop_context *cur_ioloop_ctx;
+ struct dcrypt_public_key *pkey;
+ const char *dest_username;
+ int ret = 0;
+
+ bool disallow_insecure = mail_crypt_acl_secure_sharing_enabled(user);
+
+ ret = mcp_user_create(user, target_uid, &dest_user,
+ &dest_service_user, &error);
+
+ /* to make sure we get correct logging context */
+ if (ret > 0)
+ mail_storage_service_io_deactivate_user(dest_service_user);
+ mail_storage_service_io_activate_user(user->_service_user);
+
+ if (ret <= 0) {
+ i_error("Cannot initialize destination user %s: %s",
+ target_uid, error);
+ return ret;
+ } else {
+ i_assert(dest_user != NULL);
+ dest_username = dest_user->username;
+
+ /* get public key from target user */
+ if ((ret = mail_crypt_user_get_public_key(dest_user,
+ &pkey, error_r)) <= 0) {
+ if (ret == 0 && disallow_insecure) {
+ *error_r = t_strdup_printf("User %s has no active public key",
+ dest_user->username);
+ ret = -1;
+ } else if (ret == 0) {
+ /* perform insecure sharing */
+ dest_username = NULL;
+ pkey = NULL;
+ ret = 1;
+ }
+ }
+
+ if (ret == 1) {
+ ARRAY_TYPE(dcrypt_private_key) keys;
+ t_array_init(&keys, 1);
+ array_push_back(&keys, &key);
+ ret = mail_crypt_box_share_private_keys(t, pkey,
+ dest_username,
+ &keys, error_r);
+ }
+
+ }
+
+ /* logging context swap again */
+ mail_storage_service_io_deactivate_user(user->_service_user);
+ mail_storage_service_io_activate_user(dest_service_user);
+
+ mail_user_deinit(&dest_user);
+ mail_storage_service_user_unref(&dest_service_user);
+
+ if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL)
+ io_loop_context_deactivate(cur_ioloop_ctx);
+
+ mail_storage_service_io_activate_user(user->_service_user);
+
+ return ret;
+}
+
+static int mcp_update_shared_keys(struct doveadm_mail_cmd_context *ctx,
+ struct mailbox *box, struct mail_user *user,
+ const char *pubid, struct dcrypt_private_key *key)
+{
+ const char *error;
+ int ret;
+
+ ARRAY_TYPE(const_string) ids;
+ t_array_init(&ids, 8);
+
+ /* figure out who needs the key */
+ if (mail_crypt_box_get_pvt_digests(box, pool_datastack_create(),
+ MAIL_ATTRIBUTE_TYPE_SHARED,
+ &ids, &error) < 0) {
+ i_error("mail_crypt_box_get_pvt_digests(%s, /shared) failed: %s",
+ mailbox_get_vname(box),
+ error);
+ return -1;
+ }
+
+ const char *id;
+ bool found = FALSE;
+ string_t *uid = t_str_new(64);
+
+ struct mailbox_transaction_context *t =
+ mailbox_transaction_begin(box, ctx->transaction_flags, __func__);
+
+ ret = 0;
+
+ /* then perform sharing */
+ array_foreach_elem(&ids, id) {
+ if (strchr(id, '/') != NULL) {
+ str_truncate(uid, 0);
+ const char *hexuid = t_strcut(id, '/');
+ hex_to_binary(hexuid, uid);
+ if (mcp_update_shared_key(t, user, str_c(uid), key,
+ &error) < 0) {
+ i_error("mcp_update_shared_key(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ str_c(uid),
+ error);
+ ret = -1;
+ break;
+ }
+ } else if (!found) {
+ found = TRUE;
+ if (mail_crypt_box_set_shared_key(t, pubid, key,
+ NULL, NULL,
+ &error) < 0) {
+ i_error("mail_crypt_box_set_shared_key(%s) failed: %s",
+ mailbox_get_vname(box),
+ error);
+ ret = -1;
+ break;
+ }
+ }
+ }
+
+ if (ret < 0) {
+ mailbox_transaction_rollback(&t);
+ } else if (mailbox_transaction_commit(&t) < 0) {
+ i_error("mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(box),
+ error);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int mcp_keypair_generate(struct mcp_cmd_context *ctx,
+ struct dcrypt_public_key *user_key,
+ struct mailbox *box, struct dcrypt_keypair *pair_r,
+ const char **pubid_r, const char **error_r)
+{
+ struct dcrypt_keypair pair = {NULL, NULL};
+
+ int ret;
+
+ if ((ret = mail_crypt_box_get_public_key(box, &pair.pub, error_r)) < 0) {
+ ret = -1;
+ } else if (ret == 1 && !ctx->force) {
+ i_info("Folder key exists. Use -f to generate a new one");
+ buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE);
+ const char *error;
+ if (!dcrypt_key_id_public(pair.pub,
+ MAIL_CRYPT_KEY_ID_ALGORITHM,
+ key_id, &error)) {
+ i_error("dcrypt_key_id_public() failed: %s",
+ error);
+ return -1;
+ }
+ *pubid_r = p_strdup(ctx->ctx.pool, binary_to_hex(key_id->data,
+ key_id->used));
+ *pair_r = pair;
+ return 1;
+ } else if (ret == 1 && ctx->recrypt_box_keys) {
+ /* do nothing, because force isn't being used *OR*
+ we are recrypting box keys and force refers to
+ user keypair.
+
+ FIXME: this could be less confusing altogether */
+ ret = 0;
+ } else {
+ if (mail_crypt_box_generate_keypair(box, &pair, user_key,
+ pubid_r, error_r) < 0) {
+ ret = -1;
+ } else {
+ *pubid_r = p_strdup(ctx->ctx.pool, *pubid_r);
+ *pair_r = pair;
+ return 1;
+ }
+ }
+
+ if (pair.pub != NULL)
+ dcrypt_key_unref_public(&pair.pub);
+ if (pair.priv != NULL)
+ dcrypt_key_unref_private(&pair.priv);
+
+ return ret;
+}
+
+static int mcp_keypair_generate_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user,
+ ARRAY_TYPE(generated_keys) *result)
+{
+ const char *error;
+ int ret;
+ struct dcrypt_public_key *user_key;
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+ const char *pubid;
+ bool user_key_generated = FALSE;
+ struct generated_key *res;
+
+ if ((ret = mail_crypt_user_get_public_key(user, &user_key,
+ &error)) <= 0) {
+ struct dcrypt_keypair pair;
+ if (ret < 0) {
+ i_error("mail_crypt_user_get_public_key(%s) failed: %s",
+ user->username,
+ error);
+ } else if (mail_crypt_user_generate_keypair(user, &pair,
+ &pubid, &error) < 0) {
+ ret = -1;
+ i_error("mail_crypt_user_generate_keypair(%s) failed: %s",
+ user->username,
+ error);
+ res = array_append_space(result);
+ res->name = "";
+ res->error = p_strdup(_ctx->pool, error);
+ res->success = FALSE;
+ } else {
+ res = array_append_space(result);
+ res->name = DOVEADM_MCP_USERKEY;
+ res->id = p_strdup(_ctx->pool, pubid);
+ res->success = TRUE;
+ /* don't do it again later on */
+ user_key_generated = TRUE;
+ ret = 1;
+ user_key = pair.pub;
+ dcrypt_key_unref_private(&pair.priv);
+ }
+ if (ret < 0) return ret;
+ ctx->matched_keys++;
+ }
+ if (ret == 1 && ctx->userkey_only && !user_key_generated) {
+ if (!ctx->force) {
+ i_info("userkey exists. Use -f to generate a new one");
+ buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE);
+ if (!dcrypt_key_id_public(user_key,
+ MAIL_CRYPT_KEY_ID_ALGORITHM,
+ key_id, &error)) {
+ i_error("dcrypt_key_id_public() failed: %s",
+ error);
+ dcrypt_key_unref_public(&user_key);
+ return -1;
+ }
+ const char *hash = binary_to_hex(key_id->data,
+ key_id->used);
+ res = array_append_space(result);
+ res->name = DOVEADM_MCP_USERKEY;
+ res->id = p_strdup(_ctx->pool, hash);
+ res->success = TRUE;
+ ctx->matched_keys++;
+ dcrypt_key_unref_public(&user_key);
+ return 1;
+ }
+ struct dcrypt_keypair pair;
+ dcrypt_key_unref_public(&user_key);
+ /* regen user key */
+ res = array_append_space(result);
+ res->name = DOVEADM_MCP_USERKEY;
+ if (mail_crypt_user_generate_keypair(user, &pair, &pubid,
+ &error) < 0) {
+ res->success = FALSE;
+ res->error = p_strdup(_ctx->pool, error);
+ return -1;
+ }
+ res->success = TRUE;
+ res->id = p_strdup(_ctx->pool, pubid);
+ user_key = pair.pub;
+ dcrypt_key_unref_private(&pair.priv);
+ ctx->matched_keys++;
+ }
+
+ if (ctx->userkey_only) {
+ dcrypt_key_unref_public(&user_key);
+ return 0;
+ }
+
+ const char *const *patterns = (const char *const[]){ "*", NULL };
+
+ /* only re-encrypt all folder keys if wanted */
+ if (!ctx->recrypt_box_keys) {
+ patterns = ctx->ctx.args;
+ }
+
+ const struct mailbox_info *info;
+ struct mailbox_list_iterate_context *iter =
+ mailbox_list_iter_init_namespaces(user->namespaces,
+ patterns,
+ MAIL_NAMESPACE_TYPE_PRIVATE,
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ while((info = mailbox_list_iter_next(iter)) != NULL) {
+ if ((info->flags & MAILBOX_NOSELECT) != 0 ||
+ (info->flags & MAILBOX_NONEXISTENT) != 0) continue;
+ struct dcrypt_keypair pair;
+
+ struct mailbox *box =
+ mailbox_alloc(info->ns->list,
+ info->vname, 0);
+ if (mailbox_open(box) < 0) {
+ res = array_append_space(result);
+ res->name = p_strdup(_ctx->pool, info->vname);
+ res->success = FALSE;
+ res->error = p_strdup(_ctx->pool,
+ mailbox_get_last_internal_error(box, NULL));
+ } else if ((ret = mcp_keypair_generate(ctx, user_key, box,
+ &pair, &pubid,
+ &error)) < 0) {
+ res = array_append_space(result);
+ res->name = p_strdup(_ctx->pool, info->vname);
+ res->success = FALSE;
+ res->error = p_strdup(_ctx->pool, error);
+ } else if (ret == 0) {
+ /* nothing happened because key already existed and
+ force wasn't used, skip */
+ } else if (ret > 0) {
+ res = array_append_space(result);
+ res->name = p_strdup(_ctx->pool, info->vname);
+ res->success = TRUE;
+ res->id = pubid;
+ T_BEGIN {
+ mcp_update_shared_keys(&ctx->ctx, box, user,
+ pubid, pair.priv);
+ } T_END;
+ if (pair.pub != NULL)
+ dcrypt_key_unref_public(&pair.pub);
+ if (pair.priv != NULL)
+ dcrypt_key_unref_private(&pair.priv);
+ ctx->matched_keys++;
+ }
+ mailbox_free(&box);
+ }
+
+ (void)mailbox_list_iter_deinit(&iter);
+
+ dcrypt_key_unref_public(&user_key);
+ return 0;
+}
+
+static int cmd_mcp_keypair_generate_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ int ret = 0;
+
+ ARRAY_TYPE(generated_keys) result;
+ p_array_init(&result, _ctx->pool, 8);
+
+ if (mcp_keypair_generate_run(_ctx, user, &result) < 0)
+ _ctx->exit_code = EX_DATAERR;
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ doveadm_print_header("success", " ", 0);
+ doveadm_print_header("box", "Folder", 0);
+ doveadm_print_header("pubid", "Public ID", 0);
+
+ const struct generated_key *res;
+
+ array_foreach(&result, res) {
+ if (res->success)
+ doveadm_print(DOVEADM_MCP_SUCCESS);
+ else {
+ _ctx->exit_code = EX_DATAERR;
+ ret = -1;
+ doveadm_print(DOVEADM_MCP_FAIL);
+ }
+ doveadm_print(res->name);
+ if (!res->success)
+ doveadm_print(t_strdup_printf("ERROR: %s", res->error));
+ else
+ doveadm_print(res->id);
+ }
+
+ if (ctx->matched_keys == 0)
+ i_warning("mailbox cryptokey generate: Nothing was matched. "
+ "Use -U or specify mask?");
+ return ret;
+}
+
+static void mcp_key_list(struct mcp_cmd_context *ctx,
+ struct mail_user *user,
+ void(*callback)(const struct generated_key *, void *),
+ void *context)
+{
+ const char *error;
+
+ /* we need to use the mailbox attribute API here, as we
+ are not necessarily able to decrypt any of these keys
+ */
+
+ ARRAY_TYPE(const_string) ids;
+ t_array_init(&ids, 8);
+
+ if (ctx->userkey_only) {
+ struct mailbox_attribute_iter *iter;
+ struct mail_namespace *ns =
+ mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box =
+ mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY);
+ struct mail_attribute_value value;
+ i_zero(&value);
+
+ if (mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value) < 0) {
+ i_error("mailbox_get_attribute(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+
+ iter = mailbox_attribute_iter_init(box,
+ MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ USER_CRYPT_PREFIX
+ PRIVKEYS_PREFIX);
+ const char *key_id;
+ if (value.value == NULL)
+ value.value = "<NO ACTIVE KEY>";
+ while ((key_id = mailbox_attribute_iter_next(iter)) != NULL) {
+ struct generated_key key;
+ key.id = key_id;
+ key.active = strcmp(value.value, key_id) == 0;
+ key.name = "";
+ key.box = box;
+ callback(&key, context);
+ ctx->matched_keys++;
+ }
+ if (mailbox_attribute_iter_deinit(&iter) < 0)
+ i_error("mailbox_attribute_iter_deinit(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL));
+ mailbox_free(&box);
+ return;
+ }
+
+ const struct mailbox_info *info;
+ struct mailbox_list_iterate_context *iter =
+ mailbox_list_iter_init_namespaces(user->namespaces,
+ ctx->ctx.args,
+ MAIL_NAMESPACE_TYPE_PRIVATE,
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+
+ while((info = mailbox_list_iter_next(iter)) != NULL) {
+ if ((info->flags & MAILBOX_NOSELECT) != 0 ||
+ (info->flags & MAILBOX_NONEXISTENT) != 0) continue;
+
+ struct mailbox *box =
+ mailbox_alloc(info->ns->list,
+ info->vname, MAILBOX_FLAG_READONLY);
+ struct mail_attribute_value value;
+ i_zero(&value);
+ array_clear(&ids);
+
+ /* get active ID */
+ if (mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value) < 0) {
+ i_error("mailbox_get_attribute(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ } else if (mail_crypt_box_get_pvt_digests(box, pool_datastack_create(),
+ MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ &ids, &error) < 0) {
+ i_error("mail_crypt_box_get_pvt_digests(%s) failed: %s",
+ mailbox_get_vname(box),
+ error);
+ } else {
+ const char *id;
+ const char *boxname = mailbox_get_vname(box);
+ if (value.value == NULL)
+ value.value = "<NO ACTIVE KEY>";
+ array_foreach_elem(&ids, id) {
+ struct generated_key key;
+ key.name = boxname;
+ key.id = id;
+ if (value.value != NULL)
+ key.active = strcmp(id, value.value) == 0;
+ else
+ key.active = FALSE;
+ key.box = box;
+ callback(&key, context);
+ ctx->matched_keys++;
+ }
+ }
+ mailbox_free(&box);
+ }
+
+ (void)mailbox_list_iter_deinit(&iter);
+}
+
+static void cmd_mcp_key_list_cb(const struct generated_key *_key, void *context)
+{
+ struct mcp_key_iter_ctx *ctx = context;
+ struct generated_key *key = array_append_space(&ctx->keys);
+ key->name = p_strdup(ctx->pool, _key->name);
+ key->id = p_strdup(ctx->pool, _key->id);
+ key->active = _key->active;
+}
+
+static int cmd_mcp_key_list_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+ struct mcp_key_iter_ctx iter_ctx;
+ i_zero(&iter_ctx);
+ iter_ctx.pool = _ctx->pool;
+ p_array_init(&iter_ctx.keys, _ctx->pool, 8);
+
+ mcp_key_list(ctx, user, cmd_mcp_key_list_cb, &iter_ctx);
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ doveadm_print_header("box", "Folder", 0);
+ doveadm_print_header("active", "Active", 0);
+ doveadm_print_header("pubid", "Public ID", 0);
+
+ const struct generated_key *key;
+ array_foreach(&iter_ctx.keys, key) {
+ doveadm_print(key->name);
+ doveadm_print(key->active ? "yes" : "no");
+ doveadm_print(key->id);
+ }
+
+ if (ctx->matched_keys == 0)
+ i_warning("mailbox cryptokey list: Nothing was matched. "
+ "Use -U or specify mask?");
+
+ return 0;
+}
+
+static void cmd_mcp_key_export_cb(const struct generated_key *key,
+ void *context ATTR_UNUSED)
+{
+ struct dcrypt_private_key *pkey;
+ bool user_key = FALSE;
+ const char *error = NULL;
+ int ret;
+
+ if (*key->name == '\0')
+ user_key = TRUE;
+
+ doveadm_print(key->name);
+ doveadm_print(key->id);
+
+ if ((ret = mail_crypt_get_private_key(key->box, key->id, user_key, FALSE,
+ &pkey, &error)) <= 0) {
+ if (ret == 0)
+ error = "key not found";
+ doveadm_print(t_strdup_printf("ERROR: %s", error));
+ doveadm_print("");
+ } else {
+ string_t *out = t_str_new(64);
+ if (!dcrypt_key_store_private(pkey, DCRYPT_FORMAT_PEM, NULL, out,
+ NULL, NULL, &error)) {
+ doveadm_print(t_strdup_printf("ERROR: %s", error));
+ doveadm_print("");
+ } else {
+ /* this is to make it more compatible with openssl cli
+ as it expects BEGIN on it's own line */
+ doveadm_print(t_strdup_printf("\n%s", str_c(out)));
+ }
+ dcrypt_key_unref_private(&pkey);
+ }
+}
+
+static int cmd_mcp_key_export_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER);
+ doveadm_print_header("box", "Folder", 0);
+ doveadm_print_header("name", "Public ID", 0);
+ doveadm_print_header("error", "Error", 0);
+ doveadm_print_header("key", "Key", 0);
+
+ mcp_key_list(ctx, user, cmd_mcp_key_export_cb, NULL);
+
+ return 0;
+}
+
+static int cmd_mcp_key_password_run(struct doveadm_mail_cmd_context *_ctx,
+ struct mail_user *user)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+ bool cli = (_ctx->cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI);
+
+ struct raw_key {
+ const char *attr;
+ const char *id;
+ const char *data;
+ };
+
+ ARRAY(struct raw_key) raw_keys;
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER);
+
+ doveadm_print_header_simple("result");
+
+ if (ctx->ask_old_password) {
+ if (ctx->old_password != NULL) {
+ doveadm_print("old password specified, cannot ask for it");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ if (!cli) {
+ doveadm_print("No cli - cannot ask for password");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ ctx->old_password =
+ p_strdup(_ctx->pool, t_askpass("Old password: "));
+ }
+
+ if (ctx->ask_new_password) {
+ if (ctx->new_password != NULL) {
+ doveadm_print("new password specified, cannot ask for it");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ if (!cli) {
+ doveadm_print("No cli - cannot ask for password");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ const char *passw;
+ passw = t_askpass("New password: ");
+ if (strcmp(passw, t_askpass("Confirm new password: ")) != 0) {
+ doveadm_print("Passwords don't match, aborting");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+ ctx->new_password = p_strdup(_ctx->pool, passw);
+ }
+
+ if (ctx->clear_password &&
+ (ctx->new_password != NULL ||
+ mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD) != NULL)) {
+ doveadm_print("clear password and new password specified");
+ _ctx->exit_code = EX_USAGE;
+ return -1;
+ }
+
+ struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX", 0);
+ if (mailbox_open(box) < 0) {
+ doveadm_print(t_strdup_printf("mailbox_open(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL)));
+ _ctx->exit_code = EX_TEMPFAIL;
+ return -1;
+ }
+
+ t_array_init(&raw_keys, 8);
+
+ /* then get the current user keys, all of them */
+ struct mailbox_attribute_iter *iter =
+ mailbox_attribute_iter_init(box,
+ MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ USER_CRYPT_PREFIX
+ PRIVKEYS_PREFIX);
+ const char *error;
+ const char *key_id;
+ int ret = 1;
+ unsigned int count = 0;
+
+ while ((key_id = mailbox_attribute_iter_next(iter)) != NULL) {
+ const char *attr =
+ t_strdup_printf(USER_CRYPT_PREFIX PRIVKEYS_PREFIX "%s",
+ key_id);
+
+ struct mail_attribute_value value;
+ if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ attr, &value)) < 0) {
+ doveadm_print(t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s",
+ mailbox_get_vname(box), attr,
+ mailbox_get_last_internal_error(box, NULL)));
+ _ctx->exit_code = EX_TEMPFAIL;
+ break;
+ } else if (ret > 0) {
+ struct raw_key *raw_key = array_append_space(&raw_keys);
+ raw_key->attr = p_strdup(_ctx->pool, attr);
+ raw_key->id = p_strdup(_ctx->pool, key_id);
+ raw_key->data = p_strdup(_ctx->pool, value.value);
+ }
+ }
+
+ if (ret == 1) {
+ struct mailbox_transaction_context *t =
+ mailbox_transaction_begin(box, _ctx->transaction_flags,
+ __func__);
+ struct dcrypt_private_key *key;
+ const struct raw_key *raw_key;
+ const char *algo = ctx->new_password != NULL ?
+ MAIL_CRYPT_PW_CIPHER :
+ NULL;
+ string_t *newkey = t_str_new(256);
+
+ array_foreach(&raw_keys, raw_key) {
+ struct mail_attribute_value value;
+
+ if (!dcrypt_key_load_private(&key, raw_key->data,
+ ctx->old_password, NULL,
+ &error)) {
+ doveadm_print(t_strdup_printf("dcrypt_key_load_private(%s) failed: %s",
+ raw_key->id,
+ error));
+ _ctx->exit_code = EX_DATAERR;
+ ret = -1;
+ break;
+ }
+
+ /* save it */
+ str_truncate(newkey, 0);
+
+ if (!dcrypt_key_store_private(key, DCRYPT_FORMAT_DOVECOT,
+ algo, newkey,
+ ctx->new_password,
+ NULL, &error)) {
+ doveadm_print(t_strdup_printf("dcrypt_key_store_private(%s) failed: %s",
+ raw_key->id,
+ error));
+ _ctx->exit_code = EX_DATAERR;
+ ret = -1;
+ }
+
+ dcrypt_key_unref_private(&key);
+ if (ret == -1) break;
+
+ i_zero(&value);
+ value.value = str_c(newkey);
+
+ /* and store it */
+ if (mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ raw_key->attr, &value) < 0) {
+ doveadm_print(t_strdup_printf("mailbox_attribute_set(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ raw_key->attr,
+ mailbox_get_last_internal_error(box, NULL)));
+ _ctx->exit_code = EX_TEMPFAIL;
+ ret = -1;
+ break;
+ }
+ count++;
+ }
+
+ if (ret < 1) {
+ mailbox_transaction_rollback(&t);
+ } else {
+ if (mailbox_transaction_commit(&t) < 0) {
+ doveadm_print(t_strdup_printf("mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL)));
+ } else {
+ doveadm_print(t_strdup_printf("Changed password for %u key(s)",
+ count));
+ }
+ }
+ }
+
+ (void)mailbox_attribute_iter_deinit(&iter);
+ mailbox_free(&box);
+
+ return ret;
+}
+
+
+static bool cmd_mcp_keypair_generate_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ switch (c) {
+ case 'U':
+ ctx->userkey_only = TRUE;
+ break;
+ case 'R':
+ ctx->recrypt_box_keys = TRUE;
+ break;
+ case 'f':
+ ctx->force = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+
+}
+
+static bool cmd_mcp_key_password_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ switch (c) {
+ case 'N':
+ ctx->ask_new_password = TRUE;
+ break;
+ case 'O':
+ ctx->ask_old_password = TRUE;
+ break;
+ case 'C':
+ ctx->clear_password = TRUE;
+ break;
+ case 'o':
+ ctx->old_password = p_strdup(_ctx->pool, optarg);
+ break;
+ case 'n':
+ ctx->new_password = p_strdup(_ctx->pool, optarg);
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool cmd_mcp_key_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+ struct mcp_cmd_context *ctx =
+ (struct mcp_cmd_context *)_ctx;
+
+ switch (c) {
+ case 'U':
+ ctx->userkey_only = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+
+}
+
+static struct doveadm_mail_cmd_context *cmd_mcp_keypair_generate_alloc(void)
+{
+ struct mcp_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context);
+ ctx->ctx.getopt_args = "URf";
+ ctx->ctx.v.parse_arg = cmd_mcp_keypair_generate_parse_arg;
+ ctx->ctx.v.run = cmd_mcp_keypair_generate_run;
+ return &ctx->ctx;
+}
+
+static struct doveadm_mail_cmd_context *cmd_mcp_key_list_alloc(void)
+{
+ struct mcp_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context);
+ ctx->ctx.getopt_args = "U";
+ ctx->ctx.v.parse_arg = cmd_mcp_key_parse_arg;
+ ctx->ctx.v.run = cmd_mcp_key_list_run;
+ return &ctx->ctx;
+}
+
+static struct doveadm_mail_cmd_context *cmd_mcp_key_export_alloc(void)
+{
+ struct mcp_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context);
+ ctx->ctx.getopt_args = "U";
+ ctx->ctx.v.parse_arg = cmd_mcp_key_parse_arg;
+ ctx->ctx.v.run = cmd_mcp_key_export_run;
+ return &ctx->ctx;
+}
+
+static struct doveadm_mail_cmd_context *cmd_mcp_key_password_alloc(void)
+{
+ struct mcp_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context);
+ ctx->ctx.getopt_args = "NOCo:n:";
+ ctx->ctx.v.parse_arg = cmd_mcp_key_password_parse_arg;
+ ctx->ctx.v.run = cmd_mcp_key_password_run;
+ return &ctx->ctx;
+}
+
+struct doveadm_cmd_ver2 doveadm_cmd_mcp_keypair_generate = {
+ .name = "mailbox cryptokey generate",
+ .mail_cmd = cmd_mcp_keypair_generate_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-URf] mailbox [ mailbox .. ]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('U', "user-key-only", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('R', "re-encrypt-box-keys", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('f', "force", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_list = {
+ .name = "mailbox cryptokey list",
+ .mail_cmd = cmd_mcp_key_list_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "-U | mailbox [ mailbox .. ]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('U', "user-key", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_export = {
+ .name = "mailbox cryptokey export",
+ .mail_cmd = cmd_mcp_key_export_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "-U | mailbox [ mailbox .. ]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('U', "user-key", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_password = {
+ .name = "mailbox cryptokey password",
+ .mail_cmd = cmd_mcp_key_password_alloc,
+ .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-NOC] [-opassword] [-npassword]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('C', "clear-password", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('N', "ask-new-password", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('n', "new-password", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('O', "ask-old-password", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('o', "old-password", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAMS_END
+};
+
+void doveadm_mail_crypt_plugin_init(struct module *mod ATTR_UNUSED)
+{
+ doveadm_cmd_register_ver2(&doveadm_cmd_mcp_keypair_generate);
+ doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_list);
+ doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_export);
+ doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_password);
+}
+
+void doveadm_mail_crypt_plugin_deinit(void)
+{
+}
diff --git a/src/plugins/mail-crypt/fs-crypt-common.c b/src/plugins/mail-crypt/fs-crypt-common.c
new file mode 100644
index 0000000..7432fa6
--- /dev/null
+++ b/src/plugins/mail-crypt/fs-crypt-common.c
@@ -0,0 +1,368 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "randgen.h"
+#include "istream.h"
+#include "ostream.h"
+#include "istream-decrypt.h"
+#include "ostream-encrypt.h"
+#include "iostream-temp.h"
+#include "mailbox-list.h"
+#include "mail-namespace.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "dcrypt-iostream.h"
+#include "fs-api-private.h"
+
+struct crypt_fs {
+ struct fs fs;
+ struct mail_crypt_global_keys keys;
+ bool keys_loaded;
+
+ char *enc_algo;
+ char *set_prefix;
+ char *public_key_path;
+ char *private_key_path;
+ char *password;
+};
+
+struct crypt_fs_file {
+ struct fs_file file;
+ struct crypt_fs *fs;
+ struct fs_file *super_read;
+ enum fs_open_mode open_mode;
+ struct istream *input;
+
+ struct ostream *super_output;
+ struct ostream *temp_output;
+};
+
+#define CRYPT_FS(ptr) container_of((ptr), struct crypt_fs, fs)
+#define CRYPT_FILE(ptr) container_of((ptr), struct crypt_fs_file, file)
+
+/* defined outside this file */
+extern const struct fs FS_CLASS_CRYPT;
+
+static
+int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r);
+
+static struct fs *fs_crypt_alloc(void)
+{
+ struct crypt_fs *fs;
+
+ fs = i_new(struct crypt_fs, 1);
+ fs->fs = FS_CLASS_CRYPT;
+
+ return &fs->fs;
+}
+
+static int
+fs_crypt_init(struct fs *_fs, const char *args, const struct fs_settings *set,
+ const char **error_r)
+{
+ struct crypt_fs *fs = CRYPT_FS(_fs);
+ const char *enc_algo, *set_prefix;
+ const char *p, *arg, *value, *error, *parent_name, *parent_args;
+ const char *public_key_path = "", *private_key_path = "", *password = "";
+
+ if (!dcrypt_initialize("openssl", NULL, &error))
+ i_fatal("dcrypt_initialize(): %s", error);
+
+ /* [algo=<s>:][set_prefix=<n>:][public_key_path=<s>:]
+ [private_key_path=<s>:[password=<s>:]]<parent fs> */
+ set_prefix = "mail_crypt_global";
+ enc_algo = "aes-256-gcm-sha256";
+ for (;;) {
+ p = strchr(args, ':');
+ if (p == NULL) {
+ *error_r = "Missing parameters";
+ return -1;
+ }
+ arg = t_strdup_until(args, p);
+ if ((value = strchr(arg, '=')) == NULL)
+ break;
+ arg = t_strdup_until(arg, value++);
+ args = p+1;
+
+ if (strcmp(arg, "algo") == 0)
+ enc_algo = value;
+ else if (strcmp(arg, "set_prefix") == 0)
+ set_prefix = value;
+ else if (strcmp(arg, "public_key_path") == 0)
+ public_key_path = value;
+ else if (strcmp(arg, "private_key_path") == 0)
+ private_key_path = value;
+ else if (strcmp(arg, "password") == 0)
+ password = value;
+ else {
+ *error_r = t_strdup_printf(
+ "Invalid parameter '%s'", arg);
+ return -1;
+ }
+ }
+
+ parent_args = strchr(args, ':');
+ if (parent_args == NULL) {
+ parent_name = args;
+ parent_args = "";
+ } else {
+ parent_name = t_strdup_until(args, parent_args);
+ parent_args++;
+ }
+ if (fs_init(parent_name, parent_args, set, &_fs->parent, error_r) < 0)
+ return -1;
+ fs->enc_algo = i_strdup(enc_algo);
+ fs->set_prefix = i_strdup(set_prefix);
+ fs->public_key_path = i_strdup_empty(public_key_path);
+ fs->private_key_path = i_strdup_empty(private_key_path);
+ fs->password = i_strdup_empty(password);
+ return 0;
+}
+
+static void fs_crypt_free(struct fs *_fs)
+{
+ struct crypt_fs *fs = CRYPT_FS(_fs);
+
+ mail_crypt_global_keys_free(&fs->keys);
+ i_free(fs->enc_algo);
+ i_free(fs->set_prefix);
+ i_free(fs->public_key_path);
+ i_free(fs->private_key_path);
+ i_free(fs->password);
+ i_free(fs);
+}
+
+static struct fs_file *fs_crypt_file_alloc(void)
+{
+ struct crypt_fs_file *file = i_new(struct crypt_fs_file, 1);
+ return &file->file;
+}
+
+static void
+fs_crypt_file_init(struct fs_file *_file, const char *path,
+ enum fs_open_mode mode, enum fs_open_flags flags)
+{
+ struct crypt_fs *fs = CRYPT_FS(_file->fs);
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+ file->file.path = i_strdup(path);
+ file->fs = fs;
+ file->open_mode = mode;
+
+ /* avoid unnecessarily creating two seekable streams */
+ flags &= ENUM_NEGATE(FS_OPEN_FLAG_SEEKABLE);
+
+ file->file.parent = fs_file_init_parent(_file, path, mode, flags);
+ if (mode == FS_OPEN_MODE_READONLY &&
+ (flags & FS_OPEN_FLAG_ASYNC) == 0) {
+ /* use async stream for super, so fs_read_stream() won't create
+ another seekable stream needlessly */
+ file->super_read = fs_file_init_parent(_file, path,
+ mode, flags | FS_OPEN_FLAG_ASYNC |
+ FS_OPEN_FLAG_ASYNC_NOQUEUE);
+ } else {
+ file->super_read = file->file.parent;
+ }
+}
+
+static void fs_crypt_file_deinit(struct fs_file *_file)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+ if (file->super_read != _file->parent)
+ fs_file_deinit(&file->super_read);
+ fs_file_free(_file);
+ i_free(file->file.path);
+ i_free(file);
+}
+
+static void fs_crypt_file_close(struct fs_file *_file)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+
+ i_stream_unref(&file->input);
+ fs_file_close(file->super_read);
+ fs_file_close(_file->parent);
+}
+
+static int fs_crypt_read_file(const char *set_name, const char *path,
+ char **key_data_r, const char **error_r)
+{
+ struct istream *input;
+ int ret;
+
+ input = i_stream_create_file(path, SIZE_MAX);
+ while (i_stream_read(input) > 0) ;
+ if (input->stream_errno != 0) {
+ *error_r = t_strdup_printf("%s: read(%s) failed: %s",
+ set_name, path, i_stream_get_error(input));
+ ret = -1;
+ } else {
+ size_t size;
+ const unsigned char *data = i_stream_get_data(input, &size);
+ *key_data_r = i_strndup(data, size);
+ ret = 0;
+ }
+ i_stream_unref(&input);
+ return ret;
+}
+
+static int
+fs_crypt_load_keys_from_path(struct crypt_fs *fs, const char **error_r)
+{
+ char *key_data;
+
+ mail_crypt_global_keys_init(&fs->keys);
+ if (fs->public_key_path != NULL) {
+ if (fs_crypt_read_file("crypt:public_key_path",
+ fs->public_key_path,
+ &key_data, error_r) < 0)
+ return -1;
+ if (mail_crypt_load_global_public_key("crypt:public_key_path",
+ key_data, &fs->keys,
+ error_r) < 0) {
+ i_free(key_data);
+ return -1;
+ }
+ i_free(key_data);
+ }
+ if (fs->private_key_path != NULL) {
+ if (fs_crypt_read_file("crypt:private_key_path",
+ fs->private_key_path,
+ &key_data, error_r) < 0)
+ return -1;
+ if (mail_crypt_load_global_private_key("crypt:private_key_path",
+ key_data, "crypt:password",
+ fs->password, &fs->keys,
+ error_r) < 0) {
+ i_free(key_data);
+ return -1;
+ }
+ i_free(key_data);
+ }
+ return 0;
+}
+
+static int
+fs_crypt_istream_get_key(const char *pubkey_digest,
+ struct dcrypt_private_key **priv_key_r,
+ const char **error_r, void *context)
+{
+ struct crypt_fs_file *file = context;
+
+ if (fs_crypt_load_keys(file->fs, error_r) < 0)
+ return -1;
+
+ *priv_key_r = mail_crypt_global_key_find(&file->fs->keys, pubkey_digest);
+ if (*priv_key_r == NULL)
+ return 0;
+ dcrypt_key_ref_private(*priv_key_r);
+ return 1;
+}
+
+static struct istream *
+fs_crypt_read_stream(struct fs_file *_file, size_t max_buffer_size)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+ struct istream *input;
+
+ if (file->input != NULL) {
+ i_stream_ref(file->input);
+ i_stream_seek(file->input, 0);
+ return file->input;
+ }
+
+ input = fs_read_stream(file->super_read, max_buffer_size);
+
+ file->input = i_stream_create_decrypt_callback(input,
+ fs_crypt_istream_get_key, file);
+ i_stream_unref(&input);
+ i_stream_ref(file->input);
+ return file->input;
+}
+
+static void fs_crypt_write_stream(struct fs_file *_file)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+ const char *error;
+
+ i_assert(_file->output == NULL);
+
+ if (fs_crypt_load_keys(file->fs, &error) < 0) {
+ _file->output = o_stream_create_error_str(EIO,
+ "Couldn't read settings: %s", error);
+ return;
+ }
+
+ if (file->fs->keys.public_key == NULL) {
+ if (_file->fs->set.debug)
+ i_debug("No public key provided, "
+ "NOT encrypting stream %s",
+ fs_file_path(_file));
+ file->super_output = fs_write_stream(_file->parent);
+ _file->output = file->super_output;
+ return;
+ }
+
+ enum io_stream_encrypt_flags flags;
+ if (strstr(file->fs->enc_algo, "gcm") != NULL ||
+ strstr(file->fs->enc_algo, "ccm") != NULL) {
+ flags = IO_STREAM_ENC_INTEGRITY_AEAD;
+ } else {
+ flags = IO_STREAM_ENC_INTEGRITY_HMAC;
+ }
+
+ file->temp_output =
+ iostream_temp_create_named(_file->fs->temp_path_prefix,
+ IOSTREAM_TEMP_FLAG_TRY_FD_DUP,
+ fs_file_path(_file));
+ _file->output = o_stream_create_encrypt(file->temp_output,
+ file->fs->enc_algo, file->fs->keys.public_key,
+ flags);
+}
+
+static int fs_crypt_write_stream_finish(struct fs_file *_file, bool success)
+{
+ struct crypt_fs_file *file = CRYPT_FILE(_file);
+ struct istream *input;
+ int ret;
+
+ if (_file->output != NULL) {
+ if (_file->output == file->super_output)
+ _file->output = NULL;
+ else
+ o_stream_unref(&_file->output);
+ }
+ if (!success) {
+ if (file->super_output != NULL) {
+ /* no encryption */
+ i_assert(file->temp_output == NULL);
+ fs_write_stream_abort_error(_file->parent, &file->super_output,
+ "write(%s) failed: %s",
+ o_stream_get_name(file->super_output),
+ o_stream_get_error(file->super_output));
+ } else {
+ o_stream_destroy(&file->temp_output);
+ }
+ return -1;
+ }
+
+ if (file->super_output != NULL) {
+ /* no encrypt */
+ i_assert(file->temp_output == NULL);
+ return fs_write_stream_finish(_file->parent, &file->super_output);
+ }
+ if (file->temp_output == NULL) {
+ /* finishing up */
+ i_assert(file->super_output == NULL);
+ return fs_write_stream_finish_async(_file->parent);
+ }
+
+ /* finish writing the temporary file */
+ input = iostream_temp_finish(&file->temp_output, IO_BLOCK_SIZE);
+ file->super_output = fs_write_stream(_file->parent);
+ o_stream_nsend_istream(file->super_output, input);
+ ret = fs_write_stream_finish(_file->parent, &file->super_output);
+ i_stream_unref(&input);
+ return ret;
+}
diff --git a/src/plugins/mail-crypt/fs-crypt-settings.c b/src/plugins/mail-crypt/fs-crypt-settings.c
new file mode 100644
index 0000000..ba70f8a
--- /dev/null
+++ b/src/plugins/mail-crypt/fs-crypt-settings.c
@@ -0,0 +1,35 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "mail-storage-settings.h"
+#include "fs-crypt-settings.h"
+
+static const struct setting_define fs_crypt_setting_defines[] = {
+ { .type = SET_STRLIST, .key = "plugin",
+ .offset = offsetof(struct fs_crypt_settings, plugin_envs) },
+
+ SETTING_DEFINE_LIST_END
+};
+
+const struct fs_crypt_settings fs_crypt_default_settings = {
+ .plugin_envs = ARRAY_INIT
+};
+
+static const struct setting_parser_info *fs_crypt_setting_dependencies[] = {
+ NULL
+};
+
+const struct setting_parser_info fs_crypt_setting_parser_info = {
+ .module_name = "fs-crypt",
+ .defines = fs_crypt_setting_defines,
+ .defaults = &fs_crypt_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct fs_crypt_settings),
+
+ .parent_offset = SIZE_MAX,
+ .dependencies = fs_crypt_setting_dependencies
+};
diff --git a/src/plugins/mail-crypt/fs-crypt-settings.h b/src/plugins/mail-crypt/fs-crypt-settings.h
new file mode 100644
index 0000000..df1a7b1
--- /dev/null
+++ b/src/plugins/mail-crypt/fs-crypt-settings.h
@@ -0,0 +1,11 @@
+#ifndef FS_CRYPT_SETTINGS_H
+#define FS_CRYPT_SETTINGS_H
+
+struct fs_crypt_settings {
+ ARRAY(const char *) plugin_envs;
+};
+
+extern const struct setting_parser_info fs_crypt_setting_parser_info;
+
+#endif
+
diff --git a/src/plugins/mail-crypt/fs-crypt.c b/src/plugins/mail-crypt/fs-crypt.c
new file mode 100644
index 0000000..0a81c69
--- /dev/null
+++ b/src/plugins/mail-crypt/fs-crypt.c
@@ -0,0 +1,65 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+#define FS_CLASS_CRYPT fs_class_crypt
+#include "fs-crypt-common.c"
+
+static
+int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r)
+{
+ const char *error;
+
+ if (fs->keys_loaded)
+ return 0;
+ if (fs->public_key_path != NULL || fs->private_key_path != NULL) {
+ /* overrides using settings */
+ if (fs_crypt_load_keys_from_path(fs, error_r) < 0)
+ return -1;
+ fs->keys_loaded = TRUE;
+ return 0;
+ }
+ if (mail_crypt_global_keys_load_pluginenv(fs->set_prefix, &fs->keys,
+ &error) < 0) {
+ *error_r = t_strdup_printf("%s: %s", fs->set_prefix, error);
+ return -1;
+ }
+ fs->keys_loaded = TRUE;
+ return 0;
+}
+
+const struct fs fs_class_crypt = {
+ .name = "crypt",
+ .v = {
+ fs_crypt_alloc,
+ fs_crypt_init,
+ NULL,
+ fs_crypt_free,
+ fs_wrapper_get_properties,
+ fs_crypt_file_alloc,
+ fs_crypt_file_init,
+ fs_crypt_file_deinit,
+ fs_crypt_file_close,
+ fs_wrapper_file_get_path,
+ fs_wrapper_set_async_callback,
+ fs_wrapper_wait_async,
+ fs_wrapper_set_metadata,
+ fs_wrapper_get_metadata,
+ fs_wrapper_prefetch,
+ fs_read_via_stream,
+ fs_crypt_read_stream,
+ fs_write_via_stream,
+ fs_crypt_write_stream,
+ fs_crypt_write_stream_finish,
+ fs_wrapper_lock,
+ fs_wrapper_unlock,
+ fs_wrapper_exists,
+ fs_wrapper_stat,
+ fs_wrapper_copy,
+ fs_wrapper_rename,
+ fs_wrapper_delete,
+ fs_wrapper_iter_alloc,
+ fs_wrapper_iter_init,
+ fs_wrapper_iter_next,
+ fs_wrapper_iter_deinit,
+ NULL,
+ fs_wrapper_get_nlinks,
+ }
+};
diff --git a/src/plugins/mail-crypt/fs-mail-crypt.c b/src/plugins/mail-crypt/fs-mail-crypt.c
new file mode 100644
index 0000000..b017d3f
--- /dev/null
+++ b/src/plugins/mail-crypt/fs-mail-crypt.c
@@ -0,0 +1,72 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+#define FS_CLASS_CRYPT fs_class_mail_crypt
+#include "fs-crypt-common.c"
+
+static
+int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r)
+{
+ struct mailbox_list *list = mailbox_list_fs_get_list(&fs->fs);
+ const char *error;
+
+ if (fs->keys_loaded)
+ return 0;
+ if (fs->public_key_path != NULL || fs->private_key_path != NULL) {
+ /* overrides using settings */
+ if (fs_crypt_load_keys_from_path(fs, error_r) < 0)
+ return -1;
+ fs->keys_loaded = TRUE;
+ return 0;
+ }
+ if (list == NULL) {
+ *error_r = "fs-mail-crypt can be used only via lib-storage";
+ return -1;
+ }
+
+ if (mail_crypt_global_keys_load(mailbox_list_get_namespace(list)->user,
+ fs->set_prefix, &fs->keys, FALSE,
+ &error) < 0) {
+ *error_r = t_strdup_printf("%s: %s", fs->set_prefix, error);
+ return -1;
+ }
+ fs->keys_loaded = TRUE;
+ return 0;
+}
+
+const struct fs fs_class_mail_crypt = {
+ .name = "mail-crypt",
+ .v = {
+ fs_crypt_alloc,
+ fs_crypt_init,
+ NULL,
+ fs_crypt_free,
+ fs_wrapper_get_properties,
+ fs_crypt_file_alloc,
+ fs_crypt_file_init,
+ fs_crypt_file_deinit,
+ fs_crypt_file_close,
+ fs_wrapper_file_get_path,
+ fs_wrapper_set_async_callback,
+ fs_wrapper_wait_async,
+ fs_wrapper_set_metadata,
+ fs_wrapper_get_metadata,
+ fs_wrapper_prefetch,
+ fs_read_via_stream,
+ fs_crypt_read_stream,
+ fs_write_via_stream,
+ fs_crypt_write_stream,
+ fs_crypt_write_stream_finish,
+ fs_wrapper_lock,
+ fs_wrapper_unlock,
+ fs_wrapper_exists,
+ fs_wrapper_stat,
+ fs_wrapper_copy,
+ fs_wrapper_rename,
+ fs_wrapper_delete,
+ fs_wrapper_iter_alloc,
+ fs_wrapper_iter_init,
+ fs_wrapper_iter_next,
+ fs_wrapper_iter_deinit,
+ NULL,
+ fs_wrapper_get_nlinks,
+ }
+};
diff --git a/src/plugins/mail-crypt/mail-crypt-acl-plugin.c b/src/plugins/mail-crypt/mail-crypt-acl-plugin.c
new file mode 100644
index 0000000..71ab1e7
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-acl-plugin.c
@@ -0,0 +1,431 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop-private.h"
+#include "str.h"
+#include "sha2.h"
+#include "module-dir.h"
+#include "var-expand.h"
+#include "hex-binary.h"
+#include "mail-namespace.h"
+#include "mail-storage-hooks.h"
+#include "mail-storage-service.h"
+#include "acl-plugin.h"
+#include "acl-api-private.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "mail-crypt-plugin.h"
+
+#define MAIL_CRYPT_ACL_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mail_crypt_acl_mailbox_list_module)
+
+struct mail_crypt_acl_mailbox_list {
+ union mailbox_list_module_context module_ctx;
+ struct acl_backend_vfuncs acl_vprev;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_acl_mailbox_list_module,
+ &mailbox_list_module_register);
+
+void mail_crypt_acl_plugin_init(struct module *module);
+void mail_crypt_acl_plugin_deinit(void);
+
+static int
+mail_crypt_acl_has_user_read_right(struct acl_object *aclobj,
+ const char *username,
+ const char **error_r)
+{
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+ int ret = 0;
+
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ if (rights.id_type == ACL_ID_USER &&
+ strcmp(rights.identifier, username) == 0) {
+ ret = str_array_find(rights.rights, MAIL_ACL_READ) ? 1 : 0;
+ break;
+ }
+ }
+ if (acl_object_list_deinit(&iter) < 0) {
+ *error_r = "Failed to iterate ACL objects";
+ return -1;
+ }
+
+ return ret;
+}
+
+static int mail_crypt_acl_has_nonuser_read_right(struct acl_object *aclobj,
+ const char **error_r)
+{
+ struct acl_object_list_iter *iter;
+ struct acl_rights rights;
+ int ret = 0;
+
+ iter = acl_object_list_init(aclobj);
+ while (acl_object_list_next(iter, &rights)) {
+ if (rights.id_type != ACL_ID_USER &&
+ rights.id_type != ACL_ID_OWNER &&
+ rights.rights != NULL &&
+ str_array_find(rights.rights, MAIL_ACL_READ)) {
+ ret = 1;
+ break;
+ }
+ }
+ if (acl_object_list_deinit(&iter) < 0) {
+ *error_r = "Failed to iterate ACL objects";
+ return -1;
+ }
+ return ret;
+}
+
+static int
+mail_crypt_acl_unset_private_keys(struct mailbox *src_box,
+ const char *dest_user,
+ enum mail_attribute_type type,
+ const char **error_r)
+{
+ ARRAY_TYPE(const_string) digests;
+ const char *error;
+ int ret = 1;
+
+ if (mailbox_open(src_box) < 0) {
+ *error_r = t_strdup_printf("mail-crypt-acl-plugin: "
+ "mailbox_open(%s) failed: %s",
+ mailbox_get_vname(src_box),
+ mailbox_get_last_internal_error(src_box, NULL));
+ return -1;
+ }
+
+ t_array_init(&digests, 4);
+ if (mail_crypt_box_get_pvt_digests(src_box, pool_datastack_create(),
+ type, &digests, &error) < 0) {
+ *error_r = t_strdup_printf("mail-crypt-acl-plugin: "
+ "Failed to lookup public key digests: %s",
+ error);
+ mailbox_free(&src_box);
+ return -1;
+ }
+
+ struct mailbox_transaction_context *t;
+ t = mailbox_transaction_begin(src_box, 0, __func__);
+
+ const char *hash;
+ array_foreach_elem(&digests, hash) {
+ const char *ptr;
+ /* if the id contains username part, skip to key public id */
+ if ((ptr = strchr(hash, '/')) != NULL)
+ ptr++;
+ else
+ ptr = hash;
+ if ((ret = mail_crypt_box_unset_shared_key(t, ptr, dest_user,
+ error_r)) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+
+ if (ret < 0) {
+ mailbox_transaction_rollback(&t);
+ } else if (mailbox_transaction_commit(&t) < 0) {
+ *error_r = t_strdup_printf("mail-crypt-acl-plugin: "
+ "mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(src_box),
+ mailbox_get_last_internal_error(src_box, NULL));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+mail_crypt_acl_user_create(struct mail_user *user, const char *dest_username,
+ struct mail_user **dest_user_r,
+ struct mail_storage_service_user **dest_service_user_r,
+ const char **error_r)
+{
+ const struct mail_storage_service_input *old_input;
+ struct mail_storage_service_input input;
+ struct mail_storage_service_ctx *service_ctx;
+ struct ioloop_context *cur_ioloop_ctx;
+
+ int ret;
+
+ i_assert(user->_service_user != NULL);
+ service_ctx = mail_storage_service_user_get_service_ctx(user->_service_user);
+ old_input = mail_storage_service_user_get_input(user->_service_user);
+
+ if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL)
+ io_loop_context_deactivate(cur_ioloop_ctx);
+
+ i_zero(&input);
+ input.module = old_input->module;
+ input.service = old_input->service;
+ input.username = dest_username;
+ input.session_id_prefix = user->session_id;
+ input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS |
+ MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT;
+ input.flags_override_remove = MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES;
+
+ ret = mail_storage_service_lookup_next(service_ctx, &input,
+ dest_service_user_r,
+ dest_user_r, error_r);
+
+ return ret;
+}
+
+static int
+mail_crypt_acl_update_private_key(struct mailbox *src_box,
+ struct mail_user *dest_user, bool set,
+ bool disallow_insecure,
+ const char **error_r)
+{
+ struct dcrypt_public_key *key = NULL;
+ struct dcrypt_private_key *priv_key;
+ int ret = 0;
+
+ if (!set) {
+ return mail_crypt_acl_unset_private_keys(src_box,
+ dest_user->username,
+ MAIL_ATTRIBUTE_TYPE_SHARED,
+ error_r);
+ }
+
+ if (dest_user != NULL) {
+ /* get public key from target user */
+ if ((ret = mail_crypt_user_get_public_key(dest_user,
+ &key, error_r)) <= 0) {
+ if (ret == 0 && disallow_insecure) {
+ *error_r = t_strdup_printf("User %s has no active public key",
+ dest_user->username);
+ return -1;
+ } else if (ret < 0) {
+ return -1;
+ } else if (ret == 0) {
+ /* perform insecure sharing */
+ dest_user = NULL;
+ key = NULL;
+ }
+ }
+ }
+
+ ARRAY_TYPE(dcrypt_private_key) keys;
+ t_array_init(&keys, 8);
+
+ struct mailbox_transaction_context *t =
+ mailbox_transaction_begin(src_box, 0, __func__);
+
+ /* get private keys from box */
+ if (mail_crypt_box_get_private_keys(src_box, &keys, error_r) < 0 ||
+ mail_crypt_box_share_private_keys(t, key,
+ dest_user == NULL ? NULL :
+ dest_user->username,
+ &keys, error_r) < 0)
+ ret = -1;
+ if (key != NULL)
+ dcrypt_key_unref_public(&key);
+
+ if (ret >= 0) {
+ array_foreach_elem(&keys, priv_key)
+ dcrypt_key_unref_private(&priv_key);
+ }
+
+ if (mailbox_transaction_commit(&t) < 0) {
+ *error_r = mailbox_get_last_internal_error(src_box, NULL);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int mail_crypt_acl_object_update(struct acl_object *aclobj,
+ const struct acl_rights_update *update)
+{
+ const char *error;
+ struct mail_crypt_acl_mailbox_list *mlist =
+ MAIL_CRYPT_ACL_LIST_CONTEXT(aclobj->backend->list);
+ const char *username;
+ struct mail_user *dest_user;
+ struct mail_storage_service_user *dest_service_user;
+ struct ioloop_context *cur_ioloop_ctx;
+ bool have_rights;
+ int ret = 0;
+
+ if (mlist->acl_vprev.object_update(aclobj, update) < 0)
+ return -1;
+
+ bool disallow_insecure =
+ mail_crypt_acl_secure_sharing_enabled(aclobj->backend->list->ns->user);
+
+ const char *box_name = mailbox_list_get_vname(aclobj->backend->list,
+ aclobj->name);
+ struct mailbox *box = mailbox_alloc(aclobj->backend->list, box_name, 0);
+
+ switch (update->rights.id_type) {
+ case ACL_ID_USER:
+ /* setting rights for specific user: we can encrypt the
+ mailbox key for the user. */
+ username = update->rights.identifier;
+ ret = mail_crypt_acl_has_user_read_right(aclobj, username, &error);
+
+ if (ret < 0) {
+ i_error("mail-crypt-acl-plugin: "
+ "mail_crypt_acl_has_user_read_right(%s) failed: %s",
+ username,
+ error);
+ break;
+ }
+
+ have_rights = ret > 0;
+
+ ret = mail_crypt_acl_user_create(aclobj->backend->list->ns->user,
+ username, &dest_user,
+ &dest_service_user, &error);
+
+ /* to make sure we get correct logging context */
+ if (ret > 0)
+ mail_storage_service_io_deactivate_user(dest_service_user);
+ mail_storage_service_io_activate_user(
+ aclobj->backend->list->ns->user->_service_user
+ );
+
+ if (ret <= 0) {
+ i_error("mail-crypt-acl-plugin: "
+ "Cannot initialize destination user %s: %s",
+ username, error);
+ break;
+ } else {
+ i_assert(dest_user != NULL);
+ if ((ret = mailbox_open(box)) < 0) {
+ i_error("mail-crypt-acl-plugin: "
+ "mailbox_open(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL));
+ } else if ((ret = mail_crypt_acl_update_private_key(box, dest_user,
+ have_rights,
+ disallow_insecure,
+ &error)) < 0) {
+ i_error("mail-crypt-acl-plugin: "
+ "acl_update_private_key(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ username,
+ error);
+ }
+ }
+
+ /* logging context swap again */
+ mail_storage_service_io_deactivate_user(
+ aclobj->backend->list->ns->user->_service_user
+ );
+ mail_storage_service_io_activate_user(dest_service_user);
+
+ mail_user_deinit(&dest_user);
+ mail_storage_service_user_unref(&dest_service_user);
+
+ if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL)
+ io_loop_context_deactivate(cur_ioloop_ctx);
+ mail_storage_service_io_activate_user(
+ aclobj->backend->list->ns->user->_service_user
+ );
+ break;
+ case ACL_ID_OWNER:
+ /* we should be the one doing this? ignore */
+ break;
+ case ACL_ID_ANYONE:
+ case ACL_ID_AUTHENTICATED:
+ case ACL_ID_GROUP:
+ case ACL_ID_GROUP_OVERRIDE:
+ if (disallow_insecure) {
+ i_error("mail-crypt-acl-plugin: "
+ "Secure key sharing is enabled -"
+ "Remove or set plugin { %s = no }",
+ MAIL_CRYPT_ACL_SECURE_SHARE_SETTING);
+ ret = -1;
+ break;
+ }
+ /* the mailbox key needs to be stored unencrypted. for groups
+ we could in theory use per-group encrypted keys, which the
+ users belonging to the group would able to decrypt with
+ their private key, but that becomes quite complicated. */
+ if ((ret = mail_crypt_acl_has_nonuser_read_right(aclobj, &error)) < 0) {
+ i_error("mail-crypt-acl-plugin: %s", error);
+ } else if ((ret = mailbox_open(box)) < 0) {
+ i_error("mail-crypt-acl-plugin: "
+ "mailbox_open(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL));
+ } else if ((ret = mail_crypt_acl_update_private_key(box,
+ NULL,
+ TRUE,
+ disallow_insecure,
+ &error)) < 0) {
+ i_error("mail-crypt-acl-plugin: "
+ "acl_update_private_key(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ "",
+ error);
+ }
+ break;
+ case ACL_ID_TYPE_COUNT:
+ i_unreached();
+ }
+
+ mailbox_free(&box);
+ return ret;
+}
+
+static void
+mail_crypt_acl_mail_namespace_storage_added(struct mail_namespace *ns)
+{
+ struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list);
+ struct mail_crypt_acl_mailbox_list *mlist =
+ MAIL_CRYPT_ACL_LIST_CONTEXT(ns->list);
+ struct acl_backend *backend;
+
+ if (alist == NULL)
+ return;
+
+ /* FIXME: this method works only if there's a single plugin doing it.
+ if there are ever multiple plugins hooking into ACL commands the
+ ACL core code would need some changing to make it work correctly. */
+ backend = alist->rights.backend;
+ mlist->acl_vprev = backend->v;
+ backend->v.object_update = mail_crypt_acl_object_update;
+}
+
+static void mail_crypt_acl_mailbox_list_deinit(struct mailbox_list *list)
+{
+ struct mail_crypt_acl_mailbox_list *mlist =
+ MAIL_CRYPT_ACL_LIST_CONTEXT(list);
+
+ mlist->module_ctx.super.deinit(list);
+}
+
+static void mail_crypt_acl_mailbox_list_created(struct mailbox_list *list)
+{
+ struct mailbox_list_vfuncs *v = list->vlast;
+ struct mail_crypt_acl_mailbox_list *mlist;
+
+ mlist = p_new(list->pool, struct mail_crypt_acl_mailbox_list, 1);
+ mlist->module_ctx.super = *v;
+ list->vlast = &mlist->module_ctx.super;
+ v->deinit = mail_crypt_acl_mailbox_list_deinit;
+
+ MODULE_CONTEXT_SET(list, mail_crypt_acl_mailbox_list_module, mlist);
+}
+
+static struct mail_storage_hooks mail_crypt_acl_mail_storage_hooks = {
+ .mailbox_list_created = mail_crypt_acl_mailbox_list_created,
+ .mail_namespace_storage_added = mail_crypt_acl_mail_namespace_storage_added
+};
+
+void mail_crypt_acl_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &mail_crypt_acl_mail_storage_hooks);
+}
+
+void mail_crypt_acl_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&mail_crypt_acl_mail_storage_hooks);
+}
+
+const char *mail_crypt_acl_plugin_dependencies[] = { "acl", NULL };
diff --git a/src/plugins/mail-crypt/mail-crypt-common.h b/src/plugins/mail-crypt/mail-crypt-common.h
new file mode 100644
index 0000000..57e5e2f
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-common.h
@@ -0,0 +1,30 @@
+#ifndef MAIL_CRYPT_COMMON_H
+#define MAIL_CRYPT_COMMON_H
+
+#include "dcrypt.h"
+
+#define MAIL_CRYPT_PW_CIPHER "aes-256-ctr"
+#define MAIL_CRYPT_KEY_CIPHER "ecdh-aes-256-ctr"
+#define MAIL_CRYPT_ENC_ALGORITHM "aes-256-gcm-sha256"
+#define MAIL_CRYPT_KEY_ID_ALGORITHM "sha256"
+#define MAIL_CRYPT_KEY_ATTRIBUTE_FORMAT DCRYPT_FORMAT_DOVECOT
+#define MAIL_CRYPT_ACL_SECURE_SHARE_SETTING "mail_crypt_acl_require_secure_key_sharing"
+#define MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY "mail_crypt_require_encrypted_user_key"
+#define MAIL_CRYPT_HASH_BUF_SIZE 128
+#define MAIL_CRYPT_KEY_BUF_SIZE 1024
+#define ACTIVE_KEY_NAME "active"
+#define PUBKEYS_PREFIX "pubkeys/"
+#define PRIVKEYS_PREFIX "privkeys/"
+
+#define BOX_CRYPT_PREFIX MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"crypt/"
+#define USER_CRYPT_PREFIX \
+ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER \
+ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"crypt/"
+
+#define MAIL_CRYPT_USERENV_PASSWORD "mail_crypt_private_password"
+#define MAIL_CRYPT_USERENV_KEY "mail_crypt_private_key"
+#define MAIL_CRYPT_USERENV_CURVE "mail_crypt_curve"
+
+ARRAY_DEFINE_TYPE(dcrypt_private_key, struct dcrypt_private_key*);
+
+#endif
diff --git a/src/plugins/mail-crypt/mail-crypt-global-key.c b/src/plugins/mail-crypt/mail-crypt-global-key.c
new file mode 100644
index 0000000..01cb937
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-global-key.c
@@ -0,0 +1,172 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "base64.h"
+#include "mail-user.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "mail-crypt-plugin.h"
+
+int mail_crypt_load_global_public_key(const char *set_key, const char *key_data,
+ struct mail_crypt_global_keys *global_keys,
+ const char **error_r)
+{
+ const char *error;
+ enum dcrypt_key_format format;
+ enum dcrypt_key_kind kind;
+ if (!dcrypt_key_string_get_info(key_data, &format, NULL,
+ &kind, NULL, NULL, NULL, &error)) {
+ key_data = str_c(t_base64_decode_str(key_data));
+ if (!dcrypt_key_string_get_info(key_data, &format, NULL,
+ &kind, NULL, NULL, NULL, &error)) {
+ *error_r = t_strdup_printf("%s: Couldn't parse public key: %s",
+ set_key, error);
+ return -1;
+ }
+ }
+ if (kind != DCRYPT_KEY_KIND_PUBLIC) {
+ *error_r = t_strdup_printf("%s: key is not public", set_key);
+ return -1;
+ }
+ if (!dcrypt_key_load_public(&global_keys->public_key, key_data, &error)) {
+ *error_r = t_strdup_printf("%s: Couldn't load public key: %s",
+ set_key, error);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+mail_crypt_key_get_ids(struct dcrypt_private_key *key,
+ const char **key_id_r, const char **key_id_old_r,
+ const char **error_r)
+{
+ const char *error;
+ buffer_t *key_id;
+
+ *key_id_r = NULL;
+ *key_id_old_r = NULL;
+
+ /* new key ID */
+ key_id = t_buffer_create(MAIL_CRYPT_HASH_BUF_SIZE);
+ if (!dcrypt_key_id_private(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error)) {
+ *error_r = t_strdup_printf("Failed to get private key ID: %s", error);
+ return -1;
+ }
+ *key_id_r = binary_to_hex(key_id->data, key_id->used);
+
+ buffer_set_used_size(key_id, 0);
+
+ /* old key ID */
+ if (dcrypt_key_type_private(key) != DCRYPT_KEY_EC)
+ return 0;
+
+ if (!dcrypt_key_id_private_old(key, key_id, &error)) {
+ *error_r = t_strdup_printf("Failed to get private key old ID: %s",
+ error);
+ return -1;
+ }
+ *key_id_old_r = binary_to_hex(key_id->data, key_id->used);
+ return 0;
+}
+
+int mail_crypt_load_global_private_key(const char *set_key, const char *key_data,
+ const char *set_pw, const char *key_password,
+ struct mail_crypt_global_keys *global_keys,
+ const char **error_r)
+{
+ enum dcrypt_key_format format;
+ enum dcrypt_key_kind kind;
+ enum dcrypt_key_encryption_type enc_type;
+ const char *error;
+
+ if (!dcrypt_key_string_get_info(key_data, &format, NULL, &kind,
+ &enc_type, NULL, NULL, &error)) {
+ key_data = str_c(t_base64_decode_str(key_data));
+ if (!dcrypt_key_string_get_info(key_data, &format, NULL, &kind,
+ &enc_type, NULL, NULL, &error)) {
+ *error_r = t_strdup_printf("%s: Couldn't parse private"
+ " key: %s", set_key, error);
+ return -1;
+ }
+ }
+ if (kind != DCRYPT_KEY_KIND_PRIVATE) {
+ *error_r = t_strdup_printf("%s: key is not private", set_key);
+ return -1;
+ }
+
+ if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD) {
+ /* Fail here if password is not set since openssl will prompt
+ * for it otherwise */
+ if (key_password == NULL) {
+ *error_r = t_strdup_printf("%s: %s unset, no password to decrypt the key",
+ set_key, set_pw);
+ return -1;
+ }
+ }
+
+ struct dcrypt_private_key *key = NULL;
+ if (!dcrypt_key_load_private(&key, key_data, key_password, NULL, &error)) {
+ *error_r = t_strdup_printf("%s: Couldn't load private key: %s",
+ set_key, error);
+ return -1;
+ }
+
+ const char *key_id, *key_id_old;
+ if (mail_crypt_key_get_ids(key, &key_id, &key_id_old, error_r) < 0) {
+ dcrypt_key_unref_private(&key);
+ return -1;
+ }
+
+ struct mail_crypt_global_private_key *priv_key =
+ array_append_space(&global_keys->private_keys);
+ priv_key->key = key;
+ priv_key->key_id = i_strdup(key_id);
+ priv_key->key_id_old = i_strdup(key_id_old);
+ return 0;
+}
+
+void mail_crypt_global_keys_init(struct mail_crypt_global_keys *global_keys_r)
+{
+ i_zero(global_keys_r);
+ i_array_init(&global_keys_r->private_keys, 4);
+}
+
+void mail_crypt_global_keys_free(struct mail_crypt_global_keys *global_keys)
+{
+ struct mail_crypt_global_private_key *priv_key;
+
+ if (global_keys->public_key != NULL)
+ dcrypt_key_unref_public(&global_keys->public_key);
+
+ if (!array_is_created(&global_keys->private_keys))
+ return;
+ array_foreach_modifiable(&global_keys->private_keys, priv_key) {
+ dcrypt_key_unref_private(&priv_key->key);
+ i_free(priv_key->key_id);
+ i_free(priv_key->key_id_old);
+ }
+ array_free(&global_keys->private_keys);
+}
+
+struct dcrypt_private_key *
+mail_crypt_global_key_find(struct mail_crypt_global_keys *global_keys,
+ const char *pubkey_digest)
+{
+ const struct mail_crypt_global_private_key *priv_key;
+
+ if (!array_is_created(&global_keys->private_keys))
+ return NULL;
+
+ array_foreach(&global_keys->private_keys, priv_key) {
+ if (strcmp(priv_key->key_id, pubkey_digest) == 0)
+ return priv_key->key;
+ if (priv_key->key_id_old != NULL &&
+ strcmp(priv_key->key_id_old, pubkey_digest) == 0)
+ return priv_key->key;
+ }
+ return NULL;
+}
diff --git a/src/plugins/mail-crypt/mail-crypt-global-key.h b/src/plugins/mail-crypt/mail-crypt-global-key.h
new file mode 100644
index 0000000..6f4679a
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-global-key.h
@@ -0,0 +1,38 @@
+#ifndef MAIL_CRYPT_GLOBAL_KEY_H
+#define MAIL_CRYPT_GLOBAL_KEY_H
+
+struct mail_crypt_global_private_key {
+ struct dcrypt_private_key *key;
+ char *key_id, *key_id_old;
+};
+
+struct mail_crypt_global_keys {
+ struct dcrypt_public_key *public_key;
+ ARRAY(struct mail_crypt_global_private_key) private_keys;
+};
+
+struct mail_user;
+
+int mail_crypt_global_keys_load(struct mail_user *user, const char *set_prefix,
+ struct mail_crypt_global_keys *global_keys_r,
+ bool ignore_privkey_errors,
+ const char **error_r);
+int mail_crypt_global_keys_load_pluginenv(const char *set_prefix,
+ struct mail_crypt_global_keys *global_keys_r,
+ const char **error_r);
+void mail_crypt_global_keys_init(struct mail_crypt_global_keys *global_keys_r);
+void mail_crypt_global_keys_free(struct mail_crypt_global_keys *global_keys);
+
+int mail_crypt_load_global_public_key(const char *set_key, const char *key_data,
+ struct mail_crypt_global_keys *global_keys,
+ const char **error_r);
+int mail_crypt_load_global_private_key(const char *set_key, const char *key_data,
+ const char *set_pw, const char *key_password,
+ struct mail_crypt_global_keys *global_keys,
+ const char **error_r);
+
+struct dcrypt_private_key *
+mail_crypt_global_key_find(struct mail_crypt_global_keys *global_keys,
+ const char *pubkey_digest);
+
+#endif
diff --git a/src/plugins/mail-crypt/mail-crypt-key.c b/src/plugins/mail-crypt/mail-crypt-key.c
new file mode 100644
index 0000000..22c86e3
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-key.c
@@ -0,0 +1,1242 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "dict.h"
+#include "array.h"
+#include "var-expand.h"
+#include "mail-storage.h"
+#include "mailbox-attribute.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "mail-crypt-plugin.h"
+#include "mail-user.h"
+#include "hex-binary.h"
+#include "safe-memset.h"
+#include "base64.h"
+#include "sha2.h"
+
+struct mail_crypt_key_cache_entry {
+ struct mail_crypt_key_cache_entry *next;
+
+ char *pubid;
+ /* this is lazily initialized */
+ struct dcrypt_keypair pair;
+};
+
+static
+int mail_crypt_get_key_cache(struct mail_crypt_key_cache_entry *cache,
+ const char *pubid,
+ struct dcrypt_private_key **privkey_r,
+ struct dcrypt_public_key **pubkey_r)
+{
+ for(struct mail_crypt_key_cache_entry *ent = cache;
+ ent != NULL; ent = ent->next)
+ {
+ if (strcmp(pubid, ent->pubid) == 0) {
+ if (privkey_r != NULL && ent->pair.priv != NULL) {
+ dcrypt_key_ref_private(ent->pair.priv);
+ *privkey_r = ent->pair.priv;
+ return 1;
+ } else if (pubkey_r != NULL && ent->pair.pub != NULL) {
+ dcrypt_key_ref_public(ent->pair.pub);
+ *pubkey_r = ent->pair.pub;
+ return 1;
+ } else if ((privkey_r == NULL && pubkey_r == NULL) ||
+ (ent->pair.priv == NULL &&
+ ent->pair.pub == NULL)) {
+ i_unreached();
+ }
+ }
+ }
+ return 0;
+}
+
+static
+void mail_crypt_put_key_cache(struct mail_crypt_key_cache_entry **cache,
+ const char *pubid,
+ struct dcrypt_private_key *privkey,
+ struct dcrypt_public_key *pubkey)
+{
+ for(struct mail_crypt_key_cache_entry *ent = *cache;
+ ent != NULL; ent = ent->next)
+ {
+ if (strcmp(pubid, ent->pubid) == 0) {
+ if (privkey != NULL) {
+ if (ent->pair.priv == NULL) {
+ ent->pair.priv = privkey;
+ dcrypt_key_ref_private(ent->pair.priv);
+ }
+ } else if (pubkey != NULL) {
+ if (ent->pair.pub == NULL) {
+ ent->pair.pub = pubkey;
+ dcrypt_key_ref_public(ent->pair.pub);
+ }
+ } else
+ i_unreached();
+ return;
+ }
+ }
+
+ /* not found */
+ struct mail_crypt_key_cache_entry *ent =
+ i_new(struct mail_crypt_key_cache_entry, 1);
+ ent->pubid = i_strdup(pubid);
+ ent->pair.priv = privkey;
+ ent->pair.pub = pubkey;
+ if (ent->pair.priv != NULL)
+ dcrypt_key_ref_private(ent->pair.priv);
+ if (ent->pair.pub != NULL)
+ dcrypt_key_ref_public(ent->pair.pub);
+
+ if (*cache == NULL) {
+ *cache = ent;
+ } else {
+ ent->next = *cache;
+ *cache = ent;
+ }
+}
+
+void mail_crypt_key_cache_destroy(struct mail_crypt_key_cache_entry **cache)
+{
+ struct mail_crypt_key_cache_entry *next, *cur = *cache;
+
+ *cache = NULL;
+
+ while(cur != NULL) {
+ next = cur->next;
+ i_free(cur->pubid);
+ if (cur->pair.priv != NULL)
+ dcrypt_key_unref_private(&cur->pair.priv);
+ if (cur->pair.pub != NULL)
+ dcrypt_key_unref_public(&cur->pair.pub);
+ i_free(cur);
+ cur = next;
+ }
+}
+
+int mail_crypt_private_key_id_match(struct dcrypt_private_key *key,
+ const char *pubid, const char **error_r)
+{
+ i_assert(key != NULL);
+ buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE);
+ if (!dcrypt_key_id_private(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id,
+ error_r))
+ return -1;
+ const char *hash = binary_to_hex(key_id->data, key_id->used);
+ if (strcmp(pubid, hash) == 0) return 1;
+
+ buffer_set_used_size(key_id, 0);
+ if (!dcrypt_key_id_private_old(key, key_id, error_r)) {
+ return -1;
+ }
+ hash = binary_to_hex(key_id->data, key_id->used);
+
+ if (strcmp(pubid, hash) != 0) {
+ *error_r = t_strdup_printf("Key %s does not match given ID %s",
+ hash, pubid);
+ return 0;
+ }
+ return 1;
+}
+
+int mail_crypt_public_key_id_match(struct dcrypt_public_key *key,
+ const char *pubid, const char **error_r)
+{
+ i_assert(key != NULL);
+ buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE);
+ if (!dcrypt_key_id_public(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id,
+ error_r))
+ return -1;
+ const char *hash = binary_to_hex(key_id->data, key_id->used);
+ if (strcmp(pubid, hash) == 0) return 1;
+
+ buffer_set_used_size(key_id, 0);
+ if (!dcrypt_key_id_public_old(key, key_id, error_r)) {
+ return -1;
+ }
+ hash = binary_to_hex(key_id->data, key_id->used);
+
+ if (strcmp(pubid, hash) != 0) {
+ *error_r = t_strdup_printf("Key %s does not match given ID %s",
+ hash, pubid);
+ return 0;
+ }
+ return 1;
+}
+
+static
+int mail_crypt_env_get_private_key(struct mail_user *user, const char *pubid,
+ struct dcrypt_private_key **key_r,
+ const char **error_r)
+{
+ struct mail_crypt_global_keys global_keys;
+ int ret = 0;
+ if (mail_crypt_global_keys_load(user, "mail_crypt", &global_keys,
+ TRUE, error_r) < 0) {
+ mail_crypt_global_keys_free(&global_keys);
+ return -1;
+ }
+
+ /* see if we got a key */
+ struct dcrypt_private_key *key =
+ mail_crypt_global_key_find(&global_keys, pubid);
+
+ if (key != NULL) {
+ dcrypt_key_ref_private(key);
+ *key_r = key;
+ ret = 1;
+ }
+
+ mail_crypt_global_keys_free(&global_keys);
+
+ return ret;
+}
+
+static
+const char *mail_crypt_get_key_path(bool user_key, bool public, const char *pubid)
+{
+ const char *ret = t_strdup_printf("%s%s%s",
+ user_key ? USER_CRYPT_PREFIX :
+ BOX_CRYPT_PREFIX,
+ public ? PUBKEYS_PREFIX :
+ PRIVKEYS_PREFIX,
+ pubid);
+ return ret;
+}
+
+static
+int mail_crypt_decrypt_private_key(struct mailbox *box, const char *pubid,
+ const char *data,
+ struct dcrypt_private_key **key_r,
+ const char **error_r)
+{
+ enum dcrypt_key_kind key_kind;
+ enum dcrypt_key_encryption_type enc_type;
+ const char *enc_hash = NULL, *key_hash = NULL, *pw = NULL;
+ struct dcrypt_private_key *key = NULL, *dec_key = NULL;
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ int ret = 0;
+
+ i_assert(pubid != NULL);
+ i_assert(data != NULL);
+
+ /* see what the key needs for decrypting */
+ if (!dcrypt_key_string_get_info(data, NULL, NULL, &key_kind,
+ &enc_type, &enc_hash, &key_hash, error_r)) {
+ return -1;
+ }
+
+ if (key_kind != DCRYPT_KEY_KIND_PRIVATE) {
+ *error_r = t_strdup_printf("Cannot use key %s: "
+ "Expected private key, got public key",
+ pubid);
+ return -1;
+ }
+
+ if (key_hash != NULL && strcmp(key_hash, pubid) != 0) {
+ *error_r = t_strdup_printf("Cannot use key %s: "
+ "Incorrect key hash %s stored",
+ pubid,
+ key_hash);
+ return -1;
+ }
+
+ /* see if it needs decrypting */
+ if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE) {
+ /* no key or password */
+ } else if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD) {
+ pw = mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD);
+ if (pw == NULL) {
+ *error_r = t_strdup_printf("Cannot decrypt key %s: "
+ "Password not available",
+ pubid);
+ return -1;
+ }
+ } else if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_KEY) {
+ if ((ret = mail_crypt_user_get_private_key(user, enc_hash,
+ &dec_key, error_r)) <= 0) {
+ /* last resort, look at environment */
+ if (ret == 0 && (ret = mail_crypt_env_get_private_key(user, enc_hash,
+ &dec_key, error_r)) == 0) {
+ *error_r = t_strdup_printf("Cannot decrypt key %s: "
+ "Private key %s not available:",
+ pubid, enc_hash);
+ return -1;
+ } else if (ret < 0) {
+ *error_r = t_strdup_printf("Cannot decrypt key %s: %s",
+ pubid, *error_r);
+ return ret;
+ }
+ }
+ }
+
+ bool res = dcrypt_key_load_private(&key, data, pw, dec_key, error_r);
+
+ if (dec_key != NULL)
+ dcrypt_key_unref_private(&dec_key);
+
+ if (!res)
+ return -1;
+
+ if (mail_crypt_private_key_id_match(key, pubid, error_r) <= 0) {
+ if (key != NULL)
+ dcrypt_key_unref_private(&key);
+ return -1;
+ }
+
+ i_assert(key != NULL);
+
+ *key_r = key;
+
+ return 1;
+}
+
+int mail_crypt_get_private_key(struct mailbox *box, const char *pubid,
+ bool user_key, bool shared,
+ struct dcrypt_private_key **key_r,
+ const char **error_r)
+{
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user);
+
+ /* check cache */
+ if (mail_crypt_get_key_cache(muser->key_cache, pubid, key_r, NULL) > 0) {
+ return 1;
+ }
+
+ struct mail_attribute_value value;
+ struct dcrypt_private_key *key;
+ int ret;
+ const char *attr_name = mail_crypt_get_key_path(user_key, FALSE, pubid);
+
+ if ((ret = mailbox_attribute_get(box,
+ shared ? MAIL_ATTRIBUTE_TYPE_SHARED :
+ MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ attr_name, &value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s%s) failed: %s",
+ mailbox_get_vname(box),
+ shared ? "/shared/" :
+ "/priv/",
+ attr_name,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ return ret;
+ }
+
+ if ((ret = mail_crypt_decrypt_private_key(box, pubid, value.value,
+ &key, error_r)) <= 0)
+ return ret;
+
+ i_assert(key != NULL);
+
+ mail_crypt_put_key_cache(&muser->key_cache, pubid, key, NULL);
+
+ *key_r = key;
+
+ return 1;
+}
+
+int mail_crypt_user_get_private_key(struct mail_user *user, const char *pubid,
+ struct dcrypt_private_key **key_r,
+ const char **error_r)
+{
+ struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ struct mail_attribute_value value;
+ int ret;
+
+ /* try retrieve currently active user key */
+ if (mailbox_open(box) < 0) {
+ *error_r = t_strdup_printf("mailbox_open(%s) failed: %s",
+ "INBOX",
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+
+ if (pubid == NULL) {
+ if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s",
+ mailbox_get_vname(box),
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ } else {
+ pubid = value.value;
+ ret = 1;
+ }
+ } else
+ ret = 1;
+
+ /* try to open key */
+ if (ret > 0)
+ ret = mail_crypt_get_private_key(box, pubid, TRUE, FALSE,
+ key_r, error_r);
+ mailbox_free(&box);
+ return ret;
+}
+
+int mail_crypt_box_get_private_key(struct mailbox *box,
+ struct dcrypt_private_key **key_r,
+ const char **error_r)
+{
+ struct mail_attribute_value value;
+ int ret;
+ /* get active key */
+ if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, &value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s",
+ mailbox_get_vname(box),
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ return ret;
+ }
+
+ return mail_crypt_get_private_key(box, value.value,
+ FALSE, FALSE,
+ key_r, error_r);
+}
+
+static
+int mail_crypt_set_private_key(struct mailbox_transaction_context *t,
+ bool user_key, bool shared, const char *pubid,
+ struct dcrypt_public_key *enc_key,
+ struct dcrypt_private_key *key,
+ const char **error_r)
+{
+ /* folder keys must be encrypted with some other key,
+ unless they are shared keys */
+ i_assert(user_key || shared || enc_key != NULL);
+
+ buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE);
+ const char *pw = NULL;
+ const char *algo = NULL;
+ struct mail_user *user = mail_storage_get_user(
+ mailbox_get_storage(
+ mailbox_transaction_get_mailbox(t)));
+ const char *attr_name = mail_crypt_get_key_path(user_key, FALSE, pubid);
+ struct mail_attribute_value value;
+ int ret;
+
+ if (enc_key != NULL) {
+ algo = MAIL_CRYPT_KEY_CIPHER;
+ } else if (user_key &&
+ (pw = mail_user_plugin_getenv(user,MAIL_CRYPT_USERENV_PASSWORD))
+ != NULL) {
+ algo = MAIL_CRYPT_PW_CIPHER;
+ }
+
+ /* export key */
+ if (!dcrypt_key_store_private(key, DCRYPT_FORMAT_DOVECOT, algo, data,
+ pw, enc_key, error_r)) {
+ return -1;
+ }
+
+ /* store it */
+ value.value_stream = NULL;
+ value.value = str_c(data);
+ value.last_change = 0;
+
+ if ((ret = mailbox_attribute_set(t,
+ shared ? MAIL_ATTRIBUTE_TYPE_SHARED :
+ MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ attr_name,
+ &value)) < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s/%s) failed: %s",
+ mailbox_get_vname(mailbox_transaction_get_mailbox(t)),
+ shared ? "/shared" : "/priv",
+ attr_name,
+ mailbox_get_last_internal_error(
+ mailbox_transaction_get_mailbox(t), NULL));
+ }
+
+ safe_memset(buffer_get_modifiable_data(data, NULL), 0, data->used);
+
+ return ret;
+}
+
+int mail_crypt_user_set_private_key(struct mail_user *user, const char *pubid,
+ struct dcrypt_private_key *key,
+ const char **error_r)
+{
+ struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ struct dcrypt_private_key *env_key = NULL;
+ struct dcrypt_public_key *enc_key = NULL;
+ struct mailbox_transaction_context *t;
+ int ret;
+
+ if ((ret = mail_crypt_env_get_private_key(user, NULL, &env_key,
+ error_r)) < 0) {
+ return -1;
+ } else if (ret > 0) {
+ dcrypt_key_convert_private_to_public(env_key, &enc_key);
+ dcrypt_key_unref_private(&env_key);
+ }
+
+ if (mail_user_plugin_getenv(user, MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY) != NULL &&
+ mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD) == NULL &&
+ mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_KEY) == NULL)
+ {
+ *error_r = MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY " set, cannot "
+ "generate user keypair without password or key";
+ return -1;
+ }
+
+ if (mailbox_open(box) < 0) {
+ *error_r = t_strdup_printf("mailbox_open(%s) failed: %s",
+ "INBOX",
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+
+ if ((ret = mail_crypt_set_private_key(t, TRUE, FALSE, pubid, enc_key, key,
+ error_r)) < 0) {
+ mailbox_transaction_rollback(&t);
+ } else if ((ret = mailbox_transaction_commit(&t)) < 0) {
+ *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL));
+ }
+
+ mailbox_free(&box);
+
+ return ret;
+}
+
+int mail_crypt_box_set_private_key(struct mailbox *box, const char *pubid,
+ struct dcrypt_private_key *key,
+ struct dcrypt_public_key *user_key,
+ const char **error_r)
+{
+ int ret;
+ struct mailbox_transaction_context *t;
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+ if ((ret = mail_crypt_set_private_key(t, FALSE, FALSE, pubid, user_key,
+ key, error_r)) < 0) {
+ mailbox_transaction_rollback(&t);
+ } else if ((ret = mailbox_transaction_commit(&t)) < 0) {
+ *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL));
+ }
+
+ return ret;
+}
+
+static
+int mail_crypt_get_public_key(struct mailbox *box, const char *pubid,
+ bool user_key, struct dcrypt_public_key **key_r,
+ const char **error_r)
+{
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user);
+
+ /* check cache */
+ if (mail_crypt_get_key_cache(muser->key_cache, pubid, NULL, key_r) > 0) {
+ return 1;
+ }
+
+ enum dcrypt_key_kind key_kind;
+ const char *key_hash = NULL;
+ struct dcrypt_public_key *key;
+ struct mail_attribute_value value;
+ int ret;
+ const char *attr_name = mail_crypt_get_key_path(user_key, TRUE, pubid);
+
+ if ((ret = mailbox_attribute_get(box,
+ MAIL_ATTRIBUTE_TYPE_SHARED,
+ attr_name, &value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ attr_name,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ return ret;
+ }
+
+ if (!dcrypt_key_string_get_info(value.value, NULL, NULL, &key_kind,
+ NULL, NULL, &key_hash, error_r)) {
+ return -1;
+ }
+
+ if (key_kind != DCRYPT_KEY_KIND_PUBLIC) {
+ *error_r = t_strdup_printf("Cannot use key %s: "
+ "Expected public key, got private key",
+ pubid);
+ return -1;
+ }
+
+ if (key_hash != NULL && strcmp(key_hash, pubid) != 0) {
+ *error_r = t_strdup_printf("Cannot use key %s: "
+ "Incorrect key hash %s stored",
+ pubid, key_hash);
+ return -1;
+ }
+
+ /* load the key */
+ if (!dcrypt_key_load_public(&key, value.value, error_r)) {
+ return -1;
+ }
+
+ if (pubid != NULL &&
+ mail_crypt_public_key_id_match(key, pubid, error_r) <= 0) {
+ dcrypt_key_unref_public(&key);
+ return -1;
+ }
+
+ mail_crypt_put_key_cache(&muser->key_cache, pubid, NULL, key);
+
+ *key_r = key;
+
+ return 1;
+}
+
+int mail_crypt_user_get_public_key(struct mail_user *user,
+ struct dcrypt_public_key **key_r,
+ const char **error_r)
+{
+ struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ struct mail_attribute_value value;
+ int ret;
+
+ /* try retrieve currently active user key */
+ if (mailbox_open(box) < 0) {
+ *error_r = t_strdup_printf("mailbox_open(%s) failed: %s",
+ "INBOX",
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+
+ if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s",
+ mailbox_get_vname(box),
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ } else {
+ ret = mail_crypt_get_public_key(box, value.value, TRUE, key_r, error_r);
+ }
+
+ mailbox_free(&box);
+ return ret;
+}
+
+int mail_crypt_box_get_public_key(struct mailbox *box,
+ struct dcrypt_public_key **key_r,
+ const char **error_r)
+{
+ struct mail_attribute_value value;
+ int ret;
+
+ if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s",
+ mailbox_get_vname(box),
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ return ret;
+ }
+ return mail_crypt_get_public_key(box, value.value, FALSE, key_r, error_r);
+}
+
+static
+int mail_crypt_set_public_key(struct mailbox_transaction_context *t,
+ bool user_key, const char *pubid,
+ struct dcrypt_public_key *key,
+ const char **error_r)
+{
+ buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE);
+ const char *attr_name = mail_crypt_get_key_path(user_key, TRUE, pubid);
+ struct mail_attribute_value value;
+
+ /* export key */
+ if (!dcrypt_key_store_public(key, DCRYPT_FORMAT_DOVECOT, data,
+ error_r)) {
+ return -1;
+ }
+
+ /* store it */
+ value.value_stream = NULL;
+ value.value = str_c(data);
+ value.last_change = 0;
+
+ if (mailbox_attribute_set(t,
+ MAIL_ATTRIBUTE_TYPE_SHARED,
+ attr_name,
+ &value) < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s/%s) failed: %s",
+ mailbox_get_vname(mailbox_transaction_get_mailbox(t)),
+ "/shared",
+ attr_name,
+ mailbox_get_last_internal_error(
+ mailbox_transaction_get_mailbox(t), NULL));
+ return -1;
+ }
+
+ return 0;
+}
+
+int mail_crypt_user_set_public_key(struct mail_user *user, const char *pubid,
+ struct dcrypt_public_key *key,
+ const char **error_r)
+{
+ struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ struct mailbox_transaction_context *t;
+ struct mail_attribute_value value;
+ int ret;
+
+ /* try retrieve currently active user key */
+ if (mailbox_open(box) < 0) {
+ *error_r = t_strdup_printf("mailbox_open(%s) failed: %s",
+ "INBOX",
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+
+ if ((ret = mail_crypt_set_public_key(t, TRUE, pubid, key,
+ error_r)) == 0) {
+ value.value_stream = NULL;
+ value.value = pubid;
+ value.last_change = 0;
+
+ if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED,
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value)) < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s",
+ mailbox_get_vname(box),
+ USER_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ }
+
+ if (ret < 0) {
+ mailbox_transaction_rollback(&t);
+ } else if (mailbox_transaction_commit(&t) < 0) {
+ *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ }
+
+ mailbox_free(&box);
+
+ return ret;
+}
+
+int mail_crypt_box_set_public_key(struct mailbox *box, const char *pubid,
+ struct dcrypt_public_key *key,
+ const char **error_r)
+{
+ int ret;
+ struct mailbox_transaction_context *t;
+ struct mail_attribute_value value;
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+ if ((ret = mail_crypt_set_public_key(t, FALSE, pubid, key,
+ error_r)) == 0) {
+ value.value_stream = NULL;
+ value.value = pubid;
+ value.last_change = 0;
+
+ if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED,
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ &value)) < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s",
+ mailbox_get_vname(box),
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ }
+
+ if (ret < 0) {
+ mailbox_transaction_rollback(&t);
+ } else if (mailbox_transaction_commit(&t) < 0) {
+ *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ }
+
+ return ret;
+
+}
+
+static
+int mail_crypt_user_set_keys(struct mail_user *user,
+ const char *pubid,
+ struct dcrypt_private_key *privkey,
+ struct dcrypt_public_key *pubkey,
+ const char **error_r)
+{
+ if (mail_crypt_user_set_private_key(user, pubid, privkey, error_r) < 0)
+ return -1;
+ if (mail_crypt_user_set_public_key(user, pubid, pubkey, error_r) < 0)
+ return -1;
+ return 0;
+}
+
+static
+int mail_crypt_box_set_keys(struct mailbox *box,
+ const char *pubid,
+ struct dcrypt_private_key *privkey,
+ struct dcrypt_public_key *user_key,
+ struct dcrypt_public_key *pubkey,
+ const char **error_r)
+{
+ if (mail_crypt_box_set_private_key(box, pubid, privkey, user_key,
+ error_r) < 0)
+ return -1;
+ if (mail_crypt_box_set_public_key(box, pubid, pubkey, error_r) < 0)
+ return -1;
+ return 0;
+}
+
+int mail_crypt_box_get_shared_key(struct mailbox *box,
+ const char *pubid,
+ struct dcrypt_private_key **key_r,
+ const char **error_r)
+{
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user);
+
+ struct dcrypt_private_key *key = NULL;
+ struct mail_attribute_value value;
+ int ret;
+
+ /* check cache */
+ if (mail_crypt_get_key_cache(muser->key_cache, pubid, key_r, NULL) > 0) {
+ return 1;
+ }
+
+ const char *hexname =
+ binary_to_hex((const unsigned char*)user->username,
+ strlen(user->username));
+
+ const char *attr_name = t_strdup_printf(BOX_CRYPT_PREFIX
+ PRIVKEYS_PREFIX"%s/%s",
+ hexname,
+ pubid);
+
+ if ((ret = mailbox_attribute_get(box,
+ MAIL_ATTRIBUTE_TYPE_SHARED,
+ attr_name, &value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ attr_name,
+ mailbox_get_last_internal_error(box, NULL));
+ return ret;
+ }
+ return mail_crypt_get_private_key(box, pubid, FALSE, TRUE, key_r,
+ error_r);
+ } else {
+ if ((ret = mail_crypt_decrypt_private_key(box, pubid, value.value,
+ &key, error_r)) <= 0)
+ return ret;
+ }
+
+ mail_crypt_put_key_cache(&muser->key_cache, pubid, key, NULL);
+
+ *key_r = key;
+
+ return 1;
+}
+
+int mail_crypt_box_set_shared_key(struct mailbox_transaction_context *t,
+ const char *pubid,
+ struct dcrypt_private_key *privkey,
+ const char *target_uid,
+ struct dcrypt_public_key *user_key,
+ const char **error_r)
+{
+ struct mail_attribute_value value;
+ buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE);
+ int ret;
+ const char *attr_name;
+ const char *algo = NULL;
+
+ i_assert(target_uid == NULL || user_key != NULL);
+
+ if (target_uid != NULL) {
+ /* hash target UID */
+ algo = MAIL_CRYPT_KEY_CIPHER;
+ const char *hexname =
+ binary_to_hex((const unsigned char*)target_uid,
+ strlen(target_uid));
+ attr_name = t_strdup_printf(BOX_CRYPT_PREFIX
+ PRIVKEYS_PREFIX"%s/%s",
+ hexname,
+ pubid);
+ } else {
+ attr_name = t_strdup_printf(BOX_CRYPT_PREFIX
+ PRIVKEYS_PREFIX"%s",
+ pubid);
+ }
+
+ if (!dcrypt_key_store_private(privkey, DCRYPT_FORMAT_DOVECOT,
+ algo, data,
+ NULL, user_key, error_r)) {
+ return -1;
+ }
+
+ value.value_stream = NULL;
+ value.value = str_c(data);
+ value.last_change = 0;
+
+ if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED,
+ attr_name, &value)) < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s",
+ mailbox_get_vname(
+ mailbox_transaction_get_mailbox(t)),
+ attr_name,
+ mailbox_get_last_internal_error(
+ mailbox_transaction_get_mailbox(t),
+ NULL));
+ }
+
+ safe_memset(buffer_get_modifiable_data(data, NULL), 0, data->used);
+
+ return ret;
+}
+
+int mail_crypt_box_unset_shared_key(struct mailbox_transaction_context *t,
+ const char *pubid,
+ const char *target_uid,
+ const char **error_r)
+{
+ int ret;
+
+ const char *hexname =
+ binary_to_hex((const unsigned char*)target_uid,
+ strlen(target_uid));
+
+ const char *attr_name = t_strdup_printf(BOX_CRYPT_PREFIX
+ PRIVKEYS_PREFIX"%s/%s",
+ hexname,
+ pubid);
+
+ if ((ret = mailbox_attribute_unset(t, MAIL_ATTRIBUTE_TYPE_SHARED,
+ attr_name)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_unset(%s, "
+ " /shared/%s): failed: %s",
+ mailbox_get_vname(
+ mailbox_transaction_get_mailbox(t)),
+ attr_name,
+ mailbox_get_last_internal_error(
+ mailbox_transaction_get_mailbox(t),
+ NULL));
+ }
+ }
+
+ return ret;
+}
+
+static
+int mail_crypt_generate_keypair(const char *curve,
+ struct dcrypt_keypair *pair_r,
+ const char **pubid_r,
+ const char **error_r)
+{
+ if (curve == NULL) {
+ *error_r = MAIL_CRYPT_USERENV_CURVE " not set, cannot generate EC key";
+ return -1;
+ }
+
+ if (!dcrypt_keypair_generate(pair_r, DCRYPT_KEY_EC, 0, curve, error_r)) {
+ return -1;
+ }
+
+ buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE);
+ if (!dcrypt_key_id_public(pair_r->pub, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id,
+ error_r)) {
+ dcrypt_keypair_unref(pair_r);
+ return -1;
+ }
+
+ *pubid_r = binary_to_hex(key_id->data, key_id->used);
+
+ return 0;
+}
+
+int mail_crypt_user_generate_keypair(struct mail_user *user,
+ struct dcrypt_keypair *pair,
+ const char **pubid_r,
+ const char **error_r)
+{
+ struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user);
+ const char *curve = mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_CURVE);
+
+ if (mail_crypt_generate_keypair(curve, pair, pubid_r, error_r) < 0) {
+ return -1;
+ }
+
+ if (mail_crypt_user_set_keys(user, *pubid_r,
+ pair->priv, pair->pub, error_r) < 0) {
+ dcrypt_keypair_unref(pair);
+ return -1;
+ }
+
+ mail_crypt_put_key_cache(&muser->key_cache, *pubid_r, pair->priv, pair->pub);
+
+ return 0;
+}
+
+int mail_crypt_box_generate_keypair(struct mailbox *box,
+ struct dcrypt_keypair *pair,
+ struct dcrypt_public_key *user_key,
+ const char **pubid_r,
+ const char **error_r)
+{
+ int ret;
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user);
+ const char *curve = mail_user_plugin_getenv(user,
+ MAIL_CRYPT_USERENV_CURVE);
+
+ if (user_key == NULL) {
+ if ((ret = mail_crypt_user_get_public_key(user,
+ &user_key,
+ error_r)) <= 0) {
+ if (ret < 0)
+ return ret;
+ /* generate keypair */
+ struct dcrypt_keypair user_pair;
+ const char *user_pubid;
+ if (mail_crypt_user_generate_keypair(user, &user_pair,
+ &user_pubid,
+ error_r) < 0) {
+ return -1;
+ }
+
+ mail_crypt_put_key_cache(&muser->key_cache, user_pubid,
+ user_pair.priv, user_pair.pub);
+
+ user_key = user_pair.pub;
+ dcrypt_key_unref_private(&user_pair.priv);
+ }
+ } else {
+ dcrypt_key_ref_public(user_key);
+ }
+
+ if ((ret = mail_crypt_generate_keypair(curve, pair, pubid_r, error_r)) < 0) {
+ /* failed */
+ } else if ((ret = mail_crypt_box_set_keys(box, *pubid_r,
+ pair->priv, user_key, pair->pub,
+ error_r)) < 0) {
+ dcrypt_keypair_unref(pair);
+ } else {
+ mail_crypt_put_key_cache(&muser->key_cache, *pubid_r, pair->priv,
+ pair->pub);
+ }
+
+ dcrypt_key_unref_public(&user_key);
+
+ return ret;
+}
+
+int mail_crypt_box_get_pvt_digests(struct mailbox *box, pool_t pool,
+ enum mail_attribute_type type,
+ ARRAY_TYPE(const_string) *digests,
+ const char **error_r)
+{
+ struct mailbox_attribute_iter *iter;
+ const char *key;
+ int ret;
+
+ iter = mailbox_attribute_iter_init(box, type,
+ BOX_CRYPT_PREFIX PRIVKEYS_PREFIX);
+ while ((key = mailbox_attribute_iter_next(iter)) != NULL) {
+ key = p_strdup(pool, key);
+ array_push_back(digests, &key);
+ }
+ ret = mailbox_attribute_iter_deinit(&iter);
+ if (ret < 0)
+ *error_r = mailbox_get_last_internal_error(box, NULL);
+ return ret;
+}
+
+int mail_crypt_box_get_private_keys(struct mailbox *box,
+ ARRAY_TYPE(dcrypt_private_key) *keys_r,
+ const char **error_r)
+{
+ struct mailbox_attribute_iter *iter;
+ iter = mailbox_attribute_iter_init(box, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ BOX_CRYPT_PREFIX PRIVKEYS_PREFIX);
+ const char *id;
+ int ret;
+
+ while ((id = mailbox_attribute_iter_next(iter)) != NULL) {
+ struct dcrypt_private_key *key = NULL;
+ if ((ret = mail_crypt_get_private_key(box, id, FALSE, FALSE,
+ &key, error_r)) < 0) {
+ (void)mailbox_attribute_iter_deinit(&iter);
+ return -1;
+ } else if (ret > 0)
+ array_push_back(keys_r, &key);
+ }
+
+ ret = mailbox_attribute_iter_deinit(&iter);
+ if (ret < 0)
+ *error_r = mailbox_get_last_internal_error(box, NULL);
+ return ret;
+}
+
+int mail_crypt_box_share_private_keys(struct mailbox_transaction_context *t,
+ struct dcrypt_public_key *dest_pub_key,
+ const char *dest_user,
+ const ARRAY_TYPE(dcrypt_private_key) *priv_keys,
+ const char **error_r)
+{
+ i_assert(dest_user == NULL || dest_pub_key != NULL);
+
+ struct dcrypt_private_key *priv_key;
+ buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE);
+ int ret = 0;
+
+ array_foreach_elem(priv_keys, priv_key) {
+ ret = -1;
+ if (!dcrypt_key_id_private(priv_key, MAIL_CRYPT_KEY_ID_ALGORITHM,
+ key_id, error_r) ||
+ (ret = mail_crypt_box_set_shared_key(t,
+ binary_to_hex(key_id->data,
+ key_id->used),
+ priv_key, dest_user,
+ dest_pub_key, error_r)) < 0)
+ break;
+ }
+
+ return ret;
+}
+
+int
+mail_crypt_user_get_or_gen_public_key(struct mail_user *user,
+ struct dcrypt_public_key **pub_r,
+ const char **error_r)
+{
+ i_assert(user != NULL);
+ i_assert(pub_r != NULL);
+ i_assert(error_r != NULL);
+
+ int ret;
+ if ((ret = mail_crypt_user_get_public_key(user, pub_r, error_r)) == 0) {
+ struct dcrypt_keypair pair;
+ const char *pubid = NULL;
+ if (mail_crypt_user_generate_keypair(user, &pair,
+ &pubid, error_r) < 0) {
+ return -1;
+ }
+ *pub_r = pair.pub;
+ dcrypt_key_unref_private(&pair.priv);
+ } else
+ return ret;
+ return 0;
+}
+
+int
+mail_crypt_box_get_or_gen_public_key(struct mailbox *box,
+ struct dcrypt_public_key **pub_r,
+ const char **error_r)
+{
+ i_assert(box != NULL);
+ i_assert(pub_r != NULL);
+ i_assert(error_r != NULL);
+
+ struct mail_user *user =
+ mail_storage_get_user(mailbox_get_storage(box));
+ int ret;
+ if ((ret = mail_crypt_box_get_public_key(box, pub_r, error_r)) == 0) {
+ struct dcrypt_public_key *user_key;
+ if (mail_crypt_user_get_or_gen_public_key(user, &user_key,
+ error_r) < 0) {
+ return -1;
+ }
+
+ struct dcrypt_keypair pair;
+ const char *pubid = NULL;
+ if (mail_crypt_box_generate_keypair(box, &pair, user_key,
+ &pubid, error_r) < 0) {
+ return -1;
+ }
+ *pub_r = pair.pub;
+ dcrypt_key_unref_public(&user_key);
+ dcrypt_key_unref_private(&pair.priv);
+ } else
+ return ret;
+ return 0;
+}
+
+bool mail_crypt_acl_secure_sharing_enabled(struct mail_user *user)
+{
+ const char *env =
+ mail_user_plugin_getenv(user, MAIL_CRYPT_ACL_SECURE_SHARE_SETTING);
+
+ /* disabled by default */
+ bool ret = FALSE;
+
+ if (env != NULL) {
+ /* enable unless specifically
+ requested not to */
+ ret = TRUE;
+ switch (env[0]) {
+ case 'n':
+ case 'N':
+ case '0':
+ case 'f':
+ case 'F':
+ ret = FALSE;
+ }
+ }
+
+ return ret;
+}
+
+static const struct mailbox_attribute_internal mailbox_internal_attributes[] = {
+ { .type = MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ .key = BOX_CRYPT_PREFIX,
+ .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN
+ },
+ { .type = MAIL_ATTRIBUTE_TYPE_SHARED,
+ .key = BOX_CRYPT_PREFIX,
+ .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN
+ },
+ { .type = MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ .key = USER_CRYPT_PREFIX,
+ .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN
+ },
+ { .type = MAIL_ATTRIBUTE_TYPE_SHARED,
+ .key = USER_CRYPT_PREFIX,
+ .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN
+ }
+};
+
+void mail_crypt_key_register_mailbox_internal_attributes(void)
+{
+ mailbox_attribute_register_internals(mailbox_internal_attributes,
+ N_ELEMENTS(mailbox_internal_attributes));
+}
diff --git a/src/plugins/mail-crypt/mail-crypt-key.h b/src/plugins/mail-crypt/mail-crypt-key.h
new file mode 100644
index 0000000..f4a724a
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-key.h
@@ -0,0 +1,119 @@
+#ifndef MAIL_CRYPT_KEY
+#define MAIL_CRYPT_KEY
+
+#include "mail-crypt-common.h"
+#include "mail-crypt-global-key.h"
+#include "mail-storage.h"
+
+/*
+ For mailboxes:
+
+ shared/<mailbox GUID>/.../crypt/active = digest for the active public key
+ that is used for encrypting new emails
+ shared/<mailbox GUID>/.../crypt/pubkeys/<digest> = <key>
+ private/<mailbox GUID>/.../crypt/privkeys/<digest> = <key>
+
+ Similarly for users:
+
+ shared/<INBOX GUID>/.../crypt/active = digest for the active public key that
+ is used for encrypting new folder keys
+ shared/<INBOX GUID>/.../crypt/pubkeys/<digest> = <key>
+ private/<INBOX GUID>/.../crypt/privkeys/<digest> = <key>
+*/
+
+struct mail_crypt_key_cache_entry;
+
+/**
+ * key cache management functions
+ */
+void mail_crypt_key_cache_destroy(struct mail_crypt_key_cache_entry **cache);
+void mail_crypt_key_register_mailbox_internal_attributes(void);
+
+/* returns -1 on error, 0 not found, 1 = found */
+int mail_crypt_get_private_key(struct mailbox *box, const char *pubid,
+ bool user_key, bool shared,
+ struct dcrypt_private_key **key_r,
+ const char **error_r);
+int mail_crypt_user_get_private_key(struct mail_user *user, const char *pubid,
+ struct dcrypt_private_key **key_r,
+ const char **error_r);
+int mail_crypt_box_get_private_key(struct mailbox *box,
+ struct dcrypt_private_key **key_r,
+ const char **error_r);
+int mail_crypt_box_get_private_keys(struct mailbox *box,
+ ARRAY_TYPE(dcrypt_private_key) *keys_r,
+ const char **error_r);
+int mail_crypt_user_get_public_key(struct mail_user *user,
+ struct dcrypt_public_key **key_r,
+ const char **error_r);
+int mail_crypt_box_get_public_key(struct mailbox *box,
+ struct dcrypt_public_key **key_r,
+ const char **error_r);
+int mail_crypt_box_get_shared_key(struct mailbox *box,
+ const char *pubid,
+ struct dcrypt_private_key **key_r,
+ const char **error_r);
+/* returns -1 on error, 0 no match , 1 = match */
+int mail_crypt_private_key_id_match(struct dcrypt_private_key *key,
+ const char *pubid, const char **error_r);
+int mail_crypt_public_key_id_match(struct dcrypt_public_key *key,
+ const char *pubid, const char **error_r);
+/* returns -1 on error, 0 = ok */
+int mail_crypt_user_set_private_key(struct mail_user *user, const char *pubid,
+ struct dcrypt_private_key *key,
+ const char **error_r);
+int mail_crypt_box_set_private_key(struct mailbox *box, const char *pubid,
+ struct dcrypt_private_key *key,
+ struct dcrypt_public_key *user_key,
+ const char **error_r);
+int mail_crypt_user_set_public_key(struct mail_user *user, const char *pubid,
+ struct dcrypt_public_key *key,
+ const char **error_r);
+int mail_crypt_box_set_public_key(struct mailbox *box, const char *pubid,
+ struct dcrypt_public_key *key,
+ const char **error_r);
+int mail_crypt_user_generate_keypair(struct mail_user *user,
+ struct dcrypt_keypair *pair,
+ const char **pubid_r,
+ const char **error_r);
+int mail_crypt_box_generate_keypair(struct mailbox *box,
+ struct dcrypt_keypair *pair,
+ struct dcrypt_public_key *user_key,
+ const char **pubid_r,
+ const char **error_r);
+/* returns -1 on error, 0 = ok */
+int mail_crypt_box_set_shared_key(struct mailbox_transaction_context *t,
+ const char *pubid,
+ struct dcrypt_private_key *privkey,
+ const char *target_uid,
+ struct dcrypt_public_key *user_key,
+ const char **error_r);
+int mail_crypt_box_unset_shared_key(struct mailbox_transaction_context *t,
+ const char *pubid,
+ const char *target_uid,
+ const char **error_r);
+int mail_crypt_box_share_private_keys(struct mailbox_transaction_context *t,
+ struct dcrypt_public_key *dest_pub_key,
+ const char *dest_user,
+ const ARRAY_TYPE(dcrypt_private_key) *priv_keys,
+ const char **error_r);
+/* returns -1 on error, 0 = ok
+ these will also attempt to generate a keypair
+*/
+int mail_crypt_user_get_or_gen_public_key(struct mail_user *user,
+ struct dcrypt_public_key **pub_key_r,
+ const char **error_r);
+int mail_crypt_box_get_or_gen_public_key(struct mailbox *box,
+ struct dcrypt_public_key **pub_key_r,
+ const char **error_r);
+
+/* Lookup all private keys' digests. Returns 0 if ok, -1 on error. */
+int mail_crypt_box_get_pvt_digests(struct mailbox *box, pool_t pool,
+ enum mail_attribute_type type,
+ ARRAY_TYPE(const_string) *digests,
+ const char **error_r);
+
+/* is secure sharing enabled */
+bool mail_crypt_acl_secure_sharing_enabled(struct mail_user *user);
+
+#endif
diff --git a/src/plugins/mail-crypt/mail-crypt-plugin.c b/src/plugins/mail-crypt/mail-crypt-plugin.c
new file mode 100644
index 0000000..21da348
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-plugin.c
@@ -0,0 +1,486 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+/* FIXME: cache handling could be useful to move to Dovecot core, so that if
+ we're using this plugin together with zlib plugin there would be just one
+ cache. */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "randgen.h"
+#include "module-dir.h"
+#include "str.h"
+#include "safe-mkstemp.h"
+#include "istream.h"
+#include "istream-decrypt.h"
+#include "istream-seekable.h"
+#include "ostream.h"
+#include "ostream-encrypt.h"
+#include "mail-user.h"
+#include "mail-copy.h"
+#include "index-storage.h"
+#include "index-mail.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "mail-crypt-plugin.h"
+#include "sha2.h"
+#include "dcrypt-iostream.h"
+#include "hex-binary.h"
+
+struct mail_crypt_mailbox {
+ union mailbox_module_context module_ctx;
+ struct dcrypt_public_key *pub_key;
+};
+
+const char *mail_crypt_plugin_version = DOVECOT_ABI_VERSION;
+
+#define MAIL_CRYPT_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mail_crypt_mail_module)
+#define MAIL_CRYPT_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mail_crypt_storage_module)
+#define MAIL_CRYPT_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, mail_crypt_user_module)
+#define MAIL_CRYPT_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mail_crypt_user_module)
+
+static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_user_module,
+ &mail_user_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_mail_module,
+ &mail_module_register);
+
+struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user)
+{
+ return MAIL_CRYPT_USER_CONTEXT(user);
+}
+
+static bool mail_crypt_is_stream_encrypted(struct istream *input)
+{
+ const unsigned char *data = NULL;
+ size_t size;
+
+ if (i_stream_read_data(input, &data, &size,
+ sizeof(IOSTREAM_CRYPT_MAGIC)) <= 0) {
+ return FALSE;
+ }
+
+ if (memcmp(data, IOSTREAM_CRYPT_MAGIC,
+ sizeof(IOSTREAM_CRYPT_MAGIC)) != 0) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void mail_crypt_cache_close(struct mail_crypt_user *muser)
+{
+ struct mail_crypt_cache *cache = &muser->cache;
+
+ timeout_remove(&cache->to);
+ i_stream_unref(&cache->input);
+ i_zero(cache);
+}
+
+static struct istream *
+mail_crypt_cache_open(struct mail_crypt_user *muser, struct mail *mail,
+ struct istream *input)
+{
+ struct mail_crypt_cache *cache = &muser->cache;
+ struct istream *inputs[2];
+ string_t *temp_prefix = t_str_new(128);
+
+ mail_crypt_cache_close(muser);
+
+ input->seekable = FALSE;
+ inputs[0] = input;
+ inputs[1] = NULL;
+ mail_user_set_get_temp_prefix(temp_prefix, mail->box->storage->user->set);
+ input = i_stream_create_seekable_path(inputs,
+ i_stream_get_max_buffer_size(inputs[0]),
+ str_c(temp_prefix));
+ i_stream_unref(&inputs[0]);
+
+ if (mail->uid > 0) {
+ cache->to = timeout_add(MAIL_CRYPT_MAIL_CACHE_EXPIRE_MSECS,
+ mail_crypt_cache_close, muser);
+ cache->box = mail->box;
+ cache->uid = mail->uid;
+ cache->input = input;
+ /* index-mail wants the stream to be destroyed at close, so create
+ a new stream instead of just increasing reference. */
+ return i_stream_create_limit(cache->input, UOFF_T_MAX);
+ }
+
+ return input;
+}
+
+static int mail_crypt_istream_get_private_key(const char *pubkey_digest,
+ struct dcrypt_private_key **priv_key_r,
+ const char **error_r,
+ void *context)
+{
+ /* mailbox_crypt_search_all_private_keys requires error_r != NULL */
+ i_assert(error_r != NULL);
+ int ret;
+ struct mail *_mail = context;
+ struct mail_crypt_user *muser =
+ MAIL_CRYPT_USER_CONTEXT_REQUIRE(_mail->box->storage->user);
+
+ *priv_key_r = mail_crypt_global_key_find(&muser->global_keys,
+ pubkey_digest);
+ if (*priv_key_r != NULL) {
+ dcrypt_key_ref_private(*priv_key_r);
+ return 1;
+ }
+
+ struct mail_namespace *ns = mailbox_get_namespace(_mail->box);
+
+ if (ns->type == MAIL_NAMESPACE_TYPE_SHARED) {
+ ret = mail_crypt_box_get_shared_key(_mail->box, pubkey_digest,
+ priv_key_r, error_r);
+ /* priv_key_r is already referenced */
+ } else if (ns->type != MAIL_NAMESPACE_TYPE_PUBLIC) {
+ ret = mail_crypt_get_private_key(_mail->box, pubkey_digest,
+ FALSE, FALSE, priv_key_r,
+ error_r);
+ /* priv_key_r is already referenced */
+ } else {
+ *error_r = "Public emails cannot have keys";
+ ret = -1;
+ }
+
+ i_assert(ret <= 0 || *priv_key_r != NULL);
+
+ return ret;
+}
+
+static int
+mail_crypt_istream_opened(struct mail *_mail, struct istream **stream)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_user *user = _mail->box->storage->user;
+ struct mail_crypt_user *muser = MAIL_CRYPT_USER_CONTEXT_REQUIRE(user);
+ struct mail_crypt_cache *cache = &muser->cache;
+ union mail_module_context *mmail = MAIL_CRYPT_MAIL_CONTEXT(mail);
+ struct istream *input;
+
+ if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) {
+ /* use the cached stream. when doing partial reads it should
+ already be seeked into the wanted offset. */
+ i_stream_unref(stream);
+ i_stream_seek(cache->input, 0);
+ *stream = i_stream_create_limit(cache->input, UOFF_T_MAX);
+ return mmail->super.istream_opened(_mail, stream);
+ }
+
+ /* decryption is the outmost stream, so add it before others
+ (e.g. zlib) */
+ if (!mail_crypt_is_stream_encrypted(*stream))
+ return mmail->super.istream_opened(_mail, stream);
+
+ input = *stream;
+ *stream = i_stream_create_decrypt_callback(input,
+ mail_crypt_istream_get_private_key, _mail);
+ i_stream_unref(&input);
+
+ *stream = mail_crypt_cache_open(muser, _mail, *stream);
+ return mmail->super.istream_opened(_mail, stream);
+}
+
+static void mail_crypt_close(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *mmail = MAIL_CRYPT_MAIL_CONTEXT(mail);
+ struct mail_crypt_user *muser =
+ MAIL_CRYPT_USER_CONTEXT_REQUIRE(_mail->box->storage->user);
+ struct mail_crypt_cache *cache = &muser->cache;
+ uoff_t size;
+
+ if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) {
+ /* make sure we have read the entire email into the seekable
+ stream (which causes the original input stream to be
+ unrefed). we can't safely keep the original input stream
+ open after the mail is closed. */
+ if (i_stream_get_size(cache->input, TRUE, &size) < 0)
+ mail_crypt_cache_close(muser);
+ }
+ mmail->super.close(_mail);
+}
+
+static void mail_crypt_mail_allocated(struct mail *_mail)
+{
+ struct mail_crypt_user *muser =
+ MAIL_CRYPT_USER_CONTEXT(_mail->box->storage->user);
+ if (muser == NULL) return;
+
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ union mail_module_context *mmail;
+
+ mmail = p_new(mail->pool, union mail_module_context, 1);
+ mmail->super = *v;
+ mail->vlast = &mmail->super;
+
+ v->istream_opened = mail_crypt_istream_opened;
+ v->close = mail_crypt_close;
+ MODULE_CONTEXT_SET_SELF(mail, mail_crypt_mail_module, mmail);
+}
+
+static int mail_crypt_mail_save_finish(struct mail_save_context *ctx)
+{
+ struct mailbox *box = ctx->transaction->box;
+ union mailbox_module_context *zbox = MAIL_CRYPT_CONTEXT(box);
+ struct istream *input;
+
+ if (zbox->super.save_finish(ctx) < 0)
+ return -1;
+
+ /* we're here only if mail-crypt plugin is disabled. we want to make
+ sure that even though we're saving an unencrypted mail, the mail
+ can't be faked to look like an encrypted mail. */
+ if (mail_get_stream(ctx->dest_mail, NULL, NULL, &input) < 0)
+ return -1;
+
+ if (mail_crypt_is_stream_encrypted(input)) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Saving mails encrypted by client isn't supported");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+mail_crypt_mail_save_begin(struct mail_save_context *ctx,
+ struct istream *input)
+{
+ const char *pubid;
+ struct mailbox *box = ctx->transaction->box;
+ struct mail_crypt_mailbox *mbox = MAIL_CRYPT_CONTEXT(box);
+ struct mail_crypt_user *muser = MAIL_CRYPT_USER_CONTEXT(box->storage->user);
+
+ enum io_stream_encrypt_flags enc_flags = 0;
+ if (muser != NULL) {
+ if (muser->save_version == 1) {
+ enc_flags = IO_STREAM_ENC_VERSION_1;
+ } else if (muser->save_version == 2) {
+ enc_flags = IO_STREAM_ENC_INTEGRITY_AEAD;
+ } else {
+ i_assert(muser->save_version == 0);
+ }
+ }
+
+ if (mbox->module_ctx.super.save_begin(ctx, input) < 0)
+ return -1;
+
+ if (enc_flags == 0)
+ return 0;
+
+ struct dcrypt_public_key *pub_key;
+ if (muser->global_keys.public_key != NULL)
+ pub_key = muser->global_keys.public_key;
+ else if (mbox->pub_key != NULL)
+ pub_key = mbox->pub_key;
+ else {
+ const char *error;
+ int ret;
+
+ if ((ret = mail_crypt_box_get_public_key(box, &pub_key,
+ &error)) <= 0)
+ {
+ struct dcrypt_keypair pair;
+
+ if (ret < 0) {
+ mail_storage_set_error(box->storage,
+ MAIL_ERROR_PARAMS,
+ t_strdup_printf("get_public_key(%s) failed: %s",
+ mailbox_get_vname(box),
+ error));
+ return ret;
+ }
+
+ if (muser->save_version < 2) {
+ mail_storage_set_error(box->storage,
+ MAIL_ERROR_PARAMS,
+ t_strdup_printf("generate_keypair(%s) failed: "
+ "unsupported save_version=%d",
+ mailbox_get_vname(box),
+ muser->save_version));
+ return -1;
+ }
+
+ if (mail_crypt_box_generate_keypair(box, &pair, NULL,
+ &pubid, &error) < 0) {
+ mail_storage_set_error(box->storage,
+ MAIL_ERROR_PARAMS,
+ t_strdup_printf("generate_keypair(%s) failed: %s",
+ mailbox_get_vname(box),
+ error));
+ return -1;
+ }
+ pub_key = pair.pub;
+ dcrypt_key_unref_private(&pair.priv);
+
+ }
+ mbox->pub_key = pub_key;
+ }
+
+ /* encryption is the outermost layer (zlib etc. are inside) */
+ struct ostream *output = o_stream_create_encrypt(ctx->data.output,
+ MAIL_CRYPT_ENC_ALGORITHM, pub_key, enc_flags);
+
+ o_stream_unref(&ctx->data.output);
+ ctx->data.output = output;
+ o_stream_cork(ctx->data.output);
+ return 0;
+}
+
+static int
+mail_crypt_mailbox_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+ struct mail_crypt_mailbox *mbox = MAIL_CRYPT_CONTEXT(ctx->transaction->box);
+
+ if (ctx->transaction->box != mail->box)
+ return mail_storage_copy(ctx, mail);
+ return mbox->module_ctx.super.copy(ctx, mail);
+}
+
+static void mail_crypt_mailbox_close(struct mailbox *box)
+{
+ struct mail_crypt_mailbox *mbox = MAIL_CRYPT_CONTEXT(box);
+ struct mail_crypt_user *muser =
+ MAIL_CRYPT_USER_CONTEXT(box->storage->user);
+
+ if (mbox->pub_key != NULL)
+ dcrypt_key_unref_public(&mbox->pub_key);
+ if (muser != NULL && muser->cache.box == box)
+ mail_crypt_cache_close(muser);
+ mbox->module_ctx.super.close(box);
+}
+
+static void mail_crypt_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct mail_crypt_user *muser =
+ MAIL_CRYPT_USER_CONTEXT(box->storage->user);
+ struct mail_crypt_mailbox *mbox;
+ enum mail_storage_class_flags class_flags = box->storage->class_flags;
+
+ mbox = p_new(box->pool, struct mail_crypt_mailbox, 1);
+ mbox->module_ctx.super = *v;
+ box->vlast = &mbox->module_ctx.super;
+ v->close = mail_crypt_mailbox_close;
+
+ MODULE_CONTEXT_SET(box, mail_crypt_storage_module, mbox);
+
+ if ((class_flags & MAIL_STORAGE_CLASS_FLAG_BINARY_DATA) != 0) {
+ v->save_begin = mail_crypt_mail_save_begin;
+
+ /* if global keys are used, re-encrypting on copy/move
+ is not necessary, so do not attempt to do it.
+ with per-folder keys, emails must be re-encrypted
+ when moving to another folder */
+ if (muser == NULL || muser->save_version == 0 ||
+ muser->global_keys.public_key == NULL)
+ v->copy = mail_crypt_mailbox_copy;
+ if (muser == NULL || muser->save_version == 0)
+ v->save_finish = mail_crypt_mail_save_finish;
+ }
+}
+
+static void mail_crypt_mail_user_deinit(struct mail_user *user)
+{
+ struct mail_crypt_user *muser = MAIL_CRYPT_USER_CONTEXT_REQUIRE(user);
+
+ mail_crypt_key_cache_destroy(&muser->key_cache);
+ mail_crypt_global_keys_free(&muser->global_keys);
+ mail_crypt_cache_close(muser);
+ muser->module_ctx.super.deinit(user);
+}
+
+static void mail_crypt_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct mail_crypt_user *muser;
+ const char *error = NULL;
+
+ muser = p_new(user->pool, struct mail_crypt_user, 1);
+ muser->module_ctx.super = *v;
+ user->vlast = &muser->module_ctx.super;
+
+ const char *curve = mail_user_plugin_getenv(user, "mail_crypt_curve");
+ buffer_t *tmp = t_str_new(64);
+ if (curve == NULL || *curve == '\0') {
+ e_debug(user->event, "mail_crypt_plugin: mail_crypt_curve setting "
+ "missing - generating EC keys disabled");
+ } else if (!dcrypt_name2oid(curve, tmp, &error)) {
+ user->error = p_strdup_printf(user->pool,
+ "mail_crypt_plugin: "
+ "invalid mail_crypt_curve setting %s: %s",
+ curve, error);
+ } else {
+ muser->curve = p_strdup(user->pool, curve);
+ }
+
+ const char *version = mail_user_plugin_getenv(user,
+ "mail_crypt_save_version");
+
+ if (version == NULL || *version == '\0') {
+ user->error = p_strdup_printf(user->pool,
+ "mail_crypt_plugin: "
+ "mail_crypt_save_version setting missing");
+ } else if (version[0] == '0') {
+ muser->save_version = 0;
+ } else if (version[0] == '1') {
+ muser->save_version = 1;
+ } else if (version[0] == '2') {
+ muser->save_version = 2;
+ } else {
+ user->error = p_strdup_printf(user->pool,
+ "mail_crypt_plugin: Invalid "
+ "mail_crypt_save_version %s: use 0, 1, or 2 ",
+ version);
+ }
+
+ if (mail_crypt_global_keys_load(user, "mail_crypt_global",
+ &muser->global_keys, FALSE, &error) < 0) {
+ user->error = p_strdup_printf(user->pool,
+ "mail_crypt_plugin: %s", error);
+ }
+
+ v->deinit = mail_crypt_mail_user_deinit;
+ MODULE_CONTEXT_SET(user, mail_crypt_user_module, muser);
+}
+
+static struct mail_storage_hooks mail_crypt_mail_storage_hooks = {
+ .mail_user_created = mail_crypt_mail_user_created,
+ .mail_allocated = mail_crypt_mail_allocated
+};
+
+static struct mail_storage_hooks mail_crypt_mail_storage_hooks_post = {
+ .mailbox_allocated = mail_crypt_mailbox_allocated
+};
+
+static struct module crypto_post_module = {
+ .path = "lib95_mail_crypt_plugin.so"
+};
+
+void mail_crypt_plugin_init(struct module *module)
+{
+ const char* error;
+ if (!dcrypt_initialize("openssl", NULL, &error))
+ i_fatal("dcrypt_initialize(): %s", error);
+ mail_storage_hooks_add(module, &mail_crypt_mail_storage_hooks);
+ /* when this plugin is loaded, there's the potential chance for
+ mixed delivery between encrypted and non-encrypted recipients.
+ The mail_crypt_mailbox_allocated() hook ensures encrypted
+ content isn't copied as such into cleartext recipients
+ (and the other way around) */
+ mail_storage_hooks_add_forced(&crypto_post_module,
+ &mail_crypt_mail_storage_hooks_post);
+ mail_crypt_key_register_mailbox_internal_attributes();
+}
+
+void mail_crypt_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&mail_crypt_mail_storage_hooks);
+ mail_storage_hooks_remove(&mail_crypt_mail_storage_hooks_post);
+}
diff --git a/src/plugins/mail-crypt/mail-crypt-plugin.h b/src/plugins/mail-crypt/mail-crypt-plugin.h
new file mode 100644
index 0000000..4556a92
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-plugin.h
@@ -0,0 +1,32 @@
+#ifndef MAIL_CRYPT_PLUGIN_H
+#define MAIL_CRYPT_PLUGIN_H
+
+struct mailbox;
+struct module;
+
+struct mail_crypt_cache {
+ struct timeout *to;
+ struct mailbox *box;
+ uint32_t uid;
+
+ struct istream *input;
+};
+
+struct mail_crypt_user {
+ union mail_user_module_context module_ctx;
+
+ struct mail_crypt_global_keys global_keys;
+ struct mail_crypt_cache cache;
+ struct mail_crypt_key_cache_entry *key_cache;
+ const char *curve;
+ int save_version;
+};
+
+void mail_crypt_plugin_init(struct module *module);
+void mail_crypt_plugin_deinit(void);
+
+#define MAIL_CRYPT_MAIL_CACHE_EXPIRE_MSECS (60*1000)
+
+struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user);
+
+#endif
diff --git a/src/plugins/mail-crypt/mail-crypt-pluginenv.c b/src/plugins/mail-crypt/mail-crypt-pluginenv.c
new file mode 100644
index 0000000..68cf94f
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-pluginenv.c
@@ -0,0 +1,106 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "fs-crypt-settings.h"
+
+static const struct fs_crypt_settings *
+fs_crypt_load_settings(void)
+{
+ static const struct setting_parser_info *set_roots[] = {
+ &fs_crypt_setting_parser_info,
+ NULL
+ };
+ struct master_service_settings_input input;
+ struct master_service_settings_output output;
+ const char *error;
+
+ i_zero(&input);
+ input.roots = set_roots;
+ input.module = "fs-crypt";
+ input.service = "fs-crypt";
+ if (master_service_settings_read(master_service, &input,
+ &output, &error) < 0)
+ i_fatal("Error reading configuration: %s", error);
+
+ return master_service_settings_get_others(master_service)[0];
+}
+
+static
+const char *mail_crypt_plugin_getenv(const struct fs_crypt_settings *set,
+ const char *name)
+{
+ const char *const *envs;
+ unsigned int i, count;
+
+ if (set == NULL)
+ return NULL;
+
+ if (!array_is_created(&set->plugin_envs))
+ return NULL;
+
+ envs = array_get(&set->plugin_envs, &count);
+ for (i = 0; i < count; i += 2) {
+ if (strcmp(envs[i], name) == 0)
+ return envs[i+1];
+ }
+ return NULL;
+}
+
+static int
+mail_crypt_load_global_private_keys(const struct fs_crypt_settings *set,
+ const char *set_prefix,
+ struct mail_crypt_global_keys *global_keys,
+ const char **error_r)
+{
+ string_t *set_key = t_str_new(64);
+ str_append(set_key, set_prefix);
+ str_append(set_key, "_private_key");
+ size_t prefix_len = str_len(set_key);
+
+ unsigned int i = 1;
+ const char *key_data;
+ while ((key_data = mail_crypt_plugin_getenv(set, str_c(set_key))) != NULL) {
+ const char *set_pw = t_strconcat(str_c(set_key), "_password", NULL);
+ const char *password = mail_crypt_plugin_getenv(set, set_pw);
+ if (mail_crypt_load_global_private_key(str_c(set_key), key_data,
+ set_pw, password,
+ global_keys, error_r) < 0)
+ return -1;
+ str_truncate(set_key, prefix_len);
+ str_printfa(set_key, "%u", ++i);
+ }
+ return 0;
+}
+
+int mail_crypt_global_keys_load_pluginenv(const char *set_prefix,
+ struct mail_crypt_global_keys *global_keys_r,
+ const char **error_r)
+{
+ const struct fs_crypt_settings *set = fs_crypt_load_settings();
+
+ const char *set_key = t_strconcat(set_prefix, "_public_key", NULL);
+ const char *key_data = mail_crypt_plugin_getenv(set, set_key);
+ int ret = 0;
+
+ mail_crypt_global_keys_init(global_keys_r);
+ if (key_data != NULL) {
+ if (mail_crypt_load_global_public_key(set_key, key_data,
+ global_keys_r, error_r) < 0)
+ ret = -1;
+ }
+
+ if (ret == 0 &&
+ mail_crypt_load_global_private_keys(set, set_prefix, global_keys_r,
+ error_r) < 0)
+ ret = -1;
+
+ if (ret != 0)
+ mail_crypt_global_keys_free(global_keys_r);
+ return ret;
+}
diff --git a/src/plugins/mail-crypt/mail-crypt-userenv.c b/src/plugins/mail-crypt/mail-crypt-userenv.c
new file mode 100644
index 0000000..b152a7f
--- /dev/null
+++ b/src/plugins/mail-crypt/mail-crypt-userenv.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+#include "lib.h"
+#include "str.h"
+#include "mail-user.h"
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+
+static int
+mail_crypt_load_global_private_keys(struct mail_user *user,
+ const char *set_prefix,
+ struct mail_crypt_global_keys *global_keys,
+ bool ignore_errors,
+ const char **error_r)
+{
+ string_t *set_key = t_str_new(64);
+ str_append(set_key, set_prefix);
+ str_append(set_key, "_private_key");
+ size_t prefix_len = str_len(set_key);
+
+ unsigned int i = 1;
+ const char *key_data;
+ while ((key_data = mail_user_plugin_getenv(user, str_c(set_key))) != NULL) {
+ const char *set_pw = t_strconcat(str_c(set_key), "_password", NULL);
+ const char *password = mail_user_plugin_getenv(user, set_pw);
+ if (mail_crypt_load_global_private_key(str_c(set_key), key_data,
+ set_pw, password,
+ global_keys,
+ error_r) < 0) {
+ /* skip this key */
+ if (ignore_errors) {
+ e_debug(user->event, "mail-crypt-plugin: "
+ "mail_crypt_load_global_private_key failed: %s",
+ *error_r);
+ *error_r = NULL;
+ continue;
+ }
+ return -1;
+ }
+ str_truncate(set_key, prefix_len);
+ str_printfa(set_key, "%u", ++i);
+ }
+ return 0;
+}
+
+int mail_crypt_global_keys_load(struct mail_user *user, const char *set_prefix,
+ struct mail_crypt_global_keys *global_keys_r,
+ bool ignore_privkey_errors,
+ const char **error_r)
+{
+ const char *set_key = t_strconcat(set_prefix, "_public_key", NULL);
+ const char *key_data = mail_user_plugin_getenv(user, set_key);
+
+ mail_crypt_global_keys_init(global_keys_r);
+ if (key_data != NULL) {
+ if (mail_crypt_load_global_public_key(set_key,
+ key_data,
+ global_keys_r,
+ error_r) < 0)
+ return -1;
+ }
+ if (mail_crypt_load_global_private_keys(user, set_prefix, global_keys_r,
+ ignore_privkey_errors,
+ error_r) < 0)
+ return -1;
+ return 0;
+}
diff --git a/src/plugins/mail-crypt/test-mail-global-key.c b/src/plugins/mail-crypt/test-mail-global-key.c
new file mode 100644
index 0000000..4c5ed38
--- /dev/null
+++ b/src/plugins/mail-crypt/test-mail-global-key.c
@@ -0,0 +1,130 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "randgen.h"
+#include "array.h"
+#include "dcrypt.h"
+#include "hex-binary.h"
+
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "fs-crypt-settings.h"
+
+#include "mail-crypt-pluginenv.c"
+
+static struct fs_crypt_settings fs_set;
+
+static const char *settings[] = {
+ "mail_crypt_global_private_key",
+ "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ1lJdWZKWlplMlk2aUZ6NXgKa29Jb3lzYjNkWkxaV3N5ZWtqT2MvR2pzTGQyaFJBTkNBQVNuSVdnUXVoRThqcUFMY21maXVuUnlFazd2a3EveQphOXZZSzUwYjNjRmhDc0xVNHRmVlRMa0IxWS82VmxaajYzUUtNelhOdms1RzVPRDFvZkVsY3B5agotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==",
+ "mail_crypt_global_public_key",
+ "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFcHlGb0VMb1JQSTZnQzNKbjRycDBjaEpPNzVLdgo4bXZiMkN1ZEc5M0JZUXJDMU9MWDFVeTVBZFdQK2xaV1krdDBDak0xemI1T1J1VGc5YUh4SlhLY293PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==",
+ "mail_crypt_global_private_key2",
+ "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLQpNSUhlTUVrR0NTcUdTSWIzRFFFRkRUQThNQnNHQ1NxR1NJYjNEUUVGRERBT0JBaXA2cUpja1FET3F3SUNDQUF3CkhRWUpZSVpJQVdVREJBRXFCQkFXN09oUFRlU0xSOExLcGYwZjZHa3ZCSUdRZk5rYUpodnM2VWVWS2RkN2NzdFMKMURSNXJYTWtON09FbVNjTTljRlk2UDVrMzdnY1VJUFZudTQrOTFYZUE1MTU2cnBpUEpycEdkZnprcjhPNVFqZApsMWRycmR6Z0hqZHE4T2VmbUR1MEEzMjRZd25SS3hGRExUcjlHMkxVMkhoYmV6a0xjV1FwMVJISDZsNXRRcUtwCjZid05iMnc3OXhCb01YSjN6MVZqcElOZk9wRnJ6M3lucVlqUXhseTIrQjg2Ci0tLS0tRU5EIEVOQ1JZUFRFRCBQUklWQVRFIEtFWS0tLS0tCg==",
+ "mail_crypt_global_private_key2_password",
+ "password",
+};
+
+int
+mail_crypt_load_global_private_keys(const struct fs_crypt_settings *set,
+ const char *set_prefix,
+ struct mail_crypt_global_keys *global_keys,
+ const char **error_r);
+
+static void test_setup(void)
+{
+ struct dcrypt_settings set = {
+ .module_dir = top_builddir "/src/lib-dcrypt/.libs"
+ };
+ if (!dcrypt_initialize(NULL, &set, NULL)) {
+ i_info("No functional dcrypt backend found - skipping tests");
+ test_exit(0);
+ }
+ i_array_init(&fs_set.plugin_envs, 8);
+ array_append(&fs_set.plugin_envs, settings, N_ELEMENTS(settings));
+}
+
+static void test_try_load_keys(void)
+{
+ const char *pubid1 = "c79e262924842de291a8bcd413f4122a570abd033adeff7c1cdfdc9d05998c75";
+ const char *pubid2 = "aaf927444bff8b63425e852c6b3f769e8221b952b42cf886fae7d326c5be098e";
+ buffer_t *key_id = t_buffer_create(128);
+
+ const char *error = NULL;
+ test_begin("try_load_keys");
+
+ struct mail_crypt_global_keys keys;
+ i_zero(&keys);
+ mail_crypt_global_keys_init(&keys);
+
+ const char *set_prefix = "mail_crypt_global";
+ const char *set_key = t_strconcat(set_prefix, "_public_key", NULL);
+ const char *key_data = mail_crypt_plugin_getenv(&fs_set, set_key);
+
+ test_assert(key_data != NULL);
+
+ if (key_data != NULL) {
+ test_assert(mail_crypt_load_global_public_key(set_key, key_data,
+ &keys, &error) == 0);
+ test_assert(mail_crypt_load_global_private_keys(&fs_set, set_prefix,
+ &keys, &error) == 0);
+ /* did we get two private keys? */
+ test_assert(array_count(&keys.private_keys) == 2);
+
+ /* public key id checks */
+
+ buffer_set_used_size(key_id, 0);
+ test_assert(dcrypt_key_id_public(keys.public_key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE);
+ test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid1) == 0);
+
+ const struct mail_crypt_global_private_key *key =
+ array_front(&keys.private_keys);
+
+ buffer_set_used_size(key_id, 0);
+ test_assert(dcrypt_key_id_private(key->key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE);
+ test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid1) == 0);
+
+ key = array_idx(&keys.private_keys, 1);
+ buffer_set_used_size(key_id, 0);
+ test_assert(dcrypt_key_id_private(key->key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE);
+ test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid2) == 0);
+
+ }
+
+ mail_crypt_global_keys_free(&keys);
+
+ test_end();
+}
+
+static void test_empty_keyset(void)
+{
+ test_begin("test_empty_keyset");
+
+ /* this should not crash */
+ struct mail_crypt_global_keys keys;
+ i_zero(&keys);
+ test_assert(mail_crypt_global_key_find(&keys, "423423423423") == NULL);
+
+ test_end();
+}
+
+static void test_teardown(void)
+{
+ array_free(&fs_set.plugin_envs);
+ dcrypt_deinitialize();
+}
+
+int main(void)
+{
+ void (*tests[])(void) = {
+ test_setup,
+ test_try_load_keys,
+ test_empty_keyset,
+ test_teardown,
+ NULL
+ };
+
+ int ret = test_run(tests);
+ return ret;
+}
diff --git a/src/plugins/mail-crypt/test-mail-key.c b/src/plugins/mail-crypt/test-mail-key.c
new file mode 100644
index 0000000..ac89835
--- /dev/null
+++ b/src/plugins/mail-crypt/test-mail-key.c
@@ -0,0 +1,424 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "hex-binary.h"
+#include "master-service.h"
+#include "test-mail-storage-common.h"
+#include "dcrypt.h"
+
+#include "mail-crypt-common.h"
+#include "mail-crypt-key.h"
+#include "mail-crypt-plugin.h"
+
+static const char *mcp_old_user_key = "1\t716\t0\t048FD04FD3612B22D32790C592CF21CEF417EFD2EA34AE5F688FA5B51BED29E05A308B68DA78E16E90B47A11E133BD9A208A2894FD01B0BEE865CE339EA3FB17AC\td0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0";
+static const char *mcp_old_user_key_id = "d0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0";
+static const char *mcp_old_box_key = "1\t716\t1\t0567e6bf9579813ae967314423b0fceb14bda24749303923de9a9bb9370e0026f995901a57e63113eeb2baf0c940e978d00686cbb52bd5014bc318563375876255\t0300E46DA2125427BE968EB3B649910CDC4C405E5FFDE18D433A97CABFEE28CEEFAE9EE356C792004FFB80981D67E741B8CC036A34235A8D2E1F98D1658CFC963D07EB\td0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0\t7c9a1039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00fa4f";
+static const char *mcp_old_box_key_id = "7c9a1039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00fa4f";
+
+static struct test_mail_storage_ctx *test_ctx;
+static const char *test_user_key_id;
+static const char *test_box_key_id;
+
+static struct mail_crypt_user mail_crypt_user;
+
+struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user ATTR_UNUSED)
+{
+ return &mail_crypt_user;
+}
+
+static
+int test_mail_attribute_get(struct mailbox *box, bool user_key, bool shared,
+ const char *pubid, const char **value_r, const char **error_r)
+{
+ const char *attr_name;
+ enum mail_attribute_type attr_type;
+
+ if (strcmp(pubid, ACTIVE_KEY_NAME) == 0) {
+ attr_name = user_key ? USER_CRYPT_PREFIX ACTIVE_KEY_NAME :
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME;
+ attr_type = MAIL_ATTRIBUTE_TYPE_SHARED;
+ } else {
+ attr_name = t_strdup_printf("%s%s%s",
+ user_key ? USER_CRYPT_PREFIX :
+ BOX_CRYPT_PREFIX,
+ shared ? PUBKEYS_PREFIX :
+ PRIVKEYS_PREFIX,
+ pubid);
+ attr_type = shared ? MAIL_ATTRIBUTE_TYPE_SHARED : MAIL_ATTRIBUTE_TYPE_PRIVATE;
+ }
+ struct mail_attribute_value value;
+
+ int ret;
+
+ if ((ret = mailbox_attribute_get(box, attr_type,
+ attr_name, &value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s",
+ mailbox_get_vname(box),
+ attr_name,
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ } else {
+ *value_r = t_strdup(value.value);
+ }
+ return ret;
+}
+
+static int
+test_mail_attribute_set(struct mailbox_transaction_context *t,
+ bool user_key, bool shared, const char *pubid,
+ const char *value, const char **error_r)
+{
+ const char *attr_name;
+ enum mail_attribute_type attr_type;
+
+ if (strcmp(pubid, ACTIVE_KEY_NAME) == 0) {
+ attr_name = user_key ? USER_CRYPT_PREFIX ACTIVE_KEY_NAME :
+ BOX_CRYPT_PREFIX ACTIVE_KEY_NAME;
+ attr_type = MAIL_ATTRIBUTE_TYPE_SHARED;
+ } else {
+ attr_name = t_strdup_printf("%s%s%s",
+ user_key ? USER_CRYPT_PREFIX :
+ BOX_CRYPT_PREFIX,
+ shared ? PUBKEYS_PREFIX :
+ PRIVKEYS_PREFIX,
+ pubid);
+ attr_type = shared ? MAIL_ATTRIBUTE_TYPE_SHARED : MAIL_ATTRIBUTE_TYPE_PRIVATE;
+ }
+
+ struct mail_attribute_value attr_value;
+
+ int ret;
+
+ i_zero(&attr_value);
+ attr_value.value = value;
+
+ if ((ret = mailbox_attribute_set(t, attr_type,
+ attr_name, &attr_value)) <= 0) {
+ if (ret < 0) {
+ *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s) failed: %s",
+ mailbox_get_vname(mailbox_transaction_get_mailbox(t)),
+ attr_name,
+ mailbox_get_last_internal_error(mailbox_transaction_get_mailbox(t), NULL));
+ }
+ }
+
+ return ret;
+}
+
+
+static void test_generate_user_key(void)
+{
+ struct dcrypt_keypair pair;
+ const char *pubid;
+ const char *error = NULL;
+
+ test_begin("generate user key");
+
+ /* try to generate a keypair for user */
+ if (mail_crypt_user_generate_keypair(test_ctx->user, &pair,
+ &pubid, &error) < 0) {
+ i_error("generate_keypair failed: %s", error);
+ test_exit(1);
+ }
+
+ test_assert(pubid != NULL);
+
+ test_user_key_id = p_strdup(test_ctx->pool, pubid);
+
+ dcrypt_keypair_unref(&pair);
+ error = NULL;
+
+ /* keys ought to be in cache or somewhere...*/
+ if (mail_crypt_user_get_private_key(test_ctx->user, NULL, &pair.priv, &error) <= 0)
+ {
+ i_error("Cannot get user private key: %s", error);
+ }
+
+ test_assert(pair.priv != NULL);
+
+ if (pair.priv != NULL)
+ dcrypt_key_unref_private(&pair.priv);
+
+ test_end();
+}
+
+static void test_generate_inbox_key(void)
+{
+ struct dcrypt_public_key *user_key;
+ struct dcrypt_keypair pair;
+ const char *error = NULL, *pubid = NULL;
+
+ test_begin("generate inbox key");
+
+ if (mail_crypt_user_get_public_key(test_ctx->user, &user_key,
+ &error) <= 0) {
+ i_error("Cannot get user private key: %s", error);
+ }
+ struct mail_namespace *ns =
+ mail_namespace_find_inbox(test_ctx->user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ if (mailbox_open(box) < 0)
+ i_fatal("mailbox_open(INBOX) failed: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ if (mail_crypt_box_generate_keypair(box, &pair, user_key, &pubid,
+ &error) < 0) {
+ i_error("generate_keypair failed: %s", error);
+ test_exit(1);
+ }
+
+ i_assert(pubid != NULL);
+
+ dcrypt_keypair_unref(&pair);
+ dcrypt_key_unref_public(&user_key);
+ mailbox_free(&box);
+
+ test_box_key_id = p_strdup(test_ctx->pool, pubid);
+
+ test_end();
+}
+
+static void test_cache_reset(void)
+{
+ struct dcrypt_keypair pair;
+ const char *error = NULL;
+
+ test_begin("cache reset");
+
+ struct mail_crypt_user *muser =
+ mail_crypt_get_mail_crypt_user(test_ctx->user);
+ mail_crypt_key_cache_destroy(&muser->key_cache);
+
+ test_assert(mail_crypt_user_get_private_key(test_ctx->user, NULL,
+ &pair.priv, &error) > 0);
+ if (error != NULL)
+ i_error("mail_crypt_user_get_private_key() failed: %s", error);
+ error = NULL;
+ test_assert(mail_crypt_user_get_public_key(test_ctx->user,
+ &pair.pub, &error) > 0);
+ if (error != NULL)
+ i_error("mail_crypt_user_get_public_key() failed: %s", error);
+
+ dcrypt_keypair_unref(&pair);
+
+ test_end();
+}
+
+static void test_verify_keys(void)
+{
+ const char *value = "", *error = NULL;
+
+ const char *enc_id;
+ enum dcrypt_key_encryption_type enc_type;
+
+ test_begin("verify keys");
+
+ struct dcrypt_private_key *privkey = NULL, *user_key = NULL;
+ struct dcrypt_public_key *pubkey = NULL;
+
+ struct mail_namespace *ns =
+ mail_namespace_find_inbox(test_ctx->user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ if (mailbox_open(box) < 0)
+ i_fatal("mailbox_open(INBOX) failed: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ /* verify links */
+
+ /* user's public key */
+ test_assert(test_mail_attribute_get(box, TRUE, TRUE, ACTIVE_KEY_NAME,
+ &value, &error) > 0);
+ test_assert(strcmp(value, test_user_key_id) == 0);
+
+ test_assert(test_mail_attribute_get(box, TRUE, TRUE, value, &value,
+ &error) > 0);
+
+ /* load key */
+ test_assert(dcrypt_key_load_public(&pubkey, value, &error) == TRUE);
+
+ /* see if it matches */
+ test_assert(mail_crypt_public_key_id_match(pubkey, test_user_key_id,
+ &error) > 0);
+ dcrypt_key_unref_public(&pubkey);
+
+ /* user's private key */
+ test_assert(test_mail_attribute_get(box, TRUE, FALSE, ACTIVE_KEY_NAME,
+ &value, &error) > 0);
+ test_assert(strcmp(value, test_user_key_id) == 0);
+
+ test_assert(test_mail_attribute_get(box, TRUE, FALSE, value, &value,
+ &error) > 0);
+
+ /* load key */
+ test_assert(dcrypt_key_load_private(&user_key, value, NULL, NULL,
+ &error) == TRUE);
+
+ /* see if it matches */
+ test_assert(mail_crypt_private_key_id_match(user_key, test_user_key_id,
+ &error) > 0);
+
+
+
+
+ /* inbox's public key */
+ test_assert(test_mail_attribute_get(box, FALSE, TRUE, ACTIVE_KEY_NAME,
+ &value, &error) > 0);
+ test_assert(strcmp(value, test_box_key_id) == 0);
+
+ test_assert(test_mail_attribute_get(box, FALSE, TRUE, value, &value,
+ &error) > 0);
+
+ /* load key */
+ test_assert(dcrypt_key_load_public(&pubkey, value, &error) == TRUE);
+
+ /* see if it matches */
+ test_assert(mail_crypt_public_key_id_match(pubkey, test_box_key_id,
+ &error) > 0);
+ dcrypt_key_unref_public(&pubkey);
+
+ /* user's private key */
+ test_assert(test_mail_attribute_get(box, FALSE, FALSE, ACTIVE_KEY_NAME,
+ &value, &error) > 0);
+ test_assert(strcmp(value, test_box_key_id) == 0);
+
+ test_assert(test_mail_attribute_get(box, FALSE, FALSE, value, &value,
+ &error) > 0);
+
+ test_assert(dcrypt_key_string_get_info(value, NULL, NULL, NULL,
+ &enc_type, &enc_id, NULL,
+ &error) == TRUE);
+
+ test_assert(enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_KEY);
+ test_assert(strcmp(enc_id, test_user_key_id) == 0);
+
+ /* load key */
+ test_assert(dcrypt_key_load_private(&privkey, value, NULL, user_key,
+ &error) == TRUE);
+
+ /* see if it matches */
+ test_assert(mail_crypt_private_key_id_match(privkey, test_box_key_id,
+ &error) > 0);
+ dcrypt_key_unref_private(&privkey);
+ dcrypt_key_unref_private(&user_key);
+
+ mailbox_free(&box);
+
+ test_end();
+}
+
+static void test_old_key(void)
+{
+ test_begin("old keys");
+
+ const char *error = NULL;
+ struct dcrypt_private_key *privkey = NULL;
+
+ struct mail_namespace *ns =
+ mail_namespace_find_inbox(test_ctx->user->namespaces);
+ struct mailbox *box = mailbox_alloc(ns->list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ if (mailbox_open(box) < 0)
+ i_fatal("mailbox_open(INBOX) failed: %s",
+ mailbox_get_last_internal_error(box, NULL));
+
+ struct mailbox_transaction_context *t =
+ mailbox_transaction_begin(box, 0, __func__);
+
+ test_mail_attribute_set(t, TRUE, FALSE, mcp_old_user_key_id,
+ mcp_old_user_key, &error);
+ test_mail_attribute_set(t, FALSE, FALSE, mcp_old_box_key_id,
+ mcp_old_box_key, &error);
+
+ (void)mailbox_transaction_commit(&t);
+
+ error = NULL;
+
+ /* try to load old key */
+ test_assert(mail_crypt_get_private_key(box, mcp_old_box_key_id, FALSE, FALSE,
+ &privkey, &error) > 0);
+
+ if (error != NULL)
+ i_error("mail_crypt_get_private_key(%s) failed: %s",
+ mcp_old_box_key_id,
+ error);
+
+ test_assert(privkey != NULL);
+
+ if (privkey != NULL) {
+ buffer_t *key_id = t_buffer_create(32);
+ test_assert(dcrypt_key_id_private_old(privkey, key_id, &error));
+ test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), mcp_old_box_key_id) == 0);
+ dcrypt_key_unref_private(&privkey);
+ }
+
+ mailbox_free(&box);
+
+ test_end();
+}
+
+static void test_setup(void)
+{
+ struct dcrypt_settings set = {
+ .module_dir = top_builddir "/src/lib-dcrypt/.libs"
+ };
+ if (!dcrypt_initialize(NULL, &set, NULL)) {
+ i_info("No functional dcrypt backend found - skipping tests");
+ test_exit(0);
+ }
+ test_ctx = test_mail_storage_init();
+ const char *username = "mcp_test@example.com";
+ const char *const extra_input[] = {
+ t_strdup_printf("mail_crypt_curve=prime256v1"),
+ t_strdup_printf("mail_attribute_dict=file:%s/%s/dovecot-attributes",
+ test_ctx->home_root, username),
+ NULL
+ };
+ struct test_mail_storage_settings storage_set = {
+ .username = username,
+ .driver = "maildir",
+ .hierarchy_sep = "/",
+ .extra_input = extra_input,
+ };
+ test_mail_storage_init_user(test_ctx, &storage_set);
+
+ mail_crypt_key_register_mailbox_internal_attributes();
+}
+
+static void test_teardown(void)
+{
+ struct mail_crypt_user *muser =
+ mail_crypt_get_mail_crypt_user(test_ctx->user);
+ mail_crypt_key_cache_destroy(&muser->key_cache);
+
+ test_mail_storage_deinit_user(test_ctx);
+ test_mail_storage_deinit(&test_ctx);
+ dcrypt_deinitialize();
+}
+
+int main(int argc, char **argv)
+{
+ void (*tests[])(void) = {
+ test_setup,
+ test_generate_user_key,
+ test_generate_inbox_key,
+ test_cache_reset,
+ test_verify_keys,
+ test_old_key,
+ test_teardown,
+ NULL
+ };
+
+
+ master_service = master_service_init("test-mail-key",
+ MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS |
+ MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS |
+ MASTER_SERVICE_FLAG_NO_SSL_INIT |
+ MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME,
+ &argc, &argv, "");
+ int ret = test_run(tests);
+ master_service_deinit(&master_service);
+ return ret;
+}
diff --git a/src/plugins/mail-log/Makefile.am b/src/plugins/mail-log/Makefile.am
new file mode 100644
index 0000000..2f3fbc6
--- /dev/null
+++ b/src/plugins/mail-log/Makefile.am
@@ -0,0 +1,24 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/notify
+
+NOPLUGIN_LDFLAGS =
+lib20_mail_log_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_mail_log_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+lib20_mail_log_plugin_la_LIBADD = \
+ ../notify/lib15_notify_plugin.la
+endif
+
+lib20_mail_log_plugin_la_SOURCES = \
+ mail-log-plugin.c
+
+noinst_HEADERS = \
+ mail-log-plugin.h
diff --git a/src/plugins/mail-log/Makefile.in b/src/plugins/mail-log/Makefile.in
new file mode 100644
index 0000000..3032360
--- /dev/null
+++ b/src/plugins/mail-log/Makefile.in
@@ -0,0 +1,826 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/mail-log
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+@DOVECOT_PLUGIN_DEPS_TRUE@lib20_mail_log_plugin_la_DEPENDENCIES = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../notify/lib15_notify_plugin.la
+am_lib20_mail_log_plugin_la_OBJECTS = mail-log-plugin.lo
+lib20_mail_log_plugin_la_OBJECTS = \
+ $(am_lib20_mail_log_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_mail_log_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_mail_log_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/mail-log-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_mail_log_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_mail_log_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/notify
+
+lib20_mail_log_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_mail_log_plugin.la
+
+@DOVECOT_PLUGIN_DEPS_TRUE@lib20_mail_log_plugin_la_LIBADD = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../notify/lib15_notify_plugin.la
+
+lib20_mail_log_plugin_la_SOURCES = \
+ mail-log-plugin.c
+
+noinst_HEADERS = \
+ mail-log-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/mail-log/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/mail-log/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_mail_log_plugin.la: $(lib20_mail_log_plugin_la_OBJECTS) $(lib20_mail_log_plugin_la_DEPENDENCIES) $(EXTRA_lib20_mail_log_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_mail_log_plugin_la_LINK) -rpath $(moduledir) $(lib20_mail_log_plugin_la_OBJECTS) $(lib20_mail_log_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-log-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/mail-log-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/mail-log-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/mail-log/mail-log-plugin.c b/src/plugins/mail-log/mail-log-plugin.c
new file mode 100644
index 0000000..f01cd0d
--- /dev/null
+++ b/src/plugins/mail-log/mail-log-plugin.c
@@ -0,0 +1,546 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "llist.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "imap-util.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "notify-plugin.h"
+#include "mail-log-plugin.h"
+
+
+#define MAILBOX_NAME_LOG_LEN 64
+#define HEADER_LOG_LEN 80
+
+#define MAIL_LOG_USER_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mail_log_user_module)
+
+enum mail_log_field {
+ MAIL_LOG_FIELD_UID = 0x01,
+ MAIL_LOG_FIELD_BOX = 0x02,
+ MAIL_LOG_FIELD_MSGID = 0x04,
+ MAIL_LOG_FIELD_PSIZE = 0x08,
+ MAIL_LOG_FIELD_VSIZE = 0x10,
+ MAIL_LOG_FIELD_FLAGS = 0x20,
+ MAIL_LOG_FIELD_FROM = 0x40,
+ MAIL_LOG_FIELD_SUBJECT = 0x80
+};
+#define MAIL_LOG_DEFAULT_FIELDS \
+ (MAIL_LOG_FIELD_UID | MAIL_LOG_FIELD_BOX | \
+ MAIL_LOG_FIELD_MSGID | MAIL_LOG_FIELD_PSIZE)
+
+enum mail_log_event {
+ MAIL_LOG_EVENT_DELETE = 0x01,
+ MAIL_LOG_EVENT_UNDELETE = 0x02,
+ MAIL_LOG_EVENT_EXPUNGE = 0x04,
+ MAIL_LOG_EVENT_SAVE = 0x08,
+ MAIL_LOG_EVENT_COPY = 0x10,
+ MAIL_LOG_EVENT_MAILBOX_CREATE = 0x20,
+ MAIL_LOG_EVENT_MAILBOX_DELETE = 0x40,
+ MAIL_LOG_EVENT_MAILBOX_RENAME = 0x80,
+ MAIL_LOG_EVENT_FLAG_CHANGE = 0x100
+};
+#define MAIL_LOG_DEFAULT_EVENTS \
+ (MAIL_LOG_EVENT_DELETE | MAIL_LOG_EVENT_UNDELETE | \
+ MAIL_LOG_EVENT_EXPUNGE | MAIL_LOG_EVENT_SAVE | MAIL_LOG_EVENT_COPY | \
+ MAIL_LOG_EVENT_MAILBOX_DELETE | MAIL_LOG_EVENT_MAILBOX_RENAME)
+
+static const char *field_names[] = {
+ "uid",
+ "box",
+ "msgid",
+ "size",
+ "vsize",
+ "flags",
+ "from",
+ "subject",
+ NULL
+};
+
+static const char *event_names[] = {
+ "delete",
+ "undelete",
+ "expunge",
+ "save",
+ "copy",
+ "mailbox_create",
+ "mailbox_delete",
+ "mailbox_rename",
+ "flag_change",
+ NULL
+};
+
+struct mail_log_user {
+ union mail_user_module_context module_ctx;
+
+ enum mail_log_field fields;
+ enum mail_log_event events;
+ bool cached_only;
+};
+
+struct mail_log_message {
+ struct mail_log_message *prev, *next;
+
+ enum mail_log_event event;
+ bool ignore;
+ const char *pretext, *text;
+};
+
+struct mail_log_mail_txn_context {
+ pool_t pool;
+ struct mail_log_message *messages, *messages_tail;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(mail_log_user_module,
+ &mail_user_module_register);
+
+static enum mail_log_field mail_log_field_find(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; field_names[i] != NULL; i++) {
+ if (strcmp(name, field_names[i]) == 0)
+ return 1 << i;
+ }
+ return 0;
+}
+
+static enum mail_log_event mail_log_event_find(const char *name)
+{
+ unsigned int i;
+
+ if (strcmp(name, "append") == 0) {
+ /* v1.x backwards compatibility */
+ name = "save";
+ }
+ for (i = 0; event_names[i] != NULL; i++) {
+ if (strcmp(name, event_names[i]) == 0)
+ return 1 << i;
+ }
+ return 0;
+}
+
+static enum mail_log_field mail_log_parse_fields(const char *str)
+{
+ const char *const *tmp;
+ static enum mail_log_field field, fields = 0;
+
+ for (tmp = t_strsplit_spaces(str, ", "); *tmp != NULL; tmp++) {
+ field = mail_log_field_find(*tmp);
+ if (field == 0)
+ i_fatal("Unknown field in mail_log_fields: '%s'", *tmp);
+ fields |= field;
+ }
+ return fields;
+}
+
+static enum mail_log_event mail_log_parse_events(const char *str)
+{
+ const char *const *tmp;
+ static enum mail_log_event event, events = 0;
+
+ for (tmp = t_strsplit_spaces(str, ", "); *tmp != NULL; tmp++) {
+ event = mail_log_event_find(*tmp);
+ if (event == 0)
+ i_fatal("Unknown event in mail_log_events: '%s'", *tmp);
+ events |= event;
+ }
+ return events;
+}
+
+static void mail_log_mail_user_created(struct mail_user *user)
+{
+ struct mail_log_user *muser;
+ const char *str;
+
+ muser = p_new(user->pool, struct mail_log_user, 1);
+ MODULE_CONTEXT_SET(user, mail_log_user_module, muser);
+
+ str = mail_user_plugin_getenv(user, "mail_log_fields");
+ muser->fields = str == NULL ? MAIL_LOG_DEFAULT_FIELDS :
+ mail_log_parse_fields(str);
+
+ str = mail_user_plugin_getenv(user, "mail_log_events");
+ muser->events = str == NULL ? MAIL_LOG_DEFAULT_EVENTS :
+ mail_log_parse_events(str);
+
+ muser->cached_only =
+ mail_user_plugin_getenv_bool(user, "mail_log_cached_only");
+}
+
+static void mail_log_append_mailbox_name(string_t *str, struct mail *mail)
+{
+ const char *mailbox_str;
+
+ mailbox_str = mailbox_get_vname(mail->box);
+ str_printfa(str, "box=%s",
+ str_sanitize(mailbox_str, MAILBOX_NAME_LOG_LEN));
+}
+
+static void
+mail_log_append_mail_header(string_t *str, struct mail *mail,
+ const char *name, const char *header)
+{
+ const char *value;
+
+ if (mail_get_first_header_utf8(mail, header, &value) <= 0)
+ value = "";
+ str_printfa(str, "%s=%s", name, str_sanitize(value, HEADER_LOG_LEN));
+}
+
+static void
+mail_log_append_uid(struct mail_log_mail_txn_context *ctx,
+ struct mail_log_message *msg, string_t *str, uint32_t uid)
+{
+ if (uid != 0)
+ str_printfa(str, "uid=%u", uid);
+ else {
+ /* we don't know the uid yet, assign it later */
+ str_printfa(str, "uid=");
+ msg->pretext = p_strdup(ctx->pool, str_c(str));
+ str_truncate(str, 0);
+ }
+}
+
+static void
+mail_log_update_wanted_fields(struct mail *mail, enum mail_log_field fields)
+{
+ enum mail_fetch_field wanted_fields = 0;
+ struct mailbox_header_lookup_ctx *wanted_headers = NULL;
+ const char *headers[4];
+ unsigned int hdr_idx = 0;
+
+ if ((fields & MAIL_LOG_FIELD_MSGID) != 0)
+ headers[hdr_idx++] = "Message-ID";
+ if ((fields & MAIL_LOG_FIELD_FROM) != 0)
+ headers[hdr_idx++] = "From";
+ if ((fields & MAIL_LOG_FIELD_SUBJECT) != 0)
+ headers[hdr_idx++] = "Subject";
+ if (hdr_idx > 0) {
+ i_assert(hdr_idx < N_ELEMENTS(headers));
+ headers[hdr_idx] = NULL;
+ wanted_headers = mailbox_header_lookup_init(mail->box, headers);
+ }
+
+ if ((fields & MAIL_LOG_FIELD_PSIZE) != 0)
+ wanted_fields |= MAIL_FETCH_PHYSICAL_SIZE;
+ if ((fields & MAIL_LOG_FIELD_VSIZE) != 0)
+ wanted_fields |= MAIL_FETCH_VIRTUAL_SIZE;
+
+ mail_add_temp_wanted_fields(mail, wanted_fields, wanted_headers);
+ mailbox_header_lookup_unref(&wanted_headers);
+}
+
+static void
+mail_log_append_mail_message_real(struct mail_log_mail_txn_context *ctx,
+ struct mail *mail, enum mail_log_event event,
+ const char *desc)
+{
+ struct mail_log_user *muser =
+ MAIL_LOG_USER_CONTEXT(mail->box->storage->user);
+ struct mail_log_message *msg;
+ string_t *text;
+ uoff_t size;
+
+ msg = p_new(ctx->pool, struct mail_log_message, 1);
+
+ /* avoid parsing through the message multiple times */
+ mail_log_update_wanted_fields(mail, muser->fields);
+
+ text = t_str_new(128);
+ str_append(text, desc);
+ str_append(text, ": ");
+ if ((muser->fields & MAIL_LOG_FIELD_BOX) != 0) {
+ mail_log_append_mailbox_name(text, mail);
+ str_append(text, ", ");
+ }
+ if ((muser->fields & MAIL_LOG_FIELD_UID) != 0) {
+ if (event != MAIL_LOG_EVENT_SAVE &&
+ event != MAIL_LOG_EVENT_COPY)
+ mail_log_append_uid(ctx, msg, text, mail->uid);
+ else {
+ /* with mbox mail->uid contains the uid, but handle
+ this consistently with all mailbox formats */
+ mail_log_append_uid(ctx, msg, text, 0);
+ }
+ /* make sure UID is assigned to this mail */
+ mail->transaction->flags |= MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS;
+ str_append(text, ", ");
+ }
+ if ((muser->fields & MAIL_LOG_FIELD_MSGID) != 0) {
+ mail_log_append_mail_header(text, mail, "msgid", "Message-ID");
+ str_append(text, ", ");
+ }
+ if ((muser->fields & MAIL_LOG_FIELD_PSIZE) != 0) {
+ if (mail_get_physical_size(mail, &size) == 0)
+ str_printfa(text, "size=%"PRIuUOFF_T, size);
+ else
+ str_printfa(text, "size=error");
+ str_append(text, ", ");
+ }
+ if ((muser->fields & MAIL_LOG_FIELD_VSIZE) != 0) {
+ if (mail_get_virtual_size(mail, &size) == 0)
+ str_printfa(text, "vsize=%"PRIuUOFF_T, size);
+ else
+ str_printfa(text, "vsize=error");
+ str_append(text, ", ");
+ }
+ if ((muser->fields & MAIL_LOG_FIELD_FROM) != 0) {
+ mail_log_append_mail_header(text, mail, "from", "From");
+ str_append(text, ", ");
+ }
+ if ((muser->fields & MAIL_LOG_FIELD_SUBJECT) != 0) {
+ mail_log_append_mail_header(text, mail, "subject", "Subject");
+ str_append(text, ", ");
+ }
+ if ((muser->fields & MAIL_LOG_FIELD_FLAGS) != 0) {
+ str_printfa(text, "flags=(");
+ imap_write_flags(text, mail_get_flags(mail),
+ mail_get_keywords(mail));
+ str_append(text, "), ");
+ }
+ str_truncate(text, str_len(text)-2);
+
+ msg->event = event;
+ msg->text = p_strdup(ctx->pool, str_c(text));
+ DLLIST2_APPEND(&ctx->messages, &ctx->messages_tail, msg);
+}
+
+static void mail_log_add_dummy_msg(struct mail_log_mail_txn_context *ctx,
+ enum mail_log_event event)
+{
+ struct mail_log_message *msg;
+
+ msg = p_new(ctx->pool, struct mail_log_message, 1);
+ msg->event = event;
+ msg->ignore = TRUE;
+ DLLIST2_APPEND(&ctx->messages, &ctx->messages_tail, msg);
+}
+
+static void
+mail_log_append_mail_message(struct mail_log_mail_txn_context *ctx,
+ struct mail *mail, enum mail_log_event event,
+ const char *desc)
+{
+ struct mail_log_user *muser =
+ MAIL_LOG_USER_CONTEXT(mail->box->storage->user);
+
+ if ((muser->events & event) == 0) {
+ if (event == MAIL_LOG_EVENT_SAVE ||
+ event == MAIL_LOG_EVENT_COPY)
+ mail_log_add_dummy_msg(ctx, event);
+ return;
+ }
+
+ T_BEGIN {
+ enum mail_lookup_abort orig_lookup_abort = mail->lookup_abort;
+
+ if (event != MAIL_LOG_EVENT_SAVE && muser->cached_only)
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_NOT_IN_CACHE;
+ mail_log_append_mail_message_real(ctx, mail, event, desc);
+ mail->lookup_abort = orig_lookup_abort;
+ } T_END;
+}
+
+static void *
+mail_log_mail_transaction_begin(struct mailbox_transaction_context *t ATTR_UNUSED)
+{
+ pool_t pool;
+ struct mail_log_mail_txn_context *ctx;
+
+ pool = pool_alloconly_create("mail-log", 2048);
+ ctx = p_new(pool, struct mail_log_mail_txn_context, 1);
+ ctx->pool = pool;
+ return ctx;
+}
+
+static void mail_log_mail_save(void *txn, struct mail *mail)
+{
+ struct mail_log_mail_txn_context *ctx =
+ (struct mail_log_mail_txn_context *)txn;
+
+ mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_SAVE, "save");
+}
+
+static void mail_log_mail_copy(void *txn, struct mail *src, struct mail *dst)
+{
+ struct mail_log_mail_txn_context *ctx =
+ (struct mail_log_mail_txn_context *)txn;
+ struct mail_private *src_pmail = (struct mail_private *)src;
+ struct mailbox *src_box = src->box;
+ const char *desc;
+
+ if (src_pmail->vmail != NULL) {
+ /* copying a mail from virtual storage. src points to the
+ backend mail, but we want to log the virtual mailbox name. */
+ src_box = src_pmail->vmail->box;
+ }
+ desc = t_strdup_printf("copy from %s",
+ str_sanitize(mailbox_get_vname(src_box),
+ MAILBOX_NAME_LOG_LEN));
+ mail_log_append_mail_message(ctx, dst,
+ MAIL_LOG_EVENT_COPY, desc);
+}
+
+static void mail_log_mail_expunge(void *txn, struct mail *mail)
+{
+ struct mail_log_mail_txn_context *ctx =
+ (struct mail_log_mail_txn_context *)txn;
+ struct mail_private *p = (struct mail_private*)mail;
+
+ mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_EXPUNGE,
+ p->autoexpunged ? "autoexpunge" : "expunge");
+}
+
+static void mail_log_mail_update_flags(void *txn, struct mail *mail,
+ enum mail_flags old_flags)
+{
+ struct mail_log_mail_txn_context *ctx =
+ (struct mail_log_mail_txn_context *)txn;
+ enum mail_flags new_flags = mail_get_flags(mail);
+
+ if (((old_flags ^ new_flags) & MAIL_DELETED) == 0) {
+ mail_log_append_mail_message(ctx, mail,
+ MAIL_LOG_EVENT_FLAG_CHANGE,
+ "flag_change");
+ } else if ((old_flags & MAIL_DELETED) == 0) {
+ mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_DELETE,
+ "delete");
+ } else {
+ mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_UNDELETE,
+ "undelete");
+ }
+}
+
+static void
+mail_log_mail_update_keywords(void *txn, struct mail *mail,
+ const char *const *old_keywords ATTR_UNUSED)
+{
+ struct mail_log_mail_txn_context *ctx =
+ (struct mail_log_mail_txn_context *)txn;
+
+ mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_FLAG_CHANGE,
+ "flag_change");
+}
+
+static void mail_log_save(const struct mail_log_message *msg, uint32_t uid)
+{
+ if (msg->ignore) {
+ /* not logging this save/copy */
+ } else if (msg->pretext == NULL)
+ i_info("%s", msg->text);
+ else if (uid != 0)
+ i_info("%s%u%s", msg->pretext, uid, msg->text);
+ else
+ i_info("%serror%s", msg->pretext, msg->text);
+}
+
+static void
+mail_log_mail_transaction_commit(void *txn,
+ struct mail_transaction_commit_changes *changes)
+{
+ struct mail_log_mail_txn_context *ctx =
+ (struct mail_log_mail_txn_context *)txn;
+ struct mail_log_message *msg;
+ struct seq_range_iter iter;
+ unsigned int n = 0;
+ uint32_t uid;
+
+ seq_range_array_iter_init(&iter, &changes->saved_uids);
+ for (msg = ctx->messages; msg != NULL; msg = msg->next) {
+ if (msg->event == MAIL_LOG_EVENT_SAVE ||
+ msg->event == MAIL_LOG_EVENT_COPY) {
+ if (!seq_range_array_iter_nth(&iter, n++, &uid))
+ uid = 0;
+ mail_log_save(msg, uid);
+ } else {
+ i_assert(msg->pretext == NULL);
+ i_info("%s", msg->text);
+ }
+ }
+ i_assert(!seq_range_array_iter_nth(&iter, n, &uid));
+
+ pool_unref(&ctx->pool);
+}
+
+static void mail_log_mail_transaction_rollback(void *txn)
+{
+ struct mail_log_mail_txn_context *ctx =
+ (struct mail_log_mail_txn_context *)txn;
+
+ pool_unref(&ctx->pool);
+}
+
+static void
+mail_log_mailbox_create(struct mailbox *box)
+{
+ struct mail_log_user *muser = MAIL_LOG_USER_CONTEXT(box->storage->user);
+
+ if ((muser->events & MAIL_LOG_EVENT_MAILBOX_CREATE) == 0)
+ return;
+
+ i_info("Mailbox created: %s",
+ str_sanitize(mailbox_get_vname(box), MAILBOX_NAME_LOG_LEN));
+}
+
+static void
+mail_log_mailbox_delete_commit(void *txn ATTR_UNUSED, struct mailbox *box)
+{
+ struct mail_log_user *muser = MAIL_LOG_USER_CONTEXT(box->storage->user);
+
+ if ((muser->events & MAIL_LOG_EVENT_MAILBOX_DELETE) == 0)
+ return;
+
+ i_info("Mailbox deleted: %s",
+ str_sanitize(mailbox_get_vname(box), MAILBOX_NAME_LOG_LEN));
+}
+
+static void
+mail_log_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ struct mail_log_user *muser = MAIL_LOG_USER_CONTEXT(src->storage->user);
+
+ if ((muser->events & MAIL_LOG_EVENT_MAILBOX_RENAME) == 0)
+ return;
+
+ i_info("Mailbox renamed: %s -> %s",
+ str_sanitize(mailbox_get_vname(src), MAILBOX_NAME_LOG_LEN),
+ str_sanitize(mailbox_get_vname(dest), MAILBOX_NAME_LOG_LEN));
+}
+
+static const struct notify_vfuncs mail_log_vfuncs = {
+ .mail_transaction_begin = mail_log_mail_transaction_begin,
+ .mail_save = mail_log_mail_save,
+ .mail_copy = mail_log_mail_copy,
+ .mail_expunge = mail_log_mail_expunge,
+ .mail_update_flags = mail_log_mail_update_flags,
+ .mail_update_keywords = mail_log_mail_update_keywords,
+ .mail_transaction_commit = mail_log_mail_transaction_commit,
+ .mail_transaction_rollback = mail_log_mail_transaction_rollback,
+ .mailbox_create = mail_log_mailbox_create,
+ .mailbox_delete_commit = mail_log_mailbox_delete_commit,
+ .mailbox_rename = mail_log_mailbox_rename
+};
+
+static struct notify_context *mail_log_ctx;
+
+static struct mail_storage_hooks mail_log_mail_storage_hooks = {
+ .mail_user_created = mail_log_mail_user_created
+};
+
+void mail_log_plugin_init(struct module *module)
+{
+ mail_log_ctx = notify_register(&mail_log_vfuncs);
+ mail_storage_hooks_add(module, &mail_log_mail_storage_hooks);
+}
+
+void mail_log_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&mail_log_mail_storage_hooks);
+ notify_unregister(mail_log_ctx);
+}
+
+const char *mail_log_plugin_dependencies[] = { "notify", NULL };
diff --git a/src/plugins/mail-log/mail-log-plugin.h b/src/plugins/mail-log/mail-log-plugin.h
new file mode 100644
index 0000000..0af47ac
--- /dev/null
+++ b/src/plugins/mail-log/mail-log-plugin.h
@@ -0,0 +1,9 @@
+#ifndef MAIL_LOG_PLUGIN_H
+#define MAIL_LOG_PLUGIN_H
+
+extern const char *mail_log_plugin_dependencies[];
+
+void mail_log_plugin_init(struct module *module);
+void mail_log_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/mail-lua/Makefile.am b/src/plugins/mail-lua/Makefile.am
new file mode 100644
index 0000000..1698cb4
--- /dev/null
+++ b/src/plugins/mail-lua/Makefile.am
@@ -0,0 +1,32 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-lua \
+ $(LUA_CFLAGS)
+
+NOPLUGIN_LDFLAGS =
+
+module_LTLIBRARIES = \
+ lib01_mail_lua_plugin.la
+
+lib01_mail_lua_plugin_la_LDFLAGS = -module -avoid-version $(LUA_LIBS)
+lib01_mail_lua_plugin_la_LIBADD = \
+ ../../lib-storage/libdovecot-storage-lua.la \
+ $(LIBDOVECOT)
+lib01_mail_lua_plugin_la_SOURCES = \
+ mail-lua-plugin.c
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = \
+ mail-lua-plugin.h
+
+test_programs =
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+noinst_PROGRAMS = $(test_programs)
diff --git a/src/plugins/mail-lua/Makefile.in b/src/plugins/mail-lua/Makefile.in
new file mode 100644
index 0000000..8c230a3
--- /dev/null
+++ b/src/plugins/mail-lua/Makefile.in
@@ -0,0 +1,873 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/plugins/mail-lua
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 =
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+lib01_mail_lua_plugin_la_DEPENDENCIES = \
+ ../../lib-storage/libdovecot-storage-lua.la \
+ $(am__DEPENDENCIES_1)
+am_lib01_mail_lua_plugin_la_OBJECTS = mail-lua-plugin.lo
+lib01_mail_lua_plugin_la_OBJECTS = \
+ $(am_lib01_mail_lua_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib01_mail_lua_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib01_mail_lua_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/mail-lua-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib01_mail_lua_plugin_la_SOURCES)
+DIST_SOURCES = $(lib01_mail_lua_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-lua \
+ $(LUA_CFLAGS)
+
+module_LTLIBRARIES = \
+ lib01_mail_lua_plugin.la
+
+lib01_mail_lua_plugin_la_LDFLAGS = -module -avoid-version $(LUA_LIBS)
+lib01_mail_lua_plugin_la_LIBADD = \
+ ../../lib-storage/libdovecot-storage-lua.la \
+ $(LIBDOVECOT)
+
+lib01_mail_lua_plugin_la_SOURCES = \
+ mail-lua-plugin.c
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = \
+ mail-lua-plugin.h
+
+test_programs =
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/mail-lua/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/mail-lua/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib01_mail_lua_plugin.la: $(lib01_mail_lua_plugin_la_OBJECTS) $(lib01_mail_lua_plugin_la_DEPENDENCIES) $(EXTRA_lib01_mail_lua_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib01_mail_lua_plugin_la_LINK) -rpath $(moduledir) $(lib01_mail_lua_plugin_la_OBJECTS) $(lib01_mail_lua_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-lua-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/mail-lua-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/mail-lua-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-moduleLTLIBRARIES \
+ install-pdf install-pdf-am install-pkginc_libHEADERS \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/mail-lua/mail-lua-plugin.c b/src/plugins/mail-lua/mail-lua-plugin.c
new file mode 100644
index 0000000..146d95b
--- /dev/null
+++ b/src/plugins/mail-lua/mail-lua-plugin.c
@@ -0,0 +1,167 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "module-dir.h"
+#include "mail-lua-plugin.h"
+#include "mail-storage-lua.h"
+#include "mail-storage-private.h"
+#include "mail-storage-hooks.h"
+#include "dlua-script-private.h"
+
+#define MAIL_LUA_SCRIPT "mail_lua_script"
+#define MAIL_LUA_USER_CREATED_FN "mail_user_created"
+#define MAIL_LUA_USER_DEINIT_FN "mail_user_deinit"
+#define MAIL_LUA_USER_DEINIT_PRE_FN "mail_user_deinit_pre"
+#define MAIL_LUA_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, mail_lua_user_module)
+
+static MODULE_CONTEXT_DEFINE_INIT(mail_lua_user_module,
+ &mail_user_module_register);
+
+struct mail_lua_user_context {
+ union mail_user_module_context module_ctx;
+ struct dlua_script *script;
+};
+
+static int mail_lua_call_hook(struct dlua_script *script,
+ struct mail_user *user,
+ const char *hook,
+ const char **error_r)
+{
+ const char *error;
+
+ if (!dlua_script_has_function(script, hook))
+ return 0;
+
+ if (user->mail_debug)
+ e_debug(user->event, "mail-lua: Calling %s(user)", hook);
+
+ dlua_push_mail_user(script->L, user);
+
+ if (dlua_pcall(script->L, hook, 1, 2, &error) < 0) {
+ *error_r = t_strdup_printf("%s(user) failed: %s", hook, error);
+ return -1;
+ }
+
+ int ret = lua_tonumber(script->L, -2);
+ const char *errmsg = lua_tostring(script->L, -1);
+
+ if (ret < 0) {
+ *error_r = t_strdup_printf("%s(user) failed: %s",
+ hook, errmsg);
+ }
+
+ lua_pop(script->L, 2);
+ (void)lua_gc(script->L, LUA_GCCOLLECT, 0);
+
+ return ret < 0 ? -1 : 1;
+}
+
+static void mail_lua_user_deinit_pre(struct mail_user *user)
+{
+ struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user);
+ const char *error;
+
+ if (luser == NULL)
+ return;
+
+ if (mail_lua_call_hook(luser->script, user, MAIL_LUA_USER_DEINIT_PRE_FN,
+ &error) < 0) {
+ e_error(user->event, "mail-lua: %s", error);
+ }
+
+ luser->module_ctx.super.deinit_pre(user);
+}
+
+static void mail_lua_user_deinit(struct mail_user *user)
+{
+ struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user);
+ const char *error;
+
+ if (luser == NULL)
+ return;
+
+ luser->module_ctx.super.deinit(user);
+
+ if (mail_lua_call_hook(luser->script, user, MAIL_LUA_USER_DEINIT_FN,
+ &error) < 0) {
+ e_error(user->event, "mail-lua: %s", error);
+ }
+
+ dlua_script_unref(&luser->script);
+}
+
+static void mail_lua_user_created(struct mail_user *user)
+{
+ struct mail_lua_user_context *luser;
+ struct mail_user_vfuncs *v = user->vlast;
+ struct dlua_script *script;
+ const char *error;
+ const char *script_fn = mail_user_plugin_getenv(user, MAIL_LUA_SCRIPT);
+ int ret;
+
+ if (script_fn == NULL)
+ return;
+
+ if (dlua_script_create_file(script_fn, &script, user->event, &error) < 0) {
+ user->error = p_strdup_printf(user->pool, "dlua_script_create_file(%s) failed: %s",
+ script_fn, error);
+ return;
+ }
+
+ dlua_dovecot_register(script);
+ dlua_register_mail_storage(script);
+
+ /* init */
+ if (dlua_script_init(script, &error) < 0) {
+ user->error = p_strdup_printf(user->pool, "dlua_script_init(%s) failed: %s",
+ script_fn, error);
+ dlua_script_unref(&script);
+ return;
+ }
+
+ /* call postlogin hook */
+ if ((ret = mail_lua_call_hook(script, user, MAIL_LUA_USER_CREATED_FN,
+ &error)) <= 0) {
+ if (ret < 0)
+ user->error = p_strdup(user->pool, error);
+ dlua_script_unref(&script);
+ return;
+ }
+
+ luser = p_new(user->pool, struct mail_lua_user_context, 1);
+ luser->module_ctx.super = *v;
+ v->deinit_pre = mail_lua_user_deinit_pre;
+ v->deinit = mail_lua_user_deinit;
+ luser->script = script;
+ user->vlast = &luser->module_ctx.super;
+
+ MODULE_CONTEXT_SET(user, mail_lua_user_module, luser);
+}
+
+bool mail_lua_plugin_get_script(struct mail_user *user,
+ struct dlua_script **script_r)
+{
+ struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user);
+ if (luser != NULL) {
+ *script_r = luser->script;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static const struct mail_storage_hooks mail_lua_hooks = {
+ .mail_user_created = mail_lua_user_created,
+};
+
+void mail_lua_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &mail_lua_hooks);
+}
+
+void mail_lua_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&mail_lua_hooks);
+}
+
+const char *mail_lua_plugin_dependencies[] = { NULL };
diff --git a/src/plugins/mail-lua/mail-lua-plugin.h b/src/plugins/mail-lua/mail-lua-plugin.h
new file mode 100644
index 0000000..e3bfb98
--- /dev/null
+++ b/src/plugins/mail-lua/mail-lua-plugin.h
@@ -0,0 +1,14 @@
+#ifndef MAIL_LUA_PLUGIN_H
+#define MAIL_LUA_PLUGIN_H 1
+
+struct dlua_script;
+struct mail_user;
+struct module;
+
+void mail_lua_plugin_init(struct module *module);
+void mail_lua_plugin_deinit(void);
+
+bool mail_lua_plugin_get_script(struct mail_user *user,
+ struct dlua_script **script_r);
+
+#endif
diff --git a/src/plugins/mailbox-alias/Makefile.am b/src/plugins/mailbox-alias/Makefile.am
new file mode 100644
index 0000000..fa5f785
--- /dev/null
+++ b/src/plugins/mailbox-alias/Makefile.am
@@ -0,0 +1,18 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+NOPLUGIN_LDFLAGS =
+lib20_mailbox_alias_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_mailbox_alias_plugin.la
+
+lib20_mailbox_alias_plugin_la_SOURCES = \
+ mailbox-alias-plugin.c
+
+noinst_HEADERS = \
+ mailbox-alias-plugin.h
diff --git a/src/plugins/mailbox-alias/Makefile.in b/src/plugins/mailbox-alias/Makefile.in
new file mode 100644
index 0000000..93d11a5
--- /dev/null
+++ b/src/plugins/mailbox-alias/Makefile.in
@@ -0,0 +1,821 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/mailbox-alias
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib20_mailbox_alias_plugin_la_LIBADD =
+am_lib20_mailbox_alias_plugin_la_OBJECTS = mailbox-alias-plugin.lo
+lib20_mailbox_alias_plugin_la_OBJECTS = \
+ $(am_lib20_mailbox_alias_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_mailbox_alias_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib20_mailbox_alias_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/mailbox-alias-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_mailbox_alias_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_mailbox_alias_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+lib20_mailbox_alias_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_mailbox_alias_plugin.la
+
+lib20_mailbox_alias_plugin_la_SOURCES = \
+ mailbox-alias-plugin.c
+
+noinst_HEADERS = \
+ mailbox-alias-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/mailbox-alias/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/mailbox-alias/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_mailbox_alias_plugin.la: $(lib20_mailbox_alias_plugin_la_OBJECTS) $(lib20_mailbox_alias_plugin_la_DEPENDENCIES) $(EXTRA_lib20_mailbox_alias_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_mailbox_alias_plugin_la_LINK) -rpath $(moduledir) $(lib20_mailbox_alias_plugin_la_OBJECTS) $(lib20_mailbox_alias_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-alias-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/mailbox-alias-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/mailbox-alias-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/mailbox-alias/mailbox-alias-plugin.c b/src/plugins/mailbox-alias/mailbox-alias-plugin.c
new file mode 100644
index 0000000..ebd433c
--- /dev/null
+++ b/src/plugins/mailbox-alias/mailbox-alias-plugin.c
@@ -0,0 +1,356 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "mail-storage-hooks.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "mailbox-alias-plugin.h"
+
+#define MAILBOX_ALIAS_USER_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_user_module)
+#define MAILBOX_ALIAS_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_storage_module)
+#define MAILBOX_ALIAS_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_mailbox_list_module)
+
+struct mailbox_alias {
+ const char *old_vname, *new_vname;
+};
+
+struct mailbox_alias_user {
+ union mail_user_module_context module_ctx;
+
+ ARRAY(struct mailbox_alias) aliases;
+};
+
+struct mailbox_alias_mailbox_list {
+ union mailbox_list_module_context module_ctx;
+};
+
+struct mailbox_alias_mailbox {
+ union mailbox_module_context module_ctx;
+};
+
+enum mailbox_symlink_existence {
+ MAILBOX_SYMLINK_EXISTENCE_NONEXISTENT,
+ MAILBOX_SYMLINK_EXISTENCE_SYMLINK,
+ MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_user_module,
+ &mail_user_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_mailbox_list_module,
+ &mailbox_list_module_register);
+
+const char *mailbox_alias_plugin_version = DOVECOT_ABI_VERSION;
+
+static const char *
+mailbox_alias_find_new(struct mail_user *user, const char *new_vname)
+{
+ struct mailbox_alias_user *auser = MAILBOX_ALIAS_USER_CONTEXT(user);
+ const struct mailbox_alias *alias;
+
+ array_foreach(&auser->aliases, alias) {
+ if (strcmp(alias->new_vname, new_vname) == 0)
+ return alias->old_vname;
+ }
+ return NULL;
+}
+
+static int mailbox_symlink_exists(struct mailbox_list *list, const char *vname,
+ enum mailbox_symlink_existence *existence_r)
+{
+ struct mailbox_alias_mailbox_list *alist =
+ MAILBOX_ALIAS_LIST_CONTEXT(list);
+ struct stat st;
+ const char *symlink_name, *symlink_path;
+ int ret;
+
+ symlink_name = alist->module_ctx.super.get_storage_name(list, vname);
+ ret = mailbox_list_get_path(list, symlink_name,
+ MAILBOX_LIST_PATH_TYPE_DIR, &symlink_path);
+ if (ret < 0)
+ return -1;
+ i_assert(ret > 0);
+
+ if (lstat(symlink_path, &st) < 0) {
+ if (errno == ENOENT) {
+ *existence_r = MAILBOX_SYMLINK_EXISTENCE_NONEXISTENT;
+ return 0;
+ }
+ mailbox_list_set_critical(list,
+ "lstat(%s) failed: %m", symlink_path);
+ return -1;
+ }
+ if (S_ISLNK(st.st_mode))
+ *existence_r = MAILBOX_SYMLINK_EXISTENCE_SYMLINK;
+ else
+ *existence_r = MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK;
+ return 0;
+}
+
+static int mailbox_is_alias_symlink(struct mailbox *box)
+{
+ enum mailbox_symlink_existence existence;
+
+ if (mailbox_alias_find_new(box->storage->user, box->vname) == NULL)
+ return 0;
+ if (mailbox_symlink_exists(box->list, box->vname, &existence) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ return existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK ? 1 : 0;
+}
+
+static int
+mailbox_has_aliases(struct mailbox_list *list, const char *old_vname)
+{
+ struct mailbox_alias_user *auser =
+ MAILBOX_ALIAS_USER_CONTEXT(list->ns->user);
+ const struct mailbox_alias *alias;
+ enum mailbox_symlink_existence existence;
+ int ret = 0;
+
+ array_foreach(&auser->aliases, alias) {
+ if (strcmp(alias->old_vname, old_vname) == 0) {
+ if (mailbox_symlink_exists(list, alias->new_vname,
+ &existence) < 0)
+ ret = -1;
+ else if (existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK)
+ return 1;
+ }
+ }
+ return ret;
+}
+
+static int
+mailbox_alias_create_symlink(struct mailbox *box,
+ const char *old_name, const char *new_name)
+{
+ const char *old_path, *new_path, *fname;
+ int ret;
+
+ ret = mailbox_list_get_path(box->list, old_name,
+ MAILBOX_LIST_PATH_TYPE_DIR, &old_path);
+ if (ret > 0) {
+ ret = mailbox_list_get_path(box->list, new_name,
+ MAILBOX_LIST_PATH_TYPE_DIR,
+ &new_path);
+ }
+ if (ret < 0)
+ return -1;
+ if (ret == 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Mailbox aliases not supported by storage");
+ return -1;
+ }
+ fname = strrchr(old_path, '/');
+ i_assert(fname != NULL);
+ fname++;
+ i_assert(strncmp(new_path, old_path, fname-old_path) == 0);
+
+ if (symlink(fname, new_path) < 0) {
+ if (errno == EEXIST) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+ "Mailbox already exists");
+ return -1;
+ }
+ mailbox_set_critical(box,
+ "symlink(%s, %s) failed: %m", fname, new_path);
+ return -1;
+ }
+ return 0;
+}
+
+static const char *
+mailbox_alias_get_storage_name(struct mailbox_list *list, const char *vname)
+{
+ struct mailbox_alias_mailbox_list *alist =
+ MAILBOX_ALIAS_LIST_CONTEXT(list);
+ const char *old_vname;
+ enum mailbox_symlink_existence existence;
+
+ /* access the old mailbox so that e.g. full text search won't
+ index the mailbox twice. this also means that deletion must be
+ careful to delete the symlink, box->name. */
+ old_vname = mailbox_alias_find_new(list->ns->user, vname);
+ if (old_vname != NULL &&
+ mailbox_symlink_exists(list, vname, &existence) == 0 &&
+ existence != MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK)
+ vname = old_vname;
+
+ return alist->module_ctx.super.get_storage_name(list, vname);
+}
+
+static int
+mailbox_alias_create(struct mailbox *box, const struct mailbox_update *update,
+ bool directory)
+{
+ struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(box);
+ struct mailbox_alias_mailbox_list *alist =
+ MAILBOX_ALIAS_LIST_CONTEXT(box->list);
+ const char *symlink_name;
+ int ret;
+
+ ret = abox->module_ctx.super.create_box(box, update, directory);
+ if (mailbox_alias_find_new(box->storage->user, box->vname) == NULL)
+ return ret;
+ if (ret < 0 && mailbox_get_last_mail_error(box) != MAIL_ERROR_EXISTS)
+ return ret;
+
+ /* all the code so far has actually only created the original
+ mailbox. now we'll create the symlink if it's missing. */
+ symlink_name = alist->module_ctx.super.
+ get_storage_name(box->list, box->vname);
+ return mailbox_alias_create_symlink(box, box->name, symlink_name);
+}
+
+static int mailbox_alias_delete(struct mailbox *box)
+{
+ struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(box);
+ struct mailbox_alias_mailbox_list *alist =
+ MAILBOX_ALIAS_LIST_CONTEXT(box->list);
+ const char *symlink_name;
+ int ret;
+
+ ret = mailbox_has_aliases(box->list, box->vname);
+ if (ret < 0)
+ return -1;
+ if (ret > 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't delete mailbox while it has aliases");
+ return -1;
+ }
+
+ if ((ret = mailbox_is_alias_symlink(box)) < 0)
+ return -1;
+ if (ret > 0) {
+ /* we're deleting an alias mailbox. we'll need to handle this
+ explicitly since box->name points to the original mailbox */
+ symlink_name = alist->module_ctx.super.
+ get_storage_name(box->list, box->vname);
+ if (mailbox_list_delete_symlink(box->list, symlink_name) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ return 0;
+ }
+
+ return abox->module_ctx.super.delete_box(box);
+}
+
+static int mailbox_alias_rename(struct mailbox *src, struct mailbox *dest)
+{
+ struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(src);
+ int ret;
+
+ if ((ret = mailbox_is_alias_symlink(src)) < 0)
+ return -1;
+ else if (ret > 0) {
+ mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename alias mailboxes");
+ return -1;
+ }
+ if ((ret = mailbox_is_alias_symlink(dest)) < 0)
+ return -1;
+ else if (ret > 0) {
+ mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename to mailbox alias");
+ return -1;
+ }
+ ret = mailbox_has_aliases(src->list, src->vname);
+ if (ret < 0)
+ return -1;
+ if (ret > 0) {
+ mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename mailbox while it has aliases");
+ return -1;
+ }
+
+ return abox->module_ctx.super.rename_box(src, dest);
+}
+
+static void mailbox_alias_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct mailbox_alias_user *auser;
+ struct mailbox_alias *alias;
+ string_t *oldkey, *newkey;
+ const char *old_vname, *new_vname;
+ unsigned int i;
+
+ auser = p_new(user->pool, struct mailbox_alias_user, 1);
+ auser->module_ctx.super = *v;
+ user->vlast = &auser->module_ctx.super;
+
+ p_array_init(&auser->aliases, user->pool, 8);
+
+ oldkey = t_str_new(32);
+ newkey = t_str_new(32);
+ str_append(oldkey, "mailbox_alias_old");
+ str_append(newkey, "mailbox_alias_new");
+ for (i = 2;; i++) {
+ old_vname = mail_user_plugin_getenv(user, str_c(oldkey));
+ new_vname = mail_user_plugin_getenv(user, str_c(newkey));
+ if (old_vname == NULL || new_vname == NULL)
+ break;
+
+ alias = array_append_space(&auser->aliases);
+ alias->old_vname = old_vname;
+ alias->new_vname = new_vname;
+
+ str_truncate(oldkey, 0);
+ str_truncate(newkey, 0);
+ str_printfa(oldkey, "mailbox_alias_old%u", i);
+ str_printfa(newkey, "mailbox_alias_new%u", i);
+ }
+
+ MODULE_CONTEXT_SET(user, mailbox_alias_user_module, auser);
+}
+
+static void mailbox_alias_mailbox_list_created(struct mailbox_list *list)
+{
+ struct mailbox_list_vfuncs *v = list->vlast;
+ struct mailbox_alias_mailbox_list *alist;
+
+ alist = p_new(list->pool, struct mailbox_alias_mailbox_list, 1);
+ alist->module_ctx.super = *v;
+ list->vlast = &alist->module_ctx.super;
+
+ v->get_storage_name = mailbox_alias_get_storage_name;
+ MODULE_CONTEXT_SET(list, mailbox_alias_mailbox_list_module, alist);
+}
+
+static void mailbox_alias_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct mailbox_alias_mailbox *abox;
+
+ abox = p_new(box->pool, struct mailbox_alias_mailbox, 1);
+ abox->module_ctx.super = *v;
+ box->vlast = &abox->module_ctx.super;
+
+ v->create_box = mailbox_alias_create;
+ v->delete_box = mailbox_alias_delete;
+ v->rename_box = mailbox_alias_rename;
+ MODULE_CONTEXT_SET(box, mailbox_alias_storage_module, abox);
+}
+
+static struct mail_storage_hooks mailbox_alias_mail_storage_hooks = {
+ .mail_user_created = mailbox_alias_mail_user_created,
+ .mailbox_list_created = mailbox_alias_mailbox_list_created,
+ .mailbox_allocated = mailbox_alias_mailbox_allocated
+};
+
+void mailbox_alias_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &mailbox_alias_mail_storage_hooks);
+}
+
+void mailbox_alias_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&mailbox_alias_mail_storage_hooks);
+}
diff --git a/src/plugins/mailbox-alias/mailbox-alias-plugin.h b/src/plugins/mailbox-alias/mailbox-alias-plugin.h
new file mode 100644
index 0000000..bd439b9
--- /dev/null
+++ b/src/plugins/mailbox-alias/mailbox-alias-plugin.h
@@ -0,0 +1,7 @@
+#ifndef MAILBOX_ALIAS_PLUGIN_H
+#define MAILBOX_ALIAS_PLUGIN_H
+
+void mailbox_alias_plugin_init(struct module *module);
+void mailbox_alias_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/notify-status/Makefile.am b/src/plugins/notify-status/Makefile.am
new file mode 100644
index 0000000..241e88d
--- /dev/null
+++ b/src/plugins/notify-status/Makefile.am
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/plugins/notify
+
+NOPLUGIN_LDFLAGS =
+lib20_notify_status_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_notify_status_plugin.la
+
+lib20_notify_status_plugin_la_SOURCES = \
+ notify-status-plugin.c
diff --git a/src/plugins/notify-status/Makefile.in b/src/plugins/notify-status/Makefile.in
new file mode 100644
index 0000000..cb2bf23
--- /dev/null
+++ b/src/plugins/notify-status/Makefile.in
@@ -0,0 +1,817 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/notify-status
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib20_notify_status_plugin_la_LIBADD =
+am_lib20_notify_status_plugin_la_OBJECTS = notify-status-plugin.lo
+lib20_notify_status_plugin_la_OBJECTS = \
+ $(am_lib20_notify_status_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_notify_status_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib20_notify_status_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/notify-status-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_notify_status_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_notify_status_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/plugins/notify
+
+lib20_notify_status_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_notify_status_plugin.la
+
+lib20_notify_status_plugin_la_SOURCES = \
+ notify-status-plugin.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/notify-status/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/notify-status/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_notify_status_plugin.la: $(lib20_notify_status_plugin_la_OBJECTS) $(lib20_notify_status_plugin_la_DEPENDENCIES) $(EXTRA_lib20_notify_status_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_notify_status_plugin_la_LINK) -rpath $(moduledir) $(lib20_notify_status_plugin_la_OBJECTS) $(lib20_notify_status_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-status-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/notify-status-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/notify-status-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/notify-status/notify-status-plugin.c b/src/plugins/notify-status/notify-status-plugin.c
new file mode 100644
index 0000000..f52ffa1
--- /dev/null
+++ b/src/plugins/notify-status/notify-status-plugin.c
@@ -0,0 +1,362 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "json-parser.h"
+#include "str.h"
+#include "var-expand.h"
+#include "mail-user.h"
+#include "mail-storage.h"
+#include "mail-storage-private.h"
+#include "mail-namespace.h"
+#include "mail-storage-hooks.h"
+#include "imap-match.h"
+#include "dict.h"
+#include "notify-plugin.h"
+
+#define NOTIFY_STATUS_SETTING_DICT_URI "notify_status_dict"
+#define NOTIFY_STATUS_SETTING_MAILBOX_PREFIX "notify_status_mailbox"
+#define NOTIFY_STATUS_SETTING_VALUE_TEMPLATE "notify_status_value"
+#define NOTIFY_STATUS_SETTING_VALUE_TEMPLATE_DEFAULT "{\"messages\":%{messages},\"unseen\":%{unseen}}"
+#define NOTIFY_STATUS_KEY "priv/status/%s"
+
+#define NOTIFY_STATUS_USER_CONTEXT(obj) \
+ (struct notify_status_user*)MODULE_CONTEXT(obj, notify_status_user_module)
+
+static MODULE_CONTEXT_DEFINE_INIT(notify_status_user_module,
+ &mail_user_module_register);
+
+void notify_status_plugin_init(struct module *module);
+void notify_status_plugin_deinit(void);
+
+const char *notify_status_plugin_version = DOVECOT_ABI_VERSION;
+const char *notify_status_plugin_dependencies[] = { "notify", NULL };
+
+ARRAY_DEFINE_TYPE(imap_match_glob, struct imap_match_glob*);
+
+struct notify_status_mail_txn {
+ struct mailbox *box;
+ bool changed:1;
+};
+
+struct notify_status_user {
+ union mail_user_module_context module_ctx;
+
+ ARRAY_TYPE(imap_match_glob) patterns;
+ struct dict *dict;
+ const char *value_template;
+ struct notify_context *context;
+};
+
+static int notify_status_dict_init(struct mail_user *user, const char *uri,
+ struct dict **dict_r, const char **error_r)
+{
+ struct dict_settings set = {
+ .base_dir = user->set->base_dir,
+ .event_parent = user->event,
+ };
+ if (dict_init(uri, &set, dict_r, error_r) < 0) {
+ *error_r = t_strdup_printf("dict_init(%s) failed: %s",
+ uri, *error_r);
+ return -1;
+ }
+ return 0;
+}
+
+static void notify_status_mailbox_patterns_init(struct mail_user *user,
+ ARRAY_TYPE(imap_match_glob) *patterns)
+{
+ const char *value;
+ unsigned int i;
+
+ p_array_init(patterns, user->pool, 2);
+
+ for(i=1;;i++) {
+ struct imap_match_glob **glob;
+ const char *key = NOTIFY_STATUS_SETTING_MAILBOX_PREFIX;
+ if (i > 1)
+ key = t_strdup_printf("%s%u", key, i);
+ value = mail_user_plugin_getenv(user, key);
+ if (value == NULL)
+ return;
+ char sep = mail_namespace_get_sep(user->namespaces);
+ glob = array_append_space(patterns);
+ *glob = imap_match_init(user->pool, value, TRUE, sep);
+ }
+}
+
+static bool notify_status_mailbox_enabled(struct mailbox *box)
+{
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user);
+ struct imap_match_glob *glob;
+ /* not enabled */
+ if (nuser == NULL)
+ return FALSE;
+
+ /* if no patterns defined, anything goes */
+ if (array_count(&nuser->patterns) == 0)
+ return TRUE;
+
+ array_foreach_elem(&nuser->patterns, glob) {
+ if ((imap_match(glob, mailbox_get_vname(box)) & IMAP_MATCH_YES) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void notify_update_callback(const struct dict_commit_result *result,
+ void *context ATTR_UNUSED)
+{
+ if (result->ret == DICT_COMMIT_RET_OK ||
+ result->ret == DICT_COMMIT_RET_NOTFOUND)
+ return;
+
+ i_error("notify-status: dict_transaction_commit failed: %s",
+ result->error == NULL ? "" : result->error);
+}
+
+#define MAILBOX_STATUS_NOTIFY (STATUS_MESSAGES|STATUS_UNSEEN|\
+ STATUS_RECENT|STATUS_UIDNEXT|\
+ STATUS_UIDVALIDITY|\
+ STATUS_HIGHESTMODSEQ|STATUS_FIRST_RECENT_UID|\
+ STATUS_HIGHESTPVTMODSEQ)
+static void notify_update_mailbox_status(struct mailbox *box)
+{
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user);
+ i_assert(nuser != NULL);
+ struct dict_transaction_context *t;
+ struct mailbox_status status;
+
+ e_debug(box->event, "notify-status: Updating mailbox status");
+
+ box = mailbox_alloc(mailbox_get_namespace(box)->list,
+ mailbox_get_vname(box), MAILBOX_FLAG_READONLY);
+
+ if (mailbox_open(box) < 0) {
+ i_error("notify-status: mailbox_open(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_error(box, NULL));
+ } else if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) {
+ i_error("notify-status: mailbox_sync(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_error(box, NULL));
+ } else if (mailbox_get_status(box, MAILBOX_STATUS_NOTIFY,
+ &status) < 0) {
+ i_error("notify-status: mailbox_get_status(%s) failed: %s",
+ mailbox_get_vname(box),
+ mailbox_get_last_error(box, NULL));
+ } else {
+ string_t *username = t_str_new(strlen(user->username));
+ string_t *mboxname = t_str_new(64);
+
+ json_append_escaped(username, user->username);
+ json_append_escaped(mboxname, mailbox_get_vname(box));
+
+ const struct var_expand_table values[] = {
+ { '\0', str_c(username), "username" },
+ { '\0', str_c(mboxname), "mailbox" },
+ { '\0', dec2str(status.messages), "messages" },
+ { '\0', dec2str(status.unseen), "unseen" },
+ { '\0', dec2str(status.recent), "recent" },
+ { '\0', dec2str(status.uidvalidity), "uidvalidity" },
+ { '\0', dec2str(status.uidnext), "uidnext" },
+ { '\0', dec2str(status.first_recent_uid), "first_recent_uid" },
+ { '\0', dec2str(status.highest_modseq), "highest_modseq" },
+ { '\0', dec2str(status.highest_pvt_modseq), "highest_pvt_modseq" },
+ { '\0', NULL, NULL }
+ };
+ const char *error;
+ const char *key =
+ t_strdup_printf(NOTIFY_STATUS_KEY, mailbox_get_vname(box));
+ string_t *dest = t_str_new(64);
+ if (var_expand(dest, nuser->value_template, values, &error)<0) {
+ i_error("notify-status: var_expand(%s) failed: %s",
+ nuser->value_template, error);
+ } else {
+ const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
+ t = dict_transaction_begin(nuser->dict, set);
+ dict_set(t, key, str_c(dest));
+ dict_transaction_commit_async(&t, notify_update_callback, NULL) ;
+ }
+ }
+
+ mailbox_free(&box);
+}
+
+static void notify_remove_mailbox_status(struct mailbox *box)
+{
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user);
+ i_assert(nuser != NULL);
+ struct dict_transaction_context *t;
+
+ e_debug(box->event, "notify-status: Removing mailbox status");
+
+ const char *key =
+ t_strdup_printf(NOTIFY_STATUS_KEY, mailbox_get_vname(box));
+
+ const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
+ t = dict_transaction_begin(nuser->dict, set);
+ dict_unset(t, key);
+ dict_transaction_commit_async(&t, notify_update_callback, NULL) ;
+}
+
+static void *notify_status_mail_transaction_begin(struct mailbox_transaction_context *t)
+{
+ struct notify_status_mail_txn *txn = i_new(struct notify_status_mail_txn, 1);
+ txn->box = mailbox_transaction_get_mailbox(t);
+ return txn;
+}
+
+static void
+notify_status_mail_transaction_commit(void *t,
+ struct mail_transaction_commit_changes *changes ATTR_UNUSED)
+{
+ struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t;
+ if (txn->changed && notify_status_mailbox_enabled(txn->box))
+ notify_update_mailbox_status(txn->box);
+ i_free(txn);
+}
+
+static void notify_status_mail_transaction_rollback(void *t)
+{
+ i_free(t);
+}
+
+static void notify_status_mailbox_create(struct mailbox *box)
+{
+ if (notify_status_mailbox_enabled(box))
+ notify_update_mailbox_status(box);
+}
+
+static void notify_status_mailbox_delete_commit(void *txn ATTR_UNUSED,
+ struct mailbox *box)
+{
+ if (notify_status_mailbox_enabled(box))
+ notify_remove_mailbox_status(box);
+}
+
+static void notify_status_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ if (notify_status_mailbox_enabled(src))
+ notify_remove_mailbox_status(src);
+ if (notify_status_mailbox_enabled(dest))
+ notify_update_mailbox_status(dest);
+}
+
+static void notify_status_mail_save(void *t, struct mail *mail ATTR_UNUSED)
+{
+ struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t;
+ txn->changed = TRUE;
+}
+
+static void notify_status_mail_copy(void *t, struct mail *src ATTR_UNUSED,
+ struct mail *dst ATTR_UNUSED)
+{
+ struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t;
+ txn->changed = TRUE;
+}
+
+static void notify_status_mail_expunge(void *t, struct mail *mail ATTR_UNUSED)
+{
+ struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t;
+ txn->changed = TRUE;
+}
+static void notify_status_mail_update_flags(void *t, struct mail *mail,
+ enum mail_flags old_flags)
+{
+ struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t;
+ if ((old_flags & MAIL_SEEN) != (mail_get_flags(mail) & MAIL_SEEN))
+ txn->changed = TRUE;
+}
+
+static const struct notify_vfuncs notify_vfuncs =
+{
+ .mail_transaction_begin = notify_status_mail_transaction_begin,
+ .mail_save = notify_status_mail_save,
+ .mail_copy = notify_status_mail_copy,
+ .mail_expunge = notify_status_mail_expunge,
+ .mail_update_flags = notify_status_mail_update_flags,
+ .mail_transaction_commit = notify_status_mail_transaction_commit,
+ .mail_transaction_rollback = notify_status_mail_transaction_rollback,
+ .mailbox_create = notify_status_mailbox_create,
+ .mailbox_delete_commit = notify_status_mailbox_delete_commit,
+ .mailbox_rename = notify_status_mailbox_rename,
+};
+
+static void notify_status_mail_user_deinit(struct mail_user *user)
+{
+ struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user);
+ i_assert(nuser != NULL);
+
+ dict_wait(nuser->dict);
+ dict_deinit(&nuser->dict);
+ notify_unregister(nuser->context);
+ nuser->module_ctx.super.deinit(user);
+}
+
+static void notify_status_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct notify_status_user *nuser;
+ struct dict *dict;
+ const char *error;
+ const char *template = mail_user_plugin_getenv(user, NOTIFY_STATUS_SETTING_VALUE_TEMPLATE);
+ const char *uri = mail_user_plugin_getenv(user, NOTIFY_STATUS_SETTING_DICT_URI);
+
+ if (user->autocreated)
+ return;
+
+ if (uri == NULL || *uri == '\0') {
+ e_debug(user->event, "notify-status: Disabled - Missing plugin/"
+ NOTIFY_STATUS_SETTING_DICT_URI" setting");
+ return;
+ }
+
+ if (template == NULL || *template == '\0')
+ template = NOTIFY_STATUS_SETTING_VALUE_TEMPLATE_DEFAULT;
+
+ if (notify_status_dict_init(user, uri, &dict, &error) < 0) {
+ i_error("notify-status: %s", error);
+ return;
+ }
+
+ nuser = p_new(user->pool, struct notify_status_user, 1);
+ nuser->module_ctx.super = *v;
+ nuser->dict = dict;
+ user->vlast = &nuser->module_ctx.super;
+ v->deinit = notify_status_mail_user_deinit;
+ /* either static value or lifetime is user object's lifetime */
+ nuser->value_template = template;
+
+ MODULE_CONTEXT_SET(user, notify_status_user_module, nuser);
+}
+
+static void
+notify_status_mail_namespaces_created(struct mail_namespace *namespaces)
+{
+ struct mail_user *user = namespaces->user;
+ struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user);
+ if (nuser == NULL)
+ return;
+ notify_status_mailbox_patterns_init(user, &nuser->patterns);
+ nuser->context = notify_register(&notify_vfuncs);
+}
+
+static const struct mail_storage_hooks notify_storage_hooks =
+{
+ .mail_user_created = notify_status_mail_user_created,
+ .mail_namespaces_created = notify_status_mail_namespaces_created,
+};
+
+void notify_status_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &notify_storage_hooks);
+}
+
+void notify_status_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&notify_storage_hooks);
+}
diff --git a/src/plugins/notify/Makefile.am b/src/plugins/notify/Makefile.am
new file mode 100644
index 0000000..dfa286e
--- /dev/null
+++ b/src/plugins/notify/Makefile.am
@@ -0,0 +1,25 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/maildir
+
+NOPLUGIN_LDFLAGS =
+lib15_notify_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib15_notify_plugin.la
+
+lib15_notify_plugin_la_SOURCES = \
+ notify-plugin.c \
+ notify-storage.c
+
+headers = \
+ notify-plugin.h \
+ notify-plugin-private.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
diff --git a/src/plugins/notify/Makefile.in b/src/plugins/notify/Makefile.in
new file mode 100644
index 0000000..109f11e
--- /dev/null
+++ b/src/plugins/notify/Makefile.in
@@ -0,0 +1,852 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/notify
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib15_notify_plugin_la_LIBADD =
+am_lib15_notify_plugin_la_OBJECTS = notify-plugin.lo notify-storage.lo
+lib15_notify_plugin_la_OBJECTS = $(am_lib15_notify_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib15_notify_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib15_notify_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/notify-plugin.Plo \
+ ./$(DEPDIR)/notify-storage.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib15_notify_plugin_la_SOURCES)
+DIST_SOURCES = $(lib15_notify_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/maildir
+
+lib15_notify_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib15_notify_plugin.la
+
+lib15_notify_plugin_la_SOURCES = \
+ notify-plugin.c \
+ notify-storage.c
+
+headers = \
+ notify-plugin.h \
+ notify-plugin-private.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/notify/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/notify/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib15_notify_plugin.la: $(lib15_notify_plugin_la_OBJECTS) $(lib15_notify_plugin_la_DEPENDENCIES) $(EXTRA_lib15_notify_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib15_notify_plugin_la_LINK) -rpath $(moduledir) $(lib15_notify_plugin_la_OBJECTS) $(lib15_notify_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-storage.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/notify-plugin.Plo
+ -rm -f ./$(DEPDIR)/notify-storage.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/notify-plugin.Plo
+ -rm -f ./$(DEPDIR)/notify-storage.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/notify/notify-plugin-private.h b/src/plugins/notify/notify-plugin-private.h
new file mode 100644
index 0000000..cef0771
--- /dev/null
+++ b/src/plugins/notify/notify-plugin-private.h
@@ -0,0 +1,29 @@
+#ifndef NOTIFY_PLUGIN_PRIVATE_H
+#define NOTIFY_PLUGIN_PRIVATE_H
+
+#include "notify-plugin.h"
+
+void notify_contexts_mail_transaction_begin(struct mailbox_transaction_context *t);
+void notify_contexts_mail_save(struct mail *mail);
+void notify_contexts_mail_copy(struct mail *src, struct mail *dst);
+void notify_contexts_mail_expunge(struct mail *mail);
+void notify_contexts_mail_update_flags(struct mail *mail,
+ enum mail_flags old_flags);
+void notify_contexts_mail_update_keywords(struct mail *mail,
+ const char *const *old_keywords);
+void notify_contexts_mail_transaction_commit(struct mailbox_transaction_context *t,
+ struct mail_transaction_commit_changes *changes);
+void notify_contexts_mail_transaction_rollback(struct mailbox_transaction_context *t);
+void notify_contexts_mailbox_create(struct mailbox *box);
+void notify_contexts_mailbox_update(struct mailbox *box);
+void notify_contexts_mailbox_delete_begin(struct mailbox *box);
+void notify_contexts_mailbox_delete_commit(struct mailbox *box);
+void notify_contexts_mailbox_delete_rollback(void);
+void notify_contexts_mailbox_rename(struct mailbox *src, struct mailbox *dest);
+void notify_contexts_mailbox_set_subscribed(struct mailbox *box,
+ bool subscribed);
+
+void notify_plugin_init_storage(struct module *module);
+void notify_plugin_deinit_storage(void);
+
+#endif
diff --git a/src/plugins/notify/notify-plugin.c b/src/plugins/notify/notify-plugin.c
new file mode 100644
index 0000000..e9ff9bb
--- /dev/null
+++ b/src/plugins/notify/notify-plugin.c
@@ -0,0 +1,265 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "mail-storage.h"
+#include "notify-plugin-private.h"
+
+
+struct notify_mail_txn {
+ struct notify_mail_txn *prev, *next;
+ struct mailbox_transaction_context *parent_mailbox_txn;
+ struct mail *tmp_mail;
+ void *txn;
+};
+
+struct notify_context {
+ struct notify_context *prev, *next;
+ struct notify_vfuncs v;
+ struct notify_mail_txn *mail_txn_list;
+ void *mailbox_delete_txn;
+};
+
+const char *notify_plugin_version = DOVECOT_ABI_VERSION;
+static struct notify_context *ctx_list = NULL;
+
+static struct notify_mail_txn *
+notify_context_find_mail_txn(struct notify_context *ctx,
+ struct mailbox_transaction_context *t)
+{
+ struct notify_mail_txn *mail_txn = ctx->mail_txn_list;
+
+ for (; mail_txn != NULL; mail_txn = mail_txn->next) {
+ if (mail_txn->parent_mailbox_txn == t)
+ return mail_txn;
+ }
+ i_panic("no notify_mail_txn found");
+}
+
+void notify_contexts_mail_transaction_begin(struct mailbox_transaction_context *t)
+{
+ struct notify_context *ctx;
+ struct notify_mail_txn *mail_txn;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ mail_txn = i_new(struct notify_mail_txn, 1);
+ mail_txn->parent_mailbox_txn = t;
+ mail_txn->txn = ctx->v.mail_transaction_begin == NULL ? NULL :
+ ctx->v.mail_transaction_begin(t);
+ DLLIST_PREPEND(&ctx->mail_txn_list, mail_txn);
+ }
+}
+
+void notify_contexts_mail_save(struct mail *mail)
+{
+ struct notify_context *ctx;
+ struct notify_mail_txn *mail_txn;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mail_save == NULL)
+ continue;
+ mail_txn = notify_context_find_mail_txn(ctx, mail->transaction);
+ ctx->v.mail_save(mail_txn->txn, mail);
+ }
+}
+
+void notify_contexts_mail_copy(struct mail *src, struct mail *dst)
+{
+ struct notify_context *ctx;
+ struct notify_mail_txn *mail_txn;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mail_copy == NULL)
+ continue;
+ mail_txn = notify_context_find_mail_txn(ctx, dst->transaction);
+ ctx->v.mail_copy(mail_txn->txn, src, dst);
+ }
+}
+
+void notify_contexts_mail_expunge(struct mail *mail)
+{
+ struct notify_context *ctx;
+ struct notify_mail_txn *mail_txn;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mail_expunge == NULL)
+ continue;
+ mail_txn = notify_context_find_mail_txn(ctx, mail->transaction);
+ ctx->v.mail_expunge(mail_txn->txn, mail);
+ }
+}
+
+void notify_contexts_mail_update_flags(struct mail *mail,
+ enum mail_flags old_flags)
+{
+ struct notify_context *ctx;
+ struct notify_mail_txn *mail_txn;
+
+ if (mail->saving)
+ return;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mail_update_flags == NULL)
+ continue;
+ mail_txn = notify_context_find_mail_txn(ctx, mail->transaction);
+ ctx->v.mail_update_flags(mail_txn->txn, mail, old_flags);
+ }
+}
+
+void notify_contexts_mail_update_keywords(struct mail *mail,
+ const char *const *old_keywords)
+{
+ struct notify_context *ctx;
+ struct notify_mail_txn *mail_txn;
+
+ if (mail->saving)
+ return;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mail_update_keywords == NULL)
+ continue;
+ mail_txn = notify_context_find_mail_txn(ctx, mail->transaction);
+ ctx->v.mail_update_keywords(mail_txn->txn, mail, old_keywords);
+ }
+}
+
+void notify_contexts_mail_transaction_commit(struct mailbox_transaction_context *t,
+ struct mail_transaction_commit_changes *changes)
+{
+ struct notify_context *ctx;
+ struct notify_mail_txn *mail_txn;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ mail_txn = notify_context_find_mail_txn(ctx, t);
+ if (ctx->v.mail_transaction_commit != NULL)
+ ctx->v.mail_transaction_commit(mail_txn->txn, changes);
+ DLLIST_REMOVE(&ctx->mail_txn_list, mail_txn);
+ i_free(mail_txn);
+ }
+}
+
+void notify_contexts_mail_transaction_rollback(struct mailbox_transaction_context *t)
+{
+ struct notify_context *ctx;
+ struct notify_mail_txn *mail_txn;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ mail_txn = notify_context_find_mail_txn(ctx, t);
+ if (ctx->v.mail_transaction_rollback != NULL)
+ ctx->v.mail_transaction_rollback(mail_txn->txn);
+ DLLIST_REMOVE(&ctx->mail_txn_list, mail_txn);
+ i_free(mail_txn);
+ }
+}
+
+void notify_contexts_mailbox_create(struct mailbox *box)
+{
+ struct notify_context *ctx;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mailbox_create != NULL)
+ ctx->v.mailbox_create(box);
+ }
+}
+
+void notify_contexts_mailbox_update(struct mailbox *box)
+{
+ struct notify_context *ctx;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mailbox_update != NULL)
+ ctx->v.mailbox_update(box);
+ }
+}
+
+void notify_contexts_mailbox_delete_begin(struct mailbox *box)
+{
+ struct notify_context *ctx;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ ctx->mailbox_delete_txn =
+ ctx->v.mailbox_delete_begin == NULL ? NULL :
+ ctx->v.mailbox_delete_begin(box);
+ }
+}
+
+void notify_contexts_mailbox_delete_commit(struct mailbox *box)
+{
+ struct notify_context *ctx;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mailbox_delete_commit != NULL) {
+ ctx->v.mailbox_delete_commit(ctx->mailbox_delete_txn,
+ box);
+ }
+ ctx->mailbox_delete_txn = NULL;
+ }
+}
+
+void notify_contexts_mailbox_delete_rollback(void)
+{
+ struct notify_context *ctx;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mailbox_delete_rollback != NULL)
+ ctx->v.mailbox_delete_rollback(ctx->mailbox_delete_txn);
+ ctx->mailbox_delete_txn = NULL;
+ }
+}
+
+void notify_contexts_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ struct notify_context *ctx;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mailbox_rename != NULL)
+ ctx->v.mailbox_rename(src, dest);
+ }
+}
+
+void notify_contexts_mailbox_set_subscribed(struct mailbox *box,
+ bool subscribed)
+{
+ struct notify_context *ctx;
+
+ for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) {
+ if (ctx->v.mailbox_set_subscribed != NULL)
+ ctx->v.mailbox_set_subscribed(box, subscribed);
+ }
+}
+
+struct notify_context *
+notify_register(const struct notify_vfuncs *v)
+{
+ struct notify_context *ctx;
+
+ ctx = i_new(struct notify_context, 1);
+ ctx->v = *v;
+ DLLIST_PREPEND(&ctx_list, ctx);
+ return ctx;
+}
+
+void notify_unregister(struct notify_context *ctx)
+{
+ struct notify_mail_txn *mail_txn = ctx->mail_txn_list;
+
+ for (; mail_txn != NULL; mail_txn = mail_txn->next) {
+ if (ctx->v.mail_transaction_rollback != NULL)
+ ctx->v.mail_transaction_rollback(mail_txn->txn);
+ }
+ if (ctx->mailbox_delete_txn != NULL &&
+ ctx->v.mailbox_delete_rollback != NULL)
+ ctx->v.mailbox_delete_rollback(ctx->mailbox_delete_txn);
+ DLLIST_REMOVE(&ctx_list, ctx);
+ i_free(ctx);
+}
+
+void notify_plugin_init(struct module *module)
+{
+ notify_plugin_init_storage(module);
+}
+
+void notify_plugin_deinit(void)
+{
+ notify_plugin_deinit_storage();
+}
diff --git a/src/plugins/notify/notify-plugin.h b/src/plugins/notify/notify-plugin.h
new file mode 100644
index 0000000..3c95071
--- /dev/null
+++ b/src/plugins/notify/notify-plugin.h
@@ -0,0 +1,43 @@
+#ifndef NOTIFY_PLUGIN_H
+#define NOTIFY_PLUGIN_H
+
+#include "mail-types.h"
+
+struct mail;
+struct mail_transaction_commit_changes;
+struct mail_storage;
+struct mailbox_transaction_context;
+struct mailbox_list;
+struct mailbox;
+struct notify_context;
+struct module;
+
+struct notify_vfuncs {
+ void *(*mail_transaction_begin)(struct mailbox_transaction_context *t);
+ void (*mail_save)(void *txn, struct mail *mail);
+ void (*mail_copy)(void *txn, struct mail *src, struct mail *dst);
+ void (*mail_expunge)(void *txn, struct mail *mail);
+ void (*mail_update_flags)(void *txn, struct mail *mail,
+ enum mail_flags old_flags);
+ void (*mail_update_keywords)(void *txn, struct mail *mail,
+ const char *const *old_keywords);
+ void (*mail_transaction_commit)(void *txn,
+ struct mail_transaction_commit_changes *changes);
+ void (*mail_transaction_rollback)(void *txn);
+ void (*mailbox_create)(struct mailbox *box);
+ void (*mailbox_update)(struct mailbox *box);
+ void *(*mailbox_delete_begin)(struct mailbox *box);
+ void (*mailbox_delete_commit)(void *txn, struct mailbox *box);
+ void (*mailbox_delete_rollback)(void *txn);
+ void (*mailbox_rename)(struct mailbox *src, struct mailbox *dest);
+ void (*mailbox_set_subscribed)(struct mailbox *box, bool subscribed);
+};
+
+struct notify_context *
+notify_register(const struct notify_vfuncs *vfuncs);
+void notify_unregister(struct notify_context *ctx);
+
+void notify_plugin_init(struct module *module);
+void notify_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/notify/notify-storage.c b/src/plugins/notify/notify-storage.c
new file mode 100644
index 0000000..b7a6a3a
--- /dev/null
+++ b/src/plugins/notify/notify-storage.c
@@ -0,0 +1,269 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "notify-plugin-private.h"
+
+#define NOTIFY_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, notify_storage_module)
+#define NOTIFY_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, notify_mail_module)
+
+static MODULE_CONTEXT_DEFINE_INIT(notify_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(notify_mail_module,
+ &mail_module_register);
+
+static void
+notify_mail_expunge(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *lmail = NOTIFY_MAIL_CONTEXT(mail);
+
+ notify_contexts_mail_expunge(_mail);
+ lmail->super.expunge(_mail);
+}
+
+static void
+notify_mail_update_flags(struct mail *_mail, enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *lmail = NOTIFY_MAIL_CONTEXT(mail);
+ enum mail_flags old_flags, new_flags;
+
+ old_flags = mail_get_flags(_mail);
+ lmail->super.update_flags(_mail, modify_type, flags);
+ new_flags = mail_get_flags(_mail);
+
+ if ((old_flags ^ new_flags) == 0)
+ return;
+
+ notify_contexts_mail_update_flags(_mail, old_flags);
+}
+
+static void
+notify_mail_update_keywords(struct mail *_mail, enum modify_type modify_type,
+ struct mail_keywords *keywords)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *lmail = NOTIFY_MAIL_CONTEXT(mail);
+ const char *const *old_keywords, *const *new_keywords;
+ unsigned int i;
+
+ old_keywords = mail_get_keywords(_mail);
+ lmail->super.update_keywords(_mail, modify_type, keywords);
+ new_keywords = mail_get_keywords(_mail);
+
+ for (i = 0; old_keywords[i] != NULL && new_keywords[i] != NULL; i++) {
+ if (strcmp(old_keywords[i], new_keywords[i]) != 0)
+ break;
+ }
+
+ if (old_keywords[i] == NULL && new_keywords[i] == NULL)
+ return;
+
+ notify_contexts_mail_update_keywords(_mail, old_keywords);
+}
+
+static void notify_mail_allocated(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ union mail_module_context *lmail;
+
+ if ((_mail->transaction->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) != 0)
+ return;
+
+ lmail = p_new(mail->pool, union mail_module_context, 1);
+ lmail->super = *v;
+ mail->vlast = &lmail->super;
+
+ v->expunge = notify_mail_expunge;
+ v->update_flags = notify_mail_update_flags;
+ v->update_keywords = notify_mail_update_keywords;
+ MODULE_CONTEXT_SET_SELF(mail, notify_mail_module, lmail);
+}
+
+static int
+notify_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+ union mailbox_module_context *lbox =
+ NOTIFY_CONTEXT(ctx->transaction->box);
+ int ret;
+
+ if ((ret = lbox->super.copy(ctx, mail)) < 0)
+ return -1;
+
+ if ((ctx->transaction->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) != 0) {
+ /* no notifications */
+ } else if (ctx->saving) {
+ /* we came from mailbox_save_using_mail() */
+ notify_contexts_mail_save(ctx->dest_mail);
+ } else {
+ notify_contexts_mail_copy(mail, ctx->dest_mail);
+ }
+ return ret;
+}
+
+static int
+notify_save_finish(struct mail_save_context *ctx)
+{
+ union mailbox_module_context *lbox =
+ NOTIFY_CONTEXT(ctx->transaction->box);
+ struct mail *dest_mail = ctx->copying_via_save ? NULL : ctx->dest_mail;
+
+ if (lbox->super.save_finish(ctx) < 0)
+ return -1;
+ if (dest_mail != NULL &&
+ (ctx->transaction->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) == 0)
+ notify_contexts_mail_save(dest_mail);
+ return 0;
+}
+
+static struct mailbox_transaction_context *
+notify_transaction_begin(struct mailbox *box,
+ enum mailbox_transaction_flags flags,
+ const char *reason)
+{
+ union mailbox_module_context *lbox = NOTIFY_CONTEXT(box);
+ struct mailbox_transaction_context *t;
+
+ t = lbox->super.transaction_begin(box, flags, reason);
+
+ if ((t->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) == 0)
+ notify_contexts_mail_transaction_begin(t);
+ return t;
+}
+
+static int
+notify_transaction_commit(struct mailbox_transaction_context *t,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ union mailbox_module_context *lbox = NOTIFY_CONTEXT(t->box);
+ bool no_notify = (t->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) != 0;
+
+ if ((lbox->super.transaction_commit(t, changes_r)) < 0) {
+ if (!no_notify)
+ notify_contexts_mail_transaction_rollback(t);
+ return -1;
+ }
+
+ /* FIXME: note that t is already freed at this stage. it's not actually
+ being dereferenced anymore though. still, a bit unsafe.. */
+ if (!no_notify)
+ notify_contexts_mail_transaction_commit(t, changes_r);
+ return 0;
+}
+
+static void
+notify_transaction_rollback(struct mailbox_transaction_context *t)
+{
+ union mailbox_module_context *lbox = NOTIFY_CONTEXT(t->box);
+
+ if ((t->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) == 0)
+ notify_contexts_mail_transaction_rollback(t);
+ lbox->super.transaction_rollback(t);
+}
+
+static int
+notify_mailbox_create(struct mailbox *box, const struct mailbox_update *update,
+ bool directory)
+{
+ union mailbox_module_context *lbox = NOTIFY_CONTEXT(box);
+
+ if (lbox->super.create_box(box, update, directory) < 0)
+ return -1;
+
+ notify_contexts_mailbox_create(box);
+ return 0;
+}
+
+static int
+notify_mailbox_update(struct mailbox *box, const struct mailbox_update *update)
+{
+ union mailbox_module_context *lbox = NOTIFY_CONTEXT(box);
+
+ if (lbox->super.update_box(box, update) < 0)
+ return -1;
+
+ notify_contexts_mailbox_update(box);
+ return 0;
+}
+
+static int
+notify_mailbox_delete(struct mailbox *box)
+{
+ union mailbox_module_context *lbox = NOTIFY_CONTEXT(box);
+
+ notify_contexts_mailbox_delete_begin(box);
+ if (lbox->super.delete_box(box) < 0) {
+ notify_contexts_mailbox_delete_rollback();
+ return -1;
+ }
+ notify_contexts_mailbox_delete_commit(box);
+ return 0;
+}
+
+static int
+notify_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ union mailbox_module_context *lbox = NOTIFY_CONTEXT(src);
+
+ if (lbox->super.rename_box(src, dest) < 0)
+ return -1;
+
+ notify_contexts_mailbox_rename(src, dest);
+ return 0;
+}
+
+static int
+notify_mailbox_set_subscribed(struct mailbox *box, bool set)
+{
+ union mailbox_module_context *lbox = NOTIFY_CONTEXT(box);
+
+ if (lbox->super.set_subscribed(box, set) < 0)
+ return -1;
+
+ notify_contexts_mailbox_set_subscribed(box, set);
+ return 0;
+}
+
+static void notify_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ union mailbox_module_context *lbox;
+
+ lbox = p_new(box->pool, union mailbox_module_context, 1);
+ lbox->super = *v;
+ box->vlast = &lbox->super;
+
+ v->copy = notify_copy;
+ v->save_finish = notify_save_finish;
+ v->transaction_begin = notify_transaction_begin;
+ v->transaction_commit = notify_transaction_commit;
+ v->transaction_rollback = notify_transaction_rollback;
+ v->create_box = notify_mailbox_create;
+ v->update_box = notify_mailbox_update;
+ v->delete_box = notify_mailbox_delete;
+ v->rename_box = notify_mailbox_rename;
+ v->set_subscribed = notify_mailbox_set_subscribed;
+ MODULE_CONTEXT_SET_SELF(box, notify_storage_module, lbox);
+}
+
+static struct mail_storage_hooks notify_mail_storage_hooks = {
+ .mailbox_allocated = notify_mailbox_allocated,
+ .mail_allocated = notify_mail_allocated
+};
+
+void notify_plugin_init_storage(struct module *module)
+{
+ mail_storage_hooks_add(module, &notify_mail_storage_hooks);
+}
+
+void notify_plugin_deinit_storage(void)
+{
+ mail_storage_hooks_remove(&notify_mail_storage_hooks);
+}
diff --git a/src/plugins/old-stats/Makefile.am b/src/plugins/old-stats/Makefile.am
new file mode 100644
index 0000000..8a95363
--- /dev/null
+++ b/src/plugins/old-stats/Makefile.am
@@ -0,0 +1,33 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-old-stats \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+NOPLUGIN_LDFLAGS =
+lib90_old_stats_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib90_old_stats_plugin.la
+
+lib90_old_stats_plugin_la_SOURCES = \
+ mail-stats.c \
+ mail-stats-fill.c \
+ mail-stats-connection.c \
+ stats-plugin.c
+
+noinst_HEADERS = \
+ mail-stats.h \
+ mail-stats-connection.h \
+ stats-plugin.h
+
+old_stats_moduledir = $(moduledir)/old-stats
+old_stats_module_LTLIBRARIES = libold_stats_mail.la
+
+libold_stats_mail_la_LDFLAGS = -module -avoid-version
+libold_stats_mail_la_LIBADD = mail-stats.lo $(LIBDOVECOT)
+libold_stats_mail_la_DEPENDENCIES = mail-stats.lo
+libold_stats_mail_la_SOURCES =
diff --git a/src/plugins/old-stats/Makefile.in b/src/plugins/old-stats/Makefile.in
new file mode 100644
index 0000000..be11193
--- /dev/null
+++ b/src/plugins/old-stats/Makefile.in
@@ -0,0 +1,898 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/old-stats
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(old_stats_moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES) $(old_stats_module_LTLIBRARIES)
+lib90_old_stats_plugin_la_LIBADD =
+am_lib90_old_stats_plugin_la_OBJECTS = mail-stats.lo \
+ mail-stats-fill.lo mail-stats-connection.lo stats-plugin.lo
+lib90_old_stats_plugin_la_OBJECTS = \
+ $(am_lib90_old_stats_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib90_old_stats_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib90_old_stats_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am__DEPENDENCIES_1 =
+am_libold_stats_mail_la_OBJECTS =
+libold_stats_mail_la_OBJECTS = $(am_libold_stats_mail_la_OBJECTS)
+libold_stats_mail_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libold_stats_mail_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/mail-stats-connection.Plo \
+ ./$(DEPDIR)/mail-stats-fill.Plo ./$(DEPDIR)/mail-stats.Plo \
+ ./$(DEPDIR)/stats-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib90_old_stats_plugin_la_SOURCES) \
+ $(libold_stats_mail_la_SOURCES)
+DIST_SOURCES = $(lib90_old_stats_plugin_la_SOURCES) \
+ $(libold_stats_mail_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-old-stats \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+lib90_old_stats_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib90_old_stats_plugin.la
+
+lib90_old_stats_plugin_la_SOURCES = \
+ mail-stats.c \
+ mail-stats-fill.c \
+ mail-stats-connection.c \
+ stats-plugin.c
+
+noinst_HEADERS = \
+ mail-stats.h \
+ mail-stats-connection.h \
+ stats-plugin.h
+
+old_stats_moduledir = $(moduledir)/old-stats
+old_stats_module_LTLIBRARIES = libold_stats_mail.la
+libold_stats_mail_la_LDFLAGS = -module -avoid-version
+libold_stats_mail_la_LIBADD = mail-stats.lo $(LIBDOVECOT)
+libold_stats_mail_la_DEPENDENCIES = mail-stats.lo
+libold_stats_mail_la_SOURCES =
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/old-stats/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/old-stats/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-old_stats_moduleLTLIBRARIES: $(old_stats_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(old_stats_module_LTLIBRARIES)'; test -n "$(old_stats_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(old_stats_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(old_stats_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(old_stats_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(old_stats_moduledir)"; \
+ }
+
+uninstall-old_stats_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(old_stats_module_LTLIBRARIES)'; test -n "$(old_stats_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(old_stats_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(old_stats_moduledir)/$$f"; \
+ done
+
+clean-old_stats_moduleLTLIBRARIES:
+ -test -z "$(old_stats_module_LTLIBRARIES)" || rm -f $(old_stats_module_LTLIBRARIES)
+ @list='$(old_stats_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib90_old_stats_plugin.la: $(lib90_old_stats_plugin_la_OBJECTS) $(lib90_old_stats_plugin_la_DEPENDENCIES) $(EXTRA_lib90_old_stats_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib90_old_stats_plugin_la_LINK) -rpath $(moduledir) $(lib90_old_stats_plugin_la_OBJECTS) $(lib90_old_stats_plugin_la_LIBADD) $(LIBS)
+
+libold_stats_mail.la: $(libold_stats_mail_la_OBJECTS) $(libold_stats_mail_la_DEPENDENCIES) $(EXTRA_libold_stats_mail_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libold_stats_mail_la_LINK) -rpath $(old_stats_moduledir) $(libold_stats_mail_la_OBJECTS) $(libold_stats_mail_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-stats-connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-stats-fill.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(old_stats_moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-old_stats_moduleLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/mail-stats-connection.Plo
+ -rm -f ./$(DEPDIR)/mail-stats-fill.Plo
+ -rm -f ./$(DEPDIR)/mail-stats.Plo
+ -rm -f ./$(DEPDIR)/stats-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES \
+ install-old_stats_moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/mail-stats-connection.Plo
+ -rm -f ./$(DEPDIR)/mail-stats-fill.Plo
+ -rm -f ./$(DEPDIR)/mail-stats.Plo
+ -rm -f ./$(DEPDIR)/stats-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES \
+ uninstall-old_stats_moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-old_stats_moduleLTLIBRARIES cscopelist-am ctags ctags-am \
+ distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-moduleLTLIBRARIES \
+ install-old_stats_moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES \
+ uninstall-old_stats_moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/old-stats/mail-stats-connection.c b/src/plugins/old-stats/mail-stats-connection.c
new file mode 100644
index 0000000..ca20fcb
--- /dev/null
+++ b/src/plugins/old-stats/mail-stats-connection.c
@@ -0,0 +1,75 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "base64.h"
+#include "hostpid.h"
+#include "net.h"
+#include "str.h"
+#include "strescape.h"
+#include "mail-storage.h"
+#include "stats.h"
+#include "stats-plugin.h"
+#include "mail-stats-connection.h"
+
+int mail_stats_connection_connect(struct stats_connection *conn,
+ struct mail_user *user)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ string_t *str = t_str_new(128);
+
+ str_append(str, "CONNECT\t");
+ /* required fields */
+ str_append(str, suser->stats_session_id);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, user->username);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, user->service);
+ str_printfa(str, "\t%s", my_pid);
+
+ /* optional fields */
+ if (user->conn.local_ip != NULL) {
+ str_append(str, "\tlip=");
+ str_append(str, net_ip2addr(user->conn.local_ip));
+ }
+ if (user->conn.remote_ip != NULL) {
+ str_append(str, "\trip=");
+ str_append(str, net_ip2addr(user->conn.remote_ip));
+ }
+ str_append_c(str, '\n');
+ return stats_connection_send(conn, str);
+}
+
+void mail_stats_connection_disconnect(struct stats_connection *conn,
+ struct mail_user *user)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ string_t *str = t_str_new(128);
+
+ str_append(str, "DISCONNECT\t");
+ str_append(str, suser->stats_session_id);
+ str_append_c(str, '\n');
+ if (stats_connection_send(conn, str) < 0) {
+ /* we could retry this later, but stats process will forget it
+ anyway after 15 minutes. */
+ }
+}
+
+void mail_stats_connection_send_session(struct stats_connection *conn,
+ struct mail_user *user,
+ const struct stats *stats)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ string_t *str = t_str_new(256);
+ buffer_t *buf;
+
+ buf = t_buffer_create(128);
+ stats_export(buf, stats);
+
+ str_append(str, "UPDATE-SESSION\t");
+ str_append(str, suser->stats_session_id);
+ str_append_c(str, '\t');
+ base64_encode(buf->data, buf->used, str);
+
+ str_append_c(str, '\n');
+ (void)stats_connection_send(conn, str);
+}
diff --git a/src/plugins/old-stats/mail-stats-connection.h b/src/plugins/old-stats/mail-stats-connection.h
new file mode 100644
index 0000000..f98b691
--- /dev/null
+++ b/src/plugins/old-stats/mail-stats-connection.h
@@ -0,0 +1,19 @@
+#ifndef MAIL_STATS_CONNECTION_H
+#define MAIL_STATS_CONNECTION_H
+
+#include "stats-connection.h"
+
+struct mail_stats;
+struct mail_user;
+
+int mail_stats_connection_connect(struct stats_connection *conn,
+ struct mail_user *user);
+void mail_stats_connection_disconnect(struct stats_connection *conn,
+ struct mail_user *user);
+
+void mail_stats_connection_send_session(struct stats_connection *conn,
+ struct mail_user *user,
+ const struct stats *stats);
+void mail_stats_connection_send(struct stats_connection *conn, const string_t *str);
+
+#endif
diff --git a/src/plugins/old-stats/mail-stats-fill.c b/src/plugins/old-stats/mail-stats-fill.c
new file mode 100644
index 0000000..10d8c39
--- /dev/null
+++ b/src/plugins/old-stats/mail-stats-fill.c
@@ -0,0 +1,156 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "time-util.h"
+#include "stats-plugin.h"
+#include "mail-stats.h"
+
+#include <sys/resource.h>
+
+#define PROC_IO_PATH "/proc/self/io"
+
+static bool proc_io_disabled = FALSE;
+static int proc_io_fd = -1;
+
+static int
+process_io_buffer_parse(const char *buf, struct mail_stats *stats)
+{
+ const char *const *tmp;
+
+ tmp = t_strsplit(buf, "\n");
+ for (; *tmp != NULL; tmp++) {
+ if (str_begins(*tmp, "rchar: ")) {
+ if (str_to_uint64(*tmp + 7, &stats->read_bytes) < 0)
+ return -1;
+ } else if (str_begins(*tmp, "wchar: ")) {
+ if (str_to_uint64(*tmp + 7, &stats->write_bytes) < 0)
+ return -1;
+ } else if (str_begins(*tmp, "syscr: ")) {
+ if (str_to_uint32(*tmp + 7, &stats->read_count) < 0)
+ return -1;
+ } else if (str_begins(*tmp, "syscw: ")) {
+ if (str_to_uint32(*tmp + 7, &stats->write_count) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int process_io_open(void)
+{
+ uid_t uid;
+
+ if (proc_io_fd != -1)
+ return proc_io_fd;
+
+ if (proc_io_disabled)
+ return -1;
+
+ proc_io_fd = open(PROC_IO_PATH, O_RDONLY);
+ if (proc_io_fd == -1 && errno == EACCES) {
+ /* kludge: if we're running with permissions temporarily
+ dropped, get them temporarily back so we can open
+ /proc/self/io. */
+ uid = geteuid();
+ if (seteuid(0) == 0) {
+ proc_io_fd = open(PROC_IO_PATH, O_RDONLY);
+ if (seteuid(uid) < 0) {
+ /* oops, this is bad */
+ i_fatal("stats: seteuid(%s) failed", dec2str(uid));
+ }
+ }
+ errno = EACCES;
+ }
+ if (proc_io_fd == -1) {
+ /* ignore access errors too, certain security options can
+ prevent root access to this file when not owned by root */
+ if (errno != ENOENT && errno != EACCES)
+ i_error("open(%s) failed: %m", PROC_IO_PATH);
+ proc_io_disabled = TRUE;
+ return -1;
+ }
+ return proc_io_fd;
+}
+
+static void process_read_io_stats(struct mail_stats *stats)
+{
+ char buf[1024];
+ int fd, ret;
+
+ if ((fd = process_io_open()) == -1)
+ return;
+
+ ret = pread(fd, buf, sizeof(buf), 0);
+ if (ret <= 0) {
+ if (ret == -1)
+ i_error("read(%s) failed: %m", PROC_IO_PATH);
+ else
+ i_error("read(%s) returned EOF", PROC_IO_PATH);
+ } else if (ret == sizeof(buf)) {
+ /* just shouldn't happen.. */
+ i_error("%s is larger than expected", PROC_IO_PATH);
+ proc_io_disabled = TRUE;
+ } else {
+ buf[ret] = '\0';
+ T_BEGIN {
+ if (process_io_buffer_parse(buf, stats) < 0) {
+ i_error("Invalid input in file %s",
+ PROC_IO_PATH);
+ proc_io_disabled = TRUE;
+ }
+ } T_END;
+ }
+}
+
+static void
+user_trans_stats_get(struct stats_user *suser, struct mail_stats *dest_r)
+{
+ struct stats_transaction_context *strans;
+
+ mail_stats_add_transaction(dest_r, &suser->finished_transaction_stats);
+ for (strans = suser->transactions; strans != NULL; strans = strans->next)
+ mail_stats_add_transaction(dest_r, &strans->trans->stats);
+}
+
+void mail_stats_fill(struct stats_user *suser, struct mail_stats *stats_r)
+{
+ static bool getrusage_broken = FALSE;
+ static struct rusage prev_usage;
+ struct rusage usage;
+
+ i_zero(stats_r);
+ /* cputime */
+ if (getrusage(RUSAGE_SELF, &usage) < 0) {
+ if (!getrusage_broken) {
+ i_error("getrusage() failed: %m");
+ getrusage_broken = TRUE;
+ }
+ usage = prev_usage;
+ } else if (timeval_diff_usecs(&usage.ru_stime, &prev_usage.ru_stime) < 0) {
+ /* This seems to be a Linux bug. */
+ usage.ru_stime = prev_usage.ru_stime;
+ }
+ prev_usage = usage;
+
+ stats_r->user_cpu = usage.ru_utime;
+ stats_r->sys_cpu = usage.ru_stime;
+ stats_r->min_faults = usage.ru_minflt;
+ stats_r->maj_faults = usage.ru_majflt;
+ stats_r->vol_cs = usage.ru_nvcsw;
+ stats_r->invol_cs = usage.ru_nivcsw;
+ stats_r->disk_input = (unsigned long long)usage.ru_inblock * 512ULL;
+ stats_r->disk_output = (unsigned long long)usage.ru_oublock * 512ULL;
+ i_gettimeofday(&stats_r->clock_time);
+ process_read_io_stats(stats_r);
+ user_trans_stats_get(suser, stats_r);
+}
+
+void mail_stats_global_preinit(void)
+{
+ (void)process_io_open();
+}
+
+void mail_stats_fill_global_deinit(void)
+{
+ i_close_fd(&proc_io_fd);
+}
diff --git a/src/plugins/old-stats/mail-stats.c b/src/plugins/old-stats/mail-stats.c
new file mode 100644
index 0000000..3662d23
--- /dev/null
+++ b/src/plugins/old-stats/mail-stats.c
@@ -0,0 +1,168 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "time-util.h"
+#include "stats.h"
+#include "stats-parser.h"
+#include "mail-stats.h"
+
+static struct stats_parser_field mail_stats_fields[] = {
+#define E(parsename, name, type) { parsename, offsetof(struct mail_stats, name), sizeof(((struct mail_stats *)0)->name), type }
+#define EN(parsename, name) E(parsename, name, STATS_PARSER_TYPE_UINT)
+ E("user_cpu", user_cpu, STATS_PARSER_TYPE_TIMEVAL),
+ E("sys_cpu", sys_cpu, STATS_PARSER_TYPE_TIMEVAL),
+ E("clock_time", clock_time, STATS_PARSER_TYPE_TIMEVAL),
+ EN("min_faults", min_faults),
+ EN("maj_faults", maj_faults),
+ EN("vol_cs", vol_cs),
+ EN("invol_cs", invol_cs),
+ EN("disk_input", disk_input),
+ EN("disk_output", disk_output),
+
+ EN("read_count", read_count),
+ EN("read_bytes", read_bytes),
+ EN("write_count", write_count),
+ EN("write_bytes", write_bytes),
+
+ /*EN("mopen", trans_stats.open_lookup_count),
+ EN("mstat", trans_stats.stat_lookup_count),
+ EN("mfstat", trans_stats.fstat_lookup_count),*/
+ EN("mail_lookup_path", trans_lookup_path),
+ EN("mail_lookup_attr", trans_lookup_attr),
+ EN("mail_read_count", trans_files_read_count),
+ EN("mail_read_bytes", trans_files_read_bytes),
+ EN("mail_cache_hits", trans_cache_hit_count)
+};
+
+static size_t mail_stats_alloc_size(void)
+{
+ return sizeof(struct mail_stats);
+}
+
+static unsigned int mail_stats_field_count(void)
+{
+ return N_ELEMENTS(mail_stats_fields);
+}
+
+static const char *mail_stats_field_name(unsigned int n)
+{
+ i_assert(n < N_ELEMENTS(mail_stats_fields));
+
+ return mail_stats_fields[n].name;
+}
+
+static void
+mail_stats_field_value(string_t *str, const struct stats *stats,
+ unsigned int n)
+{
+ i_assert(n < N_ELEMENTS(mail_stats_fields));
+
+ stats_parser_value(str, &mail_stats_fields[n], stats);
+}
+
+static bool
+mail_stats_diff(const struct stats *stats1, const struct stats *stats2,
+ struct stats *diff_stats_r, const char **error_r)
+{
+ return stats_parser_diff(mail_stats_fields, N_ELEMENTS(mail_stats_fields),
+ stats1, stats2, diff_stats_r, error_r);
+}
+
+static void mail_stats_add(struct stats *dest, const struct stats *src)
+{
+ stats_parser_add(mail_stats_fields, N_ELEMENTS(mail_stats_fields),
+ dest, src);
+}
+
+static bool
+mail_stats_have_changed(const struct stats *_prev, const struct stats *_cur)
+{
+ const struct mail_stats *prev = (const struct mail_stats *)_prev;
+ const struct mail_stats *cur = (const struct mail_stats *)_cur;
+
+ if (cur->disk_input != prev->disk_input ||
+ cur->disk_output != prev->disk_output ||
+ cur->trans_lookup_path != prev->trans_lookup_path ||
+ cur->trans_lookup_attr != prev->trans_lookup_attr ||
+ cur->trans_files_read_count != prev->trans_files_read_count ||
+ cur->trans_files_read_bytes != prev->trans_files_read_bytes ||
+ cur->trans_cache_hit_count != prev->trans_cache_hit_count)
+ return TRUE;
+
+ /* allow a tiny bit of changes that are caused by this
+ timeout handling */
+ if (timeval_diff_msecs(&cur->user_cpu, &prev->user_cpu) != 0)
+ return TRUE;
+ if (timeval_diff_msecs(&cur->sys_cpu, &prev->sys_cpu) != 0)
+ return TRUE;
+
+ if (cur->maj_faults > prev->maj_faults+10)
+ return TRUE;
+ if (cur->invol_cs > prev->invol_cs+10)
+ return TRUE;
+ /* don't check for read/write count/bytes changes, since they get
+ changed by stats checking itself */
+ return FALSE;
+}
+
+static void mail_stats_export(buffer_t *buf, const struct stats *_stats)
+{
+ const struct mail_stats *stats = (const struct mail_stats *)_stats;
+
+ buffer_append(buf, stats, sizeof(*stats));
+}
+
+static bool
+mail_stats_import(const unsigned char *data, size_t size, size_t *pos_r,
+ struct stats *_stats, const char **error_r)
+{
+ struct mail_stats *stats = (struct mail_stats *)_stats;
+
+ if (size < sizeof(*stats)) {
+ *error_r = "mail_stats too small";
+ return FALSE;
+ }
+ memcpy(stats, data, sizeof(*stats));
+ *pos_r = sizeof(*stats);
+ return TRUE;
+}
+
+void mail_stats_add_transaction(struct mail_stats *stats,
+ const struct mailbox_transaction_stats *trans_stats)
+{
+ stats->trans_lookup_path += trans_stats->open_lookup_count;
+ stats->trans_lookup_attr += trans_stats->stat_lookup_count +
+ trans_stats->fstat_lookup_count;
+ stats->trans_files_read_count += trans_stats->files_read_count;
+ stats->trans_files_read_bytes += trans_stats->files_read_bytes;
+ stats->trans_cache_hit_count += trans_stats->cache_hit_count;
+}
+
+const struct stats_vfuncs mail_stats_vfuncs = {
+ "mail",
+ mail_stats_alloc_size,
+ mail_stats_field_count,
+ mail_stats_field_name,
+ mail_stats_field_value,
+ mail_stats_diff,
+ mail_stats_add,
+ mail_stats_have_changed,
+ mail_stats_export,
+ mail_stats_import
+};
+
+/* for the stats_mail plugin: */
+void old_stats_mail_init(void);
+void old_stats_mail_deinit(void);
+
+static struct stats_item *mail_stats_item;
+
+void old_stats_mail_init(void)
+{
+ mail_stats_item = stats_register(&mail_stats_vfuncs);
+}
+
+void old_stats_mail_deinit(void)
+{
+ stats_unregister(&mail_stats_item);
+}
diff --git a/src/plugins/old-stats/mail-stats.h b/src/plugins/old-stats/mail-stats.h
new file mode 100644
index 0000000..7254b69
--- /dev/null
+++ b/src/plugins/old-stats/mail-stats.h
@@ -0,0 +1,41 @@
+#ifndef MAIL_STATS_H
+#define MAIL_STATS_H
+
+#include <sys/time.h>
+#include "mail-storage-private.h"
+
+struct stats_user;
+
+struct mail_stats {
+ /* user/system CPU time used */
+ struct timeval user_cpu, sys_cpu;
+ /* clock time used (not counting the time in ioloop wait) */
+ struct timeval clock_time;
+ /* minor / major page faults */
+ uint32_t min_faults, maj_faults;
+ /* voluntary / involuntary context switches */
+ uint32_t vol_cs, invol_cs;
+ /* disk input/output bytes */
+ uint64_t disk_input, disk_output;
+ /* read()/write() syscall count and number of bytes */
+ uint32_t read_count, write_count;
+ uint64_t read_bytes, write_bytes;
+
+ /* based on struct mailbox_transaction_stats: */
+ uint32_t trans_lookup_path;
+ uint32_t trans_lookup_attr;
+ uint32_t trans_files_read_count;
+ uint64_t trans_files_read_bytes;
+ uint64_t trans_cache_hit_count;
+};
+
+extern const struct stats_vfuncs mail_stats_vfuncs;
+
+void mail_stats_fill(struct stats_user *suser, struct mail_stats *mail_stats);
+void mail_stats_add_transaction(struct mail_stats *stats,
+ const struct mailbox_transaction_stats *trans_stats);
+
+void mail_stats_global_preinit(void);
+void mail_stats_fill_global_deinit(void);
+
+#endif
diff --git a/src/plugins/old-stats/stats-plugin.c b/src/plugins/old-stats/stats-plugin.c
new file mode 100644
index 0000000..9d33016
--- /dev/null
+++ b/src/plugins/old-stats/stats-plugin.c
@@ -0,0 +1,481 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "llist.h"
+#include "str.h"
+#include "time-util.h"
+#include "settings-parser.h"
+#include "mail-stats.h"
+#include "stats.h"
+#include "mail-stats-connection.h"
+#include "stats-plugin.h"
+
+#define STATS_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, stats_storage_module)
+#define STATS_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, stats_storage_module)
+
+/* If session isn't refreshed every 15 minutes, it's dropped.
+ Must be smaller than MAIL_SESSION_IDLE_TIMEOUT_MSECS in stats server */
+#define SESSION_STATS_FORCE_REFRESH_SECS (5*60)
+#define REFRESH_CHECK_INTERVAL 100
+#define MAIL_STATS_FIFO_NAME "old-stats-mail"
+
+struct stats_storage {
+ union mail_storage_module_context module_ctx;
+
+ struct mail_storage_callbacks old_callbacks;
+ void *old_context;
+};
+
+struct stats_mailbox {
+ union mailbox_module_context module_ctx;
+};
+
+const char *stats_plugin_version = DOVECOT_ABI_VERSION;
+
+struct stats_user_module stats_user_module =
+ MODULE_CONTEXT_INIT(&mail_user_module_register);
+struct stats_storage_module stats_storage_module =
+ MODULE_CONTEXT_INIT(&mail_storage_module_register);
+
+static struct stats_item *mail_stats_item;
+static struct stats_connection *global_stats_conn = NULL;
+static struct mail_user *stats_global_user = NULL;
+static unsigned int stats_user_count = 0;
+
+static void session_stats_refresh_timeout(struct mail_user *user);
+
+static void stats_io_activate(struct mail_user *user)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ struct mail_stats *mail_stats;
+
+ if (stats_user_count == 1) {
+ /* the first user sets the global user. the second user sets
+ it to NULL. when we get back to one user we'll need to set
+ the global user again somewhere. do it here. */
+ stats_global_user = user;
+ /* skip time spent waiting in ioloop */
+ mail_stats = stats_fill_ptr(suser->pre_io_stats, mail_stats_item);
+ mail_stats->clock_time = ioloop_timeval;
+ } else {
+ i_assert(stats_global_user == NULL);
+
+ mail_user_stats_fill(user, suser->pre_io_stats);
+ }
+}
+
+static void stats_add_session(struct mail_user *user)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ struct stats *new_stats, *diff_stats;
+ const char *error;
+
+ new_stats = stats_alloc(pool_datastack_create());
+ diff_stats = stats_alloc(pool_datastack_create());
+
+ mail_user_stats_fill(user, new_stats);
+ /* we'll count new_stats-pre_io_stats and add the changes to
+ session_stats. the new_stats can't be directly copied to
+ session_stats because there are some fields that don't start from
+ zero, like clock_time. (actually with stats_global_user code we're
+ requiring that clock_time is the only such field..) */
+ if (!stats_diff(suser->pre_io_stats, new_stats, diff_stats, &error))
+ i_error("stats: session stats shrank: %s", error);
+ stats_add(suser->session_stats, diff_stats);
+ /* copying is only needed if stats_global_user=NULL */
+ stats_copy(suser->pre_io_stats, new_stats);
+}
+
+static bool
+session_stats_need_send(struct stats_user *suser, time_t now,
+ bool *changed_r, unsigned int *to_next_secs_r)
+{
+ unsigned int diff;
+
+ *to_next_secs_r = SESSION_STATS_FORCE_REFRESH_SECS;
+
+ if (stats_have_changed(suser->last_sent_session_stats,
+ suser->session_stats)) {
+ *to_next_secs_r = suser->refresh_secs;
+ *changed_r = TRUE;
+ return TRUE;
+ }
+ *changed_r = FALSE;
+
+ diff = now - suser->last_session_update;
+ if (diff >= SESSION_STATS_FORCE_REFRESH_SECS)
+ return TRUE;
+ *to_next_secs_r = SESSION_STATS_FORCE_REFRESH_SECS - diff;
+
+ if (!suser->session_sent_duplicate) {
+ if (suser->last_session_update != now) {
+ /* send one duplicate notification so stats reader
+ knows that this session is idle now */
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void session_stats_refresh(struct mail_user *user)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ unsigned int to_next_secs;
+ time_t now = time(NULL);
+ bool changed;
+
+ if (!suser->stats_connected) {
+ if (mail_stats_connection_connect(suser->stats_conn, user) == 0)
+ suser->stats_connected = TRUE;
+ }
+ if (session_stats_need_send(suser, now, &changed, &to_next_secs) &&
+ suser->stats_connected) {
+ suser->session_sent_duplicate = !changed;
+ suser->last_session_update = now;
+ stats_copy(suser->last_sent_session_stats, suser->session_stats);
+ mail_stats_connection_send_session(suser->stats_conn, user,
+ suser->session_stats);
+ }
+
+ timeout_remove(&suser->to_stats_timeout);
+ suser->to_stats_timeout =
+ timeout_add(to_next_secs*1000,
+ session_stats_refresh_timeout, user);
+}
+
+static struct mailbox_transaction_context *
+stats_transaction_begin(struct mailbox *box,
+ enum mailbox_transaction_flags flags,
+ const char *reason)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(box->storage->user);
+ struct stats_mailbox *sbox = STATS_CONTEXT_REQUIRE(box);
+ struct mailbox_transaction_context *trans;
+ struct stats_transaction_context *strans;
+
+ trans = sbox->module_ctx.super.transaction_begin(box, flags, reason);
+ trans->stats_track = TRUE;
+
+ strans = i_new(struct stats_transaction_context, 1);
+ strans->trans = trans;
+ DLLIST_PREPEND(&suser->transactions, strans);
+
+ MODULE_CONTEXT_SET(trans, stats_storage_module, strans);
+ return trans;
+}
+
+static void stats_transaction_free(struct stats_user *suser,
+ struct stats_transaction_context *strans)
+{
+ const struct mailbox_transaction_stats *src = &strans->trans->stats;
+ struct mailbox_transaction_stats *dest =
+ &suser->finished_transaction_stats;
+
+ DLLIST_REMOVE(&suser->transactions, strans);
+
+ dest->open_lookup_count += src->open_lookup_count;
+ dest->stat_lookup_count += src->stat_lookup_count;
+ dest->fstat_lookup_count += src->fstat_lookup_count;
+ dest->files_read_count += src->files_read_count;
+ dest->files_read_bytes += src->files_read_bytes;
+ dest->cache_hit_count += src->cache_hit_count;
+ i_free(strans);
+}
+
+static int
+stats_transaction_commit(struct mailbox_transaction_context *ctx,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ struct stats_transaction_context *strans = STATS_CONTEXT_REQUIRE(ctx);
+ struct stats_mailbox *sbox = STATS_CONTEXT_REQUIRE(ctx->box);
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(ctx->box->storage->user);
+
+ stats_transaction_free(suser, strans);
+ return sbox->module_ctx.super.transaction_commit(ctx, changes_r);
+}
+
+static void
+stats_transaction_rollback(struct mailbox_transaction_context *ctx)
+{
+ struct stats_transaction_context *strans = STATS_CONTEXT_REQUIRE(ctx);
+ struct stats_mailbox *sbox = STATS_CONTEXT_REQUIRE(ctx->box);
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(ctx->box->storage->user);
+
+ stats_transaction_free(suser, strans);
+ sbox->module_ctx.super.transaction_rollback(ctx);
+}
+
+static bool stats_search_next_nonblock(struct mail_search_context *ctx,
+ struct mail **mail_r, bool *tryagain_r)
+{
+ struct stats_mailbox *sbox = STATS_CONTEXT_REQUIRE(ctx->transaction->box);
+ struct mail_user *user = ctx->transaction->box->storage->user;
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ bool ret;
+
+ ret = sbox->module_ctx.super.
+ search_next_nonblock(ctx, mail_r, tryagain_r);
+ if (!ret && !*tryagain_r) {
+ /* end of search */
+ return FALSE;
+ }
+
+ if (*tryagain_r ||
+ ++suser->refresh_check_counter % REFRESH_CHECK_INTERVAL == 0) {
+ /* a) retrying, so this is a long running search.
+ b) we've returned enough matches */
+ if (time(NULL) != suser->last_session_update)
+ session_stats_refresh(user);
+ }
+ return ret;
+}
+
+static void
+stats_notify_ok(struct mailbox *box, const char *text, void *context)
+{
+ struct stats_storage *sstorage = STATS_CONTEXT_REQUIRE(box->storage);
+
+ /* most importantly we want to refresh stats for very long running
+ mailbox syncs */
+ session_stats_refresh(box->storage->user);
+
+ if (sstorage->old_callbacks.notify_ok != NULL)
+ sstorage->old_callbacks.notify_ok(box, text, context);
+}
+
+static void stats_register_notify_callbacks(struct mail_storage *storage)
+{
+ struct stats_storage *sstorage = STATS_CONTEXT(storage);
+
+ if (sstorage != NULL)
+ return;
+
+ sstorage = p_new(storage->pool, struct stats_storage, 1);
+ sstorage->old_callbacks = storage->callbacks;
+ storage->callbacks.notify_ok = stats_notify_ok;
+
+ MODULE_CONTEXT_SET(storage, stats_storage_module, sstorage);
+}
+
+static void stats_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct stats_mailbox *sbox;
+ struct stats_user *suser = STATS_USER_CONTEXT(box->storage->user);
+
+ if (suser == NULL)
+ return;
+
+ stats_register_notify_callbacks(box->storage);
+
+ sbox = p_new(box->pool, struct stats_mailbox, 1);
+ sbox->module_ctx.super = *v;
+ box->vlast = &sbox->module_ctx.super;
+
+ v->transaction_begin = stats_transaction_begin;
+ v->transaction_commit = stats_transaction_commit;
+ v->transaction_rollback = stats_transaction_rollback;
+ v->search_next_nonblock = stats_search_next_nonblock;
+ MODULE_CONTEXT_SET(box, stats_storage_module, sbox);
+}
+
+static void session_stats_refresh_timeout(struct mail_user *user)
+{
+ if (stats_global_user != NULL)
+ stats_add_session(user);
+ session_stats_refresh(user);
+}
+
+static void stats_io_deactivate(struct mail_user *user)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ unsigned int last_update_secs;
+
+ if (stats_global_user == NULL)
+ stats_add_session(user);
+
+ last_update_secs = time(NULL) - suser->last_session_update;
+ if (last_update_secs >= suser->refresh_secs) {
+ if (stats_global_user != NULL)
+ stats_add_session(user);
+ session_stats_refresh(user);
+ } else if (suser->to_stats_timeout == NULL) {
+ suser->to_stats_timeout =
+ timeout_add(suser->refresh_secs*1000,
+ session_stats_refresh_timeout, user);
+ }
+}
+
+static void stats_user_stats_fill(struct mail_user *user, struct stats *stats)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ struct mail_stats *mail_stats;
+
+ mail_stats = stats_fill_ptr(stats, mail_stats_item);
+ mail_stats_fill(suser, mail_stats);
+
+ suser->module_ctx.super.stats_fill(user, stats);
+}
+
+static void stats_user_deinit(struct mail_user *user)
+{
+ struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user);
+ struct stats_connection *stats_conn = suser->stats_conn;
+
+ i_assert(stats_user_count > 0);
+
+ stats_user_count--;
+ if (stats_global_user != NULL) {
+ /* we were updating the session lazily. do one final update. */
+ i_assert(stats_global_user == user);
+ stats_add_session(user);
+ stats_global_user = NULL;
+ }
+
+ io_loop_context_remove_callbacks(suser->ioloop_ctx,
+ stats_io_activate,
+ stats_io_deactivate, user);
+ /* send final stats before disconnection */
+ session_stats_refresh(user);
+ if (suser->stats_connected)
+ mail_stats_connection_disconnect(stats_conn, user);
+
+ timeout_remove(&suser->to_stats_timeout);
+ suser->module_ctx.super.deinit(user);
+
+ stats_connection_unref(&stats_conn);
+}
+
+static void stats_user_created(struct mail_user *user)
+{
+ struct ioloop_context *ioloop_ctx =
+ io_loop_get_current_context(current_ioloop);
+ struct stats_user *suser;
+ struct mail_user_vfuncs *v = user->vlast;
+ const char *path, *str, *error;
+ unsigned int refresh_secs;
+
+ if (ioloop_ctx == NULL) {
+ /* we're probably running some test program, or at least
+ mail-storage-service wasn't used to create this user.
+ disable stats tracking. */
+ return;
+ }
+ if (user->autocreated) {
+ /* lda / shared user. we're not tracking this one. */
+ return;
+ }
+
+ /* get refresh time */
+ str = mail_user_plugin_getenv(user, "old_stats_refresh");
+ if (str == NULL)
+ return;
+ if (settings_get_time(str, &refresh_secs, &error) < 0) {
+ i_error("stats: Invalid old_stats_refresh setting: %s", error);
+ return;
+ }
+ if (refresh_secs == 0)
+ return;
+ if (refresh_secs > SESSION_STATS_FORCE_REFRESH_SECS) {
+ i_warning("stats: stats_refresh too large, changing to %u",
+ SESSION_STATS_FORCE_REFRESH_SECS);
+ refresh_secs = SESSION_STATS_FORCE_REFRESH_SECS;
+ }
+
+ if (global_stats_conn == NULL) {
+ path = mail_user_plugin_getenv(user, "old_stats_notify_path");
+ if (path == NULL)
+ path = MAIL_STATS_FIFO_NAME;
+ if (path[0] != '/')
+ path = t_strconcat(user->set->base_dir, "/", path, NULL);
+ global_stats_conn = stats_connection_create(path);
+ }
+ stats_connection_ref(global_stats_conn);
+
+ if (stats_user_count == 0) {
+ /* first user connection */
+ stats_global_user = user;
+ } else if (stats_user_count == 1) {
+ /* second user connection. we'll need to start doing
+ per-io callback tracking now. (we might have been doing it
+ also previously but just temporarily quickly dropped to
+ having 1 user, in which case stats_global_user=NULL) */
+ if (stats_global_user != NULL) {
+ stats_add_session(stats_global_user);
+ stats_global_user = NULL;
+ }
+ }
+ stats_user_count++;
+
+ suser = p_new(user->pool, struct stats_user, 1);
+ suser->module_ctx.super = *v;
+ user->vlast = &suser->module_ctx.super;
+ v->deinit = stats_user_deinit;
+ v->stats_fill = stats_user_stats_fill;
+
+ suser->refresh_secs = refresh_secs;
+ if (mail_user_plugin_getenv_bool(user, "old_stats_track_cmds"))
+ suser->track_commands = TRUE;
+
+ suser->stats_conn = global_stats_conn;
+ if (user->session_id != NULL && user->session_id[0] != '\0')
+ suser->stats_session_id = user->session_id;
+ else {
+ guid_128_t guid;
+
+ guid_128_generate(guid);
+ suser->stats_session_id =
+ p_strdup(user->pool, guid_128_to_string(guid));
+ }
+ suser->last_session_update = time(NULL);
+ user->stats_enabled = TRUE;
+
+ suser->ioloop_ctx = ioloop_ctx;
+ io_loop_context_add_callbacks(ioloop_ctx,
+ stats_io_activate,
+ stats_io_deactivate, user);
+
+ suser->pre_io_stats = stats_alloc(user->pool);
+ suser->session_stats = stats_alloc(user->pool);
+ suser->last_sent_session_stats = stats_alloc(user->pool);
+
+ MODULE_CONTEXT_SET(user, stats_user_module, suser);
+ if (mail_stats_connection_connect(suser->stats_conn, user) == 0)
+ suser->stats_connected = TRUE;
+ suser->to_stats_timeout =
+ timeout_add(suser->refresh_secs*1000,
+ session_stats_refresh_timeout, user);
+ /* fill the initial values. this is necessary for the process-global
+ values (e.g. getrusage()) if the process is reused for multiple
+ users. otherwise the next user will start with the previous one's
+ last values. */
+ mail_user_stats_fill(user, suser->pre_io_stats);
+}
+
+static struct mail_storage_hooks stats_mail_storage_hooks = {
+ .mailbox_allocated = stats_mailbox_allocated,
+ .mail_user_created = stats_user_created
+};
+
+void old_stats_plugin_init(struct module *module)
+{
+ mail_stats_item = stats_register(&mail_stats_vfuncs);
+ mail_storage_hooks_add(module, &stats_mail_storage_hooks);
+}
+
+void old_stats_plugin_preinit(void)
+{
+ mail_stats_global_preinit();
+}
+
+void old_stats_plugin_deinit(void)
+{
+ if (global_stats_conn != NULL)
+ stats_connection_unref(&global_stats_conn);
+ mail_stats_fill_global_deinit();
+ mail_storage_hooks_remove(&stats_mail_storage_hooks);
+ stats_unregister(&mail_stats_item);
+}
diff --git a/src/plugins/old-stats/stats-plugin.h b/src/plugins/old-stats/stats-plugin.h
new file mode 100644
index 0000000..dedcfbd
--- /dev/null
+++ b/src/plugins/old-stats/stats-plugin.h
@@ -0,0 +1,60 @@
+#ifndef STATS_PLUGIN_H
+#define STATS_PLUGIN_H
+
+#include "module-context.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+
+#define STATS_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, stats_user_module)
+
+#define STATS_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, stats_user_module)
+
+struct stats_user {
+ union mail_user_module_context module_ctx;
+
+ struct ioloop_context *ioloop_ctx;
+ struct stats_connection *stats_conn;
+ const char *stats_session_id;
+ bool stats_connected;
+
+ unsigned int refresh_secs;
+ bool track_commands;
+ unsigned int refresh_check_counter;
+
+ /* current session statistics */
+ struct stats *session_stats;
+ /* cumulative trans_stats for all already freed transactions. */
+ struct mailbox_transaction_stats finished_transaction_stats;
+ /* stats before calling IO callback. after IO callback this value is
+ compared to current stats to see the difference */
+ struct stats *pre_io_stats;
+
+ time_t last_session_update;
+ struct timeout *to_stats_timeout;
+ /* stats that were last sent to stats server */
+ struct stats *last_sent_session_stats;
+ bool session_sent_duplicate;
+
+ /* list of all currently existing transactions for this user */
+ struct stats_transaction_context *transactions;
+};
+
+struct stats_transaction_context {
+ union mailbox_transaction_module_context module_ctx;
+
+ struct stats_transaction_context *prev, *next;
+ struct mailbox_transaction_context *trans;
+
+ struct mailbox_transaction_stats prev_stats;
+};
+
+extern MODULE_CONTEXT_DEFINE(stats_user_module, &mail_user_module_register);
+extern MODULE_CONTEXT_DEFINE(stats_storage_module, &mail_storage_module_register);
+
+void old_stats_plugin_init(struct module *module);
+void old_stats_plugin_preinit(void);
+void old_stats_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/pop3-migration/Makefile.am b/src/plugins/pop3-migration/Makefile.am
new file mode 100644
index 0000000..f58d065
--- /dev/null
+++ b/src/plugins/pop3-migration/Makefile.am
@@ -0,0 +1,41 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index
+
+NOPLUGIN_LDFLAGS =
+lib05_pop3_migration_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib05_pop3_migration_plugin.la
+
+lib05_pop3_migration_plugin_la_SOURCES = \
+ pop3-migration-plugin.c
+
+noinst_HEADERS = \
+ pop3-migration-plugin.h
+
+noinst_PROGRAMS = $(test_programs)
+
+test_programs = \
+ test-pop3-migration-plugin
+
+test_libs = \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+test_deps = \
+ $(module_LTLIBRARIES) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+test_pop3_migration_plugin_SOURCES = test-pop3-migration-plugin.c
+test_pop3_migration_plugin_LDADD = pop3-migration-plugin.lo $(test_libs)
+test_pop3_migration_plugin_DEPENDENCIES = $(test_deps)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/plugins/pop3-migration/Makefile.in b/src/plugins/pop3-migration/Makefile.in
new file mode 100644
index 0000000..70a8e00
--- /dev/null
+++ b/src/plugins/pop3-migration/Makefile.in
@@ -0,0 +1,872 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/plugins/pop3-migration
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-pop3-migration-plugin$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib05_pop3_migration_plugin_la_LIBADD =
+am_lib05_pop3_migration_plugin_la_OBJECTS = pop3-migration-plugin.lo
+lib05_pop3_migration_plugin_la_OBJECTS = \
+ $(am_lib05_pop3_migration_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib05_pop3_migration_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib05_pop3_migration_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+am_test_pop3_migration_plugin_OBJECTS = \
+ test-pop3-migration-plugin.$(OBJEXT)
+test_pop3_migration_plugin_OBJECTS = \
+ $(am_test_pop3_migration_plugin_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/pop3-migration-plugin.Plo \
+ ./$(DEPDIR)/test-pop3-migration-plugin.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib05_pop3_migration_plugin_la_SOURCES) \
+ $(test_pop3_migration_plugin_SOURCES)
+DIST_SOURCES = $(lib05_pop3_migration_plugin_la_SOURCES) \
+ $(test_pop3_migration_plugin_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index
+
+lib05_pop3_migration_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib05_pop3_migration_plugin.la
+
+lib05_pop3_migration_plugin_la_SOURCES = \
+ pop3-migration-plugin.c
+
+noinst_HEADERS = \
+ pop3-migration-plugin.h
+
+test_programs = \
+ test-pop3-migration-plugin
+
+test_libs = \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+
+test_deps = \
+ $(module_LTLIBRARIES) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+test_pop3_migration_plugin_SOURCES = test-pop3-migration-plugin.c
+test_pop3_migration_plugin_LDADD = pop3-migration-plugin.lo $(test_libs)
+test_pop3_migration_plugin_DEPENDENCIES = $(test_deps)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/pop3-migration/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/pop3-migration/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib05_pop3_migration_plugin.la: $(lib05_pop3_migration_plugin_la_OBJECTS) $(lib05_pop3_migration_plugin_la_DEPENDENCIES) $(EXTRA_lib05_pop3_migration_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib05_pop3_migration_plugin_la_LINK) -rpath $(moduledir) $(lib05_pop3_migration_plugin_la_OBJECTS) $(lib05_pop3_migration_plugin_la_LIBADD) $(LIBS)
+
+test-pop3-migration-plugin$(EXEEXT): $(test_pop3_migration_plugin_OBJECTS) $(test_pop3_migration_plugin_DEPENDENCIES) $(EXTRA_test_pop3_migration_plugin_DEPENDENCIES)
+ @rm -f test-pop3-migration-plugin$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_pop3_migration_plugin_OBJECTS) $(test_pop3_migration_plugin_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop3-migration-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-pop3-migration-plugin.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/pop3-migration-plugin.Plo
+ -rm -f ./$(DEPDIR)/test-pop3-migration-plugin.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/pop3-migration-plugin.Plo
+ -rm -f ./$(DEPDIR)/test-pop3-migration-plugin.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-moduleLTLIBRARIES \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/pop3-migration/pop3-migration-plugin.c b/src/plugins/pop3-migration/pop3-migration-plugin.c
new file mode 100644
index 0000000..362597a
--- /dev/null
+++ b/src/plugins/pop3-migration/pop3-migration-plugin.c
@@ -0,0 +1,1058 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+#include "str.h"
+#include "sha1.h"
+#include "message-size.h"
+#include "message-header-hash.h"
+#include "message-header-parser.h"
+#include "mail-cache.h"
+#include "mail-namespace.h"
+#include "mail-search-build.h"
+#include "index-storage.h"
+#include "index-mail.h"
+#include "pop3-migration-plugin.h"
+
+#define POP3_MIGRATION_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, pop3_migration_storage_module)
+#define POP3_MIGRATION_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, pop3_migration_storage_module)
+#define POP3_MIGRATION_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, pop3_migration_mail_module)
+
+struct msg_map_common {
+ /* sha1(header) - set only when needed */
+ unsigned char hdr_sha1[SHA1_RESULTLEN];
+ bool hdr_sha1_set:1;
+};
+
+struct pop3_uidl_map {
+ struct msg_map_common common;
+
+ uint32_t pop3_seq;
+ uint32_t imap_uid;
+
+ /* UIDL */
+ const char *pop3_uidl;
+ /* LIST size */
+ uoff_t size;
+};
+
+struct imap_msg_map {
+ struct msg_map_common common;
+
+ uint32_t uid, pop3_seq;
+ uoff_t psize;
+ const char *pop3_uidl;
+};
+
+struct pop3_migration_mail_storage {
+ union mail_storage_module_context module_ctx;
+
+ const char *pop3_box_vname;
+ ARRAY(struct pop3_uidl_map) pop3_uidl_map;
+
+ bool all_mailboxes:1;
+ bool pop3_all_hdr_sha1_set:1;
+ bool ignore_missing_uidls:1;
+ bool ignore_extra_uidls:1;
+ bool skip_size_check:1;
+ bool skip_uidl_cache:1;
+};
+
+struct pop3_migration_mailbox {
+ union mailbox_module_context module_ctx;
+
+ ARRAY(struct imap_msg_map) imap_msg_map;
+ unsigned int first_unfound_idx;
+
+ struct mail_cache_field cache_field;
+
+ bool cache_field_registered:1;
+ bool uidl_synced:1;
+ bool uidl_sync_failed:1;
+};
+
+/* NOTE: these headers must be sorted */
+static const char *hdr_hash_skip_headers[] = {
+ "Content-Length",
+ "Return-Path", /* Yahoo IMAP has Return-Path, Yahoo POP3 doesn't */
+ "Status",
+ "X-IMAP",
+ "X-IMAPbase",
+ "X-Keywords",
+ "X-Message-Flag",
+ "X-Status",
+ "X-UID",
+ "X-UIDL",
+ "X-Yahoo-Newman-Property"
+};
+const char *pop3_migration_plugin_version = DOVECOT_ABI_VERSION;
+
+static MODULE_CONTEXT_DEFINE_INIT(pop3_migration_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(pop3_migration_mail_module,
+ &mail_module_register);
+
+static int imap_msg_map_uid_cmp(const struct imap_msg_map *map1,
+ const struct imap_msg_map *map2)
+{
+ if (map1->uid < map2->uid)
+ return -1;
+ if (map1->uid > map2->uid)
+ return 1;
+ return 0;
+}
+
+static int pop3_uidl_map_pop3_seq_cmp(const struct pop3_uidl_map *map1,
+ const struct pop3_uidl_map *map2)
+{
+ if (map1->pop3_seq < map2->pop3_seq)
+ return -1;
+ if (map1->pop3_seq > map2->pop3_seq)
+ return 1;
+ return 0;
+}
+
+static int pop3_uidl_map_uidl_cmp(const struct pop3_uidl_map *map1,
+ const struct pop3_uidl_map *map2)
+{
+ return strcmp(map1->pop3_uidl, map2->pop3_uidl);
+}
+
+static int imap_msg_map_uidl_cmp(const struct imap_msg_map *map1,
+ const struct imap_msg_map *map2)
+{
+ return null_strcmp(map1->pop3_uidl, map2->pop3_uidl);
+}
+
+static int pop3_uidl_map_hdr_cmp(const struct pop3_uidl_map *map1,
+ const struct pop3_uidl_map *map2)
+{
+ return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
+ sizeof(map1->common.hdr_sha1));
+}
+
+static int imap_msg_map_hdr_cmp(const struct imap_msg_map *map1,
+ const struct imap_msg_map *map2)
+{
+ return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
+ sizeof(map1->common.hdr_sha1));
+}
+
+struct pop3_hdr_context {
+ bool have_eoh;
+ bool stop;
+};
+
+static bool header_name_is_valid(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; name[i] != '\0'; i++) {
+ if ((uint8_t)name[i] <= 0x20 || name[i] >= 0x7f)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool header_value_want_skip(const struct message_header_line *hdr)
+{
+ for (size_t i = 0; i < hdr->value_len; i++) {
+ if (hdr->value[i] != ' ' && hdr->value[i] != '\t')
+ return FALSE;
+ }
+ /* "header: \r\n \r\n" - Zimbra's BODY[HEADER] strips this line away. */
+ return TRUE;
+}
+
+static void
+pop3_header_filter_callback(struct header_filter_istream *input ATTR_UNUSED,
+ struct message_header_line *hdr,
+ bool *matched, struct pop3_hdr_context *ctx)
+{
+ if (hdr == NULL)
+ return;
+ if (hdr->eoh) {
+ ctx->have_eoh = TRUE;
+ if (ctx->stop)
+ *matched = TRUE;
+ } else {
+ if (hdr->value_len > 0 && hdr->middle_len == 0 && hdr->name_len == 0 &&
+ i_memspn(hdr->value, hdr->value_len, "\r", 1U) == hdr->value_len) {
+ /* CR+CR+LF - some servers stop the header processing
+ here while others don't. To make sure they can be
+ matched correctly we want to stop here entirely. */
+ ctx->stop = TRUE;
+ } else if (!hdr->continued && hdr->middle_len == 0) {
+ /* not a valid "key: value" header -
+ Zimbra's BODY[HEADER] strips this line away. */
+ *matched = TRUE;
+ } else if (hdr->continued && header_value_want_skip(hdr)) {
+ *matched = TRUE;
+ }
+ if (ctx->stop)
+ *matched = TRUE;
+ else if (!header_name_is_valid(hdr->name)) {
+ /* Yahoo IMAP drops headers with invalid names, while
+ Yahoo POP3 preserves them. Drop them all. */
+ *matched = TRUE;
+ }
+ }
+}
+
+int pop3_migration_get_hdr_sha1(uint32_t mail_seq, struct istream *input,
+ unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN],
+ bool *have_eoh_r)
+{
+ const unsigned char *data;
+ size_t size;
+ struct message_header_hash_context hash_ctx;
+ struct sha1_ctxt sha1_ctx;
+ struct pop3_hdr_context hdr_ctx;
+
+ i_zero(&hdr_ctx);
+ /* hide headers that might change or be different in IMAP vs. POP3 */
+ input = i_stream_create_header_filter(input, HEADER_FILTER_HIDE_BODY |
+ HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR,
+ hdr_hash_skip_headers,
+ N_ELEMENTS(hdr_hash_skip_headers),
+ pop3_header_filter_callback, &hdr_ctx);
+
+ sha1_init(&sha1_ctx);
+ i_zero(&hash_ctx);
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ message_header_hash_more(&hash_ctx, &hash_method_sha1, &sha1_ctx,
+ MESSAGE_HEADER_HASH_MAX_VERSION,
+ data, size);
+ i_stream_skip(input, size);
+ }
+ if (input->stream_errno != 0) {
+ i_error("pop3_migration: Failed to read header for msg %u: %s",
+ mail_seq, i_stream_get_error(input));
+ i_stream_unref(&input);
+ return -1;
+ }
+ sha1_result(&sha1_ctx, sha1_r);
+ i_stream_unref(&input);
+
+ *have_eoh_r = hdr_ctx.have_eoh;
+ return 0;
+}
+
+static unsigned int get_cache_idx(struct mail *mail)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(mail->box);
+
+ if (mbox->cache_field_registered)
+ return mbox->cache_field.idx;
+
+ mbox->cache_field.name = "pop3-migration.hdr";
+ mbox->cache_field.type = MAIL_CACHE_FIELD_FIXED_SIZE;
+ mbox->cache_field.field_size = SHA1_RESULTLEN;
+ mail_cache_register_fields(mail->box->cache, &mbox->cache_field, 1);
+ mbox->cache_field_registered = TRUE;
+ return mbox->cache_field.idx;
+}
+
+static int
+get_hdr_sha1(struct mail *mail, unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
+{
+ struct istream *input;
+ const char *errstr;
+ enum mail_error error;
+ bool have_eoh;
+ int ret;
+
+ if (mail_get_hdr_stream(mail, NULL, &input) < 0) {
+ errstr = mailbox_get_last_internal_error(mail->box, &error);
+ i_error("pop3_migration: Failed to get header for msg %u: %s",
+ mail->seq, errstr);
+ return error == MAIL_ERROR_EXPUNGED ? 0 : -1;
+ }
+ if (pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh) < 0)
+ return -1;
+ if (have_eoh) {
+ struct index_mail *imail = (struct index_mail *)mail;
+
+ index_mail_cache_add_idx(imail, get_cache_idx(mail),
+ sha1_r, SHA1_RESULTLEN);
+ return 1;
+ }
+
+ /* The empty "end of headers" line is missing. Either this means that
+ the headers ended unexpectedly (which is ok) or that the remote
+ server is buggy. Some servers have problems with
+
+ 1) header line continuations that contain only whitespace and
+ 2) headers that have no ":". The header gets truncated when such
+ line is reached.
+
+ At least Oracle IMS IMAP FETCH BODY[HEADER] handles 1) by not
+ returning the whitespace line and 2) by returning the line but
+ truncating the rest. POP3 TOP instead returns the entire header.
+ This causes the IMAP and POP3 hashes not to match.
+
+ If there's LF+CR+CR+LF in the middle of headers, Courier IMAP's
+ FETCH BODY[HEADER] stops after that, but Courier POP3's TOP doesn't.
+
+ So we'll try to avoid this by falling back to full FETCH BODY[]
+ (and/or RETR) and we'll parse the header ourself from it. This
+ should work around any similar bugs in all IMAP/POP3 servers. */
+ if (mail_get_stream_because(mail, NULL, NULL, "pop3-migration", &input) < 0) {
+ errstr = mailbox_get_last_internal_error(mail->box, &error);
+ i_error("pop3_migration: Failed to get body for msg %u: %s",
+ mail->seq, errstr);
+ return error == MAIL_ERROR_EXPUNGED ? 0 : -1;
+ }
+ ret = pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh);
+ if (ret == 0) {
+ if (!have_eoh)
+ i_warning("pop3_migration: Truncated email with UID %u stored as truncated", mail->uid);
+ struct index_mail *imail = (struct index_mail *)mail;
+ index_mail_cache_add_idx(imail, get_cache_idx(mail),
+ sha1_r, SHA1_RESULTLEN);
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+static bool
+get_cached_hdr_sha1(struct mail *mail, buffer_t *cache_buf,
+ unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
+{
+ struct index_mail *imail = (struct index_mail *)mail;
+
+ buffer_set_used_size(cache_buf, 0);
+ if (index_mail_cache_lookup_field(imail, cache_buf,
+ get_cache_idx(mail)) > 0 &&
+ cache_buf->used == SHA1_RESULTLEN) {
+ memcpy(sha1_r, cache_buf->data, cache_buf->used);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static struct mailbox *pop3_mailbox_alloc(struct mail_storage *storage)
+{
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(storage);
+ struct mail_namespace *ns;
+ struct mailbox *box;
+
+ ns = mail_namespace_find(storage->user->namespaces,
+ mstorage->pop3_box_vname);
+ i_assert(ns != NULL);
+ box = mailbox_alloc(ns->list, mstorage->pop3_box_vname,
+ MAILBOX_FLAG_READONLY | MAILBOX_FLAG_POP3_SESSION);
+ return box;
+}
+
+static int pop3_map_read(struct mail_storage *storage, struct mailbox *pop3_box)
+{
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(storage);
+ struct mailbox_transaction_context *t;
+ struct mail_search_args *search_args;
+ struct mail_search_context *ctx;
+ struct mail *mail;
+ struct pop3_uidl_map *map;
+ const char *uidl;
+ uoff_t size = UOFF_T_MAX;
+ int ret = 0;
+
+ if (array_is_created(&mstorage->pop3_uidl_map)) {
+ /* already read these, just reset the imap_uids */
+ array_foreach_modifiable(&mstorage->pop3_uidl_map, map)
+ map->imap_uid = 0;
+ return 0;
+ }
+ i_array_init(&mstorage->pop3_uidl_map, 128);
+
+ if (mailbox_sync(pop3_box, 0) < 0) {
+ i_error("pop3_migration: Couldn't sync mailbox %s: %s",
+ pop3_box->vname, mailbox_get_last_internal_error(pop3_box, NULL));
+ return -1;
+ }
+
+ t = mailbox_transaction_begin(pop3_box, 0, __func__);
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+ ctx = mailbox_search_init(t, search_args, NULL,
+ mstorage->skip_size_check ? 0 :
+ MAIL_FETCH_PHYSICAL_SIZE, NULL);
+ mail_search_args_unref(&search_args);
+
+ while (mailbox_search_next(ctx, &mail)) {
+ /* get the size with LIST instead of RETR */
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL;
+
+ if (mstorage->skip_size_check)
+ ;
+ else if (mail_get_physical_size(mail, &size) < 0) {
+ i_error("pop3_migration: Failed to get size for msg %u: %s",
+ mail->seq,
+ mailbox_get_last_internal_error(pop3_box, NULL));
+ ret = -1;
+ break;
+ }
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER;
+
+ if (mail_get_special(mail, MAIL_FETCH_UIDL_BACKEND, &uidl) < 0) {
+ i_error("pop3_migration: Failed to get UIDL for msg %u: %s",
+ mail->seq,
+ mailbox_get_last_internal_error(pop3_box, NULL));
+ ret = -1;
+ break;
+ }
+ if (*uidl == '\0') {
+ i_warning("pop3_migration: UIDL for msg %u is empty",
+ mail->seq);
+ continue;
+ }
+
+ map = array_append_space(&mstorage->pop3_uidl_map);
+ map->pop3_seq = mail->seq;
+ map->pop3_uidl = p_strdup(storage->pool, uidl);
+ map->size = size;
+ }
+
+ if (mailbox_search_deinit(&ctx) < 0) {
+ i_error("pop3_migration: Failed to search all POP3 mails: %s",
+ mailbox_get_last_internal_error(pop3_box, NULL));
+ ret = -1;
+ }
+ (void)mailbox_transaction_commit(&t);
+ return ret;
+}
+
+static void
+pop3_map_read_cached_hdr_hashes(struct mailbox_transaction_context *t,
+ struct mail_search_args *search_args,
+ struct array *msg_map)
+{
+ struct mail_search_context *ctx;
+ struct mail *mail;
+ struct msg_map_common *map;
+ buffer_t *cache_buf;
+
+ ctx = mailbox_search_init(t, search_args, NULL, 0, NULL);
+ cache_buf = t_buffer_create(SHA1_RESULTLEN);
+
+ while (mailbox_search_next(ctx, &mail)) {
+ map = array_idx_modifiable_i(msg_map, mail->seq-1);
+
+ if (get_cached_hdr_sha1(mail, cache_buf, map->hdr_sha1))
+ map->hdr_sha1_set = TRUE;
+ }
+
+ if (mailbox_search_deinit(&ctx) < 0) {
+ i_warning("pop3_migration: Failed to search all cached POP3 header hashes: %s - ignoring",
+ mailbox_get_last_internal_error(t->box, NULL));
+ }
+}
+
+static void map_remove_found_seqs(struct mail_search_arg *search_arg,
+ struct array *msg_map, uint32_t seq1)
+{
+ const struct msg_map_common *map;
+ uint32_t seq, count = array_count_i(msg_map);
+
+ i_assert(search_arg->type == SEARCH_SEQSET);
+
+ for (seq = seq1; seq <= count; seq++) {
+ map = array_idx_i(msg_map, seq-1);
+ if (map->hdr_sha1_set)
+ seq_range_array_remove(&search_arg->value.seqset, seq);
+ }
+}
+
+static int
+map_read_hdr_hashes(struct mailbox *box, struct array *msg_map, uint32_t seq1)
+{
+ struct mailbox_transaction_context *t;
+ struct mail_search_args *search_args;
+ struct mail_search_context *ctx;
+ struct mail *mail;
+ struct msg_map_common *map;
+ int ret = 0;
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+ /* get all the cached hashes */
+ search_args = mail_search_build_init();
+ mail_search_build_add_seqset(search_args, seq1, array_count_i(msg_map));
+ pop3_map_read_cached_hdr_hashes(t, search_args, msg_map);
+ /* read all the non-cached hashes. doing this in two passes allows
+ us to set wanted_fields=MAIL_FETCH_STREAM_HEADER, which allows
+ prefetching to work without downloading all the headers even
+ for mails that already are cached. */
+ map_remove_found_seqs(search_args->args, msg_map, seq1);
+ ctx = mailbox_search_init(t, search_args, NULL,
+ MAIL_FETCH_STREAM_HEADER, NULL);
+ mail_search_args_unref(&search_args);
+
+ while (mailbox_search_next(ctx, &mail)) {
+ map = array_idx_modifiable_i(msg_map, mail->seq-1);
+
+ if ((ret = get_hdr_sha1(mail, map->hdr_sha1)) < 0) {
+ ret = -1;
+ break;
+ }
+ if (ret > 0)
+ map->hdr_sha1_set = TRUE;
+ }
+
+ if (mailbox_search_deinit(&ctx) < 0) {
+ i_error("pop3_migration: Failed to search all mail headers: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ }
+ (void)mailbox_transaction_commit(&t);
+ return ret < 0 ? -1 : 0;
+}
+
+static int
+pop3_map_read_hdr_hashes(struct mail_storage *storage, struct mailbox *pop3_box,
+ unsigned first_seq)
+{
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(storage);
+
+ if (mstorage->pop3_all_hdr_sha1_set)
+ return 0;
+ if (mstorage->all_mailboxes) {
+ /* we may be matching against multiple mailboxes.
+ read all the hashes only once. */
+ first_seq = 1;
+ }
+
+ if (map_read_hdr_hashes(pop3_box, &mstorage->pop3_uidl_map.arr,
+ first_seq) < 0)
+ return -1;
+
+ if (first_seq == 1)
+ mstorage->pop3_all_hdr_sha1_set = TRUE;
+ return 0;
+}
+
+static int imap_map_read(struct mailbox *box)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(box->storage);
+ const unsigned int uidl_cache_idx =
+ ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx;
+ struct mailbox_status status;
+ struct mailbox_transaction_context *t;
+ struct mail_search_args *search_args;
+ struct mail_search_context *ctx;
+ struct mail *mail;
+ struct imap_msg_map *map;
+ uoff_t psize = UOFF_T_MAX;
+ string_t *uidl;
+ int ret = 0;
+
+ mailbox_get_open_status(box, STATUS_MESSAGES, &status);
+
+ i_assert(!array_is_created(&mbox->imap_msg_map));
+ p_array_init(&mbox->imap_msg_map, box->pool, status.messages);
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+ ctx = mailbox_search_init(t, search_args, NULL,
+ mstorage->skip_size_check ? 0 :
+ MAIL_FETCH_PHYSICAL_SIZE, NULL);
+ mail_search_args_unref(&search_args);
+
+ uidl = t_str_new(64);
+ while (mailbox_search_next(ctx, &mail)) {
+ if (mstorage->skip_size_check)
+ ;
+ else if (mail_get_physical_size(mail, &psize) < 0) {
+ i_error("pop3_migration: Failed to get psize for imap uid %u: %s",
+ mail->uid,
+ mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ break;
+ }
+
+ if (!mstorage->skip_uidl_cache) {
+ str_truncate(uidl, 0);
+ (void)mail_cache_lookup_field(mail->transaction->cache_view,
+ uidl, mail->seq, uidl_cache_idx);
+ }
+
+ map = array_append_space(&mbox->imap_msg_map);
+ map->uid = mail->uid;
+ map->psize = psize;
+ map->pop3_uidl = p_strdup_empty(box->pool, str_c(uidl));
+ }
+
+ if (mailbox_search_deinit(&ctx) < 0) {
+ i_error("pop3_migration: Failed to search all IMAP mails: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ }
+ (void)mailbox_transaction_commit(&t);
+ return ret;
+}
+
+static int imap_map_read_hdr_hashes(struct mailbox *box)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
+
+ return map_read_hdr_hashes(box, &mbox->imap_msg_map.arr,
+ mbox->first_unfound_idx+1);
+}
+
+static void pop3_uidl_assign_cached(struct mailbox *box)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(box->storage);
+ struct pop3_uidl_map *pop3_map;
+ struct imap_msg_map *imap_map;
+ unsigned int imap_idx, pop3_idx, pop3_count, imap_count;
+ int ret;
+
+ if (mstorage->skip_uidl_cache)
+ return;
+
+ array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_uidl_cmp);
+ array_sort(&mbox->imap_msg_map, imap_msg_map_uidl_cmp);
+
+ pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count);
+ imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count);
+
+ /* see if we can match the messages using sizes */
+ for (imap_idx = pop3_idx = 0; imap_idx < imap_count; imap_idx++) {
+ if (imap_map[imap_idx].pop3_uidl == NULL)
+ continue;
+
+ ret = 1;
+ for (; pop3_idx < pop3_count; pop3_idx++) {
+ ret = strcmp(imap_map[imap_idx].pop3_uidl,
+ pop3_map[pop3_idx].pop3_uidl);
+ if (ret >= 0)
+ break;
+ }
+ if (ret == 0) {
+ imap_map[imap_idx].pop3_seq =
+ pop3_map[pop3_idx].pop3_seq;
+ pop3_map[pop3_idx].imap_uid = imap_map[imap_idx].uid;
+ }
+ }
+}
+
+static bool pop3_uidl_assign_by_size(struct mailbox *box)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(box->storage);
+ struct pop3_uidl_map *pop3_map;
+ struct imap_msg_map *imap_map;
+ unsigned int i, pop3_count, imap_count, count;
+ unsigned int size_match = 0, uidl_match = 0;
+
+ pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count);
+ imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count);
+ count = I_MIN(pop3_count, imap_count);
+
+ /* see if we can match the messages using sizes */
+ for (i = 0; i < count; i++) {
+ if (imap_map[i].pop3_uidl != NULL) {
+ /* some of the UIDLs were already found cached. */
+ if (strcmp(pop3_map[i].pop3_uidl, imap_map[i].pop3_uidl) == 0) {
+ uidl_match++;
+ continue;
+ }
+ /* mismatch - can't trust the sizes */
+ break;
+ }
+
+ if (pop3_map[i].size != imap_map[i].psize ||
+ mstorage->skip_size_check)
+ break;
+ if (i+1 < count && pop3_map[i].size == pop3_map[i+1].size) {
+ /* two messages with same size, don't trust them */
+ break;
+ }
+
+ size_match++;
+ pop3_map[i].imap_uid = imap_map[i].uid;
+ imap_map[i].pop3_uidl = pop3_map[i].pop3_uidl;
+ imap_map[i].pop3_seq = pop3_map[i].pop3_seq;
+ }
+ mbox->first_unfound_idx = i;
+ e_debug(box->event, "pop3_migration: cached uidls=%u, size matches=%u, total=%u",
+ uidl_match, size_match, count);
+ return i == count && imap_count == pop3_count;
+}
+
+static int
+pop3_uidl_assign_by_hdr_hash(struct mailbox *box, struct mailbox *pop3_box)
+{
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(box->storage);
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
+ struct pop3_uidl_map *pop3_map;
+ struct imap_msg_map *imap_map;
+ unsigned int pop3_idx, imap_idx, pop3_count, imap_count;
+ unsigned int first_seq, missing_uids_count;
+ uint32_t first_missing_idx = 0, first_missing_seq = (uint32_t)-1;
+ int ret;
+
+ first_seq = mbox->first_unfound_idx+1;
+ if (pop3_map_read_hdr_hashes(box->storage, pop3_box, first_seq) < 0 ||
+ imap_map_read_hdr_hashes(box) < 0)
+ return -1;
+
+ array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_hdr_cmp);
+ array_sort(&mbox->imap_msg_map, imap_msg_map_hdr_cmp);
+
+ pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count);
+ imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count);
+
+ pop3_idx = imap_idx = 0;
+ while (pop3_idx < pop3_count && imap_idx < imap_count) {
+ if (!pop3_map[pop3_idx].common.hdr_sha1_set ||
+ pop3_map[pop3_idx].imap_uid != 0) {
+ pop3_idx++;
+ continue;
+ }
+ if (!imap_map[imap_idx].common.hdr_sha1_set ||
+ imap_map[imap_idx].pop3_uidl != NULL) {
+ imap_idx++;
+ continue;
+ }
+ ret = memcmp(pop3_map[pop3_idx].common.hdr_sha1,
+ imap_map[imap_idx].common.hdr_sha1,
+ sizeof(pop3_map[pop3_idx].common.hdr_sha1));
+ if (ret < 0)
+ pop3_idx++;
+ else if (ret > 0)
+ imap_idx++;
+ else {
+ pop3_map[pop3_idx].imap_uid = imap_map[imap_idx].uid;
+ imap_map[imap_idx].pop3_uidl =
+ pop3_map[pop3_idx].pop3_uidl;
+ imap_map[imap_idx].pop3_seq =
+ pop3_map[pop3_idx].pop3_seq;
+ }
+ }
+ missing_uids_count = 0;
+ for (pop3_idx = 0; pop3_idx < pop3_count; pop3_idx++) {
+ if (pop3_map[pop3_idx].imap_uid != 0) {
+ /* matched */
+ } else if (!pop3_map[pop3_idx].common.hdr_sha1_set) {
+ /* we treated this mail as expunged - ignore */
+ } else {
+ uint32_t seq = pop3_map[pop3_idx].pop3_seq;
+ if (first_missing_seq > seq) {
+ first_missing_seq = seq;
+ first_missing_idx = pop3_idx;
+ }
+ missing_uids_count++;
+ }
+ }
+ if (missing_uids_count > 0 && !mstorage->all_mailboxes) {
+ string_t *str = t_str_new(128);
+ bool all_imap_mails_found = FALSE;
+
+ str_printfa(str, "pop3_migration: %u POP3 messages have no "
+ "matching IMAP messages (first POP3 msg %u UIDL %s)",
+ missing_uids_count, first_missing_seq,
+ pop3_map[first_missing_idx].pop3_uidl);
+ if (imap_count + missing_uids_count == pop3_count) {
+ str_append(str, " - all IMAP messages were found "
+ "(POP3 contains more than IMAP INBOX - you may want to set pop3_migration_all_mailboxes=yes)");
+ all_imap_mails_found = TRUE;
+ }
+ if (all_imap_mails_found && mstorage->ignore_extra_uidls) {
+ /* pop3 had more mails than imap. maybe it was just
+ that a new mail was just delivered. */
+ } else if (!mstorage->ignore_missing_uidls) {
+ str_append(str, " - set pop3_migration_ignore_missing_uidls=yes");
+ if (all_imap_mails_found)
+ str_append(str, " or pop3_migration_ignore_extra_uidls=yes");
+ i_error("%s to continue anyway", str_c(str));
+ return -1;
+ }
+ i_warning("%s", str_c(str));
+ } else
+ e_debug(box->event, "pop3_migration: %u mails matched by headers", pop3_count);
+ array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_pop3_seq_cmp);
+ array_sort(&mbox->imap_msg_map, imap_msg_map_uid_cmp);
+ return 0;
+}
+
+static void imap_uidls_add_to_cache(struct mailbox *box)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
+ struct mailbox_transaction_context *t;
+ struct mail *mail;
+ struct index_mail *imail;
+ struct imap_msg_map *imap_map;
+ unsigned int i, count;
+ unsigned int field_idx;
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+ mail = mail_alloc(t, 0, NULL);
+ imail = INDEX_MAIL(mail);
+ field_idx = imail->ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx;
+
+ imap_map = array_get_modifiable(&mbox->imap_msg_map, &count);
+ for (i = 0; i < count; i++) {
+ if (imap_map[i].pop3_uidl == NULL)
+ continue;
+
+ if (!mail_set_uid(mail, imap_map[i].uid))
+ i_unreached();
+ if (mail_cache_field_can_add(t->cache_trans, mail->seq, field_idx)) {
+ index_mail_cache_add_idx(imail, field_idx,
+ imap_map[i].pop3_uidl, strlen(imap_map[i].pop3_uidl)+1);
+ }
+ }
+ mail_free(&mail);
+ (void)mailbox_transaction_commit(&t);
+}
+
+static int pop3_migration_uidl_sync(struct mailbox *box)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(box->storage);
+ struct mailbox *pop3_box;
+
+ pop3_box = pop3_mailbox_alloc(box->storage);
+ /* the POP3 server isn't connected to yet. handle all IMAP traffic
+ first before connecting, so POP3 server won't disconnect us due to
+ idling. */
+ if (imap_map_read(box) < 0 ||
+ pop3_map_read(box->storage, pop3_box) < 0) {
+ mailbox_free(&pop3_box);
+ return -1;
+ }
+
+ pop3_uidl_assign_cached(box);
+
+ array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_pop3_seq_cmp);
+ array_sort(&mbox->imap_msg_map, imap_msg_map_uid_cmp);
+
+ if (!pop3_uidl_assign_by_size(box)) {
+ /* everything wasn't assigned, figure out the rest with
+ header hashes */
+ if (pop3_uidl_assign_by_hdr_hash(box, pop3_box) < 0) {
+ mailbox_free(&pop3_box);
+ return -1;
+ }
+ }
+
+ if (!mstorage->skip_uidl_cache)
+ imap_uidls_add_to_cache(box);
+
+ mbox->uidl_synced = TRUE;
+ mailbox_free(&pop3_box);
+ return 0;
+}
+
+static int pop3_migration_uidl_sync_if_needed(struct mailbox *box)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
+ int ret = 0;
+
+ if (mbox->uidl_synced)
+ return 0;
+
+ if (mbox->uidl_sync_failed)
+ ret = -1;
+ else {
+ struct event_reason *reason =
+ event_reason_begin("pop3_migration:uidl_sync");
+ ret = pop3_migration_uidl_sync(box);
+ event_reason_end(&reason);
+ }
+ if (ret < 0) {
+ mbox->uidl_sync_failed = TRUE;
+ mail_storage_set_error(box->storage, MAIL_ERROR_TEMP,
+ "POP3 UIDLs couldn't be synced");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+pop3_migration_get_special(struct mail *_mail, enum mail_fetch_field field,
+ const char **value_r)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ union mail_module_context *mmail = POP3_MIGRATION_MAIL_CONTEXT(mail);
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(_mail->box);
+ struct imap_msg_map map_key, *map;
+
+ if (field == MAIL_FETCH_UIDL_BACKEND ||
+ field == MAIL_FETCH_POP3_ORDER) {
+ if (pop3_migration_uidl_sync_if_needed(_mail->box) < 0)
+ return -1;
+
+ i_zero(&map_key);
+ map_key.uid = _mail->uid;
+ map = array_bsearch(&mbox->imap_msg_map, &map_key,
+ imap_msg_map_uid_cmp);
+ if (map != NULL && map->pop3_uidl != NULL) {
+ if (field == MAIL_FETCH_UIDL_BACKEND)
+ *value_r = map->pop3_uidl;
+ else
+ *value_r = t_strdup_printf("%u", map->pop3_seq);
+ return 0;
+ }
+ /* not found from POP3 server, fallback to default */
+ }
+ return mmail->super.get_special(_mail, field, value_r);
+}
+
+static void pop3_migration_mail_allocated(struct mail *_mail)
+{
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT(_mail->box->storage);
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ union mail_module_context *mmail;
+ struct mail_namespace *ns;
+
+ if (mstorage == NULL ||
+ (!mstorage->all_mailboxes && !_mail->box->inbox_user)) {
+ /* assigns UIDLs only for INBOX */
+ return;
+ }
+
+ ns = mail_namespace_find(_mail->box->storage->user->namespaces,
+ mstorage->pop3_box_vname);
+ if (ns == mailbox_get_namespace(_mail->box)) {
+ /* we're accessing the pop3-migration namespace itself */
+ return;
+ }
+
+ mmail = p_new(mail->pool, union mail_module_context, 1);
+ mmail->super = *v;
+ mail->vlast = &mmail->super;
+
+ v->get_special = pop3_migration_get_special;
+ MODULE_CONTEXT_SET_SELF(mail, pop3_migration_mail_module, mmail);
+}
+
+static struct mail_search_context *
+pop3_migration_mailbox_search_init(struct mailbox_transaction_context *t,
+ struct mail_search_args *args,
+ const enum mail_sort_type *sort_program,
+ enum mail_fetch_field wanted_fields,
+ struct mailbox_header_lookup_ctx *wanted_headers)
+{
+ struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(t->box);
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(t->box->storage);
+
+ if ((wanted_fields & (MAIL_FETCH_UIDL_BACKEND |
+ MAIL_FETCH_POP3_ORDER)) != 0 &&
+ (mstorage->all_mailboxes || t->box->inbox_user)) {
+ /* Start POP3 UIDL syncing before the search, so we'll do it
+ before we start sending any FETCH BODY[]s to IMAP. It
+ shouldn't matter much, except this works around a bug in
+ Yahoo IMAP where it sometimes breaks its state when doing
+ a FETCH BODY[] followed by FETCH BODY[HEADER].. */
+ (void)pop3_migration_uidl_sync_if_needed(t->box);
+ }
+
+ return mbox->module_ctx.super.search_init(t, args, sort_program,
+ wanted_fields, wanted_headers);
+}
+
+static void pop3_migration_mailbox_allocated(struct mailbox *box)
+{
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT(box->storage);
+ struct mailbox_vfuncs *v = box->vlast;
+ struct pop3_migration_mailbox *mbox;
+
+ if (mstorage == NULL)
+ return;
+
+ mbox = p_new(box->pool, struct pop3_migration_mailbox, 1);
+ mbox->module_ctx.super = *v;
+ box->vlast = &mbox->module_ctx.super;
+
+ v->search_init = pop3_migration_mailbox_search_init;
+
+ MODULE_CONTEXT_SET(box, pop3_migration_storage_module, mbox);
+}
+
+static void pop3_migration_mail_storage_destroy(struct mail_storage *storage)
+{
+ struct pop3_migration_mail_storage *mstorage =
+ POP3_MIGRATION_CONTEXT_REQUIRE(storage);
+
+ if (array_is_created(&mstorage->pop3_uidl_map))
+ array_free(&mstorage->pop3_uidl_map);
+
+ mstorage->module_ctx.super.destroy(storage);
+}
+
+static void pop3_migration_mail_storage_created(struct mail_storage *storage)
+{
+ struct pop3_migration_mail_storage *mstorage;
+ struct mail_storage_vfuncs *v = storage->vlast;
+ const char *pop3_box_vname;
+
+ pop3_box_vname = mail_user_plugin_getenv(storage->user,
+ "pop3_migration_mailbox");
+ if (pop3_box_vname == NULL) {
+ e_debug(storage->user->event, "pop3_migration: No pop3_migration_mailbox setting - disabled");
+ return;
+ }
+
+ mstorage = p_new(storage->pool, struct pop3_migration_mail_storage, 1);
+ mstorage->module_ctx.super = *v;
+ storage->vlast = &mstorage->module_ctx.super;
+ v->destroy = pop3_migration_mail_storage_destroy;
+
+ mstorage->pop3_box_vname = p_strdup(storage->pool, pop3_box_vname);
+ mstorage->all_mailboxes =
+ mail_user_plugin_getenv_bool(storage->user,
+ "pop3_migration_all_mailboxes");
+ mstorage->ignore_missing_uidls =
+ mail_user_plugin_getenv_bool(storage->user,
+ "pop3_migration_ignore_missing_uidls");
+ mstorage->ignore_extra_uidls =
+ mail_user_plugin_getenv_bool(storage->user,
+ "pop3_migration_ignore_extra_uidls");
+ mstorage->skip_size_check =
+ mail_user_plugin_getenv_bool(storage->user,
+ "pop3_migration_skip_size_check");
+ mstorage->skip_uidl_cache =
+ mail_user_plugin_getenv_bool(storage->user,
+ "pop3_migration_skip_uidl_cache");
+
+ MODULE_CONTEXT_SET(storage, pop3_migration_storage_module, mstorage);
+}
+
+static struct mail_storage_hooks pop3_migration_mail_storage_hooks = {
+ .mail_allocated = pop3_migration_mail_allocated,
+ .mailbox_allocated = pop3_migration_mailbox_allocated,
+ .mail_storage_created = pop3_migration_mail_storage_created
+};
+
+void pop3_migration_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &pop3_migration_mail_storage_hooks);
+}
+
+void pop3_migration_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&pop3_migration_mail_storage_hooks);
+}
diff --git a/src/plugins/pop3-migration/pop3-migration-plugin.h b/src/plugins/pop3-migration/pop3-migration-plugin.h
new file mode 100644
index 0000000..1dc84e0
--- /dev/null
+++ b/src/plugins/pop3-migration/pop3-migration-plugin.h
@@ -0,0 +1,13 @@
+#ifndef POP3_MIGRATION_PLUGIN_H
+#define POP3_MIGRATION_PLUGIN_H
+
+struct module;
+
+void pop3_migration_plugin_init(struct module *module);
+void pop3_migration_plugin_deinit(void);
+
+int pop3_migration_get_hdr_sha1(uint32_t mail_seq, struct istream *input,
+ unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN],
+ bool *have_eoh_r);
+
+#endif
diff --git a/src/plugins/pop3-migration/test-pop3-migration-plugin.c b/src/plugins/pop3-migration/test-pop3-migration-plugin.c
new file mode 100644
index 0000000..f8e52d8
--- /dev/null
+++ b/src/plugins/pop3-migration/test-pop3-migration-plugin.c
@@ -0,0 +1,63 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "sha1.h"
+#include "hex-binary.h"
+#include "istream.h"
+#include "test-common.h"
+#include "pop3-migration-plugin.h"
+
+static void test_pop3_migration_get_hdr_sha1(void)
+{
+ static const struct {
+ const char *input;
+ const char *sha1;
+ bool have_eoh;
+ } tests[] = {
+ { "", "da39a3ee5e6b4b0d3255bfef95601890afd80709", FALSE },
+ { "\n", "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc", TRUE },
+ { "a: \r\n", "a3871371f2d468493005286282ae10549dab2c57", FALSE },
+ { "a: b\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b\r\n\r\n", "938b96404495cced816e3a4f6031734eab4e71b3", TRUE },
+ { "a: b\r\n\r\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b\r\n\r\r\nc: d\r\n\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", TRUE },
+ { "a: b\r\n \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b\r\n \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b\r\n\t\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b\t\t\t\t\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+ { "a: b\nfoo\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE },
+
+ { "a: b\nc: d\n", "4dbea2c1bdd1323e15931382c1835200d9286230", FALSE },
+ { "a:b\nc:d\n", "4dbea2c1bdd1323e15931382c1835200d9286230", FALSE },
+ { "a: b\nfoo\nc: d\n", "4dbea2c1bdd1323e15931382c1835200d9286230", FALSE },
+ };
+ struct istream *input;
+ unsigned char digest[SHA1_RESULTLEN];
+ unsigned int i;
+ bool have_eoh;
+
+ test_begin("pop3 migration get hdr sha1");
+
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ input = i_stream_create_from_data(tests[i].input,
+ strlen(tests[i].input));
+ test_assert_idx(pop3_migration_get_hdr_sha1(1, input, digest, &have_eoh) == 0, i);
+ test_assert_idx(strcasecmp(binary_to_hex(digest, sizeof(digest)), tests[i].sha1) == 0, i);
+ test_assert_idx(tests[i].have_eoh == have_eoh, i);
+ i_stream_unref(&input);
+ }
+
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_pop3_migration_get_hdr_sha1,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/plugins/push-notification/Makefile.am b/src/plugins/push-notification/Makefile.am
new file mode 100644
index 0000000..c065e38
--- /dev/null
+++ b/src/plugins/push-notification/Makefile.am
@@ -0,0 +1,83 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/plugins/notify
+
+NOPLUGIN_LDFLAGS =
+lib20_push_notification_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = lib20_push_notification_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+notify_deps = ../notify/lib15_notify_plugin.la
+endif
+
+lib20_push_notification_plugin_la_LIBADD = \
+ $(notify_deps)
+
+lib20_push_notification_plugin_la_SOURCES = \
+ push-notification-driver-dlog.c \
+ push-notification-driver-ox.c \
+ push-notification-drivers.c \
+ push-notification-event-flagsclear.c \
+ push-notification-event-flagsset.c \
+ push-notification-event-mailboxcreate.c \
+ push-notification-event-mailboxdelete.c \
+ push-notification-event-mailboxrename.c \
+ push-notification-event-mailboxsubscribe.c \
+ push-notification-event-mailboxunsubscribe.c \
+ push-notification-event-messageappend.c \
+ push-notification-event-messageexpunge.c \
+ push-notification-event-messagenew.c \
+ push-notification-event-messageread.c \
+ push-notification-event-messagetrash.c \
+ push-notification-event-message-common.c \
+ push-notification-events.c \
+ push-notification-events-rfc5423.c \
+ push-notification-plugin.c \
+ push-notification-triggers.c \
+ push-notification-txn-mbox.c \
+ push-notification-txn-msg.c
+
+headers = \
+ push-notification-drivers.h \
+ push-notification-event-flagsclear.h \
+ push-notification-event-flagsset.h \
+ push-notification-event-mailboxcreate.h \
+ push-notification-event-mailboxdelete.h \
+ push-notification-event-mailboxrename.h \
+ push-notification-event-mailboxsubscribe.h \
+ push-notification-event-mailboxunsubscribe.h \
+ push-notification-event-message-common.h \
+ push-notification-event-messageappend.h \
+ push-notification-event-messageexpunge.h \
+ push-notification-event-messagenew.h \
+ push-notification-event-messageread.h \
+ push-notification-event-messagetrash.h \
+ push-notification-events.h \
+ push-notification-events-rfc5423.h \
+ push-notification-plugin.h \
+ push-notification-triggers.h \
+ push-notification-txn-mbox.h \
+ push-notification-txn-msg.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+if HAVE_LUA
+lib22_push_notification_lua_plugin_la_CFLAGS = $(AM_CPPFLAGS) \
+ -I$(top_srcdir)/src/lib-lua \
+ -I$(top_srcdir)/src/plugins/mail-lua \
+ $(LUA_CFLAGS)
+lib22_push_notification_lua_plugin_la_LDFLAGS = module -avoid-version
+module_LTLIBRARIES += \
+ lib22_push_notification_lua_plugin.la
+lib22_push_notification_lua_plugin_la_LIBADD = $(notify_deps) $(LUA_LIBS)
+lib22_push_notification_lua_plugin_la_SOURCES = \
+ push-notification-driver-lua.c
+endif
diff --git a/src/plugins/push-notification/Makefile.in b/src/plugins/push-notification/Makefile.in
new file mode 100644
index 0000000..f785877
--- /dev/null
+++ b/src/plugins/push-notification/Makefile.in
@@ -0,0 +1,1037 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_LUA_TRUE@am__append_1 = \
+@HAVE_LUA_TRUE@ lib22_push_notification_lua_plugin.la
+
+subdir = src/plugins/push-notification
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib20_push_notification_plugin_la_DEPENDENCIES = $(notify_deps)
+am_lib20_push_notification_plugin_la_OBJECTS = \
+ push-notification-driver-dlog.lo \
+ push-notification-driver-ox.lo push-notification-drivers.lo \
+ push-notification-event-flagsclear.lo \
+ push-notification-event-flagsset.lo \
+ push-notification-event-mailboxcreate.lo \
+ push-notification-event-mailboxdelete.lo \
+ push-notification-event-mailboxrename.lo \
+ push-notification-event-mailboxsubscribe.lo \
+ push-notification-event-mailboxunsubscribe.lo \
+ push-notification-event-messageappend.lo \
+ push-notification-event-messageexpunge.lo \
+ push-notification-event-messagenew.lo \
+ push-notification-event-messageread.lo \
+ push-notification-event-messagetrash.lo \
+ push-notification-event-message-common.lo \
+ push-notification-events.lo \
+ push-notification-events-rfc5423.lo \
+ push-notification-plugin.lo push-notification-triggers.lo \
+ push-notification-txn-mbox.lo push-notification-txn-msg.lo
+lib20_push_notification_plugin_la_OBJECTS = \
+ $(am_lib20_push_notification_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_push_notification_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) \
+ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \
+ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lib20_push_notification_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+am__DEPENDENCIES_1 =
+@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_DEPENDENCIES = \
+@HAVE_LUA_TRUE@ $(notify_deps) $(am__DEPENDENCIES_1)
+am__lib22_push_notification_lua_plugin_la_SOURCES_DIST = \
+ push-notification-driver-lua.c
+@HAVE_LUA_TRUE@am_lib22_push_notification_lua_plugin_la_OBJECTS = lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo
+lib22_push_notification_lua_plugin_la_OBJECTS = \
+ $(am_lib22_push_notification_lua_plugin_la_OBJECTS)
+lib22_push_notification_lua_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) \
+ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \
+ $(CCLD) $(lib22_push_notification_lua_plugin_la_CFLAGS) \
+ $(CFLAGS) $(lib22_push_notification_lua_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+@HAVE_LUA_TRUE@am_lib22_push_notification_lua_plugin_la_rpath = \
+@HAVE_LUA_TRUE@ -rpath $(moduledir)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo \
+ ./$(DEPDIR)/push-notification-driver-dlog.Plo \
+ ./$(DEPDIR)/push-notification-driver-ox.Plo \
+ ./$(DEPDIR)/push-notification-drivers.Plo \
+ ./$(DEPDIR)/push-notification-event-flagsclear.Plo \
+ ./$(DEPDIR)/push-notification-event-flagsset.Plo \
+ ./$(DEPDIR)/push-notification-event-mailboxcreate.Plo \
+ ./$(DEPDIR)/push-notification-event-mailboxdelete.Plo \
+ ./$(DEPDIR)/push-notification-event-mailboxrename.Plo \
+ ./$(DEPDIR)/push-notification-event-mailboxsubscribe.Plo \
+ ./$(DEPDIR)/push-notification-event-mailboxunsubscribe.Plo \
+ ./$(DEPDIR)/push-notification-event-message-common.Plo \
+ ./$(DEPDIR)/push-notification-event-messageappend.Plo \
+ ./$(DEPDIR)/push-notification-event-messageexpunge.Plo \
+ ./$(DEPDIR)/push-notification-event-messagenew.Plo \
+ ./$(DEPDIR)/push-notification-event-messageread.Plo \
+ ./$(DEPDIR)/push-notification-event-messagetrash.Plo \
+ ./$(DEPDIR)/push-notification-events-rfc5423.Plo \
+ ./$(DEPDIR)/push-notification-events.Plo \
+ ./$(DEPDIR)/push-notification-plugin.Plo \
+ ./$(DEPDIR)/push-notification-triggers.Plo \
+ ./$(DEPDIR)/push-notification-txn-mbox.Plo \
+ ./$(DEPDIR)/push-notification-txn-msg.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_push_notification_plugin_la_SOURCES) \
+ $(lib22_push_notification_lua_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_push_notification_plugin_la_SOURCES) \
+ $(am__lib22_push_notification_lua_plugin_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/plugins/notify
+
+lib20_push_notification_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = lib20_push_notification_plugin.la $(am__append_1)
+@DOVECOT_PLUGIN_DEPS_TRUE@notify_deps = ../notify/lib15_notify_plugin.la
+lib20_push_notification_plugin_la_LIBADD = \
+ $(notify_deps)
+
+lib20_push_notification_plugin_la_SOURCES = \
+ push-notification-driver-dlog.c \
+ push-notification-driver-ox.c \
+ push-notification-drivers.c \
+ push-notification-event-flagsclear.c \
+ push-notification-event-flagsset.c \
+ push-notification-event-mailboxcreate.c \
+ push-notification-event-mailboxdelete.c \
+ push-notification-event-mailboxrename.c \
+ push-notification-event-mailboxsubscribe.c \
+ push-notification-event-mailboxunsubscribe.c \
+ push-notification-event-messageappend.c \
+ push-notification-event-messageexpunge.c \
+ push-notification-event-messagenew.c \
+ push-notification-event-messageread.c \
+ push-notification-event-messagetrash.c \
+ push-notification-event-message-common.c \
+ push-notification-events.c \
+ push-notification-events-rfc5423.c \
+ push-notification-plugin.c \
+ push-notification-triggers.c \
+ push-notification-txn-mbox.c \
+ push-notification-txn-msg.c
+
+headers = \
+ push-notification-drivers.h \
+ push-notification-event-flagsclear.h \
+ push-notification-event-flagsset.h \
+ push-notification-event-mailboxcreate.h \
+ push-notification-event-mailboxdelete.h \
+ push-notification-event-mailboxrename.h \
+ push-notification-event-mailboxsubscribe.h \
+ push-notification-event-mailboxunsubscribe.h \
+ push-notification-event-message-common.h \
+ push-notification-event-messageappend.h \
+ push-notification-event-messageexpunge.h \
+ push-notification-event-messagenew.h \
+ push-notification-event-messageread.h \
+ push-notification-event-messagetrash.h \
+ push-notification-events.h \
+ push-notification-events-rfc5423.h \
+ push-notification-plugin.h \
+ push-notification-triggers.h \
+ push-notification-txn-mbox.h \
+ push-notification-txn-msg.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_CFLAGS = $(AM_CPPFLAGS) \
+@HAVE_LUA_TRUE@ -I$(top_srcdir)/src/lib-lua \
+@HAVE_LUA_TRUE@ -I$(top_srcdir)/src/plugins/mail-lua \
+@HAVE_LUA_TRUE@ $(LUA_CFLAGS)
+
+@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_LDFLAGS = module -avoid-version
+@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_LIBADD = $(notify_deps) $(LUA_LIBS)
+@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_SOURCES = \
+@HAVE_LUA_TRUE@ push-notification-driver-lua.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/push-notification/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/push-notification/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_push_notification_plugin.la: $(lib20_push_notification_plugin_la_OBJECTS) $(lib20_push_notification_plugin_la_DEPENDENCIES) $(EXTRA_lib20_push_notification_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_push_notification_plugin_la_LINK) -rpath $(moduledir) $(lib20_push_notification_plugin_la_OBJECTS) $(lib20_push_notification_plugin_la_LIBADD) $(LIBS)
+
+lib22_push_notification_lua_plugin.la: $(lib22_push_notification_lua_plugin_la_OBJECTS) $(lib22_push_notification_lua_plugin_la_DEPENDENCIES) $(EXTRA_lib22_push_notification_lua_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib22_push_notification_lua_plugin_la_LINK) $(am_lib22_push_notification_lua_plugin_la_rpath) $(lib22_push_notification_lua_plugin_la_OBJECTS) $(lib22_push_notification_lua_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-driver-dlog.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-driver-ox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-drivers.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-flagsclear.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-flagsset.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxcreate.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxdelete.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxrename.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxsubscribe.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxunsubscribe.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-message-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messageappend.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messageexpunge.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messagenew.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messageread.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messagetrash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-events-rfc5423.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-events.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-triggers.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-txn-mbox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-txn-msg.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo: push-notification-driver-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib22_push_notification_lua_plugin_la_CFLAGS) $(CFLAGS) -MT lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo -MD -MP -MF $(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Tpo -c -o lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo `test -f 'push-notification-driver-lua.c' || echo '$(srcdir)/'`push-notification-driver-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Tpo $(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='push-notification-driver-lua.c' object='lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib22_push_notification_lua_plugin_la_CFLAGS) $(CFLAGS) -c -o lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo `test -f 'push-notification-driver-lua.c' || echo '$(srcdir)/'`push-notification-driver-lua.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo
+ -rm -f ./$(DEPDIR)/push-notification-driver-dlog.Plo
+ -rm -f ./$(DEPDIR)/push-notification-driver-ox.Plo
+ -rm -f ./$(DEPDIR)/push-notification-drivers.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-flagsclear.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-flagsset.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxcreate.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxdelete.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxrename.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxsubscribe.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxunsubscribe.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-message-common.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messageappend.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messageexpunge.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messagenew.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messageread.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messagetrash.Plo
+ -rm -f ./$(DEPDIR)/push-notification-events-rfc5423.Plo
+ -rm -f ./$(DEPDIR)/push-notification-events.Plo
+ -rm -f ./$(DEPDIR)/push-notification-plugin.Plo
+ -rm -f ./$(DEPDIR)/push-notification-triggers.Plo
+ -rm -f ./$(DEPDIR)/push-notification-txn-mbox.Plo
+ -rm -f ./$(DEPDIR)/push-notification-txn-msg.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo
+ -rm -f ./$(DEPDIR)/push-notification-driver-dlog.Plo
+ -rm -f ./$(DEPDIR)/push-notification-driver-ox.Plo
+ -rm -f ./$(DEPDIR)/push-notification-drivers.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-flagsclear.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-flagsset.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxcreate.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxdelete.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxrename.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxsubscribe.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-mailboxunsubscribe.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-message-common.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messageappend.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messageexpunge.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messagenew.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messageread.Plo
+ -rm -f ./$(DEPDIR)/push-notification-event-messagetrash.Plo
+ -rm -f ./$(DEPDIR)/push-notification-events-rfc5423.Plo
+ -rm -f ./$(DEPDIR)/push-notification-events.Plo
+ -rm -f ./$(DEPDIR)/push-notification-plugin.Plo
+ -rm -f ./$(DEPDIR)/push-notification-triggers.Plo
+ -rm -f ./$(DEPDIR)/push-notification-txn-mbox.Plo
+ -rm -f ./$(DEPDIR)/push-notification-txn-msg.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/push-notification/push-notification-driver-dlog.c b/src/plugins/push-notification/push-notification-driver-dlog.c
new file mode 100644
index 0000000..e0cf790
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-driver-dlog.c
@@ -0,0 +1,114 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-mbox.h"
+#include "push-notification-txn-msg.h"
+
+static int
+push_notification_driver_dlog_init(
+ struct push_notification_driver_config *config,
+ struct mail_user *user ATTR_UNUSED, pool_t pool ATTR_UNUSED,
+ void **context ATTR_UNUSED, const char **error_r ATTR_UNUSED)
+{
+ i_debug("Called init push_notification plugin hook.");
+
+ if (config->raw_config != NULL) {
+ i_debug("Config string for dlog push_notification driver: %s",
+ config->raw_config);
+ }
+
+ return 0;
+}
+
+static bool
+push_notification_driver_dlog_begin_txn(
+ struct push_notification_driver_txn *dtxn)
+{
+ const struct push_notification_event *event;
+
+ i_debug("Called begin_txn push_notification plugin hook.");
+
+ array_foreach_elem(&push_notification_events, event)
+ push_notification_event_init(dtxn, event->name, NULL);
+ return TRUE;
+}
+
+static void
+push_notification_driver_dlog_process_mbox(
+ struct push_notification_driver_txn *dtxn ATTR_UNUSED,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_txn_event *event;
+
+ i_debug("Called process_mbox push_notification plugin hook.");
+
+ i_debug("Mailbox data: Mailbox [%s]", mbox->mailbox);
+
+ if (array_is_created(&mbox->eventdata)) {
+ array_foreach_elem(&mbox->eventdata, event) {
+ if (event->event->event->mbox.debug_mbox != NULL)
+ event->event->event->mbox.debug_mbox(event);
+ }
+ }
+}
+
+static void
+push_notification_driver_dlog_process_msg(
+ struct push_notification_driver_txn *dtxn ATTR_UNUSED,
+ struct push_notification_txn_msg *msg)
+{
+ struct push_notification_txn_event *event;
+
+ i_debug("Called process_msg push_notification plugin hook.");
+
+ i_debug("Message data: Mailbox [%s], UID [%u], UIDVALIDITY [%u]",
+ msg->mailbox, msg->uid, msg->uid_validity);
+
+ if (array_is_created(&msg->eventdata)) {
+ array_foreach_elem(&msg->eventdata, event) {
+ if (event->event->event->msg.debug_msg != NULL)
+ event->event->event->msg.debug_msg(event);
+ }
+ }
+}
+
+static void
+push_notification_driver_dlog_end_txn(
+ struct push_notification_driver_txn *dtxn ATTR_UNUSED,
+ bool success ATTR_UNUSED)
+{
+ i_debug("Called end_txn push_notification plugin hook.");
+}
+
+static void
+push_notification_driver_dlog_deinit(
+ struct push_notification_driver_user *duser ATTR_UNUSED)
+{
+ i_debug("Called deinit push_notification plugin hook.");
+}
+
+static void push_notification_driver_dlog_cleanup(void)
+{
+ i_debug("Called cleanup push_notification plugin hook.");
+}
+
+/* Driver definition */
+
+extern struct push_notification_driver push_notification_driver_dlog;
+
+struct push_notification_driver push_notification_driver_dlog = {
+ .name = "dlog",
+ .v = {
+ .init = push_notification_driver_dlog_init,
+ .begin_txn = push_notification_driver_dlog_begin_txn,
+ .process_mbox = push_notification_driver_dlog_process_mbox,
+ .process_msg = push_notification_driver_dlog_process_msg,
+ .end_txn = push_notification_driver_dlog_end_txn,
+ .deinit = push_notification_driver_dlog_deinit,
+ .cleanup = push_notification_driver_dlog_cleanup
+ }
+};
diff --git a/src/plugins/push-notification/push-notification-driver-lua.c b/src/plugins/push-notification/push-notification-driver-lua.c
new file mode 100644
index 0000000..e1178fa
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-driver-lua.c
@@ -0,0 +1,663 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "str.h"
+#include "hash.h"
+#include "dlua-script.h"
+#include "dlua-script-private.h"
+
+#include "mail-storage.h"
+#include "mail-user.h"
+#include "mail-lua-plugin.h"
+#include "mail-storage-lua.h"
+
+#include "push-notification-plugin.h"
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-message-common.h"
+#include "push-notification-txn-mbox.h"
+#include "push-notification-txn-msg.h"
+
+#include "push-notification-event-flagsclear.h"
+#include "push-notification-event-flagsset.h"
+#include "push-notification-event-mailboxcreate.h"
+#include "push-notification-event-mailboxdelete.h"
+#include "push-notification-event-mailboxrename.h"
+#include "push-notification-event-mailboxsubscribe.h"
+#include "push-notification-event-mailboxunsubscribe.h"
+#include "push-notification-event-messageappend.h"
+#include "push-notification-event-message-common.h"
+#include "push-notification-event-messageexpunge.h"
+#include "push-notification-event-messagenew.h"
+#include "push-notification-event-messageread.h"
+#include "push-notification-event-messagetrash.h"
+
+#define DLUA_LOG_USERENV_KEY "push_notification_lua_script_file"
+
+#define DLUA_FN_BEGIN_TXN "dovecot_lua_notify_begin_txn"
+#define DLUA_FN_EVENT_PREFIX "dovecot_lua_notify_event"
+#define DLUA_FN_END_TXN "dovecot_lua_notify_end_txn"
+
+#define DLUA_CALL_FINISHED "push_notification_lua_call_finished"
+
+struct dlua_push_notification_context {
+ struct dlua_script *script;
+ struct event *event;
+ bool debug;
+
+ struct push_notification_event_messagenew_config config_mn;
+ struct push_notification_event_messageappend_config config_ma;
+ struct push_notification_event_flagsclear_config config_fc;
+ struct push_notification_event_flagsset_config config_fs;
+};
+
+struct dlua_push_notification_txn_context {
+ int tx_ref;
+};
+
+#define DLUA_DEFAULT_EVENTS (\
+ PUSH_NOTIFICATION_MESSAGE_HDR_FROM | \
+ PUSH_NOTIFICATION_MESSAGE_HDR_TO | \
+ PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT | \
+ PUSH_NOTIFICATION_MESSAGE_HDR_DATE | \
+ PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET | \
+ PUSH_NOTIFICATION_MESSAGE_FLAGS | \
+ PUSH_NOTIFICATION_MESSAGE_KEYWORDS | \
+ PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID)
+
+static const char *push_notification_driver_lua_to_fn(const char *evname);
+
+static int
+push_notification_driver_lua_init(
+ struct push_notification_driver_config *config, struct mail_user *user,
+ pool_t pool, void **context, const char **error_r)
+{
+ struct dlua_push_notification_context *ctx;
+ const char *tmp, *file;
+ struct event *event = event_create(user->event);
+ event_add_category(event, push_notification_get_event_category());
+ event_set_append_log_prefix(event, "lua: ");
+
+ if ((tmp = mail_user_plugin_getenv(user, DLUA_LOG_USERENV_KEY)) == NULL)
+ tmp = hash_table_lookup(config->config, (const char *)"file");
+
+ if (tmp == NULL) {
+ struct dlua_script *script;
+ /* If there is a script loaded, use the same context */
+ if (mail_lua_plugin_get_script(user, &script)) {
+ dlua_script_ref(script);
+ ctx = p_new(
+ pool, struct dlua_push_notification_context, 1);
+ ctx->script = script;
+ ctx->event = event;
+ *context = ctx;
+ return 0;
+ }
+
+ event_unref(&event);
+ *error_r = "No file in config and no "
+ DLUA_LOG_USERENV_KEY " set";
+ return -1;
+ }
+ file = tmp;
+
+ ctx = p_new(pool, struct dlua_push_notification_context, 1);
+ ctx->event = event;
+
+ e_debug(ctx->event, "Loading %s", file);
+
+ if (dlua_script_create_file(file, &ctx->script, event, error_r) < 0) {
+ /* There is a T_POP after this, which will break errors */
+ event_unref(&event);
+ *error_r = p_strdup(pool, *error_r);
+ return -1;
+ }
+
+ /* Register dovecot helpers */
+ dlua_dovecot_register(ctx->script);
+ dlua_register_mail_storage(ctx->script);
+
+ e_debug(ctx->event, "Calling script_init");
+
+ /* Initialize script */
+ if (dlua_script_init(ctx->script, error_r) < 0) {
+ *error_r = p_strdup(pool, *error_r);
+ event_unref(&event);
+ dlua_script_unref(&ctx->script);
+ return -1;
+ }
+
+ *context = ctx;
+ return 0;
+}
+
+static bool
+push_notification_driver_lua_init_events(
+ struct push_notification_driver_txn *dtxn)
+{
+ struct dlua_push_notification_context *ctx = dtxn->duser->context;
+ const struct push_notification_event *event;
+ ctx->config_mn.flags = DLUA_DEFAULT_EVENTS;
+ ctx->config_ma.flags = DLUA_DEFAULT_EVENTS;
+ ctx->config_fc.store_old = TRUE;
+ bool found_one = FALSE;
+
+ /* Register *all* events that are present in Lua */
+ array_foreach_elem(push_notification_get_events(), event) {
+ const char *name = event->name;
+ const char *fn = push_notification_driver_lua_to_fn(name);
+ if (!dlua_script_has_function(ctx->script, fn))
+ continue;
+
+ found_one = TRUE;
+
+ e_debug(ctx->event, "Found %s, handling %s event", fn, name);
+
+ if (strcmp(name, "MessageNew") == 0) {
+ push_notification_event_init(dtxn, name,
+ &ctx->config_mn);
+ } else if (strcmp(name, "MessageAppend") == 0) {
+ push_notification_event_init(dtxn, name,
+ &ctx->config_ma);
+ } else if (strcmp(name, "FlagsSet") == 0) {
+ push_notification_event_init(dtxn, name,
+ &ctx->config_fs);
+ } else if (strcmp(name, "FlagsClear") == 0) {
+ push_notification_event_init(dtxn, name,
+ &ctx->config_fc);
+ } else if (event->init.default_config != NULL) {
+ void *config = event->init.default_config();
+ push_notification_event_init(dtxn, name, config);
+ } else {
+ push_notification_event_init(dtxn, name, NULL);
+ }
+ }
+
+ return found_one;
+}
+
+static bool
+push_notification_driver_lua_begin_txn(
+ struct push_notification_driver_txn *dtxn)
+{
+ struct mail_user *user = dtxn->ptxn->muser;
+ struct dlua_push_notification_context *ctx = dtxn->duser->context;
+ struct event *event = event_create(ctx->event);
+ const char *error;
+
+ event_set_name(event, DLUA_CALL_FINISHED);
+ event_add_str(event, "function_name", DLUA_FN_BEGIN_TXN);
+
+ if (!dlua_script_has_function(ctx->script, DLUA_FN_BEGIN_TXN)) {
+ event_add_str(event, "error",
+ "Missing function " DLUA_FN_BEGIN_TXN);
+ e_error(event, "Missing function " DLUA_FN_BEGIN_TXN);
+ event_unref(&event);
+ return FALSE;
+ }
+
+ if (!push_notification_driver_lua_init_events(dtxn)) {
+ e_debug(event, "No event handlers found in script");
+ event_unref(&event);
+ return FALSE;
+ }
+
+ e_debug(ctx->event, "Calling " DLUA_FN_BEGIN_TXN "(%s)",
+ user->username);
+
+ /* Push mail user as argument */
+ dlua_push_mail_user(ctx->script->L, user);
+ if (dlua_pcall(ctx->script->L, DLUA_FN_BEGIN_TXN, 1, 1, &error) < 0) {
+ event_add_str(event, "error", error);
+ e_error(event, "%s", error);
+ return FALSE;
+ }
+
+ e_debug(event, "Called " DLUA_FN_BEGIN_TXN);
+ event_unref(&event);
+
+ /* Store the result */
+ struct dlua_push_notification_txn_context *tctx =
+ p_new(dtxn->ptxn->pool,
+ struct dlua_push_notification_txn_context, 1);
+
+ tctx->tx_ref = luaL_ref(ctx->script->L, LUA_REGISTRYINDEX);
+ dtxn->context = tctx;
+ mail_user_ref(user);
+
+ return TRUE;
+}
+
+/* This function only works here, it converts MessageType to event_message_type
+ */
+static const char *push_notification_driver_lua_to_fn(const char *evname)
+{
+ /* Camelcase to event_event_name (most events have two underscores) */
+ string_t *fn = t_str_new(strlen(evname) +
+ strlen(DLUA_FN_EVENT_PREFIX) + 2);
+ str_append(fn, DLUA_FN_EVENT_PREFIX);
+
+ for(; *evname != '\0'; evname++) {
+ if (*evname >= 'A' && *evname <= 'Z') {
+ str_append_c(fn, '_');
+ str_append_c(fn, (*evname) - 'A' + 'a');
+ } else {
+ str_append_c(fn, *evname);
+ }
+ }
+
+ return str_c(fn);
+}
+
+/* Pushes lua list of flags */
+static void dlua_push_flags(struct dlua_script *script, enum mail_flags flags)
+{
+ lua_newtable(script->L);
+ int idx = 1;
+
+ if ((flags & MAIL_ANSWERED) != 0) {
+ lua_pushliteral(script->L, "\\Answered");
+ lua_rawseti(script->L, -2, idx++);
+ }
+ if ((flags & MAIL_FLAGGED) != 0) {
+ lua_pushliteral(script->L, "\\Flagged");
+ lua_rawseti(script->L, -2, idx++);
+ }
+ if ((flags & MAIL_DELETED) != 0) {
+ lua_pushliteral(script->L, "\\Deleted");
+ lua_rawseti(script->L, -2, idx++);
+ }
+ if ((flags & MAIL_SEEN) != 0) {
+ lua_pushliteral(script->L, "\\Seen");
+ lua_rawseti(script->L, -2, idx++);
+ }
+ if ((flags & MAIL_DRAFT) != 0) {
+ lua_pushliteral(script->L, "\\Draft");
+ lua_rawseti(script->L, -2, idx++);
+ }
+ if ((flags & MAIL_RECENT) != 0) {
+ lua_pushliteral(script->L, "\\Recent");
+ lua_rawseti(script->L, -2, idx++);
+ }
+}
+
+static void
+dlua_push_keywords(struct dlua_script *script, const char *const *keywords,
+ unsigned int count)
+{
+ lua_newtable(script->L);
+ if (keywords == NULL)
+ return;
+ for (unsigned int idx = 0; idx < count; idx++) {
+ lua_pushstring(script->L, keywords[idx]);
+ lua_rawseti(script->L, -2, idx+1);
+ }
+}
+
+static void
+push_notification_lua_push_flagsclear(
+ const struct push_notification_txn_event *event,
+ struct dlua_script *script)
+{
+ /* Push cleared flags */
+ unsigned int size = 0;
+ struct push_notification_event_flagsclear_data *data = event->data;
+
+ dlua_push_flags(script, data->flags_clear);
+ lua_setfield(script->L, -2, "flags");
+ dlua_push_flags(script, data->flags_old);
+ lua_setfield(script->L, -2, "flags_old");
+
+ if (array_is_created(&data->keywords_clear)) {
+ const char *const *kw = array_get(&data->keywords_clear, &size);
+ dlua_push_keywords(script, kw, size);
+ lua_setfield(script->L, -2, "keywords");
+ }
+
+ if (array_is_created(&data->keywords_old)) {
+ const char *const *kw = array_get(&data->keywords_old, &size);
+ dlua_push_keywords(script, kw, size);
+ lua_setfield(script->L, -2, "keywords_old");
+ }
+}
+
+static void
+push_notification_lua_push_flagsset(
+ const struct push_notification_txn_event *event,
+ struct dlua_script *script)
+{
+ /* push set flags */
+ unsigned int size = 0;
+ struct push_notification_event_flagsset_data *data = event->data;
+
+ dlua_push_flags(script, data->flags_set);
+ lua_setfield(script->L, -2, "flags");
+
+ if (array_is_created(&data->keywords_set)) {
+ const char *const *kw = array_get(&data->keywords_set, &size);
+ dlua_push_keywords(script, kw, size);
+ lua_setfield(script->L, -2, "keywords");
+ }
+}
+
+static void
+push_notification_lua_push_mailboxrename(
+ const struct push_notification_txn_event *event,
+ struct dlua_script *script)
+{
+ struct push_notification_event_mailboxrename_data *data = event->data;
+
+ lua_pushstring(script->L, data->old_mbox);
+ lua_setfield(script->L, -2, "mailbox_old");
+}
+
+#define push_notification_lua_push_string(L, value) \
+ lua_pushstring((L), (value) == NULL ? "" : (value))
+
+static void
+push_notification_lua_push_message_ext(
+ const struct push_notification_message_ext *ext,
+ struct dlua_script *script)
+{
+ push_notification_lua_push_string(script->L, ext->from_address);
+ lua_setfield(script->L, -2, "from_address");
+ push_notification_lua_push_string(script->L, ext->from_display_name_utf8);
+ lua_setfield(script->L, -2, "from_display_name");
+
+ push_notification_lua_push_string(script->L, ext->to_address);
+ lua_setfield(script->L, -2, "to_address");
+ push_notification_lua_push_string(script->L, ext->to_display_name_utf8);
+ lua_setfield(script->L, -2, "to_display_name");
+
+ lua_pushstring(script->L, ext->subject_utf8);
+ lua_setfield(script->L, -2, "subject");
+}
+
+static void
+push_notification_lua_push_messageappend(
+ const struct push_notification_txn_event *event,
+ struct dlua_script *script)
+{
+ struct push_notification_event_messageappend_data *data = event->data;
+
+ lua_pushnumber(script->L, data->date);
+ lua_setfield(script->L, -2, "date");
+
+ lua_pushnumber(script->L, data->date_tz);
+ lua_setfield(script->L, -2, "tz");
+
+ push_notification_lua_push_string(script->L, data->from);
+ lua_setfield(script->L, -2, "from");
+
+ push_notification_lua_push_string(script->L, data->to);
+ lua_setfield(script->L, -2, "to");
+
+ lua_pushstring(script->L, data->snippet);
+ lua_setfield(script->L, -2, "snippet");
+
+ dlua_push_flags(script, data->flags);
+ lua_setfield(script->L, -2, "flags");
+
+ dlua_push_keywords(script, data->keywords,
+ str_array_length(data->keywords));
+ lua_setfield(script->L, -2, "keywords");
+
+ lua_pushstring(script->L, data->message_id);
+ lua_setfield(script->L, -2, "message_id");
+
+ push_notification_lua_push_message_ext(&data->ext, script);
+}
+
+static void
+push_notification_lua_push_messagenew(
+ const struct push_notification_txn_event *event,
+ struct dlua_script *script)
+{
+ struct push_notification_event_messagenew_data *data = event->data;
+
+ lua_pushnumber(script->L, data->date);
+ lua_setfield(script->L, -2, "date");
+
+ lua_pushnumber(script->L, data->date_tz);
+ lua_setfield(script->L, -2, "tz");
+
+ push_notification_lua_push_string(script->L, data->from);
+ lua_setfield(script->L, -2, "from");
+
+ push_notification_lua_push_string(script->L, data->to);
+ lua_setfield(script->L, -2, "to");
+
+ lua_pushstring(script->L, data->snippet);
+ lua_setfield(script->L, -2, "snippet");
+
+ dlua_push_flags(script, data->flags);
+ lua_setfield(script->L, -2, "flags");
+
+ dlua_push_keywords(script, data->keywords,
+ str_array_length(data->keywords));
+ lua_setfield(script->L, -2, "keywords");
+
+ lua_pushstring(script->L, data->message_id);
+ lua_setfield(script->L, -2, "message_id");
+
+ push_notification_lua_push_message_ext(&data->ext, script);
+}
+
+/* Events that need special treatment */
+static struct push_notification_event_to_lua {
+ const char *event_name;
+ void (*push)(const struct push_notification_txn_event *event,
+ struct dlua_script *script);
+} event_to_push_table[] = {
+ {
+ .event_name = "FlagsClear",
+ .push = push_notification_lua_push_flagsclear
+ },
+ {
+ .event_name = "FlagsSet",
+ .push = push_notification_lua_push_flagsset
+ },
+ {
+ .event_name = "MailboxRename",
+ .push = push_notification_lua_push_mailboxrename
+ },
+ {
+ .event_name = "MessageAppend",
+ .push = push_notification_lua_push_messageappend
+ },
+ {
+ .event_name = "MessageNew",
+ .push = push_notification_lua_push_messagenew
+ },
+};
+
+static void
+push_notification_driver_lua_push_event(
+ const struct push_notification_txn_event *event,
+ struct dlua_push_notification_context *ctx)
+{
+ struct dlua_script *script = ctx->script;
+ const char *name = event->event->event->name;
+
+ /* Create a table */
+ lua_newtable(script->L);
+
+ /* Event name */
+ lua_pushstring(script->L, name);
+ lua_setfield(script->L, -2, "name");
+
+ for(size_t i = 0; i < N_ELEMENTS(event_to_push_table); i++)
+ if (strcmp(event_to_push_table[i].event_name, name) == 0)
+ event_to_push_table[i].push(event, script);
+}
+
+static void
+push_notification_driver_lua_call(
+ struct dlua_push_notification_context *ctx,
+ struct dlua_push_notification_txn_context *tctx,
+ const struct push_notification_txn_event *event,
+ const struct push_notification_txn_mbox *mbox,
+ struct push_notification_txn_msg *msg)
+{
+ const char *error;
+ const char *fn =
+ push_notification_driver_lua_to_fn(event->event->event->name);
+ struct event *e = event_create(ctx->event);
+ event_set_name(e, DLUA_CALL_FINISHED);
+ event_add_str(e, "event_name", event->event->event->name);
+ event_add_str(e, "function_name", fn);
+
+ /* Push context */
+ lua_rawgeti(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref);
+
+ /* Push event + common fields */
+ push_notification_driver_lua_push_event(event, ctx);
+
+ if (mbox != NULL) {
+ lua_pushstring(ctx->script->L, mbox->mailbox);
+ lua_setfield(ctx->script->L, -2, "mailbox");
+ e_debug(ctx->event,
+ "Calling %s(ctx, event[name=%s,mailbox=%s])",
+ fn, event->event->event->name, mbox->mailbox);
+ event_add_str(e, "mailbox", mbox->mailbox);
+ } else if (msg != NULL) {
+ lua_pushstring(ctx->script->L, msg->mailbox);
+ lua_setfield(ctx->script->L, -2, "mailbox");
+ lua_pushnumber(ctx->script->L, msg->uid);
+ lua_setfield(ctx->script->L, -2, "uid");
+ lua_pushnumber(ctx->script->L, msg->uid_validity);
+ lua_setfield(ctx->script->L, -2, "uid_validity");
+ e_debug(ctx->event,
+ "Calling %s(ctx, event[name=%s,mailbox=%s,uid=%u])",
+ fn, event->event->event->name, msg->mailbox, msg->uid);
+ event_add_str(e, "mailbox", msg->mailbox);
+ event_add_int(e, "uid", msg->uid);
+ } else
+ i_unreached();
+
+ /* Perform call */
+ if (dlua_pcall(ctx->script->L, fn, 2, 0, &error) < 0) {
+ event_add_str(e, "error", error);
+ e_error(e, "%s", error);
+ } else {
+ e_debug(e, "Called %s", fn);
+ }
+ event_unref(&e);
+}
+
+static void
+push_notification_driver_lua_process_mbox(
+ struct push_notification_driver_txn *dtxn,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_txn_event *event;
+ struct dlua_push_notification_context *ctx = dtxn->duser->context;
+ struct dlua_push_notification_txn_context *tctx = dtxn->context;
+
+ if (array_is_created(&mbox->eventdata)) {
+ array_foreach_elem(&mbox->eventdata, event) {
+ push_notification_driver_lua_call(ctx, tctx,
+ event, mbox, NULL);
+ }
+ }
+}
+
+static void
+push_notification_driver_lua_process_msg(
+ struct push_notification_driver_txn *dtxn,
+ struct push_notification_txn_msg *msg)
+{
+ struct push_notification_txn_event *event;
+ struct dlua_push_notification_context *ctx = dtxn->duser->context;
+ struct dlua_push_notification_txn_context *tctx = dtxn->context;
+
+ if (array_is_created(&msg->eventdata)) {
+ array_foreach_elem(&msg->eventdata, event) {
+ push_notification_driver_lua_call(ctx, tctx,
+ event, NULL, msg);
+ }
+ }
+}
+
+static void
+push_notification_driver_lua_end_txn(struct push_notification_driver_txn *dtxn,
+ bool success)
+{
+ /* Call end txn */
+ const char *error;
+ struct dlua_push_notification_context *ctx = dtxn->duser->context;
+ struct dlua_push_notification_txn_context *tctx = dtxn->context;
+ struct mail_user *user = dtxn->ptxn->muser;
+ struct event *event = event_create(ctx->event);
+ event_set_name(event, DLUA_CALL_FINISHED);
+ event_add_str(event, "function_name", DLUA_FN_END_TXN);
+
+ if (!dlua_script_has_function(ctx->script, DLUA_FN_END_TXN)) {
+ e_error(event, "Missing function " DLUA_FN_END_TXN);
+ } else {
+ e_debug(ctx->event, "Calling " DLUA_FN_END_TXN);
+ lua_rawgeti(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref);
+ lua_pushboolean(ctx->script->L, success);
+ if (dlua_pcall(ctx->script->L, DLUA_FN_END_TXN, 2, 0, &error) < 0) {
+ event_add_str(event, "error", error);
+ e_error(event, "%s", error);
+ } else {
+ e_debug(event, "Called " DLUA_FN_END_TXN);
+ }
+ }
+
+ event_unref(&event);
+ /* Release context */
+ luaL_unref(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref);
+ /* Call gc here */
+ (void)lua_gc(ctx->script->L, LUA_GCCOLLECT, 1);
+ mail_user_unref(&user);
+}
+
+static void
+push_notification_driver_lua_deinit(struct push_notification_driver_user *duser)
+{
+ /* Call lua deinit */
+ struct dlua_push_notification_context *ctx = duser->context;
+ dlua_script_unref(&ctx->script);
+ event_unref(&ctx->event);
+}
+
+static void push_notification_driver_lua_cleanup(void)
+{
+ /* noop */
+}
+
+/* Driver definition */
+
+struct push_notification_driver push_notification_driver_lua = {
+ .name = "lua",
+ .v = {
+ .init = push_notification_driver_lua_init,
+ .begin_txn = push_notification_driver_lua_begin_txn,
+ .process_mbox = push_notification_driver_lua_process_mbox,
+ .process_msg = push_notification_driver_lua_process_msg,
+ .end_txn = push_notification_driver_lua_end_txn,
+ .deinit = push_notification_driver_lua_deinit,
+ .cleanup = push_notification_driver_lua_cleanup,
+ },
+};
+
+void push_notification_lua_plugin_init(struct module *module);
+void push_notification_lua_plugin_deinit(void);
+
+void push_notification_lua_plugin_init(struct module *module ATTR_UNUSED)
+{
+ push_notification_driver_register(&push_notification_driver_lua);
+}
+
+void push_notification_lua_plugin_deinit(void)
+{
+ push_notification_driver_unregister(&push_notification_driver_lua);
+}
+
+const char *push_notification_lua_plugin_version = DOVECOT_ABI_VERSION;
+const char *push_notification_lua_plugin_dependencies[] =
+ { "push_notification", "mail_lua", NULL};
diff --git a/src/plugins/push-notification/push-notification-driver-ox.c b/src/plugins/push-notification/push-notification-driver-ox.c
new file mode 100644
index 0000000..728cce9
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-driver-ox.c
@@ -0,0 +1,470 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "http-client.h"
+#include "http-url.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "settings-parser.h"
+#include "json-parser.h"
+#include "mailbox-attribute.h"
+#include "mail-storage-private.h"
+#include "str.h"
+#include "strescape.h"
+#include "iostream-ssl.h"
+
+#include "push-notification-plugin.h"
+#include "push-notification-drivers.h"
+#include "push-notification-event-messagenew.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-msg.h"
+
+#define OX_METADATA_KEY \
+ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER \
+ "vendor/vendor.dovecot/http-notify"
+
+/* Default values. */
+static const char *const default_events[] = { "MessageNew", NULL };
+static const char *const default_mboxes[] = { "INBOX", NULL };
+#define DEFAULT_CACHE_LIFETIME_SECS 60
+#define DEFAULT_TIMEOUT_MSECS 2000
+#define DEFAULT_RETRY_COUNT 1
+
+/* This is data that is shared by all plugin users. */
+struct push_notification_driver_ox_global {
+ struct http_client *http_client;
+ int refcount;
+};
+static struct push_notification_driver_ox_global *ox_global = NULL;
+
+/* This is data specific to an OX driver. */
+struct push_notification_driver_ox_config {
+ struct http_url *http_url;
+ struct event *event;
+ unsigned int cached_ox_metadata_lifetime_secs;
+ bool use_unsafe_username;
+ unsigned int http_max_retries;
+ unsigned int http_timeout_msecs;
+
+ char *cached_ox_metadata;
+ time_t cached_ox_metadata_timestamp;
+};
+
+/* This is data specific to an OX driver transaction. */
+struct push_notification_driver_ox_txn {
+ const char *unsafe_user;
+};
+
+static void
+push_notification_driver_ox_init_global(
+ struct mail_user *user,
+ struct push_notification_driver_ox_config *config)
+{
+ struct http_client_settings http_set;
+ struct ssl_iostream_settings ssl_set;
+
+ if (ox_global->http_client == NULL) {
+ /* This is going to use the first user's settings, but these are
+ unlikely to change between users so it shouldn't matter much.
+ */
+ i_zero(&http_set);
+ http_set.debug = user->mail_debug;
+ http_set.max_attempts = config->http_max_retries+1;
+ http_set.request_timeout_msecs = config->http_timeout_msecs;
+ http_set.event_parent = user->event;
+ mail_user_init_ssl_client_settings(user, &ssl_set);
+ http_set.ssl = &ssl_set;
+
+ ox_global->http_client = http_client_init(&http_set);
+ }
+}
+
+static int
+push_notification_driver_ox_init(struct push_notification_driver_config *config,
+ struct mail_user *user, pool_t pool,
+ void **context, const char **error_r)
+{
+ struct push_notification_driver_ox_config *dconfig;
+ const char *error, *tmp;
+
+ /* Valid config keys: cache_lifetime, url */
+ tmp = hash_table_lookup(config->config, (const char *)"url");
+ if (tmp == NULL) {
+ *error_r = "Driver requires the url parameter";
+ return -1;
+ }
+
+ dconfig = p_new(pool, struct push_notification_driver_ox_config, 1);
+ dconfig->event = event_create(user->event);
+ event_add_category(dconfig->event, &event_category_push_notification);
+ event_set_append_log_prefix(dconfig->event, "push-notification-ox: ");
+
+ if (http_url_parse(tmp, NULL, HTTP_URL_ALLOW_USERINFO_PART, pool,
+ &dconfig->http_url, &error) < 0) {
+ event_unref(&dconfig->event);
+ *error_r = t_strdup_printf("Failed to parse OX REST URL %s: %s",
+ tmp, error);
+ return -1;
+ }
+ dconfig->use_unsafe_username =
+ hash_table_lookup(config->config,
+ (const char *)"user_from_metadata") != NULL;
+
+ e_debug(dconfig->event, "Using URL %s", tmp);
+
+ tmp = hash_table_lookup(config->config, (const char *)"cache_lifetime");
+ if (tmp == NULL) {
+ dconfig->cached_ox_metadata_lifetime_secs =
+ DEFAULT_CACHE_LIFETIME_SECS;
+ } else if (settings_get_time(
+ tmp, &dconfig->cached_ox_metadata_lifetime_secs, &error) < 0) {
+ event_unref(&dconfig->event);
+ *error_r = t_strdup_printf(
+ "Failed to parse OX cache_lifetime %s: %s", tmp, error);
+ return -1;
+ }
+
+ tmp = hash_table_lookup(config->config, (const char *)"max_retries");
+ if ((tmp == NULL) ||
+ (str_to_uint(tmp, &dconfig->http_max_retries) < 0)) {
+ dconfig->http_max_retries = DEFAULT_RETRY_COUNT;
+ }
+ tmp = hash_table_lookup(config->config, (const char *)"timeout_msecs");
+ if ((tmp == NULL) ||
+ (str_to_uint(tmp, &dconfig->http_timeout_msecs) < 0)) {
+ dconfig->http_timeout_msecs = DEFAULT_TIMEOUT_MSECS;
+ }
+
+ e_debug(dconfig->event, "Using cache lifetime: %u",
+ dconfig->cached_ox_metadata_lifetime_secs);
+
+ if (ox_global == NULL) {
+ ox_global = i_new(struct push_notification_driver_ox_global, 1);
+ ox_global->refcount = 0;
+ }
+
+ ++ox_global->refcount;
+ *context = dconfig;
+
+ return 0;
+}
+
+static const char *
+push_notification_driver_ox_get_metadata(
+ struct push_notification_driver_txn *dtxn)
+{
+ struct push_notification_driver_ox_config *dconfig =
+ dtxn->duser->context;
+ struct mail_attribute_value attr;
+ struct mailbox *inbox;
+ struct mail_namespace *ns;
+ bool success = FALSE, use_existing_txn = FALSE;
+ int ret;
+
+ if ((dconfig->cached_ox_metadata != NULL) &&
+ ((dconfig->cached_ox_metadata_timestamp +
+ (time_t)dconfig->cached_ox_metadata_lifetime_secs) >
+ ioloop_time)) {
+ return dconfig->cached_ox_metadata;
+ }
+
+ /* Get canonical INBOX, where private server-level metadata is stored.
+ * See imap/cmd-getmetadata.c */
+ if ((dtxn->ptxn->t != NULL) && dtxn->ptxn->mbox->inbox_user) {
+ inbox = dtxn->ptxn->mbox;
+ use_existing_txn = TRUE;
+ } else {
+ ns = mail_namespace_find_inbox(dtxn->ptxn->muser->namespaces);
+ inbox = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY);
+ }
+
+ ret = mailbox_attribute_get(inbox, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ OX_METADATA_KEY, &attr);
+ if (ret < 0) {
+ e_error(dconfig->event,
+ "Skipped because unable to get attribute: %s",
+ mailbox_get_last_internal_error(inbox, NULL));
+ } else if (ret == 0) {
+ e_debug(dconfig->event,
+ "Skipped because not active "
+ "(/private/"OX_METADATA_KEY" METADATA not set)");
+ } else {
+ success = TRUE;
+ }
+
+ if (!use_existing_txn)
+ mailbox_free(&inbox);
+ if (!success)
+ return NULL;
+
+ i_free(dconfig->cached_ox_metadata);
+ dconfig->cached_ox_metadata = i_strdup(attr.value);
+ dconfig->cached_ox_metadata_timestamp = ioloop_time;
+
+ return dconfig->cached_ox_metadata;
+}
+
+static bool
+push_notification_driver_ox_begin_txn(struct push_notification_driver_txn *dtxn)
+{
+ const char *const *args;
+ struct push_notification_event_messagenew_config *config;
+ const char *key, *mbox_curr, *md_value, *value;
+ bool mbox_found = FALSE;
+ struct push_notification_driver_ox_txn *txn;
+ struct push_notification_driver_ox_config *dconfig =
+ dtxn->duser->context;
+
+ md_value = push_notification_driver_ox_get_metadata(dtxn);
+ if (md_value == NULL)
+ return FALSE;
+
+ /* Unused keys: events, expire, folder */
+ /* TODO: To be implemented later(?) */
+ const char *const *events = default_events;
+ time_t expire = INT_MAX;
+ const char *const *mboxes = default_mboxes;
+
+ if (expire < ioloop_time) {
+ e_debug(dconfig->event, "Skipped due to expiration (%ld < %ld)",
+ (long)expire, (long)ioloop_time);
+ return FALSE;
+ }
+
+ mbox_curr = mailbox_get_vname(dtxn->ptxn->mbox);
+ for (; *mboxes != NULL; mboxes++) {
+ if (strcmp(mbox_curr, *mboxes) == 0) {
+ mbox_found = TRUE;
+ break;
+ }
+ }
+
+ if (mbox_found == FALSE) {
+ e_debug(dconfig->event,
+ "Skipped because %s is not a watched mailbox",
+ mbox_curr);
+ return FALSE;
+ }
+
+ txn = p_new(dtxn->ptxn->pool,
+ struct push_notification_driver_ox_txn, 1);
+
+ /* Valid keys: user */
+ args = t_strsplit_tabescaped(md_value);
+ for (; *args != NULL; args++) {
+ key = *args;
+
+ value = strchr(key, '=');
+ if (value != NULL) {
+ key = t_strdup_until(key, value++);
+ if (strcmp(key, "user") == 0) {
+ txn->unsafe_user =
+ p_strdup(dtxn->ptxn->pool, value);
+ }
+ }
+ }
+
+ if (txn->unsafe_user == NULL) {
+ e_error(dconfig->event, "No user provided in config");
+ return FALSE;
+ }
+
+ e_debug(dconfig->event, "User (%s)", txn->unsafe_user);
+
+ for (; *events != NULL; events++) {
+ if (strcmp(*events, "MessageNew") == 0) {
+ config = p_new(
+ dtxn->ptxn->pool,
+ struct push_notification_event_messagenew_config, 1);
+ config->flags = PUSH_NOTIFICATION_MESSAGE_HDR_FROM |
+ PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT |
+ PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET;
+ push_notification_event_init(
+ dtxn, "MessageNew", config);
+ e_debug(dconfig->event, "Handling MessageNew event");
+ }
+ }
+
+ dtxn->context = txn;
+
+ return TRUE;
+}
+
+static void
+push_notification_driver_ox_http_callback(
+ const struct http_response *response,
+ struct push_notification_driver_ox_config *dconfig)
+{
+ switch (response->status / 100) {
+ case 2:
+ // Success.
+ e_debug(dconfig->event, "Notification sent successfully: %s",
+ http_response_get_message(response));
+ break;
+
+ default:
+ // Error.
+ e_error(dconfig->event, "Error when sending notification: %s",
+ http_response_get_message(response));
+ break;
+ }
+}
+
+/* Callback needed for i_stream_add_destroy_callback() in
+ push_notification_driver_ox_process_msg. */
+static void str_free_i(string_t *str)
+{
+ str_free(&str);
+}
+
+static int
+push_notification_driver_ox_get_mailbox_status(
+ struct push_notification_driver_txn *dtxn,
+ struct mailbox_status *r_box_status)
+{
+ struct push_notification_driver_ox_config *dconfig =
+ dtxn->duser->context;
+ /* The already opened mailbox. We cannot use or sync it, because we are
+ within a save transaction. */
+ struct mailbox *mbox = dtxn->ptxn->mbox;
+ struct mailbox *box;
+ int ret;
+
+ /* Open and sync new instance of the same mailbox to get most recent
+ status */
+ box = mailbox_alloc(mailbox_get_namespace(mbox)->list,
+ mailbox_get_name(mbox), MAILBOX_FLAG_READONLY);
+ if (mailbox_sync(box, 0) < 0) {
+ e_error(dconfig->event, "mailbox_sync(%s) failed: %s",
+ mailbox_get_vname(mbox),
+ mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ } else {
+ /* only 'unseen' is needed at the moment */
+ mailbox_get_open_status(box, STATUS_UNSEEN, r_box_status);
+ e_debug(dconfig->event,
+ "Got status of mailbox '%s': (unseen: %u)",
+ mailbox_get_vname(box), r_box_status->unseen);
+ ret = 0;
+ }
+
+ mailbox_free(&box);
+ return ret;
+}
+
+
+static void
+push_notification_driver_ox_process_msg(
+ struct push_notification_driver_txn *dtxn,
+ struct push_notification_txn_msg *msg)
+{
+ struct push_notification_driver_ox_config *dconfig =
+ (struct push_notification_driver_ox_config *)
+ dtxn->duser->context;
+ struct http_client_request *http_req;
+ struct push_notification_event_messagenew_data *messagenew;
+ struct istream *payload;
+ string_t *str;
+ struct push_notification_driver_ox_txn *txn =
+ (struct push_notification_driver_ox_txn *)dtxn->context;
+ struct mail_user *user = dtxn->ptxn->muser;
+ struct mailbox_status box_status;
+ bool status_success = TRUE;
+
+ if (push_notification_driver_ox_get_mailbox_status(
+ dtxn, &box_status) < 0) {
+ status_success = FALSE;
+ }
+
+ messagenew = push_notification_txn_msg_get_eventdata(msg, "MessageNew");
+ if (messagenew == NULL)
+ return;
+
+ push_notification_driver_ox_init_global(user, dconfig);
+
+ http_req = http_client_request_url(
+ ox_global->http_client, "PUT", dconfig->http_url,
+ push_notification_driver_ox_http_callback, dconfig);
+ http_client_request_set_event(http_req, dtxn->ptxn->event);
+ http_client_request_add_header(http_req, "Content-Type",
+ "application/json; charset=utf-8");
+
+ str = str_new(default_pool, 256);
+ str_append(str, "{\"user\":\"");
+ json_append_escaped(str, dconfig->use_unsafe_username ?
+ txn->unsafe_user : user->username);
+ str_append(str, "\",\"event\":\"messageNew\",\"folder\":\"");
+ json_append_escaped(str, msg->mailbox);
+ str_printfa(str, "\",\"imap-uidvalidity\":%u,\"imap-uid\":%u",
+ msg->uid_validity, msg->uid);
+ if (messagenew->from != NULL) {
+ str_append(str, ",\"from\":\"");
+ json_append_escaped(str, messagenew->from);
+ str_append(str, "\"");
+ }
+ if (messagenew->subject != NULL) {
+ str_append(str, ",\"subject\":\"");
+ json_append_escaped(str, messagenew->subject);
+ str_append(str, "\"");
+ }
+ if (messagenew->snippet != NULL) {
+ str_append(str, ",\"snippet\":\"");
+ json_append_escaped(str, messagenew->snippet);
+ str_append(str, "\"");
+ }
+ if (status_success) {
+ str_printfa(str, ",\"unseen\":%u", box_status.unseen);
+ }
+ str_append(str, "}");
+
+ e_debug(dconfig->event, "Sending notification: %s", str_c(str));
+
+ payload = i_stream_create_from_data(str_data(str), str_len(str));
+ i_stream_add_destroy_callback(payload, str_free_i, str);
+ http_client_request_set_payload(http_req, payload, FALSE);
+
+ http_client_request_submit(http_req);
+ i_stream_unref(&payload);
+}
+
+static void
+push_notification_driver_ox_deinit(
+ struct push_notification_driver_user *duser ATTR_UNUSED)
+{
+ struct push_notification_driver_ox_config *dconfig = duser->context;
+
+ i_free(dconfig->cached_ox_metadata);
+ if (ox_global != NULL) {
+ if (ox_global->http_client != NULL)
+ http_client_wait(ox_global->http_client);
+ i_assert(ox_global->refcount > 0);
+ --ox_global->refcount;
+ }
+ event_unref(&dconfig->event);
+}
+
+static void push_notification_driver_ox_cleanup(void)
+{
+ if ((ox_global != NULL) && (ox_global->refcount <= 0)) {
+ if (ox_global->http_client != NULL) {
+ http_client_deinit(&ox_global->http_client);
+ }
+ i_free_and_null(ox_global);
+ }
+}
+
+/* Driver definition */
+
+extern struct push_notification_driver push_notification_driver_ox;
+
+struct push_notification_driver push_notification_driver_ox = {
+ .name = "ox",
+ .v = {
+ .init = push_notification_driver_ox_init,
+ .begin_txn = push_notification_driver_ox_begin_txn,
+ .process_msg = push_notification_driver_ox_process_msg,
+ .deinit = push_notification_driver_ox_deinit,
+ .cleanup = push_notification_driver_ox_cleanup,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-drivers.c b/src/plugins/push-notification/push-notification-drivers.c
new file mode 100644
index 0000000..67295d1
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-drivers.c
@@ -0,0 +1,181 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "mail-user.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+
+static ARRAY(const struct push_notification_driver *) push_notification_drivers;
+
+static bool
+push_notification_driver_find(const char *name, unsigned int *idx_r)
+{
+ unsigned int count, i;
+ const struct push_notification_driver *const *drivers;
+
+ drivers = array_get(&push_notification_drivers, &count);
+ for (i = 0; i < count; i++) {
+ if (strcasecmp(drivers[i]->name, name) == 0) {
+ *idx_r = i;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static const struct push_notification_driver *
+push_notification_driver_find_class(const char *driver)
+{
+ unsigned int idx;
+
+ if (!push_notification_driver_find(driver, &idx))
+ return NULL;
+
+ return array_idx_elem(&push_notification_drivers, idx);
+}
+
+static struct push_notification_driver_config *
+push_notification_driver_parse_config(const char *p)
+{
+ const char **args, *key, *p2, *value;
+ struct push_notification_driver_config *config;
+
+ config = t_new(struct push_notification_driver_config, 1);
+ config->raw_config = p;
+
+ hash_table_create(&config->config, unsafe_data_stack_pool, 0,
+ str_hash, strcmp);
+
+ if (p == NULL)
+ return config;
+
+ args = t_strsplit_spaces(p, " ");
+
+ for (; *args != NULL; args++) {
+ p2 = strchr(*args, '=');
+ if (p2 != NULL) {
+ key = t_strdup_until(*args, p2);
+ value = t_strdup(p2 + 1);
+ } else {
+ key = *args;
+ value = "";
+ }
+ hash_table_update(config->config, key, value);
+ }
+
+ return config;
+}
+
+int push_notification_driver_init(
+ struct mail_user *user, const char *config_in, pool_t pool,
+ struct push_notification_driver_user **duser_r)
+{
+ void *context = NULL;
+ const struct push_notification_driver *driver;
+ const char *driver_name, *error_r, *p;
+ struct push_notification_driver_user *duser;
+ int ret;
+
+ /* <driver>[:<driver config>] */
+ p = strchr(config_in, ':');
+ if (p == NULL)
+ driver_name = config_in;
+ else
+ driver_name = t_strdup_until(config_in, p);
+
+ driver = push_notification_driver_find_class(driver_name);
+ if (driver == NULL) {
+ i_error("Unknown push notification driver: %s", driver_name);
+ return -1;
+ }
+
+ if (driver->v.init != NULL) {
+ T_BEGIN {
+ struct push_notification_driver_config *config;
+
+ config = push_notification_driver_parse_config(
+ (p == NULL) ? p : p + 1);
+ ret = driver->v.init(config, user, pool,
+ &context, &error_r);
+ if (ret < 0)
+ i_error("%s: %s", driver_name, error_r);
+ hash_table_destroy(&config->config);
+ } T_END;
+
+ if (ret < 0)
+ return -1;
+ }
+
+ duser = p_new(pool, struct push_notification_driver_user, 1);
+ duser->context = context;
+ duser->driver = driver;
+
+ *duser_r = duser;
+
+ return 0;
+}
+
+void push_notification_driver_cleanup_all(void)
+{
+ const struct push_notification_driver *driver;
+
+ /* Loop through driver list and perform global cleanup tasks. We may not
+ have used all drivers in this plugin/worker, but the cleanup hooks
+ are designed to ignore these unused drivers. */
+ array_foreach_elem(&push_notification_drivers, driver) {
+ if (driver->v.cleanup != NULL)
+ driver->v.cleanup();
+ }
+}
+
+void ATTR_FORMAT(3, 4)
+push_notification_driver_debug(const char *label, struct mail_user *user,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ T_BEGIN {
+ va_start(args, fmt);
+ e_debug(user->event, "%s%s", label,
+ t_strdup_vprintf(fmt, args));
+ va_end(args);
+ } T_END;
+}
+
+void push_notification_driver_register(
+ const struct push_notification_driver *driver)
+{
+ unsigned int idx;
+
+ if (!array_is_created(&push_notification_drivers))
+ i_array_init(&push_notification_drivers, 4);
+
+ if (push_notification_driver_find(driver->name, &idx)) {
+ i_panic("push_notification_driver_register(%s): "
+ "duplicate driver", driver->name);
+ }
+
+ array_push_back(&push_notification_drivers, &driver);
+}
+
+void push_notification_driver_unregister(
+ const struct push_notification_driver *driver)
+{
+ unsigned int idx;
+
+ if (!push_notification_driver_find(driver->name, &idx)) {
+ i_panic("push_notification_driver_register(%s): "
+ "unknown driver", driver->name);
+ }
+
+ if (array_is_created(&push_notification_drivers)) {
+ array_delete(&push_notification_drivers, idx, 1);
+
+ if (array_is_empty(&push_notification_drivers))
+ array_free(&push_notification_drivers);
+ }
+}
diff --git a/src/plugins/push-notification/push-notification-drivers.h b/src/plugins/push-notification/push-notification-drivers.h
new file mode 100644
index 0000000..07b570f
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-drivers.h
@@ -0,0 +1,123 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_DRIVERS_H
+#define PUSH_NOTIFICATION_DRIVERS_H
+
+#include "mail-user.h"
+#include "push-notification-triggers.h"
+
+struct mail_user;
+struct push_notification_driver_config;
+struct push_notification_driver_txn;
+struct push_notification_driver_user;
+struct push_notification_txn_mbox;
+struct push_notification_txn_msg;
+
+HASH_TABLE_DEFINE_TYPE(push_notification_config, const char *, const char *);
+HASH_TABLE_DEFINE_TYPE(push_notification_msgs, void *,
+ struct push_notification_txn_msg *);
+
+struct push_notification_driver_vfuncs {
+ /* Init driver. Config (from plugin configuration) is parsed once (no
+ user variable substitutions). Return 0 on success, or -1 if this
+ driver should be disabled (or on error). */
+ int (*init)(struct push_notification_driver_config *config,
+ struct mail_user *user, pool_t pool, void **context,
+ const char **error_r);
+ /* Called at the beginning of a notification transaction. Return TRUE on
+ success, or FALSE if this driver should be ignored for this
+ transaction. */
+ bool (*begin_txn)(struct push_notification_driver_txn *dtxn);
+ /* Called once for every mailbox processed. */
+ void (*process_mbox)(struct push_notification_driver_txn *dtxn,
+ struct push_notification_txn_mbox *mbox);
+ /* Called once for every message processed. */
+ void (*process_msg)(struct push_notification_driver_txn *dtxn,
+ struct push_notification_txn_msg *msg);
+ /* Called at the end of a successful notification transaction. */
+ void (*end_txn)(struct push_notification_driver_txn *dtxn,
+ bool success);
+ /* Called when plugin is deinitialized. */
+ void (*deinit)(struct push_notification_driver_user *duser);
+ /* Called to cleanup any global resources used in plugin. */
+ void (*cleanup)(void);
+};
+
+struct push_notification_driver {
+ const char *name;
+ struct push_notification_driver_vfuncs v;
+};
+
+struct push_notification_driver_config {
+ HASH_TABLE_TYPE(push_notification_config) config;
+ const char *raw_config;
+};
+
+struct push_notification_driver_user {
+ const struct push_notification_driver *driver;
+ void *context;
+};
+
+struct push_notification_driver_txn {
+ const struct push_notification_driver_user *duser;
+ struct push_notification_txn *ptxn;
+
+ /* Transaction context. */
+ void *context;
+};
+
+struct push_notification_driver_list {
+ ARRAY(struct push_notification_driver_user *) drivers;
+};
+
+struct push_notification_user {
+ union mail_user_module_context module_ctx;
+ struct push_notification_driver_list *driverlist;
+};
+
+struct push_notification_trigger_ctx {
+ const char *name;
+ void *context;
+};
+
+struct push_notification_txn {
+ pool_t pool;
+
+ struct mailbox *mbox;
+ struct mail_user *muser;
+ struct push_notification_user *puser;
+ bool initialized;
+
+ enum push_notification_event_trigger trigger;
+ struct push_notification_trigger_ctx *trigger_ctx;
+ ARRAY(struct push_notification_driver_txn *) drivers;
+ ARRAY(struct push_notification_event_config *) events;
+
+ struct event *event;
+
+ /* Used with mailbox events. */
+ struct push_notification_txn_mbox *mbox_txn;
+
+ /* Used with mailbox events. */
+ HASH_TABLE_TYPE(push_notification_msgs) messages;
+
+ /* Private (used with message events). */
+ struct mailbox_transaction_context *t;
+};
+
+
+int push_notification_driver_init(
+ struct mail_user *user, const char *config_in, pool_t pool,
+ struct push_notification_driver_user **duser_r);
+void push_notification_driver_cleanup_all(void);
+
+void ATTR_FORMAT(3, 4)
+push_notification_driver_debug(const char *label, struct mail_user *user,
+ const char *fmt, ...);
+
+void push_notification_driver_register(
+ const struct push_notification_driver *driver);
+void push_notification_driver_unregister(
+ const struct push_notification_driver *driver);
+
+#endif
diff --git a/src/plugins/push-notification/push-notification-event-flagsclear.c b/src/plugins/push-notification/push-notification-event-flagsclear.c
new file mode 100644
index 0000000..2ce0c3f
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-flagsclear.c
@@ -0,0 +1,170 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-flagsclear.h"
+#include "push-notification-txn-msg.h"
+
+#define EVENT_NAME "FlagsClear"
+
+static struct push_notification_event_flagsclear_config default_config;
+
+
+static void *push_notification_event_flagsclear_default_config(void)
+{
+ i_zero(&default_config);
+
+ return &default_config;
+}
+
+static void
+push_notification_event_flagsclear_debug_msg(
+ struct push_notification_txn_event *event)
+{
+ struct push_notification_event_flagsclear_data *data = event->data;
+ const char *keyword;
+
+ if ((data->flags_clear & MAIL_ANSWERED) != 0)
+ i_debug("%s: Answered flag cleared", EVENT_NAME);
+ if ((data->flags_clear & MAIL_FLAGGED) != 0)
+ i_debug("%s: Flagged flag cleared", EVENT_NAME);
+ if ((data->flags_clear & MAIL_DELETED) != 0)
+ i_debug("%s: Deleted flag cleared", EVENT_NAME);
+ if ((data->flags_clear & MAIL_SEEN) != 0)
+ i_debug("%s: Seen flag cleared", EVENT_NAME);
+ if ((data->flags_clear & MAIL_DRAFT) != 0)
+ i_debug("%s: Draft flag cleared", EVENT_NAME);
+
+ array_foreach_elem(&data->keywords_clear, keyword)
+ i_debug("%s: Keyword clear [%s]", EVENT_NAME, keyword);
+}
+
+static struct push_notification_event_flagsclear_data *
+push_notification_event_flagsclear_get_data(
+ struct push_notification_txn *ptxn,
+ struct push_notification_txn_msg *msg,
+ struct push_notification_event_config *ec)
+{
+ struct push_notification_event_flagsclear_config *config =
+ (struct push_notification_event_flagsclear_config *)ec->config;
+ struct push_notification_event_flagsclear_data *data;
+
+ data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+ if (data == NULL) {
+ data = p_new(ptxn->pool,
+ struct push_notification_event_flagsclear_data, 1);
+ data->flags_clear = 0;
+ data->flags_old = 0;
+ p_array_init(&data->keywords_clear, ptxn->pool, 4);
+ if (config->store_old == TRUE)
+ p_array_init(&data->keywords_old, ptxn->pool, 4);
+
+ push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+ }
+
+ return data;
+}
+
+static void
+push_notification_event_flagsclear_flags_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg, struct mail *mail,
+ enum mail_flags old_flags)
+{
+ struct push_notification_event_flagsclear_config *config =
+ (struct push_notification_event_flagsclear_config *)ec->config;
+ struct push_notification_event_flagsclear_data *data;
+ enum mail_flags flag_check_always[] = {
+ MAIL_ANSWERED,
+ MAIL_DELETED,
+ MAIL_DRAFT,
+ MAIL_FLAGGED,
+ MAIL_SEEN
+ };
+ enum mail_flags flags;
+ unsigned int i;
+
+ data = push_notification_event_flagsclear_get_data(ptxn, msg, ec);
+ flags = mail_get_flags(mail);
+
+ for (i = 0; i < N_ELEMENTS(flag_check_always); i++) {
+ if ((flags & flag_check_always[i]) == 0 &&
+ (old_flags & flag_check_always[i]) != 0)
+ data->flags_clear |= flag_check_always[i];
+ }
+
+ if (config->store_old == TRUE)
+ data->flags_old = old_flags;
+}
+
+static void
+push_notification_event_flagsclear_keywords_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg, struct mail *mail,
+ const char *const *old_keywords)
+{
+ struct push_notification_event_flagsclear_config *config =
+ (struct push_notification_event_flagsclear_config *)ec->config;
+ struct push_notification_event_flagsclear_data *data;
+ const char *const *keywords, *const *kp, *ok;
+
+ data = push_notification_event_flagsclear_get_data(ptxn, msg, ec);
+ keywords = mail_get_keywords(mail);
+
+ for (; *old_keywords != NULL; old_keywords++) {
+ for (kp = keywords; *kp != NULL; kp++) {
+ if (strcmp(*old_keywords, *kp) == 0)
+ break;
+ }
+
+ if (*kp == NULL) {
+ ok = p_strdup(ptxn->pool, *old_keywords);
+ array_push_back(&data->keywords_clear, &ok);
+ }
+
+ if (config->store_old == TRUE) {
+ ok = p_strdup(ptxn->pool, *old_keywords);
+ array_push_back(&data->keywords_old, &ok);
+ }
+ }
+}
+
+static void
+push_notification_event_flagsclear_free_msg(
+ struct push_notification_txn_event *event)
+{
+ struct push_notification_event_flagsclear_data *data = event->data;
+
+ if (array_is_created(&data->keywords_clear))
+ array_free(&data->keywords_clear);
+ if (array_is_created(&data->keywords_old))
+ array_free(&data->keywords_old);
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_flagsclear;
+
+struct push_notification_event push_notification_event_flagsclear = {
+ .name = EVENT_NAME,
+ .init = {
+ .default_config =
+ push_notification_event_flagsclear_default_config,
+ },
+ .msg = {
+ .debug_msg = push_notification_event_flagsclear_debug_msg,
+ .free_msg = push_notification_event_flagsclear_free_msg,
+ },
+ .msg_triggers = {
+ .flagchange = push_notification_event_flagsclear_flags_event,
+ .keywordchange =
+ push_notification_event_flagsclear_keywords_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-flagsclear.h b/src/plugins/push-notification/push-notification-event-flagsclear.h
new file mode 100644
index 0000000..5412335
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-flagsclear.h
@@ -0,0 +1,22 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_FLAGSCLEAR_H
+#define PUSH_NOTIFICATION_EVENT_FLAGSCLEAR_H
+
+#include "mail-types.h"
+
+struct push_notification_event_flagsclear_config {
+ /* Store the old flags/keywords? */
+ bool store_old;
+};
+
+struct push_notification_event_flagsclear_data {
+ enum mail_flags flags_clear;
+ ARRAY_TYPE(keywords) keywords_clear;
+
+ enum mail_flags flags_old;
+ ARRAY_TYPE(keywords) keywords_old;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-flagsset.c b/src/plugins/push-notification/push-notification-event-flagsset.c
new file mode 100644
index 0000000..c596ced
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-flagsset.c
@@ -0,0 +1,167 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-flagsset.h"
+#include "push-notification-txn-msg.h"
+
+
+#define EVENT_NAME "FlagsSet"
+
+static struct push_notification_event_flagsset_config default_config;
+
+
+static void *push_notification_event_flagsset_default_config(void)
+{
+ i_zero(&default_config);
+
+ return &default_config;
+}
+
+static void
+push_notification_event_flagsset_debug_msg(
+ struct push_notification_txn_event *event)
+{
+ struct push_notification_event_flagsset_data *data = event->data;
+ const char *keyword;
+
+ if ((data->flags_set & MAIL_ANSWERED) != 0)
+ i_debug("%s: Answered flag set", EVENT_NAME);
+ if ((data->flags_set & MAIL_FLAGGED) != 0)
+ i_debug("%s: Flagged flag set", EVENT_NAME);
+ if ((data->flags_set & MAIL_DELETED) != 0)
+ i_debug("%s: Deleted flag set", EVENT_NAME);
+ if ((data->flags_set & MAIL_SEEN) != 0)
+ i_debug("%s: Seen flag set", EVENT_NAME);
+ if ((data->flags_set & MAIL_DRAFT) != 0)
+ i_debug("%s: Draft flag set", EVENT_NAME);
+
+ array_foreach_elem(&data->keywords_set, keyword)
+ i_debug("%s: Keyword set [%s]", EVENT_NAME, keyword);
+}
+
+static struct push_notification_event_flagsset_data *
+push_notification_event_flagsset_get_data(
+ struct push_notification_txn *ptxn,
+ struct push_notification_txn_msg *msg,
+ struct push_notification_event_config *ec)
+{
+ struct push_notification_event_flagsset_data *data;
+
+ data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+ if (data == NULL) {
+ data = p_new(ptxn->pool,
+ struct push_notification_event_flagsset_data, 1);
+ data->flags_set = 0;
+ p_array_init(&data->keywords_set, ptxn->pool, 4);
+
+ push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+ }
+
+ return data;
+}
+
+static void
+push_notification_event_flagsset_flags_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg, struct mail *mail,
+ enum mail_flags old_flags)
+{
+ struct push_notification_event_flagsset_config *config =
+ (struct push_notification_event_flagsset_config *)ec->config;
+ struct push_notification_event_flagsset_data *data;
+ enum mail_flags flag_check_always[] = {
+ MAIL_ANSWERED,
+ MAIL_DRAFT,
+ MAIL_FLAGGED
+ };
+ enum mail_flags flags, flags_set = 0;
+ unsigned int i;
+
+ flags = mail_get_flags(mail);
+
+ for (i = 0; i < N_ELEMENTS(flag_check_always); i++) {
+ if ((flags & flag_check_always[i]) != 0 &&
+ (old_flags & flag_check_always[i]) == 0)
+ flags_set |= flag_check_always[i];
+ }
+
+ if (!config->hide_deleted && (flags & MAIL_DELETED) != 0 &&
+ (old_flags & MAIL_DELETED) == 0) {
+ flags_set |= MAIL_DELETED;
+ }
+
+ if (!config->hide_seen && (flags & MAIL_SEEN) != 0 &&
+ (old_flags & MAIL_SEEN) == 0) {
+ flags_set |= MAIL_SEEN;
+ }
+
+ /* Only create data element if at least one flag was set. */
+ if (flags_set != 0) {
+ data = push_notification_event_flagsset_get_data(ptxn, msg, ec);
+ data->flags_set |= flags_set;
+ }
+}
+
+static void
+push_notification_event_flagsset_keywords_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg, struct mail *mail,
+ const char *const *old_keywords)
+{
+ struct push_notification_event_flagsset_data *data;
+ const char *k, *const *keywords, *const *op;
+
+ data = push_notification_event_flagsset_get_data(ptxn, msg, ec);
+ keywords = mail_get_keywords(mail);
+
+ for (; *keywords != NULL; keywords++) {
+ for (op = old_keywords; *op != NULL; op++) {
+ if (strcmp(*keywords, *op) == 0)
+ break;
+ }
+
+ if (*op == NULL) {
+ k = p_strdup(ptxn->pool, *keywords);
+ array_push_back(&data->keywords_set, &k);
+ }
+ }
+}
+
+static void
+push_notification_event_flagsset_free_msg(
+ struct push_notification_txn_event *event)
+{
+ struct push_notification_event_flagsset_data *data = event->data;
+
+ if (array_is_created(&data->keywords_set))
+ array_free(&data->keywords_set);
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_flagsset;
+
+struct push_notification_event push_notification_event_flagsset = {
+ .name = EVENT_NAME,
+ .init = {
+ .default_config =
+ push_notification_event_flagsset_default_config,
+ },
+ .msg = {
+ .debug_msg = push_notification_event_flagsset_debug_msg,
+ .free_msg = push_notification_event_flagsset_free_msg,
+ },
+ .msg_triggers = {
+ .flagchange = push_notification_event_flagsset_flags_event,
+ .keywordchange =
+ push_notification_event_flagsset_keywords_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-flagsset.h b/src/plugins/push-notification/push-notification-event-flagsset.h
new file mode 100644
index 0000000..4830260
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-flagsset.h
@@ -0,0 +1,22 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_FLAGSSET_H
+#define PUSH_NOTIFICATION_EVENT_FLAGSSET_H
+
+#include "mail-types.h"
+
+struct push_notification_event_flagsset_config {
+ /* RFC 5423[4.2] - allow configuration whether FlagsSet event returns
+ Deleted and/or Seen flags, since these flags are also settable via
+ MessageRead/MessageTrash events. By default, include them here. */
+ bool hide_deleted;
+ bool hide_seen;
+};
+
+struct push_notification_event_flagsset_data {
+ enum mail_flags flags_set;
+ ARRAY_TYPE(keywords) keywords_set;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-mailboxcreate.c b/src/plugins/push-notification/push-notification-event-mailboxcreate.c
new file mode 100644
index 0000000..87e46c1
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxcreate.c
@@ -0,0 +1,56 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxcreate.h"
+#include "push-notification-txn-mbox.h"
+
+#define EVENT_NAME "MailboxCreate"
+
+static void
+push_notification_event_mailboxcreate_debug_mbox(
+ struct push_notification_txn_event *event ATTR_UNUSED)
+{
+ i_debug("%s: Mailbox was created", EVENT_NAME);
+}
+
+static void
+push_notification_event_mailboxcreate_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_event_mailboxcreate_data *data;
+ struct mailbox_status status;
+
+ if (mailbox_get_status(ptxn->mbox, STATUS_UIDVALIDITY, &status) < 0) {
+ i_error(EVENT_NAME
+ "Failed to get created mailbox '%s' uidvalidity: %s",
+ mailbox_get_vname(ptxn->mbox),
+ mailbox_get_last_internal_error(ptxn->mbox, NULL));
+ status.uidvalidity = 0;
+ }
+
+ data = p_new(ptxn->pool,
+ struct push_notification_event_mailboxcreate_data, 1);
+ data->uid_validity = status.uidvalidity;
+
+ push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxcreate;
+
+struct push_notification_event push_notification_event_mailboxcreate = {
+ .name = EVENT_NAME,
+ .mbox = {
+ .debug_mbox = push_notification_event_mailboxcreate_debug_mbox,
+ },
+ .mbox_triggers = {
+ .create = push_notification_event_mailboxcreate_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-mailboxcreate.h b/src/plugins/push-notification/push-notification-event-mailboxcreate.h
new file mode 100644
index 0000000..9a5e1bf
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxcreate.h
@@ -0,0 +1,12 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXCREATE_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXCREATE_H
+
+struct push_notification_event_mailboxcreate_data {
+ /* RFC 5423 [4.4]: UIDVALIDITY required for create event. */
+ uint32_t uid_validity;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-mailboxdelete.c b/src/plugins/push-notification/push-notification-event-mailboxdelete.c
new file mode 100644
index 0000000..8e65f3c
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxdelete.c
@@ -0,0 +1,46 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxdelete.h"
+#include "push-notification-txn-mbox.h"
+
+#define EVENT_NAME "MailboxDelete"
+
+static void
+push_notification_event_mailboxdelete_debug_mbox(
+ struct push_notification_txn_event *event ATTR_UNUSED)
+{
+ i_debug("%s: Mailbox was deleted", EVENT_NAME);
+}
+
+static void
+push_notification_event_mailboxdelete_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_event_mailboxdelete_data *data;
+
+ data = p_new(ptxn->pool,
+ struct push_notification_event_mailboxdelete_data, 1);
+ data->deleted = TRUE;
+
+ push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxdelete;
+
+struct push_notification_event push_notification_event_mailboxdelete = {
+ .name = EVENT_NAME,
+ .mbox = {
+ .debug_mbox = push_notification_event_mailboxdelete_debug_mbox,
+ },
+ .mbox_triggers = {
+ .delete = push_notification_event_mailboxdelete_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-mailboxdelete.h b/src/plugins/push-notification/push-notification-event-mailboxdelete.h
new file mode 100644
index 0000000..ac36747
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxdelete.h
@@ -0,0 +1,12 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXDELETE_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXDELETE_H
+
+struct push_notification_event_mailboxdelete_data {
+ /* Can only be TRUE. */
+ bool deleted;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-mailboxrename.c b/src/plugins/push-notification/push-notification-event-mailboxrename.c
new file mode 100644
index 0000000..e4c2cae
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxrename.c
@@ -0,0 +1,50 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxrename.h"
+#include "push-notification-txn-mbox.h"
+
+#define EVENT_NAME "MailboxRename"
+
+static void
+push_notification_event_mailboxrename_debug_mbox(
+ struct push_notification_txn_event *event)
+{
+ struct push_notification_event_mailboxrename_data *data = event->data;
+
+ i_debug("%s: Mailbox was renamed (old name: %s)",
+ EVENT_NAME, data->old_mbox);
+}
+
+static void
+push_notification_event_mailboxrename_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox, struct mailbox *old)
+{
+ struct push_notification_event_mailboxrename_data *data;
+
+ data = p_new(ptxn->pool,
+ struct push_notification_event_mailboxrename_data, 1);
+ data->old_mbox = mailbox_get_vname(old);
+
+ push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxrename;
+
+struct push_notification_event push_notification_event_mailboxrename = {
+ .name = EVENT_NAME,
+ .mbox = {
+ .debug_mbox = push_notification_event_mailboxrename_debug_mbox,
+ },
+ .mbox_triggers = {
+ .rename = push_notification_event_mailboxrename_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-mailboxrename.h b/src/plugins/push-notification/push-notification-event-mailboxrename.h
new file mode 100644
index 0000000..66e21f8
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxrename.h
@@ -0,0 +1,11 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXRENAME_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXRENAME_H
+
+struct push_notification_event_mailboxrename_data {
+ const char *old_mbox;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-mailboxsubscribe.c b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.c
new file mode 100644
index 0000000..0bcd101
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.c
@@ -0,0 +1,48 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxsubscribe.h"
+#include "push-notification-txn-mbox.h"
+
+#define EVENT_NAME "MailboxSubscribe"
+
+static void
+push_notification_event_mailboxsubscribe_debug_mbox(
+ struct push_notification_txn_event *event ATTR_UNUSED)
+{
+ i_debug("%s: Mailbox was subscribed to", EVENT_NAME);
+}
+
+static void
+push_notification_event_mailboxsubscribe_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_event_mailboxsubscribe_data *data;
+
+ data = p_new(ptxn->pool,
+ struct push_notification_event_mailboxsubscribe_data, 1);
+ data->subscribe = TRUE;
+
+ push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxsubscribe;
+
+struct push_notification_event push_notification_event_mailboxsubscribe = {
+ .name = EVENT_NAME,
+ .mbox = {
+ .debug_mbox =
+ push_notification_event_mailboxsubscribe_debug_mbox,
+ },
+ .mbox_triggers = {
+ .subscribe = push_notification_event_mailboxsubscribe_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-mailboxsubscribe.h b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.h
new file mode 100644
index 0000000..d278482
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.h
@@ -0,0 +1,12 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXSUBSCRIBE_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXSUBSCRIBE_H
+
+struct push_notification_event_mailboxsubscribe_data {
+ /* Can only be TRUE. */
+ bool subscribe;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c
new file mode 100644
index 0000000..b8d078f
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c
@@ -0,0 +1,48 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxunsubscribe.h"
+#include "push-notification-txn-mbox.h"
+
+#define EVENT_NAME "MailboxUnsubscribe"
+
+static void
+push_notification_event_mailboxunsubscribe_debug_mbox(
+ struct push_notification_txn_event *event ATTR_UNUSED)
+{
+ i_debug("%s: Mailbox was subscribed to", EVENT_NAME);
+}
+
+static void
+push_notification_event_mailboxunsubscribe_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_event_mailboxunsubscribe_data *data;
+
+ data = p_new(ptxn->pool,
+ struct push_notification_event_mailboxunsubscribe_data, 1);
+ data->subscribe = FALSE;
+
+ push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxunsubscribe;
+
+struct push_notification_event push_notification_event_mailboxunsubscribe = {
+ .name = EVENT_NAME,
+ .mbox = {
+ .debug_mbox =
+ push_notification_event_mailboxunsubscribe_debug_mbox,
+ },
+ .mbox_triggers = {
+ .unsubscribe = push_notification_event_mailboxunsubscribe_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h
new file mode 100644
index 0000000..c519da2
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h
@@ -0,0 +1,12 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXUNSUBSCRIBE_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXUNSUBSCRIBE_H
+
+struct push_notification_event_mailboxunsubscribe_data {
+ /* Can only be FALSE. */
+ bool subscribe;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-message-common.c b/src/plugins/push-notification/push-notification-event-message-common.c
new file mode 100644
index 0000000..7965039
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-message-common.c
@@ -0,0 +1,117 @@
+/* Copyright (c) 2015-2019 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "message-address.h"
+#include "message-header-decode.h"
+#include "mail-storage.h"
+#include "push-notification-event-message-common.h"
+
+static void
+decode_address_header(pool_t pool, const char *hdr,
+ const char **address_r, const char **name_r)
+{
+ struct message_address *addr;
+ const char *display_name;
+
+ if (hdr == NULL)
+ return;
+
+ addr = message_address_parse(pool_datastack_create(),
+ (const unsigned char *)hdr, strlen(hdr), 1, 0);
+ if (addr == NULL)
+ return;
+
+ display_name = addr->name;
+ if (addr->domain == NULL) {
+ /* group */
+ display_name = addr->mailbox;
+ } else if (addr->domain[0] != '\0')
+ *address_r = p_strdup_printf(pool, "%s@%s", addr->mailbox,
+ addr->domain);
+ else if (addr->mailbox != NULL && addr->mailbox[0] != '\0')
+ *address_r = p_strdup(pool, addr->mailbox);
+
+ if (display_name != NULL && display_name[0] != '\0') {
+ string_t *name_utf8 = t_str_new(128);
+
+ message_header_decode_utf8((const unsigned char *)display_name,
+ strlen(display_name), name_utf8, NULL);
+ *name_r = p_strdup(pool, str_c(name_utf8));
+ }
+}
+
+void push_notification_message_fill(
+ struct mail *mail, pool_t pool,
+ enum push_notification_event_message_flags event_flags,
+ const char **from, const char **to, const char **subject, time_t *date,
+ int *date_tz, const char **message_id, enum mail_flags *flags,
+ bool *flags_set, const char *const **keywords, const char **snippet,
+ struct push_notification_message_ext *ext)
+{
+ const char *value;
+ time_t tmp_date;
+ int tmp_tz;
+
+ if ((*from == NULL) &&
+ (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_FROM) != 0 &&
+ (mail_get_first_header(mail, "From", &value) >= 0)) {
+ *from = p_strdup(pool, value);
+ decode_address_header(pool, value, &ext->from_address,
+ &ext->from_display_name_utf8);
+ }
+
+ if ((*to == NULL) &&
+ (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_TO) != 0 &&
+ (mail_get_first_header(mail, "To", &value) >= 0)) {
+ *to = p_strdup(pool, value);
+ decode_address_header(pool, value, &ext->to_address,
+ &ext->to_display_name_utf8);
+ }
+
+ if ((*subject == NULL) &&
+ (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT) != 0 &&
+ (mail_get_first_header(mail, "Subject", &value) >= 0)) {
+ string_t *subject_utf8 = t_str_new(128);
+
+ *subject = p_strdup(pool, value);
+ if (value != NULL) {
+ message_header_decode_utf8(
+ (const unsigned char *)value,
+ strlen(value), subject_utf8, NULL);
+ ext->subject_utf8 = p_strdup(pool, str_c(subject_utf8));
+ }
+ }
+
+ if ((*date == -1) &&
+ (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_DATE) != 0 &&
+ (mail_get_date(mail, &tmp_date, &tmp_tz) >= 0)) {
+ *date = tmp_date;
+ *date_tz = tmp_tz;
+ }
+
+ if ((*message_id == NULL) &&
+ (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID) != 0 &&
+ (mail_get_first_header(mail, "Message-ID", &value) >= 0)) {
+ *message_id = p_strdup(pool, value);
+ }
+
+ if (!*flags_set &&
+ (event_flags & PUSH_NOTIFICATION_MESSAGE_FLAGS) != 0) {
+ *flags = mail_get_flags(mail);
+ *flags_set = TRUE;
+ }
+
+ if ((*keywords == NULL) &&
+ (event_flags & PUSH_NOTIFICATION_MESSAGE_KEYWORDS) != 0) {
+ *keywords = p_strarray_dup(pool, mail_get_keywords(mail));
+ }
+
+ if ((*snippet == NULL) &&
+ (event_flags & PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET) != 0 &&
+ (mail_get_special(mail, MAIL_FETCH_BODY_SNIPPET, &value) >= 0)) {
+ /* [0] contains the snippet algorithm, skip over it */
+ i_assert(value[0] != '\0');
+ *snippet = p_strdup(pool, value + 1);
+ }
+}
diff --git a/src/plugins/push-notification/push-notification-event-message-common.h b/src/plugins/push-notification/push-notification-event-message-common.h
new file mode 100644
index 0000000..48c37de
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-message-common.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGE_COMMON_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGE_COMMON_H
+
+enum push_notification_event_message_flags {
+ /* Header: From */
+ PUSH_NOTIFICATION_MESSAGE_HDR_FROM = 0x01,
+ /* Header: To */
+ PUSH_NOTIFICATION_MESSAGE_HDR_TO = 0x02,
+ /* Header: Subject */
+ PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT = 0x04,
+ /* Header: Date */
+ PUSH_NOTIFICATION_MESSAGE_HDR_DATE = 0x08,
+ /* Body: Snippet */
+ PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET = 0x10,
+ /* Meta: Flags */
+ PUSH_NOTIFICATION_MESSAGE_FLAGS = 0x20,
+ /* Meta: Keywords */
+ PUSH_NOTIFICATION_MESSAGE_KEYWORDS = 0x40,
+ /* Header: Message-ID */
+ PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID = 0x80,
+};
+
+struct push_notification_message_ext {
+ const char *from_address, *from_display_name_utf8;
+ const char *to_address, *to_display_name_utf8;
+ const char *subject_utf8;
+};
+
+void push_notification_message_fill(
+ struct mail *mail, pool_t pool,
+ enum push_notification_event_message_flags event_flags,
+ const char **from, const char **to, const char **subject, time_t *date,
+ int *date_tz, const char **message_id, enum mail_flags *flags,
+ bool *flags_set, const char *const **keywords, const char **snippet,
+ struct push_notification_message_ext *ext);
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-messageappend.c b/src/plugins/push-notification/push-notification-event-messageappend.c
new file mode 100644
index 0000000..b6e0318
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messageappend.c
@@ -0,0 +1,99 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "istream.h"
+#include "iso8601-date.h"
+#include "mail-storage.h"
+
+#include <time.h>
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-message-common.h"
+#include "push-notification-event-messageappend.h"
+#include "push-notification-txn-msg.h"
+
+#define EVENT_NAME "MessageAppend"
+
+static struct push_notification_event_messageappend_config default_config;
+
+static void *push_notification_event_messageappend_default_config(void)
+{
+ i_zero(&default_config);
+
+ return &default_config;
+}
+
+static void
+push_notification_event_messageappend_debug_msg(
+ struct push_notification_txn_event *event)
+{
+ struct push_notification_event_messageappend_data *data = event->data;
+ struct tm *tm;
+
+ if (data->date != -1) {
+ tm = gmtime(&data->date);
+ i_debug("%s: Date [%s]", EVENT_NAME,
+ iso8601_date_create_tm(tm, data->date_tz));
+ }
+
+ if (data->from != NULL)
+ i_debug("%s: From [%s]", EVENT_NAME, data->from);
+ if (data->snippet != NULL)
+ i_debug("%s: Snippet [%s]", EVENT_NAME, data->snippet);
+ if (data->subject != NULL)
+ i_debug("%s: Subject [%s]", EVENT_NAME, data->subject);
+ if (data->to != NULL)
+ i_debug("%s: To [%s]", EVENT_NAME, data->to);
+}
+
+static void
+push_notification_event_messageappend_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg, struct mail *mail)
+{
+ struct push_notification_event_messageappend_config *config =
+ (struct push_notification_event_messageappend_config *)
+ ec->config;
+ struct push_notification_event_messageappend_data *data;
+
+ if (config->flags == 0)
+ return;
+
+ data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+ if (data == NULL) {
+ data = p_new(ptxn->pool,
+ struct push_notification_event_messageappend_data, 1);
+ data->date = -1;
+ push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+ }
+
+ push_notification_message_fill(mail, ptxn->pool, config->flags,
+ &data->from, &data->to, &data->subject,
+ &data->date, &data->date_tz,
+ &data->message_id,
+ &data->flags, &data->flags_set,
+ &data->keywords,
+ &data->snippet, &data->ext);
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messageappend;
+
+struct push_notification_event push_notification_event_messageappend = {
+ .name = EVENT_NAME,
+ .init = {
+ .default_config =
+ push_notification_event_messageappend_default_config,
+ },
+ .msg = {
+ .debug_msg = push_notification_event_messageappend_debug_msg,
+ },
+ .msg_triggers = {
+ .append = push_notification_event_messageappend_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-messageappend.h b/src/plugins/push-notification/push-notification-event-messageappend.h
new file mode 100644
index 0000000..511fc6b
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messageappend.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEAPPEND_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGEAPPEND_H
+
+struct push_notification_event_messageappend_config {
+ enum push_notification_event_message_flags flags;
+};
+
+struct push_notification_event_messageappend_data {
+ const char *from;
+ const char *to;
+ const char *subject;
+ const char *snippet;
+ /* PUSH_NOTIFICATION_MESSAGE_HDR_DATE */
+ time_t date;
+ int date_tz;
+ /* PUSH_NOTIFICATION_MESSAGE_FLAGS */
+ bool flags_set;
+ enum mail_flags flags;
+ /* PUSH_NOTIFICATION_MESSAGE_KEYWORDS */
+ const char *const *keywords;
+ /* PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID */
+ const char *message_id;
+
+ struct push_notification_message_ext ext;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-messageexpunge.c b/src/plugins/push-notification/push-notification-event-messageexpunge.c
new file mode 100644
index 0000000..cfa301e
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messageexpunge.c
@@ -0,0 +1,53 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-event-messageexpunge.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-msg.h"
+
+#define EVENT_NAME "MessageExpunge"
+
+static void
+push_notification_event_messageexpunge_debug_msg(
+ struct push_notification_txn_event *event)
+{
+ struct push_notification_event_messageexpunge_data *data = event->data;
+
+ if (data != NULL)
+ i_debug("%s: Message was expunged", EVENT_NAME);
+}
+
+static void
+push_notification_event_messageexpunge_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg)
+{
+ struct push_notification_event_messageexpunge_data *data;
+
+ data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+ if (data == NULL) {
+ data = p_new(ptxn->pool,
+ struct push_notification_event_messageexpunge_data, 1);
+ data->expunge = TRUE;
+ push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+ }
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messageexpunge;
+
+struct push_notification_event push_notification_event_messageexpunge = {
+ .name = EVENT_NAME,
+ .msg = {
+ .debug_msg = push_notification_event_messageexpunge_debug_msg,
+ },
+ .msg_triggers = {
+ .expunge = push_notification_event_messageexpunge_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-messageexpunge.h b/src/plugins/push-notification/push-notification-event-messageexpunge.h
new file mode 100644
index 0000000..bc20f73
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messageexpunge.h
@@ -0,0 +1,12 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEEXPUNGE_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGEEXPUNGE_H
+
+struct push_notification_event_messageexpunge_data {
+ /* Can only be TRUE. */
+ bool expunge;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-messagenew.c b/src/plugins/push-notification/push-notification-event-messagenew.c
new file mode 100644
index 0000000..930c0ee
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messagenew.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "iso8601-date.h"
+#include "istream.h"
+#include "mail-storage.h"
+
+#include <time.h>
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-message-common.h"
+#include "push-notification-event-messagenew.h"
+#include "push-notification-txn-msg.h"
+
+#define EVENT_NAME "MessageNew"
+
+static struct push_notification_event_messagenew_config default_config;
+
+static void *push_notification_event_messagenew_default_config(void)
+{
+ i_zero(&default_config);
+
+ return &default_config;
+}
+
+static void
+push_notification_event_messagenew_debug_msg(
+ struct push_notification_txn_event *event)
+{
+ struct push_notification_event_messagenew_data *data = event->data;
+ struct tm *tm;
+
+ if (data->date != -1) {
+ tm = gmtime(&data->date);
+ i_debug("%s: Date [%s]", EVENT_NAME,
+ iso8601_date_create_tm(tm, data->date_tz));
+ }
+
+ if (data->from != NULL)
+ i_debug("%s: From [%s]", EVENT_NAME, data->from);
+ if (data->snippet != NULL)
+ i_debug("%s: Snippet [%s]", EVENT_NAME, data->snippet);
+ if (data->subject != NULL)
+ i_debug("%s: Subject [%s]", EVENT_NAME, data->subject);
+ if (data->to != NULL)
+ i_debug("%s: To [%s]", EVENT_NAME, data->to);
+}
+
+static void
+push_notification_event_messagenew_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg, struct mail *mail)
+{
+ struct push_notification_event_messagenew_config *config =
+ (struct push_notification_event_messagenew_config *)ec->config;
+ struct push_notification_event_messagenew_data *data;
+
+ if (config->flags == 0)
+ return;
+
+ data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+ if (data == NULL) {
+ data = p_new(ptxn->pool,
+ struct push_notification_event_messagenew_data, 1);
+ data->date = -1;
+
+ push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+ }
+
+ push_notification_message_fill(mail, ptxn->pool, config->flags,
+ &data->from, &data->to, &data->subject,
+ &data->date, &data->date_tz,
+ &data->message_id,
+ &data->flags, &data->flags_set,
+ &data->keywords,
+ &data->snippet, &data->ext);
+}
+
+/* Event definition */
+
+struct push_notification_event push_notification_event_messagenew = {
+ .name = EVENT_NAME,
+ .init = {
+ .default_config =
+ push_notification_event_messagenew_default_config,
+ },
+ .msg = {
+ .debug_msg = push_notification_event_messagenew_debug_msg,
+ },
+ .msg_triggers = {
+ .save = push_notification_event_messagenew_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-messagenew.h b/src/plugins/push-notification/push-notification-event-messagenew.h
new file mode 100644
index 0000000..a939668
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messagenew.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGENEW_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGENEW_H
+
+#include "push-notification-event-message-common.h"
+
+struct push_notification_event_messagenew_config {
+ enum push_notification_event_message_flags flags;
+};
+
+struct push_notification_event_messagenew_data {
+ /* PUSH_NOTIFICATION_MESSAGE_HDR_FROM */
+ const char *from;
+ /* PUSH_NOTIFICATION_MESSAGE_HDR_TO */
+ const char *to;
+ /* PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT */
+ const char *subject;
+ /* PUSH_NOTIFICATION_MESSAGE_HDR_DATE */
+ time_t date;
+ int date_tz;
+ /* PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET */
+ const char *snippet;
+ /* PUSH_NOTIFICATION_MESSAGE_FLAGS */
+ bool flags_set;
+ enum mail_flags flags;
+ /* PUSH_NOTIFICATION_MESSAGE_KEYWORDS */
+ const char *const *keywords;
+ /* PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID */
+ const char *message_id;
+
+ struct push_notification_message_ext ext;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-event-messageread.c b/src/plugins/push-notification/push-notification-event-messageread.c
new file mode 100644
index 0000000..8f0d709
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messageread.c
@@ -0,0 +1,58 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-messageread.h"
+#include "push-notification-txn-msg.h"
+
+#define EVENT_NAME "MessageRead"
+
+static void
+push_notification_event_messageread_debug_msg(
+ struct push_notification_txn_event *event ATTR_UNUSED)
+{
+ i_debug("%s: Message was flagged as seen", EVENT_NAME);
+}
+
+static void
+push_notification_event_messageread_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg, struct mail *mail,
+ enum mail_flags old_flags)
+{
+ struct push_notification_event_messageread_data *data;
+ enum mail_flags flags;
+
+ /* If data struct exists, that means the read flag was changed. */
+ data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+ if ((data == NULL) && (old_flags & MAIL_SEEN) == 0) {
+ flags = mail_get_flags(mail);
+ if ((flags & MAIL_SEEN) != 0) {
+ data = p_new(
+ ptxn->pool,
+ struct push_notification_event_messageread_data, 1);
+ data->read = TRUE;
+ push_notification_txn_msg_set_eventdata(
+ ptxn, msg, ec, data);
+ }
+ }
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messageread;
+
+struct push_notification_event push_notification_event_messageread = {
+ .name = EVENT_NAME,
+ .msg = {
+ .debug_msg = push_notification_event_messageread_debug_msg,
+ },
+ .msg_triggers = {
+ .flagchange = push_notification_event_messageread_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-messageread.h b/src/plugins/push-notification/push-notification-event-messageread.h
new file mode 100644
index 0000000..6eb5c18
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messageread.h
@@ -0,0 +1,11 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEREAD_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGEREAD_H
+
+struct push_notification_event_messageread_data {
+ /* Can only be TRUE. */
+ bool read;
+};
+
+#endif
diff --git a/src/plugins/push-notification/push-notification-event-messagetrash.c b/src/plugins/push-notification/push-notification-event-messagetrash.c
new file mode 100644
index 0000000..2284b6f
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messagetrash.c
@@ -0,0 +1,58 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-messagetrash.h"
+#include "push-notification-txn-msg.h"
+
+#define EVENT_NAME "MessageTrash"
+
+static void
+push_notification_event_messagetrash_debug_msg(
+ struct push_notification_txn_event *event ATTR_UNUSED)
+{
+ i_debug("%s: Message was marked as deleted", EVENT_NAME);
+}
+
+static void
+push_notification_event_messagetrash_event(
+ struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg, struct mail *mail,
+ enum mail_flags old_flags)
+{
+ struct push_notification_event_messagetrash_data *data;
+ enum mail_flags flags;
+
+ /* If data struct exists, that means the deleted flag was changed. */
+ data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+ if ((data == NULL) && (old_flags & MAIL_DELETED) == 0) {
+ flags = mail_get_flags(mail);
+ if ((flags & MAIL_DELETED) != 0) {
+ data = p_new(
+ ptxn->pool,
+ struct push_notification_event_messagetrash_data, 1);
+ data->trash = TRUE;
+ push_notification_txn_msg_set_eventdata(
+ ptxn, msg, ec, data);
+ }
+ }
+}
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messagetrash;
+
+struct push_notification_event push_notification_event_messagetrash = {
+ .name = EVENT_NAME,
+ .msg = {
+ .debug_msg = push_notification_event_messagetrash_debug_msg,
+ },
+ .msg_triggers = {
+ .flagchange = push_notification_event_messagetrash_event,
+ },
+};
diff --git a/src/plugins/push-notification/push-notification-event-messagetrash.h b/src/plugins/push-notification/push-notification-event-messagetrash.h
new file mode 100644
index 0000000..712732f
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-event-messagetrash.h
@@ -0,0 +1,12 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGETRASH_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGETRASH_H
+
+struct push_notification_event_messagetrash_data {
+ /* Can only be TRUE. */
+ bool trash;
+};
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-events-rfc5423.c b/src/plugins/push-notification/push-notification-events-rfc5423.c
new file mode 100644
index 0000000..10dd0c7
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-events-rfc5423.c
@@ -0,0 +1,59 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#include "push-notification-events.h"
+#include "push-notification-events-rfc5423.h"
+
+/* These are the RFC 5423 Mail Store Events currently handled within the core
+ push-notification code.
+
+ @todo: These events are not currently handled:
+ - Login
+ - Logout
+ - QuotaExceed
+ - Quota Within
+ */
+extern struct push_notification_event push_notification_event_flagsclear;
+extern struct push_notification_event push_notification_event_flagsset;
+extern struct push_notification_event push_notification_event_mailboxcreate;
+extern struct push_notification_event push_notification_event_mailboxdelete;
+extern struct push_notification_event push_notification_event_mailboxrename;
+extern struct push_notification_event push_notification_event_mailboxsubscribe;
+extern struct push_notification_event push_notification_event_mailboxunsubscribe;
+extern struct push_notification_event push_notification_event_messageappend;
+extern struct push_notification_event push_notification_event_messageexpunge;
+extern struct push_notification_event push_notification_event_messagenew;
+extern struct push_notification_event push_notification_event_messageread;
+extern struct push_notification_event push_notification_event_messagetrash;
+
+static struct push_notification_event *rfc5423_events[] = {
+ &push_notification_event_flagsclear,
+ &push_notification_event_flagsset,
+ &push_notification_event_mailboxcreate,
+ &push_notification_event_mailboxdelete,
+ &push_notification_event_mailboxrename,
+ &push_notification_event_mailboxsubscribe,
+ &push_notification_event_mailboxunsubscribe,
+ &push_notification_event_messageappend,
+ &push_notification_event_messageexpunge,
+ &push_notification_event_messagenew,
+ &push_notification_event_messageread,
+ &push_notification_event_messagetrash
+};
+
+void push_notification_event_register_rfc5423_events(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(rfc5423_events); i++)
+ push_notification_event_register(rfc5423_events[i]);
+}
+
+void push_notification_event_unregister_rfc5423_events(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(rfc5423_events); i++)
+ push_notification_event_unregister(rfc5423_events[i]);
+}
diff --git a/src/plugins/push-notification/push-notification-events-rfc5423.h b/src/plugins/push-notification/push-notification-events-rfc5423.h
new file mode 100644
index 0000000..71f7009
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-events-rfc5423.h
@@ -0,0 +1,10 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENTS_RFC5423_H
+#define PUSH_NOTIFICATION_EVENTS_RFC5423_H
+
+void push_notification_event_register_rfc5423_events(void);
+void push_notification_event_unregister_rfc5423_events(void);
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-events.c b/src/plugins/push-notification/push-notification-events.c
new file mode 100644
index 0000000..a71e8a6
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-events.c
@@ -0,0 +1,100 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+
+ARRAY_TYPE(push_notification_event) push_notification_events;
+
+ARRAY_TYPE(push_notification_event) *push_notification_get_events(void)
+{
+ return &push_notification_events;
+}
+
+static bool
+push_notification_event_find(const char *name, unsigned int *idx_r)
+{
+ unsigned int count, i;
+ const struct push_notification_event *const *events;
+
+ events = array_get(&push_notification_events, &count);
+ for (i = 0; i < count; i++) {
+ if (strcasecmp(events[i]->name, name) == 0) {
+ *idx_r = i;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static const struct push_notification_event *
+push_notification_event_find_class(const char *driver)
+{
+ unsigned int idx;
+
+ if (!push_notification_event_find(driver, &idx))
+ return NULL;
+
+ return array_idx_elem(&push_notification_events, idx);
+}
+
+void push_notification_event_init(struct push_notification_driver_txn *dtxn,
+ const char *event_name, void *config)
+{
+ const struct push_notification_event *event;
+ struct push_notification_event_config *ec;
+
+ if (!array_is_created(&dtxn->ptxn->events))
+ p_array_init(&dtxn->ptxn->events, dtxn->ptxn->pool, 4);
+
+ event = push_notification_event_find_class(event_name);
+ if (event != NULL) {
+ if ((config == NULL) && (event->init.default_config != NULL)) {
+ config = event->init.default_config();
+ }
+
+ ec = p_new(dtxn->ptxn->pool,
+ struct push_notification_event_config, 1);
+ ec->config = config;
+ ec->event = event;
+
+ array_push_back(&dtxn->ptxn->events, &ec);
+ }
+}
+
+void push_notification_event_register(
+ const struct push_notification_event *event)
+{
+ unsigned int idx;
+
+ if (!array_is_created(&push_notification_events))
+ i_array_init(&push_notification_events, 16);
+
+ if (push_notification_event_find(event->name, &idx)) {
+ i_panic("push_notification_event_register(%s): duplicate event",
+ event->name);
+ }
+
+ array_push_back(&push_notification_events, &event);
+}
+
+void push_notification_event_unregister(
+ const struct push_notification_event *event)
+{
+ unsigned int idx;
+
+ if (!push_notification_event_find(event->name, &idx)) {
+ i_panic("push_notification_event_register(%s): unknown event",
+ event->name);
+ }
+
+ if (array_is_created(&push_notification_events)) {
+ array_delete(&push_notification_events, idx, 1);
+
+ if (array_is_empty(&push_notification_events))
+ array_free(&push_notification_events);
+ }
+}
diff --git a/src/plugins/push-notification/push-notification-events.h b/src/plugins/push-notification/push-notification-events.h
new file mode 100644
index 0000000..f20075f
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-events.h
@@ -0,0 +1,124 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENTS_H
+#define PUSH_NOTIFICATION_EVENTS_H
+
+#include "mail-types.h"
+
+struct mail;
+struct mailbox;
+struct push_notification_event_config;
+struct push_notification_driver_txn;
+struct push_notification_txn;
+struct push_notification_txn_event;
+struct push_notification_txn_mbox;
+struct push_notification_txn_msg;
+
+struct push_notification_event_vfuncs_init {
+ /* Return the default config for an event (or NULL if config is
+ required). */
+ void *(*default_config)(void);
+};
+
+struct push_notification_event_vfuncs_mbox {
+ /* Output debug information about a message event. */
+ void (*debug_mbox)(struct push_notification_txn_event *event);
+ /* Called when message data is about to be free'd. */
+ void (*free_mbox)(struct push_notification_txn_event *event);
+};
+
+struct push_notification_event_vfuncs_mbox_triggers {
+ /* Mailbox event: create mailbox. */
+ void (*create)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox);
+ /* Mailbox event: delete mailbox. */
+ void (*delete)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox);
+ /* Mailbox event: rename mailbox. */
+ void (*rename)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox,
+ struct mailbox *old);
+ /* Mailbox event: subscribe mailbox. */
+ void (*subscribe)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox);
+ /* Mailbox event: unsubscribe mailbox. */
+ void (*unsubscribe)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_mbox *mbox);
+};
+
+struct push_notification_event_vfuncs_msg {
+ /* Output debug information about a message event. */
+ void (*debug_msg)(struct push_notification_txn_event *event);
+ /* Called when message data is about to be free'd. */
+ void (*free_msg)(struct push_notification_txn_event *event);
+};
+
+struct push_notification_event_vfuncs_msg_triggers {
+ /* Message event: save message (from MTA). */
+ void (*save)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg,
+ struct mail *mail);
+ /* Message event: append message (from MUA). */
+ void (*append)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg,
+ struct mail *mail);
+ /* Message event: expunge message. */
+ void (*expunge)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg);
+ /* Message event: flag change. */
+ void (*flagchange)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg,
+ struct mail *mail, enum mail_flags old_flags);
+ /* Message event: keyword change. */
+ void (*keywordchange)(struct push_notification_txn *ptxn,
+ struct push_notification_event_config *ec,
+ struct push_notification_txn_msg *msg,
+ struct mail *mail,
+ const char *const *old_keywords);
+};
+
+struct push_notification_event_config {
+ const struct push_notification_event *event;
+ void *config;
+};
+
+struct push_notification_event {
+ const char *name;
+ struct push_notification_event_vfuncs_init init;
+ struct push_notification_event_vfuncs_mbox mbox;
+ struct push_notification_event_vfuncs_mbox_triggers mbox_triggers;
+ struct push_notification_event_vfuncs_msg msg;
+ struct push_notification_event_vfuncs_msg_triggers msg_triggers;
+};
+
+struct push_notification_txn_event {
+ struct push_notification_event_config *event;
+ void *data;
+};
+
+ARRAY_DEFINE_TYPE(push_notification_event,
+ const struct push_notification_event *);
+extern ARRAY_TYPE(push_notification_event) push_notification_events;
+
+ARRAY_TYPE(push_notification_event) *push_notification_get_events(void);
+
+
+void push_notification_event_init(struct push_notification_driver_txn *dtxn,
+ const char *event_name, void *config);
+
+void push_notification_event_register(
+ const struct push_notification_event *event);
+void push_notification_event_unregister(
+ const struct push_notification_event *event);
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-plugin.c b/src/plugins/push-notification/push-notification-plugin.c
new file mode 100644
index 0000000..22c2fb0
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-plugin.c
@@ -0,0 +1,390 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-storage-private.h"
+#include "notify-plugin.h"
+#include "str.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-events-rfc5423.h"
+#include "push-notification-plugin.h"
+#include "push-notification-triggers.h"
+#include "push-notification-txn-mbox.h"
+#include "push-notification-txn-msg.h"
+
+#define PUSH_NOTIFICATION_CONFIG "push_notification_driver"
+#define PUSH_NOTIFICATION_CONFIG_OLD "push_notification_backend"
+#define PUSH_NOTIFICATION_EVENT_FINISHED "push_notification_finished"
+
+#define PUSH_NOTIFICATION_USER_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, push_notification_user_module)
+static MODULE_CONTEXT_DEFINE_INIT(push_notification_user_module,
+ &mail_user_module_register);
+static struct ioloop *main_ioloop;
+
+struct event_category event_category_push_notification = {
+ .name = "push-notification",
+};
+
+struct event_category *push_notification_get_event_category(void)
+{
+ return &event_category_push_notification;
+}
+
+struct push_notification_event *push_notification_get_event_messagenew(void)
+{
+ return &push_notification_event_messagenew;
+}
+
+static void
+push_notification_transaction_init(struct push_notification_txn *ptxn)
+{
+ struct push_notification_driver_txn *dtxn;
+ struct push_notification_driver_user *duser;
+ struct mail_storage *storage;
+
+ if (ptxn->initialized)
+ return;
+
+ ptxn->initialized = TRUE;
+
+ storage = mailbox_get_storage(ptxn->mbox);
+ if (storage->user->autocreated &&
+ (strcmp(storage->name, "raw") == 0)) {
+ /* No notifications for autocreated raw users */
+ return;
+ }
+
+ array_foreach_elem(&ptxn->puser->driverlist->drivers, duser) {
+ dtxn = p_new(ptxn->pool, struct push_notification_driver_txn, 1);
+ dtxn->duser = duser;
+ dtxn->ptxn = ptxn;
+
+ if ((dtxn->duser->driver->v.begin_txn == NULL) ||
+ dtxn->duser->driver->v.begin_txn(dtxn)) {
+ array_push_back(&ptxn->drivers, &dtxn);
+ }
+ }
+}
+
+static struct push_notification_txn *
+push_notification_transaction_create(struct mailbox *box,
+ struct mailbox_transaction_context *t)
+{
+ pool_t pool;
+ struct push_notification_txn *ptxn;
+ struct mail_storage *storage;
+
+ pool = pool_alloconly_create("push notification transaction", 2048);
+
+ ptxn = p_new(pool, struct push_notification_txn, 1);
+ ptxn->mbox = box;
+ storage = mailbox_get_storage(box);
+ ptxn->muser = mail_storage_get_user(storage);
+ ptxn->pool = pool;
+ ptxn->puser = PUSH_NOTIFICATION_USER_CONTEXT(ptxn->muser);
+ ptxn->t = t;
+ ptxn->trigger = PUSH_NOTIFICATION_EVENT_TRIGGER_NONE;
+ ptxn->event = event_create(ptxn->muser->event);
+ event_add_category(ptxn->event, &event_category_push_notification);
+ event_set_append_log_prefix(ptxn->event, "push-notification: ");
+ p_array_init(&ptxn->drivers, pool, 4);
+
+ return ptxn;
+}
+
+static void
+push_notification_transaction_end(struct push_notification_txn *ptxn,
+ bool success)
+{
+ struct push_notification_driver_txn *dtxn;
+
+ if (ptxn->initialized) {
+ array_foreach_elem(&ptxn->drivers, dtxn) {
+ if (dtxn->duser->driver->v.end_txn != NULL)
+ dtxn->duser->driver->v.end_txn(dtxn, success);
+ }
+ }
+
+ if (success && ptxn->trigger != 0) {
+ struct event_passthrough *e = event_create_passthrough(ptxn->event)->
+ set_name(PUSH_NOTIFICATION_EVENT_FINISHED);
+ /* Emit event */
+ e_debug(e->event(), "Push notification transaction completed");
+ }
+
+ event_unref(&ptxn->event);
+ pool_unref(&ptxn->pool);
+}
+
+static void
+push_notification_transaction_commit(
+ void *txn, struct mail_transaction_commit_changes *changes)
+{
+ struct push_notification_txn *ptxn = (struct push_notification_txn *)txn;
+ struct ioloop *prev_ioloop = current_ioloop;
+
+ /* Make sure we're not in just any random ioloop, which could get
+ destroyed soon. This way the push-notification drivers can do async
+ operations that finish in the main ioloop. */
+ io_loop_set_current(main_ioloop);
+ if (changes == NULL)
+ push_notification_txn_mbox_end(ptxn);
+ else
+ push_notification_txn_msg_end(ptxn, changes);
+
+ push_notification_transaction_end(ptxn, TRUE);
+ io_loop_set_current(prev_ioloop);
+}
+
+static void push_notification_mailbox_create(struct mailbox *box)
+{
+ struct push_notification_txn *ptxn;
+
+ ptxn = push_notification_transaction_create(box, NULL);
+ push_notification_transaction_init(ptxn);
+ push_notification_trigger_mbox_create(ptxn, box, NULL);
+ push_notification_transaction_commit(ptxn, NULL);
+}
+
+static void
+push_notification_mailbox_delete(void *txn ATTR_UNUSED, struct mailbox *box)
+{
+ struct push_notification_txn *ptxn;
+
+ ptxn = push_notification_transaction_create(box, NULL);
+ push_notification_transaction_init(ptxn);
+ push_notification_trigger_mbox_delete(ptxn, box, NULL);
+ push_notification_transaction_commit(ptxn, NULL);
+}
+
+static void
+push_notification_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ struct push_notification_txn *ptxn;
+
+ ptxn = push_notification_transaction_create(dest, NULL);
+ push_notification_transaction_init(ptxn);
+ push_notification_trigger_mbox_rename(ptxn, src, dest, NULL);
+ push_notification_transaction_commit(ptxn, NULL);
+}
+
+static void
+push_notification_mailbox_subscribe(struct mailbox *box, bool subscribed)
+{
+ struct push_notification_txn *ptxn;
+
+ ptxn = push_notification_transaction_create(box, NULL);
+ push_notification_transaction_init(ptxn);
+ push_notification_trigger_mbox_subscribe(ptxn, box, subscribed, NULL);
+ push_notification_transaction_commit(ptxn, NULL);
+}
+
+static void push_notification_mail_save(void *txn, struct mail *mail)
+{
+ struct push_notification_txn *ptxn = txn;
+
+ push_notification_transaction_init(ptxn);
+
+ /* POST_SESSION means MTA delivery. */
+ if ((mail->box->flags & MAILBOX_FLAG_POST_SESSION) != 0)
+ push_notification_trigger_msg_save_new(ptxn, mail, NULL);
+ else
+ push_notification_trigger_msg_save_append(ptxn, mail, NULL);
+}
+
+static void
+push_notification_mail_copy(void *txn, struct mail *src ATTR_UNUSED,
+ struct mail *dest)
+{
+ push_notification_mail_save(txn, dest);
+}
+
+static void push_notification_mail_expunge(void *txn, struct mail *mail)
+{
+ struct push_notification_txn *ptxn = txn;
+
+ push_notification_transaction_init(ptxn);
+ push_notification_trigger_msg_save_expunge(txn, mail, NULL);
+}
+
+static void
+push_notification_mail_update_flags(void *txn, struct mail *mail,
+ enum mail_flags old_flags)
+{
+ struct push_notification_txn *ptxn = txn;
+
+ push_notification_transaction_init(ptxn);
+ push_notification_trigger_msg_flag_change(txn, mail, NULL, old_flags);
+}
+
+static void
+push_notification_mail_update_keywords(void *txn, struct mail *mail,
+ const char *const *old_keywords)
+{
+ struct push_notification_txn *ptxn = txn;
+
+ push_notification_transaction_init(ptxn);
+ push_notification_trigger_msg_keyword_change(
+ txn, mail, NULL, old_keywords);
+}
+
+static void *
+push_notification_transaction_begin(struct mailbox_transaction_context *t)
+{
+ return push_notification_transaction_create(
+ mailbox_transaction_get_mailbox(t), t);
+}
+
+static void push_notification_transaction_rollback(void *txn)
+{
+ struct push_notification_txn *ptxn = txn;
+
+ push_notification_transaction_end(ptxn, FALSE);
+}
+
+static void
+push_notification_config_init(const char *config_name, struct mail_user *user,
+ struct push_notification_driver_list *dlist)
+{
+ struct push_notification_driver_user *duser;
+ const char *env;
+ unsigned int i;
+ string_t *root_name;
+
+ root_name = t_str_new(32);
+ str_append(root_name, config_name);
+
+ for (i = 2;; i++) {
+ env = mail_user_plugin_getenv(user, str_c(root_name));
+ if ((env == NULL) || (*env == '\0'))
+ break;
+
+ if (push_notification_driver_init(
+ user, env, user->pool, &duser) < 0)
+ break;
+
+ /* Add driver. */
+ array_push_back(&dlist->drivers, &duser);
+
+ str_truncate(root_name, strlen(config_name));
+ str_printfa(root_name, "%d", i);
+ }
+}
+
+static struct push_notification_driver_list *
+push_notification_driver_list_init(struct mail_user *user)
+{
+ struct push_notification_driver_list *dlist;
+
+ dlist = p_new(user->pool, struct push_notification_driver_list, 1);
+ p_array_init(&dlist->drivers, user->pool, 4);
+
+ push_notification_config_init(PUSH_NOTIFICATION_CONFIG, user, dlist);
+
+ if (array_is_empty(&dlist->drivers)) {
+ /* Support old configuration (it was available at time initial
+ OX driver was first released). */
+ push_notification_config_init(PUSH_NOTIFICATION_CONFIG_OLD,
+ user, dlist);
+ }
+ return dlist;
+}
+
+static void push_notification_user_deinit(struct mail_user *user)
+{
+ struct push_notification_user *puser =
+ PUSH_NOTIFICATION_USER_CONTEXT(user);
+ struct push_notification_driver_list *dlist = puser->driverlist;
+ struct push_notification_driver_user *duser;
+ struct ioloop *prev_ioloop = current_ioloop;
+
+ /* Make sure we're in the main ioloop, so if the deinit/cleanup moves
+ any I/Os or timeouts they won't get moved to some temporary ioloop.
+ */
+ io_loop_set_current(main_ioloop);
+
+ array_foreach_elem(&dlist->drivers, duser) {
+ if (duser->driver->v.deinit != NULL)
+ duser->driver->v.deinit(duser);
+ if (duser->driver->v.cleanup != NULL)
+ duser->driver->v.cleanup();
+ }
+ io_loop_set_current(prev_ioloop);
+
+ puser->module_ctx.super.deinit(user);
+}
+
+static void push_notification_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct push_notification_user *puser;
+
+ puser = p_new(user->pool, struct push_notification_user, 1);
+ puser->module_ctx.super = *v;
+ user->vlast = &puser->module_ctx.super;
+ v->deinit = push_notification_user_deinit;
+ puser->driverlist = push_notification_driver_list_init(user);
+
+ MODULE_CONTEXT_SET(user, push_notification_user_module, puser);
+}
+
+/* Plugin interface. */
+
+const char *push_notification_plugin_version = DOVECOT_ABI_VERSION;
+const char *push_notification_plugin_dependencies[] = { "notify", NULL };
+
+extern struct push_notification_driver push_notification_driver_dlog;
+extern struct push_notification_driver push_notification_driver_ox;
+
+static struct notify_context *push_notification_ctx;
+
+static const struct notify_vfuncs push_notification_vfuncs = {
+ /* Mailbox Events */
+ .mailbox_create = push_notification_mailbox_create,
+ .mailbox_delete_commit = push_notification_mailbox_delete,
+ .mailbox_rename = push_notification_mailbox_rename,
+ .mailbox_set_subscribed = push_notification_mailbox_subscribe,
+
+ /* Mail Events */
+ .mail_copy = push_notification_mail_copy,
+ .mail_save = push_notification_mail_save,
+ .mail_expunge = push_notification_mail_expunge,
+ .mail_update_flags = push_notification_mail_update_flags,
+ .mail_update_keywords = push_notification_mail_update_keywords,
+ .mail_transaction_begin = push_notification_transaction_begin,
+ .mail_transaction_commit = push_notification_transaction_commit,
+ .mail_transaction_rollback = push_notification_transaction_rollback,
+};
+
+static struct mail_storage_hooks push_notification_storage_hooks = {
+ .mail_user_created = push_notification_user_created,
+};
+
+void push_notification_plugin_init(struct module *module)
+{
+ push_notification_ctx = notify_register(&push_notification_vfuncs);
+ mail_storage_hooks_add(module, &push_notification_storage_hooks);
+
+ push_notification_driver_register(&push_notification_driver_dlog);
+ push_notification_driver_register(&push_notification_driver_ox);
+
+ push_notification_event_register_rfc5423_events();
+ main_ioloop = current_ioloop;
+ i_assert(main_ioloop != NULL);
+}
+
+void push_notification_plugin_deinit(void)
+{
+ push_notification_driver_unregister(&push_notification_driver_dlog);
+ push_notification_driver_unregister(&push_notification_driver_ox);
+
+ push_notification_event_unregister_rfc5423_events();
+ mail_storage_hooks_remove(&push_notification_storage_hooks);
+ notify_unregister(push_notification_ctx);
+}
diff --git a/src/plugins/push-notification/push-notification-plugin.h b/src/plugins/push-notification/push-notification-plugin.h
new file mode 100644
index 0000000..7db5397
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-plugin.h
@@ -0,0 +1,18 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_PLUGIN_H
+#define PUSH_NOTIFICATION_PLUGIN_H
+
+extern const char *push_notification_plugin_dependencies[];
+extern struct event_category event_category_push_notification;
+extern struct push_notification_event push_notification_event_messagenew;
+
+struct module;
+
+struct event_category *push_notification_get_event_category(void);
+struct push_notification_event *push_notification_get_event_messagenew(void);
+
+void push_notification_plugin_init(struct module *module);
+void push_notification_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/push-notification/push-notification-triggers.c b/src/plugins/push-notification/push-notification-triggers.c
new file mode 100644
index 0000000..3882982
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-triggers.c
@@ -0,0 +1,215 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-triggers.h"
+#include "push-notification-txn-mbox.h"
+#include "push-notification-txn-msg.h"
+
+static void
+push_notification_trigger_mbox_common(
+ struct push_notification_txn *txn, struct mailbox *box,
+ struct push_notification_txn_mbox **mbox,
+ enum push_notification_event_trigger trigger)
+{
+ if (*mbox == NULL) {
+ *mbox = push_notification_txn_mbox_create(txn, box);
+ }
+
+ txn->trigger |= trigger;
+ event_add_str(txn->event, "mailbox", mailbox_get_vname(box));
+}
+
+void push_notification_trigger_mbox_create(
+ struct push_notification_txn *txn, struct mailbox *box,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_mbox_common(
+ txn, box, &mbox, PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_CREATE);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (ec->event->mbox_triggers.create != NULL)
+ ec->event->mbox_triggers.create(txn, ec, mbox);
+ }
+ }
+}
+
+void push_notification_trigger_mbox_delete(
+ struct push_notification_txn *txn, struct mailbox *box,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_mbox_common(
+ txn, box, &mbox, PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_DELETE);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (ec->event->mbox_triggers.delete != NULL)
+ ec->event->mbox_triggers.delete(txn, ec, mbox);
+ }
+ }
+}
+
+void push_notification_trigger_mbox_rename(
+ struct push_notification_txn *txn,
+ struct mailbox *src, struct mailbox *dest,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_mbox_common(
+ txn, dest, &mbox, PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_RENAME);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (ec->event->mbox_triggers.rename != NULL) {
+ ec->event->mbox_triggers.rename(
+ txn, ec, mbox, src);
+ }
+ }
+ }
+}
+
+void push_notification_trigger_mbox_subscribe(
+ struct push_notification_txn *txn, struct mailbox *box, bool subscribed,
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_mbox_common(
+ txn, box, &mbox,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_SUBSCRIBE);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (subscribed == TRUE) {
+ if (ec->event->mbox_triggers.subscribe != NULL) {
+ ec->event->mbox_triggers.subscribe(
+ txn, ec, mbox);
+ }
+ } else {
+ if (ec->event->mbox_triggers.unsubscribe != NULL) {
+ ec->event->mbox_triggers.unsubscribe(
+ txn, ec, mbox);
+ }
+ }
+ }
+ }
+}
+
+static void
+push_notification_trigger_msg_common(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg **msg,
+ enum push_notification_event_trigger trigger)
+{
+ if (*msg == NULL)
+ *msg = push_notification_txn_msg_create(txn, mail);
+
+ txn->trigger |= trigger;
+}
+
+void push_notification_trigger_msg_save_new(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_msg_common(
+ txn, mail, &msg, PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_NEW);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (ec->event->msg_triggers.save != NULL) {
+ ec->event->msg_triggers.save(
+ txn, ec, msg, mail);
+ }
+ }
+ }
+}
+
+void push_notification_trigger_msg_save_append(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_msg_common(
+ txn, mail, &msg,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_APPEND);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (ec->event->msg_triggers.append != NULL) {
+ ec->event->msg_triggers.append(
+ txn, ec, msg, mail);
+ }
+ }
+ }
+}
+
+void push_notification_trigger_msg_save_expunge(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_msg_common(
+ txn, mail, &msg, PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_EXPUNGE);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (ec->event->msg_triggers.expunge != NULL)
+ ec->event->msg_triggers.expunge(txn, ec, msg);
+ }
+ }
+}
+
+void push_notification_trigger_msg_flag_change(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg, enum mail_flags old_flags)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_msg_common(
+ txn, mail, &msg,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_FLAGCHANGE);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (ec->event->msg_triggers.flagchange != NULL) {
+ ec->event->msg_triggers.flagchange(
+ txn, ec, msg, mail, old_flags);
+ }
+ }
+ }
+}
+
+void push_notification_trigger_msg_keyword_change(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg, const char *const *old_keywords)
+{
+ struct push_notification_event_config *ec;
+
+ push_notification_trigger_msg_common(
+ txn, mail, &msg,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_KEYWORDCHANGE);
+
+ if (array_is_created(&txn->events)) {
+ array_foreach_elem(&txn->events, ec) {
+ if (ec->event->msg_triggers.keywordchange != NULL) {
+ ec->event->msg_triggers.keywordchange(
+ txn, ec, msg, mail, old_keywords);
+ }
+ }
+ }
+}
diff --git a/src/plugins/push-notification/push-notification-triggers.h b/src/plugins/push-notification/push-notification-triggers.h
new file mode 100644
index 0000000..b29bc47
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-triggers.h
@@ -0,0 +1,64 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_TRIGGERS_H
+#define PUSH_NOTIFICATION_TRIGGERS_H
+
+#include "mail-types.h"
+
+struct mail;
+struct mailbox;
+struct push_notification_txn;
+struct push_notification_txn_mbox;
+struct push_notification_txn_msg;
+
+enum push_notification_event_trigger {
+ PUSH_NOTIFICATION_EVENT_TRIGGER_NONE,
+
+ /* Mailbox actions */
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_CREATE = 0x001,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_DELETE = 0x002,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_RENAME = 0x004,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_SUBSCRIBE = 0x008,
+
+ /* Message actions */
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_NEW = 0x010,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_APPEND = 0x020,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_EXPUNGE = 0x040,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_FLAGCHANGE = 0x080,
+ PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_KEYWORDCHANGE = 0x100,
+};
+
+/* Mailbox actions. */
+void push_notification_trigger_mbox_create(
+ struct push_notification_txn *txn, struct mailbox *box,
+ struct push_notification_txn_mbox *mbox);
+void push_notification_trigger_mbox_delete(
+ struct push_notification_txn *txn, struct mailbox *box,
+ struct push_notification_txn_mbox *mbox);
+void push_notification_trigger_mbox_rename(
+ struct push_notification_txn *txn,
+ struct mailbox *src, struct mailbox *dest,
+ struct push_notification_txn_mbox *mbox);
+void push_notification_trigger_mbox_subscribe(
+ struct push_notification_txn *txn, struct mailbox *box, bool subscribed,
+ struct push_notification_txn_mbox *mbox);
+
+/* Message actions. */
+void push_notification_trigger_msg_save_new(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg);
+void push_notification_trigger_msg_save_append(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg);
+void push_notification_trigger_msg_save_expunge(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg);
+void push_notification_trigger_msg_flag_change(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg, enum mail_flags old_flags);
+void push_notification_trigger_msg_keyword_change(
+ struct push_notification_txn *txn, struct mail *mail,
+ struct push_notification_txn_msg *msg, const char *const *old_keywords);
+
+#endif
+
diff --git a/src/plugins/push-notification/push-notification-txn-mbox.c b/src/plugins/push-notification/push-notification-txn-mbox.c
new file mode 100644
index 0000000..4401819
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-txn-mbox.c
@@ -0,0 +1,90 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "mail-storage-private.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-mbox.h"
+
+struct push_notification_txn_mbox *
+push_notification_txn_mbox_create(struct push_notification_txn *txn,
+ struct mailbox *box)
+{
+ if (txn->mbox_txn == NULL) {
+ txn->mbox_txn = p_new(txn->pool,
+ struct push_notification_txn_mbox, 1);
+ txn->mbox_txn->mailbox = mailbox_get_vname(box);
+ }
+
+ return txn->mbox_txn;
+}
+
+void push_notification_txn_mbox_end(struct push_notification_txn *ptxn)
+{
+ struct push_notification_driver_txn **dtxn;
+
+ if (ptxn->mbox_txn != NULL) {
+ array_foreach_modifiable(&ptxn->drivers, dtxn) {
+ if ((*dtxn)->duser->driver->v.process_mbox != NULL) {
+ (*dtxn)->duser->driver->v.process_mbox(
+ *dtxn, ptxn->mbox_txn);
+ }
+ }
+
+ push_notification_txn_mbox_deinit_eventdata(ptxn->mbox_txn);
+ }
+}
+
+void *
+push_notification_txn_mbox_get_eventdata(
+ struct push_notification_txn_mbox *mbox, const char *event_name)
+{
+ struct push_notification_txn_event **mevent;
+
+ if (array_is_created(&mbox->eventdata)) {
+ array_foreach_modifiable(&mbox->eventdata, mevent) {
+ if (strcmp((*mevent)->event->event->name,
+ event_name) == 0) {
+ return (*mevent)->data;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void push_notification_txn_mbox_set_eventdata(
+ struct push_notification_txn *txn,
+ struct push_notification_txn_mbox *mbox,
+ struct push_notification_event_config *event, void *data)
+{
+ struct push_notification_txn_event *mevent;
+
+ if (!array_is_created(&mbox->eventdata)) {
+ p_array_init(&mbox->eventdata, txn->pool, 4);
+ }
+
+ mevent = p_new(txn->pool, struct push_notification_txn_event, 1);
+ mevent->data = data;
+ mevent->event = event;
+
+ array_push_back(&mbox->eventdata, &mevent);
+}
+
+void push_notification_txn_mbox_deinit_eventdata(
+ struct push_notification_txn_mbox *mbox)
+{
+ struct push_notification_txn_event **mevent;
+
+ if (array_is_created(&mbox->eventdata)) {
+ array_foreach_modifiable(&mbox->eventdata, mevent) {
+ if (((*mevent)->data != NULL) &&
+ ((*mevent)->event->event->mbox.free_mbox != NULL)) {
+ (*mevent)->event->event->mbox.free_mbox(
+ *mevent);
+ }
+ }
+ }
+}
diff --git a/src/plugins/push-notification/push-notification-txn-mbox.h b/src/plugins/push-notification/push-notification-txn-mbox.h
new file mode 100644
index 0000000..c7a7e55
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-txn-mbox.h
@@ -0,0 +1,29 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_TXN_MBOX_H
+#define PUSH_NOTIFICATION_TXN_MBOX_H
+
+struct push_notification_txn_event;
+
+struct push_notification_txn_mbox {
+ const char *mailbox;
+
+ ARRAY(struct push_notification_txn_event *) eventdata;
+};
+
+struct push_notification_txn_mbox *
+push_notification_txn_mbox_create(struct push_notification_txn *txn,
+ struct mailbox *box);
+void push_notification_txn_mbox_end(struct push_notification_txn *ptxn);
+
+void *
+push_notification_txn_mbox_get_eventdata(
+ struct push_notification_txn_mbox *mbox, const char *event_name);
+void push_notification_txn_mbox_set_eventdata(
+ struct push_notification_txn *txn,
+ struct push_notification_txn_mbox *mbox,
+ struct push_notification_event_config *event, void *data);
+void push_notification_txn_mbox_deinit_eventdata(
+ struct push_notification_txn_mbox *mbox);
+
+#endif
diff --git a/src/plugins/push-notification/push-notification-txn-msg.c b/src/plugins/push-notification/push-notification-txn-msg.c
new file mode 100644
index 0000000..ff37bff
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-txn-msg.c
@@ -0,0 +1,139 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "mail-storage-private.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-msg.h"
+
+
+struct push_notification_txn_msg *
+push_notification_txn_msg_create(struct push_notification_txn *txn,
+ struct mail *mail)
+{
+ struct push_notification_txn_msg *msg = NULL;
+
+ if (hash_table_is_created(txn->messages)) {
+ msg = hash_table_lookup(txn->messages,
+ POINTER_CAST(mail->seq));
+ } else {
+ hash_table_create_direct(&txn->messages, txn->pool, 4);
+ }
+
+ if (msg == NULL) {
+ msg = p_new(txn->pool, struct push_notification_txn_msg, 1);
+ msg->mailbox = mailbox_get_vname(mail->box);
+ /* Save sequence number - used to determine UID later. */
+ if (mail->uid == 0)
+ msg->save_idx = txn->t->save_count;
+ else
+ msg->save_idx = UINT_MAX;
+ msg->uid = mail->uid;
+
+ hash_table_insert(txn->messages, POINTER_CAST(mail->seq),
+ msg);
+ }
+
+ return msg;
+}
+
+void push_notification_txn_msg_end(
+ struct push_notification_txn *ptxn,
+ struct mail_transaction_commit_changes *changes)
+{
+ struct hash_iterate_context *hiter;
+ void *key;
+ struct push_notification_driver_txn **dtxn;
+ struct seq_range_iter siter;
+ struct mailbox_status status;
+ uint32_t uid, uid_validity;
+ struct push_notification_txn_msg *value;
+
+ if (!hash_table_is_created(ptxn->messages)) {
+ return;
+ }
+
+ hiter = hash_table_iterate_init(ptxn->messages);
+ seq_range_array_iter_init(&siter, &changes->saved_uids);
+
+ /* uid_validity is only set in changes if message is new. */
+ if (changes->uid_validity == 0) {
+ mailbox_get_open_status(ptxn->mbox, STATUS_UIDVALIDITY, &status);
+ uid_validity = status.uidvalidity;
+ } else {
+ uid_validity = changes->uid_validity;
+ }
+
+ while (hash_table_iterate(hiter, ptxn->messages, &key, &value)) {
+ if (value->uid == 0) {
+ if (seq_range_array_iter_nth(&siter, value->save_idx, &uid)) {
+ value->uid = uid;
+ }
+ } else
+ i_assert(value->save_idx == UINT_MAX);
+ value->uid_validity = uid_validity;
+
+ array_foreach_modifiable(&ptxn->drivers, dtxn) {
+ if ((*dtxn)->duser->driver->v.process_msg != NULL) {
+ (*dtxn)->duser->driver->v.process_msg(*dtxn, value);
+ }
+ }
+
+ push_notification_txn_msg_deinit_eventdata(value);
+ }
+
+ hash_table_iterate_deinit(&hiter);
+ hash_table_destroy(&ptxn->messages);
+}
+
+void *
+push_notification_txn_msg_get_eventdata(struct push_notification_txn_msg *msg,
+ const char *event_name)
+{
+ struct push_notification_txn_event **mevent;
+
+ if (array_is_created(&msg->eventdata)) {
+ array_foreach_modifiable(&msg->eventdata, mevent) {
+ if (strcmp((*mevent)->event->event->name, event_name) == 0) {
+ return (*mevent)->data;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void push_notification_txn_msg_set_eventdata(
+ struct push_notification_txn *txn,
+ struct push_notification_txn_msg *msg,
+ struct push_notification_event_config *event, void *data)
+{
+ struct push_notification_txn_event *mevent;
+
+ if (!array_is_created(&msg->eventdata)) {
+ p_array_init(&msg->eventdata, txn->pool, 4);
+ }
+
+ mevent = p_new(txn->pool, struct push_notification_txn_event, 1);
+ mevent->data = data;
+ mevent->event = event;
+
+ array_push_back(&msg->eventdata, &mevent);
+}
+
+void push_notification_txn_msg_deinit_eventdata(
+ struct push_notification_txn_msg *msg)
+{
+ struct push_notification_txn_event **mevent;
+
+ if (array_is_created(&msg->eventdata)) {
+ array_foreach_modifiable(&msg->eventdata, mevent) {
+ if (((*mevent)->data != NULL) &&
+ ((*mevent)->event->event->msg.free_msg != NULL)) {
+ (*mevent)->event->event->msg.free_msg(*mevent);
+ }
+ }
+ }
+}
diff --git a/src/plugins/push-notification/push-notification-txn-msg.h b/src/plugins/push-notification/push-notification-txn-msg.h
new file mode 100644
index 0000000..777f115
--- /dev/null
+++ b/src/plugins/push-notification/push-notification-txn-msg.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_TXN_MSG_H
+#define PUSH_NOTIFICATION_TXN_MSG_H
+
+struct mail_transaction_commit_changes;
+struct push_notification_event_config;
+struct push_notification_txn;
+struct push_notification_txn_event;
+
+struct push_notification_txn_msg {
+ const char *mailbox;
+ uint32_t uid;
+ uint32_t uid_validity;
+
+ ARRAY(struct push_notification_txn_event *) eventdata;
+
+ /* Private */
+ unsigned int save_idx;
+};
+
+struct push_notification_txn_msg *
+push_notification_txn_msg_create(struct push_notification_txn *txn,
+ struct mail *mail);
+void push_notification_txn_msg_end(
+ struct push_notification_txn *ptxn,
+ struct mail_transaction_commit_changes *changes);
+
+void *
+push_notification_txn_msg_get_eventdata(struct push_notification_txn_msg *msg,
+ const char *event_name);
+void push_notification_txn_msg_set_eventdata(
+ struct push_notification_txn *txn,
+ struct push_notification_txn_msg *msg,
+ struct push_notification_event_config *event, void *data);
+void push_notification_txn_msg_deinit_eventdata(
+ struct push_notification_txn_msg *msg);
+
+#endif
diff --git a/src/plugins/quota-clone/Makefile.am b/src/plugins/quota-clone/Makefile.am
new file mode 100644
index 0000000..ed4ba68
--- /dev/null
+++ b/src/plugins/quota-clone/Makefile.am
@@ -0,0 +1,19 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/quota
+
+NOPLUGIN_LDFLAGS =
+lib20_quota_clone_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_quota_clone_plugin.la
+
+lib20_quota_clone_plugin_la_SOURCES = \
+ quota-clone-plugin.c
+
+noinst_HEADERS = \
+ quota-clone-plugin.h
diff --git a/src/plugins/quota-clone/Makefile.in b/src/plugins/quota-clone/Makefile.in
new file mode 100644
index 0000000..3f6157b
--- /dev/null
+++ b/src/plugins/quota-clone/Makefile.in
@@ -0,0 +1,822 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/quota-clone
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib20_quota_clone_plugin_la_LIBADD =
+am_lib20_quota_clone_plugin_la_OBJECTS = quota-clone-plugin.lo
+lib20_quota_clone_plugin_la_OBJECTS = \
+ $(am_lib20_quota_clone_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_quota_clone_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_quota_clone_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/quota-clone-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_quota_clone_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_quota_clone_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/quota
+
+lib20_quota_clone_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_quota_clone_plugin.la
+
+lib20_quota_clone_plugin_la_SOURCES = \
+ quota-clone-plugin.c
+
+noinst_HEADERS = \
+ quota-clone-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/quota-clone/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/quota-clone/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_quota_clone_plugin.la: $(lib20_quota_clone_plugin_la_OBJECTS) $(lib20_quota_clone_plugin_la_DEPENDENCIES) $(EXTRA_lib20_quota_clone_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_quota_clone_plugin_la_LINK) -rpath $(moduledir) $(lib20_quota_clone_plugin_la_OBJECTS) $(lib20_quota_clone_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-clone-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/quota-clone-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/quota-clone-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/quota-clone/quota-clone-plugin.c b/src/plugins/quota-clone/quota-clone-plugin.c
new file mode 100644
index 0000000..5f5efa4
--- /dev/null
+++ b/src/plugins/quota-clone/quota-clone-plugin.c
@@ -0,0 +1,308 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "module-context.h"
+#include "ioloop.h"
+#include "dict.h"
+#include "mail-storage-private.h"
+#include "quota.h"
+#include "quota-clone-plugin.h"
+
+/* If mailbox is kept open for this many milliseconds after quota update,
+ flush quota-clone. */
+#define QUOTA_CLONE_FLUSH_DELAY_MSECS (10*1000)
+
+#define DICT_QUOTA_CLONE_PATH DICT_PATH_PRIVATE"quota/"
+#define DICT_QUOTA_CLONE_BYTES_PATH DICT_QUOTA_CLONE_PATH"storage"
+#define DICT_QUOTA_CLONE_COUNT_PATH DICT_QUOTA_CLONE_PATH"messages"
+
+#define QUOTA_CLONE_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, quota_clone_user_module)
+#define QUOTA_CLONE_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, quota_clone_user_module)
+#define QUOTA_CLONE_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, quota_clone_storage_module)
+
+static MODULE_CONTEXT_DEFINE_INIT(quota_clone_user_module,
+ &mail_user_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(quota_clone_storage_module,
+ &mail_storage_module_register);
+
+struct quota_clone_user {
+ union mail_user_module_context module_ctx;
+ struct dict *dict;
+ struct timeout *to_quota_flush;
+ bool quota_changed;
+ bool quota_flushing;
+};
+
+static void
+quota_clone_dict_commit(const struct dict_commit_result *result,
+ struct quota_clone_user *quser)
+{
+ switch (result->ret) {
+ case DICT_COMMIT_RET_OK:
+ case DICT_COMMIT_RET_NOTFOUND:
+ if (!quser->quota_changed)
+ timeout_remove(&quser->to_quota_flush);
+ break;
+ case DICT_COMMIT_RET_FAILED:
+ quser->quota_changed = TRUE;
+ i_error("quota_clone_dict: Failed to write value: %s",
+ result->error);
+ break;
+ case DICT_COMMIT_RET_WRITE_UNCERTAIN:
+ quser->quota_changed = TRUE;
+ i_error("quota_clone_dict: Write was unconfirmed (timeout or disconnect): %s",
+ result->error);
+ break;
+ }
+
+ quser->quota_flushing = FALSE;
+}
+
+static bool quota_clone_flush_real(struct mail_user *user)
+{
+ struct quota_clone_user *quser =
+ QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);
+ struct dict_transaction_context *trans;
+ struct quota_root_iter *iter;
+ struct quota_root *root;
+ uint64_t bytes_value, count_value, limit;
+ const char *error;
+ enum quota_get_result bytes_res, count_res;
+
+ /* we'll clone the first quota root */
+ iter = quota_root_iter_init_user(user);
+ root = quota_root_iter_next(iter);
+ quota_root_iter_deinit(&iter);
+ if (root == NULL) {
+ /* no quota roots defined - ignore */
+ quser->quota_changed = FALSE;
+ return TRUE;
+ }
+
+ /* get new values first */
+ bytes_res = quota_get_resource(root, "", QUOTA_NAME_STORAGE_BYTES,
+ &bytes_value, &limit, &error);
+ if (bytes_res == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ i_error("quota_clone_plugin: "
+ "Failed to get quota resource "QUOTA_NAME_STORAGE_BYTES": %s",
+ error);
+ return TRUE;
+ }
+ count_res = quota_get_resource(root, "", QUOTA_NAME_MESSAGES,
+ &count_value, &limit, &error);
+ if (count_res == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ i_error("quota_clone_plugin: "
+ "Failed to get quota resource "QUOTA_NAME_MESSAGES": %s",
+ error);
+ return TRUE;
+ }
+ if (bytes_res == QUOTA_GET_RESULT_UNKNOWN_RESOURCE &&
+ count_res == QUOTA_GET_RESULT_UNKNOWN_RESOURCE) {
+ /* quota resources don't exist - no point in updating it */
+ return TRUE;
+ }
+ if (bytes_res == QUOTA_GET_RESULT_BACKGROUND_CALC &&
+ count_res == QUOTA_GET_RESULT_BACKGROUND_CALC) {
+ /* Blocked by an ongoing quota calculation - try again later */
+ quser->quota_flushing = FALSE;
+ return FALSE;
+ }
+
+ /* Then update the resources that exist. The resources' existence can't
+ change unless the quota backend is changed, so we don't worry about
+ the special case of lookup changing from
+ RESULT_LIMITED/RESULT_UNLIMITED to RESULT_UNKNOWN_RESOURCE, which
+ leaves the old value unchanged. */
+ const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
+ trans = dict_transaction_begin(quser->dict, set);
+ if (bytes_res == QUOTA_GET_RESULT_LIMITED ||
+ bytes_res == QUOTA_GET_RESULT_UNLIMITED) {
+ dict_set(trans, DICT_QUOTA_CLONE_BYTES_PATH,
+ t_strdup_printf("%"PRIu64, bytes_value));
+ }
+ if (count_res == QUOTA_GET_RESULT_LIMITED ||
+ count_res == QUOTA_GET_RESULT_UNLIMITED) {
+ dict_set(trans, DICT_QUOTA_CLONE_COUNT_PATH,
+ t_strdup_printf("%"PRIu64, count_value));
+ }
+ quser->quota_changed = FALSE;
+ dict_transaction_commit_async(&trans, quota_clone_dict_commit, quser);
+ return FALSE;
+}
+
+static void quota_clone_flush(struct mail_user *user)
+{
+ struct quota_clone_user *quser =
+ QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);
+
+ if (quser->quota_changed) {
+ i_assert(quser->to_quota_flush != NULL);
+ if (quser->quota_flushing) {
+ /* async quota commit is running in background. timeout is still
+ active, so another update will be done later. */
+ } else {
+ quser->quota_flushing = TRUE;
+ /* Returns TRUE if flushing action is complete. */
+ if (quota_clone_flush_real(user)) {
+ quser->quota_flushing = FALSE;
+ timeout_remove(&quser->to_quota_flush);
+ }
+ }
+ } else {
+ timeout_remove(&quser->to_quota_flush);
+ }
+}
+
+static struct mail_user *quota_mailbox_get_user(struct mailbox *box)
+{
+ struct mail_namespace *ns = mailbox_list_get_namespace(box->list);
+ return ns->owner != NULL ? ns->owner : ns->user;
+}
+
+static void quota_clone_changed(struct mailbox *box)
+{
+ struct mail_user *user = quota_mailbox_get_user(box);
+ struct quota_clone_user *quser =
+ QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);
+
+ quser->quota_changed = TRUE;
+ if (quser->to_quota_flush == NULL) {
+ quser->to_quota_flush = timeout_add(QUOTA_CLONE_FLUSH_DELAY_MSECS,
+ quota_clone_flush, user);
+ }
+}
+
+static int quota_clone_save_finish(struct mail_save_context *ctx)
+{
+ union mailbox_module_context *qbox =
+ QUOTA_CLONE_CONTEXT(ctx->transaction->box);
+
+ quota_clone_changed(ctx->transaction->box);
+ return qbox->super.save_finish(ctx);
+}
+
+static int
+quota_clone_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+ union mailbox_module_context *qbox =
+ QUOTA_CLONE_CONTEXT(ctx->transaction->box);
+
+ quota_clone_changed(ctx->transaction->box);
+ return qbox->super.copy(ctx, mail);
+}
+
+static void
+quota_clone_mailbox_sync_notify(struct mailbox *box, uint32_t uid,
+ enum mailbox_sync_type sync_type)
+{
+ union mailbox_module_context *qbox = QUOTA_CLONE_CONTEXT(box);
+
+ if (qbox->super.sync_notify != NULL)
+ qbox->super.sync_notify(box, uid, sync_type);
+
+ if (sync_type == MAILBOX_SYNC_TYPE_EXPUNGE)
+ quota_clone_changed(box);
+}
+
+static void quota_clone_mailbox_allocated(struct mailbox *box)
+{
+ struct quota_clone_user *quser =
+ QUOTA_CLONE_USER_CONTEXT(box->storage->user);
+ struct mailbox_vfuncs *v = box->vlast;
+ union mailbox_module_context *qbox;
+
+ if (quser == NULL)
+ return;
+
+ qbox = p_new(box->pool, union mailbox_module_context, 1);
+ qbox->super = *v;
+ box->vlast = &qbox->super;
+
+ v->save_finish = quota_clone_save_finish;
+ v->copy = quota_clone_copy;
+ v->sync_notify = quota_clone_mailbox_sync_notify;
+ MODULE_CONTEXT_SET_SELF(box, quota_clone_storage_module, qbox);
+}
+
+static void quota_clone_mail_user_deinit_pre(struct mail_user *user)
+{
+ struct quota_clone_user *quser = QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);
+
+ dict_wait(quser->dict);
+ /* Check once more if quota needs to be updated. This needs to be done
+ in deinit_pre(), because at deinit() the quota is already
+ deinitialized. */
+ if (quser->to_quota_flush != NULL) {
+ i_assert(!quser->quota_flushing);
+ quota_clone_flush(user);
+ dict_wait(quser->dict);
+ /* If dict update fails or background calculation is running,
+ the timeout is still set. Just forget about it. */
+ timeout_remove(&quser->to_quota_flush);
+ }
+ quser->module_ctx.super.deinit_pre(user);
+}
+
+static void quota_clone_mail_user_deinit(struct mail_user *user)
+{
+ struct quota_clone_user *quser = QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);
+
+ /* wait once more, just in case something changed quota during
+ deinit_pre() */
+ dict_wait(quser->dict);
+ i_assert(quser->to_quota_flush == NULL);
+ dict_deinit(&quser->dict);
+ quser->module_ctx.super.deinit(user);
+}
+
+static void quota_clone_mail_user_created(struct mail_user *user)
+{
+ struct quota_clone_user *quser;
+ struct mail_user_vfuncs *v = user->vlast;
+ struct dict_settings dict_set;
+ struct dict *dict;
+ const char *uri, *error;
+
+ uri = mail_user_plugin_getenv(user, "quota_clone_dict");
+ if (uri == NULL || uri[0] == '\0') {
+ e_debug(user->event, "The quota_clone_dict setting is missing from configuration");
+ return;
+ }
+
+ i_zero(&dict_set);
+ dict_set.base_dir = user->set->base_dir;
+ dict_set.event_parent = user->event;
+ if (dict_init(uri, &dict_set, &dict, &error) < 0) {
+ i_error("quota_clone_dict: Failed to initialize '%s': %s",
+ uri, error);
+ return;
+ }
+
+ quser = p_new(user->pool, struct quota_clone_user, 1);
+ quser->module_ctx.super = *v;
+ user->vlast = &quser->module_ctx.super;
+ v->deinit_pre = quota_clone_mail_user_deinit_pre;
+ v->deinit = quota_clone_mail_user_deinit;
+ quser->dict = dict;
+ MODULE_CONTEXT_SET(user, quota_clone_user_module, quser);
+}
+
+static struct mail_storage_hooks quota_clone_mail_storage_hooks = {
+ .mailbox_allocated = quota_clone_mailbox_allocated,
+ .mail_user_created = quota_clone_mail_user_created
+};
+
+void quota_clone_plugin_init(struct module *module ATTR_UNUSED)
+{
+ mail_storage_hooks_add(module, &quota_clone_mail_storage_hooks);
+}
+
+void quota_clone_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&quota_clone_mail_storage_hooks);
+}
+
+const char *quota_clone_plugin_dependencies[] = { "quota", NULL };
diff --git a/src/plugins/quota-clone/quota-clone-plugin.h b/src/plugins/quota-clone/quota-clone-plugin.h
new file mode 100644
index 0000000..9ae1d8f
--- /dev/null
+++ b/src/plugins/quota-clone/quota-clone-plugin.h
@@ -0,0 +1,7 @@
+#ifndef QUOTA_CLONE_PLUGIN_H
+#define QUOTA_CLONE_PLUGIN_H
+
+void quota_clone_plugin_init(struct module *module);
+void quota_clone_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/quota/Makefile.am b/src/plugins/quota/Makefile.am
new file mode 100644
index 0000000..e8bad8c
--- /dev/null
+++ b/src/plugins/quota/Makefile.am
@@ -0,0 +1,144 @@
+doveadm_moduledir = $(moduledir)/doveadm
+
+pkglibexecdir = $(libexecdir)/dovecot
+pkglibexec_PROGRAMS = quota-status
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-smtp \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-client \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/imapc \
+ -I$(top_srcdir)/src/lib-storage/index/maildir \
+ -I$(top_srcdir)/src/lib-program-client \
+ -I$(top_srcdir)/src/doveadm \
+ $(LIBTIRPC_CFLAGS)
+
+NOPLUGIN_LDFLAGS =
+lib10_doveadm_quota_plugin_la_LDFLAGS = -module -avoid-version
+lib10_quota_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib10_quota_plugin.la
+
+quota_dist_sources = \
+ quota.c \
+ quota-count.c \
+ quota-fs.c \
+ quota-dict.c \
+ quota-dirsize.c \
+ quota-imapc.c \
+ quota-maildir.c \
+ quota-plugin.c \
+ quota-storage.c \
+ quota-util.c
+
+quota_common_objects = \
+ quota.lo \
+ quota-count.lo \
+ quota-fs.lo \
+ quota-dict.lo \
+ quota-dirsize.lo \
+ quota-imapc.lo \
+ quota-maildir.lo \
+ quota-plugin.lo \
+ quota-storage.lo \
+ quota-util.lo \
+ $(RQUOTA_XDR_LO)
+
+lib10_quota_plugin_la_SOURCES = $(quota_dist_sources)
+nodist_lib10_quota_plugin_la_SOURCES = $(RQUOTA_XDR)
+lib10_quota_plugin_la_LIBADD = $(QUOTA_LIBS)
+
+doveadm_module_LTLIBRARIES = \
+ lib10_doveadm_quota_plugin.la
+
+lib10_doveadm_quota_plugin_la_SOURCES = \
+ doveadm-quota.c
+
+quota_status_SOURCES = \
+ quota-status.c \
+ quota-status-settings.c
+
+quota_status_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+quota_status_LDADD = \
+ $(quota_common_objects) \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT) \
+ $(QUOTA_LIBS) \
+ $(BINARY_LDFLAGS)
+quota_status_DEPENDENCIES = \
+ $(quota_common_objects) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+if HAVE_RQUOTA
+RQUOTA_XDR = rquota_xdr.c
+RQUOTA_XDR_LO = rquota_xdr.lo
+#RQUOTA_X = /usr/include/rpcsvc/rquota.x
+RQUOTA_X = $(srcdir)/rquota.x
+rquota_xdr.c: Makefile rquota.h
+ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \
+ cp $(RQUOTA_X) $(top_builddir)/src/plugins/quota/; \
+ fi; \
+ (echo '#include "lib.h"'; \
+ echo '#undef FALSE'; \
+ echo '#undef TRUE'; \
+ echo '#include <rpc/rpc.h>'; \
+ $(RPCGEN) -c $(top_builddir)/src/plugins/quota/rquota.x | \
+ sed -e 's/IXDR_PUT/(void)IXDR_PUT/g' \
+ -e 's,!xdr_,0 == xdr_,' \
+ -e 's,/usr/include/rpcsvc/rquota.h,rquota.h,' \
+ -e 's/int32_t \*buf/int32_t *buf ATTR_UNUSED/' \
+ -e 's/^static char rcsid.*//' ) > rquota_xdr.c
+
+rquota.h: Makefile $(RQUOTA_X)
+ $(RPCGEN) -h $(RQUOTA_X) > rquota.h
+
+quota-fs.lo: rquota.h
+
+endif
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = \
+ quota.h \
+ quota-fs.h \
+ quota-plugin.h \
+ quota-private.h
+noinst_HEADERS = \
+ quota-status-settings.h
+
+EXTRA_DIST = rquota.x
+
+clean-generic:
+ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \
+ rm -f $(top_builddir)/src/plugins/quota/rquota.x; \
+ fi; \
+ rm -f rquota_xdr.c rquota.h
+
+test_programs = \
+ test-quota-util
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+ ../../lib-test/libtest.la \
+ ../../lib/liblib.la
+test_deps = $(noinst_LTLIBRARIES) $(test_libs)
+
+test_quota_util_SOURCES = test-quota-util.c
+test_quota_util_LDADD = quota-util.lo $(test_libs)
+test_quota_util_DEPENDENCIES = quota-util.lo $(test_deps)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/plugins/quota/Makefile.in b/src/plugins/quota/Makefile.in
new file mode 100644
index 0000000..a8a824f
--- /dev/null
+++ b/src/plugins/quota/Makefile.in
@@ -0,0 +1,1179 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+pkglibexec_PROGRAMS = quota-status$(EXEEXT)
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/plugins/quota
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-quota-util$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \
+ "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES)
+lib10_doveadm_quota_plugin_la_LIBADD =
+am_lib10_doveadm_quota_plugin_la_OBJECTS = doveadm-quota.lo
+lib10_doveadm_quota_plugin_la_OBJECTS = \
+ $(am_lib10_doveadm_quota_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib10_doveadm_quota_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib10_doveadm_quota_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+am__DEPENDENCIES_1 =
+lib10_quota_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__objects_1 = quota.lo quota-count.lo quota-fs.lo quota-dict.lo \
+ quota-dirsize.lo quota-imapc.lo quota-maildir.lo \
+ quota-plugin.lo quota-storage.lo quota-util.lo
+am_lib10_quota_plugin_la_OBJECTS = $(am__objects_1)
+@HAVE_RQUOTA_TRUE@am__objects_2 = rquota_xdr.lo
+nodist_lib10_quota_plugin_la_OBJECTS = $(am__objects_2)
+lib10_quota_plugin_la_OBJECTS = $(am_lib10_quota_plugin_la_OBJECTS) \
+ $(nodist_lib10_quota_plugin_la_OBJECTS)
+lib10_quota_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib10_quota_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_quota_status_OBJECTS = quota_status-quota-status.$(OBJEXT) \
+ quota_status-quota-status-settings.$(OBJEXT)
+quota_status_OBJECTS = $(am_quota_status_OBJECTS)
+am_test_quota_util_OBJECTS = test-quota-util.$(OBJEXT)
+test_quota_util_OBJECTS = $(am_test_quota_util_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/doveadm-quota.Plo \
+ ./$(DEPDIR)/quota-count.Plo ./$(DEPDIR)/quota-dict.Plo \
+ ./$(DEPDIR)/quota-dirsize.Plo ./$(DEPDIR)/quota-fs.Plo \
+ ./$(DEPDIR)/quota-imapc.Plo ./$(DEPDIR)/quota-maildir.Plo \
+ ./$(DEPDIR)/quota-plugin.Plo ./$(DEPDIR)/quota-storage.Plo \
+ ./$(DEPDIR)/quota-util.Plo ./$(DEPDIR)/quota.Plo \
+ ./$(DEPDIR)/quota_status-quota-status-settings.Po \
+ ./$(DEPDIR)/quota_status-quota-status.Po \
+ ./$(DEPDIR)/rquota_xdr.Plo ./$(DEPDIR)/test-quota-util.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib10_doveadm_quota_plugin_la_SOURCES) \
+ $(lib10_quota_plugin_la_SOURCES) \
+ $(nodist_lib10_quota_plugin_la_SOURCES) \
+ $(quota_status_SOURCES) $(test_quota_util_SOURCES)
+DIST_SOURCES = $(lib10_doveadm_quota_plugin_la_SOURCES) \
+ $(lib10_quota_plugin_la_SOURCES) $(quota_status_SOURCES) \
+ $(test_quota_util_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+pkglibexecdir = $(libexecdir)/dovecot
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+doveadm_moduledir = $(moduledir)/doveadm
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-smtp \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-client \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/imapc \
+ -I$(top_srcdir)/src/lib-storage/index/maildir \
+ -I$(top_srcdir)/src/lib-program-client \
+ -I$(top_srcdir)/src/doveadm \
+ $(LIBTIRPC_CFLAGS)
+
+lib10_doveadm_quota_plugin_la_LDFLAGS = -module -avoid-version
+lib10_quota_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib10_quota_plugin.la
+
+quota_dist_sources = \
+ quota.c \
+ quota-count.c \
+ quota-fs.c \
+ quota-dict.c \
+ quota-dirsize.c \
+ quota-imapc.c \
+ quota-maildir.c \
+ quota-plugin.c \
+ quota-storage.c \
+ quota-util.c
+
+quota_common_objects = \
+ quota.lo \
+ quota-count.lo \
+ quota-fs.lo \
+ quota-dict.lo \
+ quota-dirsize.lo \
+ quota-imapc.lo \
+ quota-maildir.lo \
+ quota-plugin.lo \
+ quota-storage.lo \
+ quota-util.lo \
+ $(RQUOTA_XDR_LO)
+
+lib10_quota_plugin_la_SOURCES = $(quota_dist_sources)
+nodist_lib10_quota_plugin_la_SOURCES = $(RQUOTA_XDR)
+lib10_quota_plugin_la_LIBADD = $(QUOTA_LIBS)
+doveadm_module_LTLIBRARIES = \
+ lib10_doveadm_quota_plugin.la
+
+lib10_doveadm_quota_plugin_la_SOURCES = \
+ doveadm-quota.c
+
+quota_status_SOURCES = \
+ quota-status.c \
+ quota-status-settings.c
+
+quota_status_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+quota_status_LDADD = \
+ $(quota_common_objects) \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT) \
+ $(QUOTA_LIBS) \
+ $(BINARY_LDFLAGS)
+
+quota_status_DEPENDENCIES = \
+ $(quota_common_objects) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+@HAVE_RQUOTA_TRUE@RQUOTA_XDR = rquota_xdr.c
+@HAVE_RQUOTA_TRUE@RQUOTA_XDR_LO = rquota_xdr.lo
+#RQUOTA_X = /usr/include/rpcsvc/rquota.x
+@HAVE_RQUOTA_TRUE@RQUOTA_X = $(srcdir)/rquota.x
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = \
+ quota.h \
+ quota-fs.h \
+ quota-plugin.h \
+ quota-private.h
+
+noinst_HEADERS = \
+ quota-status-settings.h
+
+EXTRA_DIST = rquota.x
+test_programs = \
+ test-quota-util
+
+test_libs = \
+ ../../lib-test/libtest.la \
+ ../../lib/liblib.la
+
+test_deps = $(noinst_LTLIBRARIES) $(test_libs)
+test_quota_util_SOURCES = test-quota-util.c
+test_quota_util_LDADD = quota-util.lo $(test_libs)
+test_quota_util_DEPENDENCIES = quota-util.lo $(test_deps)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/quota/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/quota/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files
+
+clean-pkglibexecPROGRAMS:
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \
+ }
+
+uninstall-doveadm_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \
+ done
+
+clean-doveadm_moduleLTLIBRARIES:
+ -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES)
+ @list='$(doveadm_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib10_doveadm_quota_plugin.la: $(lib10_doveadm_quota_plugin_la_OBJECTS) $(lib10_doveadm_quota_plugin_la_DEPENDENCIES) $(EXTRA_lib10_doveadm_quota_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib10_doveadm_quota_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib10_doveadm_quota_plugin_la_OBJECTS) $(lib10_doveadm_quota_plugin_la_LIBADD) $(LIBS)
+
+lib10_quota_plugin.la: $(lib10_quota_plugin_la_OBJECTS) $(lib10_quota_plugin_la_DEPENDENCIES) $(EXTRA_lib10_quota_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib10_quota_plugin_la_LINK) -rpath $(moduledir) $(lib10_quota_plugin_la_OBJECTS) $(lib10_quota_plugin_la_LIBADD) $(LIBS)
+
+quota-status$(EXEEXT): $(quota_status_OBJECTS) $(quota_status_DEPENDENCIES) $(EXTRA_quota_status_DEPENDENCIES)
+ @rm -f quota-status$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(quota_status_OBJECTS) $(quota_status_LDADD) $(LIBS)
+
+test-quota-util$(EXEEXT): $(test_quota_util_OBJECTS) $(test_quota_util_DEPENDENCIES) $(EXTRA_test_quota_util_DEPENDENCIES)
+ @rm -f test-quota-util$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_quota_util_OBJECTS) $(test_quota_util_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-quota.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-count.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-dict.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-dirsize.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-fs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-imapc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-maildir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-util.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota_status-quota-status-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota_status-quota-status.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rquota_xdr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-quota-util.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+quota_status-quota-status.o: quota-status.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status.o -MD -MP -MF $(DEPDIR)/quota_status-quota-status.Tpo -c -o quota_status-quota-status.o `test -f 'quota-status.c' || echo '$(srcdir)/'`quota-status.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status.Tpo $(DEPDIR)/quota_status-quota-status.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status.c' object='quota_status-quota-status.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status.o `test -f 'quota-status.c' || echo '$(srcdir)/'`quota-status.c
+
+quota_status-quota-status.obj: quota-status.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status.obj -MD -MP -MF $(DEPDIR)/quota_status-quota-status.Tpo -c -o quota_status-quota-status.obj `if test -f 'quota-status.c'; then $(CYGPATH_W) 'quota-status.c'; else $(CYGPATH_W) '$(srcdir)/quota-status.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status.Tpo $(DEPDIR)/quota_status-quota-status.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status.c' object='quota_status-quota-status.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status.obj `if test -f 'quota-status.c'; then $(CYGPATH_W) 'quota-status.c'; else $(CYGPATH_W) '$(srcdir)/quota-status.c'; fi`
+
+quota_status-quota-status-settings.o: quota-status-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status-settings.o -MD -MP -MF $(DEPDIR)/quota_status-quota-status-settings.Tpo -c -o quota_status-quota-status-settings.o `test -f 'quota-status-settings.c' || echo '$(srcdir)/'`quota-status-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status-settings.Tpo $(DEPDIR)/quota_status-quota-status-settings.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status-settings.c' object='quota_status-quota-status-settings.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status-settings.o `test -f 'quota-status-settings.c' || echo '$(srcdir)/'`quota-status-settings.c
+
+quota_status-quota-status-settings.obj: quota-status-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status-settings.obj -MD -MP -MF $(DEPDIR)/quota_status-quota-status-settings.Tpo -c -o quota_status-quota-status-settings.obj `if test -f 'quota-status-settings.c'; then $(CYGPATH_W) 'quota-status-settings.c'; else $(CYGPATH_W) '$(srcdir)/quota-status-settings.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status-settings.Tpo $(DEPDIR)/quota_status-quota-status-settings.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status-settings.c' object='quota_status-quota-status-settings.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status-settings.obj `if test -f 'quota-status-settings.c'; then $(CYGPATH_W) 'quota-status-settings.c'; else $(CYGPATH_W) '$(srcdir)/quota-status-settings.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-noinstPROGRAMS \
+ clean-pkglibexecPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/doveadm-quota.Plo
+ -rm -f ./$(DEPDIR)/quota-count.Plo
+ -rm -f ./$(DEPDIR)/quota-dict.Plo
+ -rm -f ./$(DEPDIR)/quota-dirsize.Plo
+ -rm -f ./$(DEPDIR)/quota-fs.Plo
+ -rm -f ./$(DEPDIR)/quota-imapc.Plo
+ -rm -f ./$(DEPDIR)/quota-maildir.Plo
+ -rm -f ./$(DEPDIR)/quota-plugin.Plo
+ -rm -f ./$(DEPDIR)/quota-storage.Plo
+ -rm -f ./$(DEPDIR)/quota-util.Plo
+ -rm -f ./$(DEPDIR)/quota.Plo
+ -rm -f ./$(DEPDIR)/quota_status-quota-status-settings.Po
+ -rm -f ./$(DEPDIR)/quota_status-quota-status.Po
+ -rm -f ./$(DEPDIR)/rquota_xdr.Plo
+ -rm -f ./$(DEPDIR)/test-quota-util.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-doveadm_moduleLTLIBRARIES \
+ install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibexecPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/doveadm-quota.Plo
+ -rm -f ./$(DEPDIR)/quota-count.Plo
+ -rm -f ./$(DEPDIR)/quota-dict.Plo
+ -rm -f ./$(DEPDIR)/quota-dirsize.Plo
+ -rm -f ./$(DEPDIR)/quota-fs.Plo
+ -rm -f ./$(DEPDIR)/quota-imapc.Plo
+ -rm -f ./$(DEPDIR)/quota-maildir.Plo
+ -rm -f ./$(DEPDIR)/quota-plugin.Plo
+ -rm -f ./$(DEPDIR)/quota-storage.Plo
+ -rm -f ./$(DEPDIR)/quota-util.Plo
+ -rm -f ./$(DEPDIR)/quota.Plo
+ -rm -f ./$(DEPDIR)/quota_status-quota-status-settings.Po
+ -rm -f ./$(DEPDIR)/quota_status-quota-status.Po
+ -rm -f ./$(DEPDIR)/rquota_xdr.Plo
+ -rm -f ./$(DEPDIR)/test-quota-util.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \
+ uninstall-pkglibexecPROGRAMS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-doveadm_moduleLTLIBRARIES \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ clean-noinstPROGRAMS clean-pkglibexecPROGRAMS cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-doveadm_moduleLTLIBRARIES install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-pkglibexecPROGRAMS \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \
+ uninstall-pkglibexecPROGRAMS
+
+.PRECIOUS: Makefile
+
+@HAVE_RQUOTA_TRUE@rquota_xdr.c: Makefile rquota.h
+@HAVE_RQUOTA_TRUE@ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \
+@HAVE_RQUOTA_TRUE@ cp $(RQUOTA_X) $(top_builddir)/src/plugins/quota/; \
+@HAVE_RQUOTA_TRUE@ fi; \
+@HAVE_RQUOTA_TRUE@ (echo '#include "lib.h"'; \
+@HAVE_RQUOTA_TRUE@ echo '#undef FALSE'; \
+@HAVE_RQUOTA_TRUE@ echo '#undef TRUE'; \
+@HAVE_RQUOTA_TRUE@ echo '#include <rpc/rpc.h>'; \
+@HAVE_RQUOTA_TRUE@ $(RPCGEN) -c $(top_builddir)/src/plugins/quota/rquota.x | \
+@HAVE_RQUOTA_TRUE@ sed -e 's/IXDR_PUT/(void)IXDR_PUT/g' \
+@HAVE_RQUOTA_TRUE@ -e 's,!xdr_,0 == xdr_,' \
+@HAVE_RQUOTA_TRUE@ -e 's,/usr/include/rpcsvc/rquota.h,rquota.h,' \
+@HAVE_RQUOTA_TRUE@ -e 's/int32_t \*buf/int32_t *buf ATTR_UNUSED/' \
+@HAVE_RQUOTA_TRUE@ -e 's/^static char rcsid.*//' ) > rquota_xdr.c
+
+@HAVE_RQUOTA_TRUE@rquota.h: Makefile $(RQUOTA_X)
+@HAVE_RQUOTA_TRUE@ $(RPCGEN) -h $(RQUOTA_X) > rquota.h
+
+@HAVE_RQUOTA_TRUE@quota-fs.lo: rquota.h
+
+clean-generic:
+ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \
+ rm -f $(top_builddir)/src/plugins/quota/rquota.x; \
+ fi; \
+ rm -f rquota_xdr.c rquota.h
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/quota/doveadm-quota.c b/src/plugins/quota/doveadm-quota.c
new file mode 100644
index 0000000..8a42b22
--- /dev/null
+++ b/src/plugins/quota/doveadm-quota.c
@@ -0,0 +1,165 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "module-dir.h"
+#include "quota-plugin.h"
+#include "quota-private.h"
+#include "doveadm-print.h"
+#include "doveadm-mail.h"
+
+const char *doveadm_quota_plugin_version = DOVECOT_ABI_VERSION;
+
+void doveadm_quota_plugin_init(struct module *module);
+void doveadm_quota_plugin_deinit(void);
+
+static int cmd_quota_get_root(struct quota_root *root)
+{
+ const char *const *res;
+ const char *error;
+ uint64_t value, limit;
+ enum quota_get_result qret;
+ int ret = 0;
+
+ res = quota_root_get_resources(root);
+ for (; *res != NULL; res++) {
+ qret = quota_get_resource(root, "", *res, &value, &limit, &error);
+ doveadm_print(root->set->name);
+ doveadm_print(*res);
+ if (qret == QUOTA_GET_RESULT_LIMITED) {
+ doveadm_print_num(value);
+ doveadm_print_num(limit);
+ if (limit > 0)
+ doveadm_print_num(value*100 / limit);
+ else
+ doveadm_print("0");
+ } else if (qret == QUOTA_GET_RESULT_UNLIMITED) {
+ doveadm_print_num(value);
+ doveadm_print("-");
+ doveadm_print("0");
+ } else {
+ i_error("Failed to get quota resource %s: %s",
+ *res, error);
+ doveadm_print("error");
+ doveadm_print("error");
+ doveadm_print("error");
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+static int
+cmd_quota_get_run(struct doveadm_mail_cmd_context *ctx,
+ struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+ struct quota_root *const *root;
+
+ if (quser == NULL) {
+ i_error("Quota not enabled");
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ return -1;
+ }
+
+ int ret = 0;
+ array_foreach(&quser->quota->roots, root)
+ if (cmd_quota_get_root(*root) < 0)
+ ret = -1;
+ if (ret < 0)
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+ return ret;
+}
+
+static void cmd_quota_get_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[] ATTR_UNUSED)
+{
+ doveadm_print_header("root", "Quota name", 0);
+ doveadm_print_header("type", "Type", 0);
+ doveadm_print_header("value", "Value",
+ DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
+ doveadm_print_header("limit", "Limit",
+ DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
+ doveadm_print_header("percent", "%",
+ DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_quota_get_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_quota_get_run;
+ ctx->v.init = cmd_quota_get_init;
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ return ctx;
+}
+
+static int
+cmd_quota_recalc_run(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+ struct quota_root *const *root;
+ struct quota_transaction_context trans;
+
+ if (quser == NULL) {
+ i_error("Quota not enabled");
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ return -1;
+ }
+
+ i_zero(&trans);
+ trans.quota = quser->quota;
+ trans.recalculate = QUOTA_RECALCULATE_FORCED;
+
+ array_foreach(&quser->quota->roots, root) {
+ const char *error;
+ if ((*root)->backend.v.update(*root, &trans, &error) < 0)
+ i_error("Recalculating quota failed: %s", error);
+ if ((*root)->backend.v.flush != NULL)
+ (*root)->backend.v.flush(*root);
+ }
+ return 0;
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_quota_recalc_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_quota_recalc_run;
+ return ctx;
+}
+
+static struct doveadm_cmd_ver2 quota_commands[] = {
+ {
+ .name = "quota get",
+ .usage = "",
+ .mail_cmd = cmd_quota_get_alloc,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAMS_END
+ },
+ {
+ .name = "quota recalc",
+ .usage = "",
+ .mail_cmd = cmd_quota_recalc_alloc,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAMS_END
+ }
+};
+
+void doveadm_quota_plugin_init(struct module *module ATTR_UNUSED)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(quota_commands); i++)
+ doveadm_cmd_register_ver2(&quota_commands[i]);
+}
+
+void doveadm_quota_plugin_deinit(void)
+{
+}
diff --git a/src/plugins/quota/quota-count.c b/src/plugins/quota/quota-count.c
new file mode 100644
index 0000000..00e25e6
--- /dev/null
+++ b/src/plugins/quota/quota-count.c
@@ -0,0 +1,400 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mailbox-list-iter.h"
+#include "quota-private.h"
+
+struct count_quota_root {
+ struct quota_root root;
+
+ struct timeval cache_timeval;
+ uint64_t cached_bytes, cached_count;
+};
+
+struct quota_mailbox_iter {
+ struct quota_root *root;
+ struct mail_namespace *ns;
+ unsigned int ns_idx;
+ struct mailbox_list_iterate_context *iter;
+ struct mailbox_info info;
+ const char *error;
+};
+
+extern struct quota_backend quota_backend_count;
+
+static int
+quota_count_mailbox(struct quota_root *root, struct mail_namespace *ns,
+ const char *vname, uint64_t *bytes, uint64_t *count,
+ enum quota_get_result *error_result_r,
+ const char **error_r)
+{
+ struct quota_rule *rule;
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+ struct mailbox_status status;
+ enum mail_error error;
+ const char *errstr;
+ int ret;
+
+ rule = quota_root_rule_find(root->set, vname);
+ if (rule != NULL && rule->ignore) {
+ /* mailbox not included in quota */
+ return 0;
+ }
+
+ box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_READONLY);
+ if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0) {
+ /* quota doesn't exist for this mailbox/storage */
+ ret = 0;
+ } else if (mailbox_get_metadata(box, root->quota->set->vsizes ?
+ MAILBOX_METADATA_VIRTUAL_SIZE :
+ MAILBOX_METADATA_PHYSICAL_SIZE,
+ &metadata) < 0 ||
+ mailbox_get_status(box, STATUS_MESSAGES, &status) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ if (error == MAIL_ERROR_TEMP) {
+ *error_r = t_strdup_printf(
+ "Couldn't get size of mailbox %s: %s",
+ vname, errstr);
+ *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR;
+ ret = -1;
+ } else if (error == MAIL_ERROR_INUSE) {
+ /* started on background. don't log an error. */
+ *error_r = t_strdup_printf(
+ "Ongoing quota calculation blocked getting size of %s: %s",
+ vname, errstr);
+ *error_result_r = QUOTA_GET_RESULT_BACKGROUND_CALC;
+ ret = -1;
+ } else {
+ /* non-temporary error, e.g. ACLs denied access. */
+ ret = 0;
+ }
+ } else {
+ ret = 0;
+ *bytes += root->quota->set->vsizes ?
+ metadata.virtual_size : metadata.physical_size;
+ *count += status.messages;
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static struct quota_mailbox_iter *
+quota_mailbox_iter_begin(struct quota_root *root)
+{
+ struct quota_mailbox_iter *iter;
+
+ iter = i_new(struct quota_mailbox_iter, 1);
+ iter->root = root;
+ iter->error = "";
+ return iter;
+}
+
+static int
+quota_mailbox_iter_deinit(struct quota_mailbox_iter **_iter,
+ const char **error_r)
+{
+ struct quota_mailbox_iter *iter = *_iter;
+ int ret = *iter->error != '\0' ? -1 : 0;
+
+ *_iter = NULL;
+
+ const char *error2 = "";
+ if (iter->iter != NULL) {
+ if (mailbox_list_iter_deinit(&iter->iter) < 0) {
+ error2 = t_strdup_printf(
+ "Listing namespace '%s' failed: %s",
+ iter->ns->prefix,
+ mailbox_list_get_last_internal_error(iter->ns->list, NULL));
+ ret = -1;
+ }
+ }
+ if (ret < 0) {
+ const char *separator =
+ *iter->error != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strdup_printf("%s%s%s",
+ iter->error, separator, error2);
+ }
+ i_free(iter);
+ return ret;
+}
+
+static const struct mailbox_info *
+quota_mailbox_iter_next(struct quota_mailbox_iter *iter)
+{
+ struct mail_namespace *const *namespaces;
+ const struct mailbox_info *info;
+ unsigned int count;
+
+ if (iter->iter == NULL) {
+ namespaces = array_get(&iter->root->quota->namespaces, &count);
+ do {
+ if (iter->ns_idx >= count)
+ return NULL;
+
+ iter->ns = namespaces[iter->ns_idx++];
+ } while (!quota_root_is_namespace_visible(iter->root, iter->ns));
+ iter->iter = mailbox_list_iter_init(iter->ns->list, "*",
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES);
+ }
+ while ((info = mailbox_list_iter_next(iter->iter)) != NULL) {
+ if ((info->flags & (MAILBOX_NONEXISTENT |
+ MAILBOX_NOSELECT)) == 0)
+ return info;
+ }
+ if (mailbox_list_iter_deinit(&iter->iter) < 0) {
+ iter->error = t_strdup_printf(
+ "Listing namespace '%s' failed: %s",
+ iter->ns->prefix,
+ mailbox_list_get_last_internal_error(iter->ns->list, NULL));
+ }
+ if (iter->ns->prefix_len > 0 &&
+ (iter->ns->prefix_len != 6 ||
+ strncasecmp(iter->ns->prefix, "INBOX", 5) != 0)) {
+ /* if the namespace prefix itself exists, count it also */
+ iter->info.ns = iter->ns;
+ iter->info.vname = t_strndup(iter->ns->prefix,
+ iter->ns->prefix_len-1);
+ return &iter->info;
+ }
+ /* try the next namespace */
+ return quota_mailbox_iter_next(iter);
+}
+
+int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r,
+ enum quota_get_result *error_result_r, const char **error_r)
+{
+ struct quota_mailbox_iter *iter;
+ const struct mailbox_info *info;
+ const char *error1 = "", *error2 = "";
+ int ret = 1;
+
+ *bytes_r = *count_r = 0;
+ if (root->recounting)
+ return 0;
+ root->recounting = TRUE;
+
+ struct event_reason *reason = event_reason_begin("quota:count");
+
+ iter = quota_mailbox_iter_begin(root);
+ while ((info = quota_mailbox_iter_next(iter)) != NULL) {
+ if (quota_count_mailbox(root, info->ns, info->vname,
+ bytes_r, count_r, error_result_r,
+ &error1) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+ if (quota_mailbox_iter_deinit(&iter, &error2) < 0) {
+ *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR;
+ ret = -1;
+ }
+ if (ret < 0) {
+ const char *separator =
+ *error1 != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strconcat(error1, separator, error2, NULL);
+ }
+ event_reason_end(&reason);
+ root->recounting = FALSE;
+ return ret;
+}
+
+static enum quota_get_result
+quota_count_cached(struct count_quota_root *root,
+ uint64_t *bytes_r, uint64_t *count_r,
+ const char **error_r)
+{
+ int ret;
+
+ if (root->cache_timeval.tv_usec == ioloop_timeval.tv_usec &&
+ root->cache_timeval.tv_sec == ioloop_timeval.tv_sec &&
+ ioloop_timeval.tv_sec != 0) {
+ *bytes_r = root->cached_bytes;
+ *count_r = root->cached_count;
+ return QUOTA_GET_RESULT_LIMITED;
+ }
+
+ enum quota_get_result error_res;
+ ret = quota_count(&root->root, bytes_r, count_r, &error_res, error_r);
+ if (ret < 0) {
+ return error_res;
+ } else if (ret > 0) {
+ root->cache_timeval = ioloop_timeval;
+ root->cached_bytes = *bytes_r;
+ root->cached_count = *count_r;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static struct quota_root *count_quota_alloc(void)
+{
+ struct count_quota_root *root;
+
+ root = i_new(struct count_quota_root, 1);
+ return &root->root;
+}
+
+static int count_quota_init(struct quota_root *root, const char *args,
+ const char **error_r)
+{
+ if (!root->quota->set->vsizes) {
+ *error_r = "quota count backend requires quota_vsizes=yes";
+ return -1;
+ }
+ event_set_append_log_prefix(root->backend.event, "quota-count: ");
+
+ root->auto_updating = TRUE;
+ return quota_root_default_init(root, args, error_r);
+}
+
+static void count_quota_deinit(struct quota_root *_root)
+{
+ i_free(_root);
+}
+
+static const char *const *
+count_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES, QUOTA_NAME_MESSAGES, NULL
+ };
+ return resources;
+}
+
+static enum quota_get_result
+count_quota_get_resource(struct quota_root *_root,
+ const char *name, uint64_t *value_r,
+ const char **error_r)
+{
+ struct count_quota_root *root = (struct count_quota_root *)_root;
+ uint64_t bytes, count;
+ enum quota_get_result ret;
+
+ ret = quota_count_cached(root, &bytes, &count, error_r);
+ if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR)
+ return ret;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *value_r = bytes;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ *value_r = count;
+ else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int quota_count_recalculate_box(struct mailbox *box,
+ const char **error_r)
+{
+ struct mail_index_transaction *trans;
+ struct mailbox_metadata metadata;
+ struct mailbox_index_vsize vsize_hdr;
+ const char *errstr;
+ enum mail_error error;
+
+ if (mailbox_open(box) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ if (error != MAIL_ERROR_TEMP) {
+ /* non-temporary error, e.g. ACLs denied access. */
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "Couldn't open mailbox %s: %s", box->vname, errstr);
+ return -1;
+ }
+
+ /* reset the vsize header first */
+ trans = mail_index_transaction_begin(box->view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ i_zero(&vsize_hdr);
+ mail_index_update_header_ext(trans, box->vsize_hdr_ext_id,
+ 0, &vsize_hdr, sizeof(vsize_hdr));
+ if (mail_index_transaction_commit(&trans) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't commit mail index transaction for %s: %s",
+ box->vname,
+ mail_index_get_error_message(box->view->index));
+ return -1;
+ }
+ /* getting the vsize now forces its recalculation */
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_VIRTUAL_SIZE,
+ &metadata) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't get mailbox %s vsize: %s", box->vname,
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+ /* call sync to write the change to mailbox list index */
+ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't sync mailbox %s: %s", box->vname,
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+ return 0;
+}
+
+static int quota_count_recalculate(struct quota_root *root,
+ const char **error_r)
+{
+ struct event_reason *reason;
+ struct quota_mailbox_iter *iter;
+ const struct mailbox_info *info;
+ struct mailbox *box;
+ int ret = 0;
+ const char *error1 = "", *error2 = "";
+
+ reason = event_reason_begin("quota:recalculate");
+
+ iter = quota_mailbox_iter_begin(root);
+ while ((info = quota_mailbox_iter_next(iter)) != NULL) {
+ box = mailbox_alloc(info->ns->list, info->vname, 0);
+ if (quota_count_recalculate_box(box, &error1) < 0)
+ ret = -1;
+ mailbox_free(&box);
+ }
+ if (quota_mailbox_iter_deinit(&iter, &error2) < 0)
+ ret = -1;
+ if (ret < 0) {
+ const char *separator =
+ *error1 != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strdup_printf(
+ "quota-count: recalculate failed: %s%s%s",
+ error1, separator, error2);
+ }
+ event_reason_end(&reason);
+ return ret;
+}
+
+static int
+count_quota_update(struct quota_root *root,
+ struct quota_transaction_context *ctx,
+ const char **error_r)
+{
+ struct count_quota_root *croot = (struct count_quota_root *)root;
+
+ croot->cache_timeval.tv_sec = 0;
+ if (ctx->recalculate == QUOTA_RECALCULATE_FORCED) {
+ if (quota_count_recalculate(root, error_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+struct quota_backend quota_backend_count = {
+ .name = "count",
+
+ .v = {
+ .alloc = count_quota_alloc,
+ .init = count_quota_init,
+ .deinit = count_quota_deinit,
+ .get_resources = count_quota_root_get_resources,
+ .get_resource = count_quota_get_resource,
+ .update = count_quota_update,
+ }
+};
diff --git a/src/plugins/quota/quota-dict.c b/src/plugins/quota/quota-dict.c
new file mode 100644
index 0000000..02e444a
--- /dev/null
+++ b/src/plugins/quota/quota-dict.c
@@ -0,0 +1,269 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "dict.h"
+#include "mail-user.h"
+#include "mail-namespace.h"
+#include "quota-private.h"
+
+
+#define DICT_QUOTA_CURRENT_PATH DICT_PATH_PRIVATE"quota/"
+#define DICT_QUOTA_CURRENT_BYTES_PATH DICT_QUOTA_CURRENT_PATH"storage"
+#define DICT_QUOTA_CURRENT_COUNT_PATH DICT_QUOTA_CURRENT_PATH"messages"
+
+struct dict_quota_root {
+ struct quota_root root;
+ struct dict *dict;
+ struct timeout *to_update;
+ bool disable_unset;
+};
+
+extern struct quota_backend quota_backend_dict;
+
+static struct quota_root *dict_quota_alloc(void)
+{
+ struct dict_quota_root *root;
+
+ root = i_new(struct dict_quota_root, 1);
+ return &root->root;
+}
+
+static void handle_nounset_param(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ ((struct dict_quota_root *)_root)->disable_unset = TRUE;
+}
+
+static int dict_quota_init(struct quota_root *_root, const char *args,
+ const char **error_r)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *)_root;
+ struct dict_settings set;
+ const char *username, *p, *error;
+
+ event_set_append_log_prefix(_root->backend.event, "quota-dict: ");
+
+ const struct quota_param_parser dict_params[] = {
+ {.param_name = "no-unset", .param_handler = handle_nounset_param},
+ quota_param_hidden, quota_param_ignoreunlimited, quota_param_noenforcing, quota_param_ns,
+ {.param_name = NULL}
+ };
+
+ p = args == NULL ? NULL : strchr(args, ':');
+ if (p == NULL) {
+ *error_r = "URI missing from parameters";
+ return -1;
+ }
+
+ username = t_strdup_until(args, p);
+ args = p+1;
+
+ if (quota_parse_parameters(_root, &args, error_r, dict_params, FALSE) < 0)
+ i_unreached();
+
+ if (*username == '\0')
+ username = _root->quota->user->username;
+
+ e_debug(_root->backend.event, "user=%s, uri=%s, noenforcing=%d",
+ username, args, _root->no_enforcing ? 1 : 0);
+
+ /* FIXME: we should use 64bit integer as datatype instead but before
+ it can actually be used don't bother */
+ i_zero(&set);
+ set.base_dir = _root->quota->user->set->base_dir;
+ set.event_parent = _root->quota->user->event;
+ if (dict_init(args, &set, &root->dict, &error) < 0) {
+ *error_r = t_strdup_printf("dict_init(%s) failed: %s", args, error);
+ return -1;
+ }
+ return 0;
+}
+
+static void dict_quota_deinit(struct quota_root *_root)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *)_root;
+
+ i_assert(root->to_update == NULL);
+
+ if (root->dict != NULL) {
+ dict_wait(root->dict);
+ dict_deinit(&root->dict);
+ }
+ i_free(root);
+}
+
+static const char *const *
+dict_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES, QUOTA_NAME_MESSAGES, NULL
+ };
+
+ return resources;
+}
+
+static enum quota_get_result
+dict_quota_count(struct dict_quota_root *root,
+ bool want_bytes, uint64_t *value_r,
+ const char **error_r)
+{
+ struct dict_transaction_context *dt;
+ struct event_reason *reason;
+ uint64_t bytes, count;
+ enum quota_get_result error_res;
+ const struct dict_op_settings *set;
+
+ reason = event_reason_begin("quota:recalculate");
+ int ret = quota_count(&root->root, &bytes, &count, &error_res, error_r);
+ event_reason_end(&reason);
+ if (ret < 0)
+ return error_res;
+
+ set = mail_user_get_dict_op_settings(root->root.quota->user);
+ dt = dict_transaction_begin(root->dict, set);
+ /* these unsets are mainly necessary for pgsql, because its
+ trigger otherwise increases quota without deleting it.
+ but some people with other databases want to store the
+ quota usage among other data in the same row, which
+ shouldn't be deleted. */
+ if (!root->disable_unset) {
+ dict_unset(dt, DICT_QUOTA_CURRENT_BYTES_PATH);
+ dict_unset(dt, DICT_QUOTA_CURRENT_COUNT_PATH);
+ }
+ dict_set(dt, DICT_QUOTA_CURRENT_BYTES_PATH, dec2str(bytes));
+ dict_set(dt, DICT_QUOTA_CURRENT_COUNT_PATH, dec2str(count));
+
+ e_debug(root->root.backend.event, "Quota recalculated: "
+ "count=%"PRIu64" bytes=%"PRIu64, count, bytes);
+
+ dict_transaction_commit_async_nocallback(&dt);
+ *value_r = want_bytes ? bytes : count;
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static enum quota_get_result
+dict_quota_get_resource(struct quota_root *_root,
+ const char *name, uint64_t *value_r,
+ const char **error_r)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *)_root;
+ bool want_bytes;
+ int ret;
+ const struct dict_op_settings *set;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ want_bytes = TRUE;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ want_bytes = FALSE;
+ else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+
+ set = mail_user_get_dict_op_settings(root->root.quota->user);
+ const char *key, *value, *error;
+ key = want_bytes ? DICT_QUOTA_CURRENT_BYTES_PATH :
+ DICT_QUOTA_CURRENT_COUNT_PATH;
+ ret = dict_lookup(root->dict, set, unsafe_data_stack_pool,
+ key, &value, &error);
+ if (ret < 0) {
+ *error_r = t_strdup_printf(
+ "dict_lookup(%s) failed: %s", key, error);
+ *value_r = 0;
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ }
+
+ intmax_t tmp;
+ /* recalculate quota if it's negative or if it wasn't found */
+ if (ret == 0 || str_to_intmax(value, &tmp) < 0)
+ tmp = -1;
+ if (tmp >= 0)
+ *value_r = tmp;
+ else
+ return dict_quota_count(root, want_bytes, value_r, error_r);
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static void dict_quota_recalc_timeout(struct dict_quota_root *root)
+{
+ uint64_t value;
+ const char *error;
+
+ timeout_remove(&root->to_update);
+ if (dict_quota_count(root, TRUE, &value, &error)
+ <= QUOTA_GET_RESULT_INTERNAL_ERROR)
+ e_error(root->root.backend.event,
+ "Recalculation failed: %s", error);
+}
+
+static void dict_quota_update_callback(const struct dict_commit_result *result,
+ struct dict_quota_root *root)
+{
+ if (result->ret == 0) {
+ /* row doesn't exist, need to recalculate it */
+ if (root->to_update == NULL)
+ root->to_update = timeout_add_short(0, dict_quota_recalc_timeout, root);
+ } else if (result->ret < 0) {
+ e_error(root->root.backend.event,
+ "Quota update failed: %s "
+ "- Quota is now desynced", result->error);
+ }
+}
+
+static int
+dict_quota_update(struct quota_root *_root,
+ struct quota_transaction_context *ctx,
+ const char **error_r)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *) _root;
+ struct dict_transaction_context *dt;
+ uint64_t value;
+ const struct dict_op_settings *set;
+
+ if (ctx->recalculate != QUOTA_RECALCULATE_DONT) {
+ if (dict_quota_count(root, TRUE, &value, error_r)
+ <= QUOTA_GET_RESULT_INTERNAL_ERROR)
+ return -1;
+ } else {
+ set = mail_user_get_dict_op_settings(root->root.quota->user);
+ dt = dict_transaction_begin(root->dict, set);
+ if (ctx->bytes_used != 0) {
+ dict_atomic_inc(dt, DICT_QUOTA_CURRENT_BYTES_PATH,
+ ctx->bytes_used);
+ }
+ if (ctx->count_used != 0) {
+ dict_atomic_inc(dt, DICT_QUOTA_CURRENT_COUNT_PATH,
+ ctx->count_used);
+ }
+ dict_transaction_no_slowness_warning(dt);
+ dict_transaction_commit_async(&dt, dict_quota_update_callback,
+ root);
+ }
+ return 0;
+}
+
+static void dict_quota_flush(struct quota_root *_root)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *)_root;
+
+ dict_wait(root->dict);
+ if (root->to_update != NULL) {
+ dict_quota_recalc_timeout(root);
+ dict_wait(root->dict);
+ }
+}
+
+struct quota_backend quota_backend_dict = {
+ .name = "dict",
+
+ .v = {
+ .alloc = dict_quota_alloc,
+ .init = dict_quota_init,
+ .deinit = dict_quota_deinit,
+ .get_resources = dict_quota_root_get_resources,
+ .get_resource = dict_quota_get_resource,
+ .update = dict_quota_update,
+ .flush = dict_quota_flush,
+ }
+};
diff --git a/src/plugins/quota/quota-dirsize.c b/src/plugins/quota/quota-dirsize.c
new file mode 100644
index 0000000..a8305d8
--- /dev/null
+++ b/src/plugins/quota/quota-dirsize.c
@@ -0,0 +1,232 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+/* Quota reporting based on simply summing sizes of all files in mailbox
+ together. */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "quota-private.h"
+
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+struct quota_count_path {
+ const char *path;
+ bool is_file;
+};
+ARRAY_DEFINE_TYPE(quota_count_path, struct quota_count_path);
+
+extern struct quota_backend quota_backend_dirsize;
+
+static struct quota_root *dirsize_quota_alloc(void)
+{
+ return i_new(struct quota_root, 1);
+}
+
+static int dirsize_quota_init(struct quota_root *root, const char *args,
+ const char **error_r)
+{
+ root->auto_updating = TRUE;
+ event_set_append_log_prefix(root->backend.event, "quota-dirsize: ");
+ return quota_root_default_init(root, args, error_r);
+}
+
+static void dirsize_quota_deinit(struct quota_root *_root)
+{
+ i_free(_root);
+}
+
+static const char *const *
+dirsize_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources[] = { QUOTA_NAME_STORAGE_KILOBYTES, NULL };
+
+ return resources;
+}
+
+static int get_dir_usage(const char *dir, uint64_t *value,
+ const char **error_r)
+{
+ DIR *dirp;
+ string_t *path;
+ struct dirent *d;
+ struct stat st;
+ unsigned int path_pos;
+ int ret;
+
+ dirp = opendir(dir);
+ if (dirp == NULL) {
+ if (errno == ENOENT)
+ return 0;
+
+ *error_r = t_strdup_printf("opendir(%s) failed: %m", dir);
+ return -1;
+ }
+
+ path = t_str_new(128);
+ str_append(path, dir);
+ str_append_c(path, '/');
+ path_pos = str_len(path);
+
+ ret = 0;
+ while ((d = readdir(dirp)) != NULL) {
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0'))) {
+ /* skip . and .. */
+ continue;
+ }
+
+ str_truncate(path, path_pos);
+ str_append(path, d->d_name);
+
+ if (lstat(str_c(path), &st) < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ *error_r = t_strdup_printf("lstat(%s) failed: %m", dir);
+ ret = -1;
+ break;
+ } else if (S_ISDIR(st.st_mode)) {
+ if (get_dir_usage(str_c(path), value, error_r) < 0) {
+ ret = -1;
+ break;
+ }
+ } else {
+ *value += st.st_size;
+ }
+ }
+
+ (void)closedir(dirp);
+ return ret;
+}
+
+static int get_usage(const char *path, bool is_file, uint64_t *value_r,
+ const char **error_r)
+{
+ struct stat st;
+
+ if (is_file) {
+ if (lstat(path, &st) < 0) {
+ if (errno == ENOENT)
+ return 0;
+
+ *error_r = t_strdup_printf("lstat(%s) failed: %m", path);
+ return -1;
+ }
+ *value_r += st.st_size;
+ } else {
+ if (get_dir_usage(path, value_r, error_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void quota_count_path_add(ARRAY_TYPE(quota_count_path) *paths,
+ const char *path, bool is_file)
+{
+ struct quota_count_path *count_path;
+ unsigned int i, count;
+ size_t path_len;
+
+ path_len = strlen(path);
+ count_path = array_get_modifiable(paths, &count);
+ for (i = 0; i < count; ) {
+ if (strncmp(count_path[i].path, path,
+ strlen(count_path[i].path)) == 0) {
+ /* this path has already been counted */
+ return;
+ }
+ if (strncmp(count_path[i].path, path, path_len) == 0 &&
+ count_path[i].path[path_len] == '/') {
+ /* the new path contains the existing path.
+ drop it and see if there are more to drop. */
+ array_delete(paths, i, 1);
+ count_path = array_get_modifiable(paths, &count);
+ } else {
+ i++;
+ }
+ }
+
+ count_path = array_append_space(paths);
+ count_path->path = t_strdup(path);
+ count_path->is_file = is_file;
+}
+
+static int
+get_quota_root_usage(struct quota_root *root, uint64_t *value_r,
+ const char **error_r)
+{
+ struct mail_namespace *const *namespaces;
+ ARRAY_TYPE(quota_count_path) paths;
+ const struct quota_count_path *count_paths;
+ unsigned int i, count;
+ const char *path;
+ bool is_file;
+
+ t_array_init(&paths, 8);
+ namespaces = array_get(&root->quota->namespaces, &count);
+ for (i = 0; i < count; i++) {
+ if (!quota_root_is_namespace_visible(root, namespaces[i]))
+ continue;
+
+ is_file = mail_storage_is_mailbox_file(namespaces[i]->storage);
+ if (mailbox_list_get_root_path(namespaces[i]->list,
+ MAILBOX_LIST_PATH_TYPE_DIR, &path))
+ quota_count_path_add(&paths, path, FALSE);
+
+ /* INBOX may be in different path. */
+ if (mailbox_list_get_path(namespaces[i]->list, "INBOX",
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) > 0)
+ quota_count_path_add(&paths, path, is_file);
+ }
+
+ /* now sum up the found paths */
+ *value_r = 0;
+ count_paths = array_get(&paths, &count);
+ for (i = 0; i < count; i++) {
+ if (get_usage(count_paths[i].path, count_paths[i].is_file,
+ value_r, error_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static enum quota_get_result
+dirsize_quota_get_resource(struct quota_root *_root, const char *name,
+ uint64_t *value_r, const char **error_r)
+{
+ int ret;
+
+ if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0) {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+
+ ret = get_quota_root_usage(_root, value_r, error_r);
+
+ return ret < 0 ? QUOTA_GET_RESULT_INTERNAL_ERROR : QUOTA_GET_RESULT_LIMITED;
+}
+
+static int
+dirsize_quota_update(struct quota_root *root ATTR_UNUSED,
+ struct quota_transaction_context *ctx ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ return 0;
+}
+
+struct quota_backend quota_backend_dirsize = {
+ .name = "dirsize",
+
+ .v = {
+ .alloc = dirsize_quota_alloc,
+ .init = dirsize_quota_init,
+ .deinit = dirsize_quota_deinit,
+ .get_resources = dirsize_quota_root_get_resources,
+ .get_resource = dirsize_quota_get_resource,
+ .update = dirsize_quota_update,
+ }
+};
diff --git a/src/plugins/quota/quota-fs.c b/src/plugins/quota/quota-fs.c
new file mode 100644
index 0000000..57620c1
--- /dev/null
+++ b/src/plugins/quota/quota-fs.c
@@ -0,0 +1,970 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+/* Only for reporting filesystem quota */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "hostpid.h"
+#include "mountpoint.h"
+#include "quota-private.h"
+#include "quota-fs.h"
+
+#ifdef HAVE_FS_QUOTA
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#ifdef HAVE_LINUX_DQBLK_XFS_H
+# include <linux/dqblk_xfs.h>
+# define HAVE_XFS_QUOTA
+#elif defined (HAVE_XFS_XQM_H)
+# include <xfs/xqm.h> /* CentOS 4.x at least uses this */
+# define HAVE_XFS_QUOTA
+#endif
+
+#ifdef HAVE_RQUOTA
+# include "rquota.h"
+# define RQUOTA_GETQUOTA_TIMEOUT_SECS 10
+#endif
+
+#ifndef DEV_BSIZE
+# ifdef DQBSIZE
+# define DEV_BSIZE DQBSIZE /* AIX */
+# else
+# define DEV_BSIZE 512
+# endif
+#endif
+
+#ifdef HAVE_STRUCT_DQBLK_CURSPACE
+# define dqb_curblocks dqb_curspace
+#endif
+
+/* Very old sys/quota.h doesn't define _LINUX_QUOTA_VERSION at all, which means
+ it supports only v1 quota. However, new sys/quota.h (glibc 2.25) removes
+ support for v1 entirely and again it doesn't define it. I guess we can just
+ assume v2 now, and if someone still wants v1 support they can add
+ -D_LINUX_QUOTA_VERSION=1 to CFLAGS. */
+#ifndef _LINUX_QUOTA_VERSION
+# define _LINUX_QUOTA_VERSION 2
+#endif
+
+#define mount_type_is_nfs(mount) \
+ (strcmp((mount)->type, "nfs") == 0 || \
+ strcmp((mount)->type, "nfs4") == 0)
+
+struct fs_quota_mountpoint {
+ int refcount;
+
+ char *mount_path;
+ char *device_path;
+ char *type;
+ unsigned int block_size;
+
+#ifdef FS_QUOTA_SOLARIS
+ int fd;
+ char *path;
+#endif
+};
+
+struct fs_quota_root {
+ struct quota_root root;
+ char *storage_mount_path;
+
+ uid_t uid;
+ gid_t gid;
+ struct fs_quota_mountpoint *mount;
+
+ bool inode_per_mail:1;
+ bool user_disabled:1;
+ bool group_disabled:1;
+#ifdef FS_QUOTA_NETBSD
+ struct quotahandle *qh;
+#endif
+};
+
+extern struct quota_backend quota_backend_fs;
+
+static struct quota_root *fs_quota_alloc(void)
+{
+ struct fs_quota_root *root;
+
+ root = i_new(struct fs_quota_root, 1);
+ root->uid = geteuid();
+ root->gid = getegid();
+
+ return &root->root;
+}
+
+static void handle_user_param(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ ((struct fs_quota_root *)_root)->group_disabled = TRUE;
+}
+
+static void handle_group_param(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ ((struct fs_quota_root *)_root)->user_disabled = TRUE;
+}
+
+static void handle_inode_param(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ ((struct fs_quota_root *)_root)->inode_per_mail = TRUE;
+}
+
+static void handle_mount_param(struct quota_root *_root, const char *param_value)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+ i_free(root->storage_mount_path);
+ root->storage_mount_path = i_strdup(param_value);
+}
+
+static int fs_quota_init(struct quota_root *_root, const char *args,
+ const char **error_r)
+{
+ const struct quota_param_parser fs_params[] = {
+ {.param_name = "user", .param_handler = handle_user_param},
+ {.param_name = "group", .param_handler = handle_group_param},
+ {.param_name = "mount=", .param_handler = handle_mount_param},
+ {.param_name = "inode_per_mail", .param_handler = handle_inode_param},
+ quota_param_hidden, quota_param_noenforcing, quota_param_ns,
+ {.param_name = NULL}
+ };
+
+ event_set_append_log_prefix(_root->backend.event, "quota-fs: ");
+
+ if (quota_parse_parameters(_root, &args, error_r, fs_params, TRUE) < 0)
+ return -1;
+ _root->auto_updating = TRUE;
+ return 0;
+}
+
+static void fs_quota_mountpoint_free(struct fs_quota_mountpoint *mount)
+{
+ if (--mount->refcount > 0)
+ return;
+
+#ifdef FS_QUOTA_SOLARIS
+ i_close_fd_path(&mount->fd, mount->path);
+ i_free(mount->path);
+#endif
+
+ i_free(mount->device_path);
+ i_free(mount->mount_path);
+ i_free(mount->type);
+ i_free(mount);
+}
+
+static void fs_quota_deinit(struct quota_root *_root)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+
+ if (root->mount != NULL)
+ fs_quota_mountpoint_free(root->mount);
+ i_free(root->storage_mount_path);
+ i_free(root);
+}
+
+static struct fs_quota_mountpoint *fs_quota_mountpoint_get(const char *dir)
+{
+ struct fs_quota_mountpoint *mount;
+ struct mountpoint point;
+ int ret;
+
+ ret = mountpoint_get(dir, default_pool, &point);
+ if (ret <= 0)
+ return NULL;
+
+ mount = i_new(struct fs_quota_mountpoint, 1);
+ mount->refcount = 1;
+ mount->device_path = point.device_path;
+ mount->mount_path = point.mount_path;
+ mount->type = point.type;
+ mount->block_size = point.block_size;
+#ifdef FS_QUOTA_SOLARIS
+ mount->fd = -1;
+#endif
+
+ if (mount_type_is_nfs(mount)) {
+ if (strchr(mount->device_path, ':') == NULL) {
+ e_error(quota_backend_fs.event,
+ "%s is not a valid NFS device path",
+ mount->device_path);
+ fs_quota_mountpoint_free(mount);
+ return NULL;
+ }
+ }
+ return mount;
+}
+
+#define QUOTA_ROOT_MATCH(root, mount) \
+ ((root)->root.backend.name == quota_backend_fs.name && \
+ ((root)->storage_mount_path == NULL || \
+ strcmp((root)->storage_mount_path, (mount)->mount_path) == 0))
+
+static struct fs_quota_root *
+fs_quota_root_find_mountpoint(struct quota *quota,
+ const struct fs_quota_mountpoint *mount)
+{
+ struct quota_root *const *roots;
+ struct fs_quota_root *empty = NULL;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ struct fs_quota_root *root = (struct fs_quota_root *)roots[i];
+ if (QUOTA_ROOT_MATCH(root, mount)) {
+ if (root->mount == NULL)
+ empty = root;
+ else if (strcmp(root->mount->mount_path,
+ mount->mount_path) == 0)
+ return root;
+ }
+ }
+ return empty;
+}
+
+static void
+fs_quota_mount_init(struct fs_quota_root *root,
+ struct fs_quota_mountpoint *mount, const char *dir)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+#ifdef FS_QUOTA_SOLARIS
+#ifdef HAVE_RQUOTA
+ if (mount_type_is_nfs(mount)) {
+ /* using rquota for this mount */
+ } else
+#endif
+ if (mount->path == NULL) {
+ mount->path = i_strconcat(mount->mount_path, "/quotas", NULL);
+ mount->fd = open(mount->path, O_RDONLY);
+ if (mount->fd == -1 && errno != ENOENT)
+ e_error(root->root.backend.event,
+ "open(%s) failed: %m", mount->path);
+ }
+#endif
+ root->mount = mount;
+
+ e_debug(root->root.backend.event, "fs quota add mailbox dir = %s", dir);
+ e_debug(root->root.backend.event, "fs quota block device = %s", mount->device_path);
+ e_debug(root->root.backend.event, "fs quota mount point = %s", mount->mount_path);
+ e_debug(root->root.backend.event, "fs quota mount type = %s", mount->type);
+
+ /* if there are more unused quota roots, copy this mount to them */
+ roots = array_get(&root->root.quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ root = (struct fs_quota_root *)roots[i];
+ if (QUOTA_ROOT_MATCH(root, mount) && root->mount == NULL) {
+ mount->refcount++;
+ root->mount = mount;
+ }
+ }
+}
+
+static void fs_quota_add_missing_mounts(struct quota *quota)
+{
+ struct fs_quota_mountpoint *mount;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ struct fs_quota_root *root = (struct fs_quota_root *)roots[i];
+
+ if (root->root.backend.name != quota_backend_fs.name ||
+ root->storage_mount_path == NULL || root->mount != NULL)
+ continue;
+
+ mount = fs_quota_mountpoint_get(root->storage_mount_path);
+ if (mount != NULL) {
+ fs_quota_mount_init(root, mount,
+ root->storage_mount_path);
+ }
+ }
+}
+
+static void fs_quota_namespace_added(struct quota *quota,
+ struct mail_namespace *ns)
+{
+ struct fs_quota_mountpoint *mount;
+ struct fs_quota_root *root;
+ const char *dir;
+
+ if (!mailbox_list_get_root_path(ns->list, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &dir))
+ mount = NULL;
+ else
+ mount = fs_quota_mountpoint_get(dir);
+ if (mount != NULL) {
+ root = fs_quota_root_find_mountpoint(quota, mount);
+ if (root != NULL && root->mount == NULL)
+ fs_quota_mount_init(root, mount, dir);
+ else
+ fs_quota_mountpoint_free(mount);
+ }
+
+ /* we would actually want to do this only once after all quota roots
+ are created, but there's no way to do this right now */
+ fs_quota_add_missing_mounts(quota);
+}
+
+static const char *const *
+fs_quota_root_get_resources(struct quota_root *_root)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+ static const char *resources_kb[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES,
+ NULL
+ };
+ static const char *resources_kb_messages[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES,
+ QUOTA_NAME_MESSAGES,
+ NULL
+ };
+
+ return root->inode_per_mail ? resources_kb_messages : resources_kb;
+}
+
+#if defined(FS_QUOTA_LINUX) || defined(FS_QUOTA_BSDAIX) || \
+ defined(FS_QUOTA_NETBSD) || defined(HAVE_RQUOTA)
+static void fs_quota_root_disable(struct fs_quota_root *root, bool group)
+{
+ if (group)
+ root->group_disabled = TRUE;
+ else
+ root->user_disabled = TRUE;
+}
+#endif
+
+#ifdef HAVE_RQUOTA
+static void
+rquota_get_result(const rquota *rq,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r)
+{
+ /* use soft limits if they exist, fallback to hard limits */
+
+ /* convert the results from blocks to bytes */
+ *bytes_value_r = (uint64_t)rq->rq_curblocks *
+ (uint64_t)rq->rq_bsize;
+ if (rq->rq_bsoftlimit != 0) {
+ *bytes_limit_r = (uint64_t)rq->rq_bsoftlimit *
+ (uint64_t)rq->rq_bsize;
+ } else {
+ *bytes_limit_r = (uint64_t)rq->rq_bhardlimit *
+ (uint64_t)rq->rq_bsize;
+ }
+
+ *count_value_r = rq->rq_curfiles;
+ if (rq->rq_fsoftlimit != 0)
+ *count_limit_r = rq->rq_fsoftlimit;
+ else
+ *count_limit_r = rq->rq_fhardlimit;
+}
+
+static int
+do_rquota_user(struct fs_quota_root *root,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct getquota_rslt result;
+ struct getquota_args args;
+ struct timeval timeout;
+ enum clnt_stat call_status;
+ CLIENT *cl;
+ struct fs_quota_mountpoint *mount = root->mount;
+ const char *host;
+ char *path;
+
+ path = strchr(mount->device_path, ':');
+ i_assert(path != NULL);
+
+ host = t_strdup_until(mount->device_path, path);
+ path++;
+
+ /* For NFSv4, we send the filesystem path without initial /. Server
+ prepends proper NFS pseudoroot automatically and uses this for
+ detection of NFSv4 mounts. */
+ if (strcmp(root->mount->type, "nfs4") == 0) {
+ while (*path == '/')
+ path++;
+ }
+
+ e_debug(root->root.backend.event, "host=%s, path=%s, uid=%s",
+ host, path, dec2str(root->uid));
+
+ /* clnt_create() polls for a while to establish a connection */
+ cl = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp");
+ if (cl == NULL) {
+ *error_r = t_strdup_printf(
+ "could not contact RPC service on %s", host);
+ return -1;
+ }
+
+ /* Establish some RPC credentials */
+ auth_destroy(cl->cl_auth);
+ cl->cl_auth = authunix_create_default();
+
+ /* make the rquota call on the remote host */
+ args.gqa_pathp = path;
+ args.gqa_uid = root->uid;
+
+ timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS;
+ timeout.tv_usec = 0;
+ call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA,
+ (xdrproc_t)xdr_getquota_args, (char *)&args,
+ (xdrproc_t)xdr_getquota_rslt, (char *)&result,
+ timeout);
+
+ /* the result has been deserialized, let the client go */
+ auth_destroy(cl->cl_auth);
+ clnt_destroy(cl);
+
+ if (call_status != RPC_SUCCESS) {
+ const char *rpc_error_msg = clnt_sperrno(call_status);
+
+ *error_r = t_strdup_printf(
+ "remote rquota call failed: %s",
+ rpc_error_msg);
+ return -1;
+ }
+
+ switch (result.status) {
+ case Q_OK: {
+ rquota_get_result(&result.getquota_rslt_u.gqr_rquota,
+ bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r);
+ e_debug(root->root.backend.event, "uid=%s, bytes=%"PRIu64"/%"PRIu64" "
+ "files=%"PRIu64"/%"PRIu64,
+ dec2str(root->uid),
+ *bytes_value_r, *bytes_limit_r,
+ *count_value_r, *count_limit_r);
+ return 1;
+ }
+ case Q_NOQUOTA:
+ e_debug(root->root.backend.event, "uid=%s, limit=unlimited",
+ dec2str(root->uid));
+ fs_quota_root_disable(root, FALSE);
+ return 0;
+ case Q_EPERM:
+ *error_r = "permission denied to rquota service";
+ return -1;
+ default:
+ *error_r = t_strdup_printf(
+ "unrecognized status code (%d) from rquota service",
+ result.status);
+ return -1;
+ }
+}
+
+static int
+do_rquota_group(struct fs_quota_root *root ATTR_UNUSED,
+ uint64_t *bytes_value_r ATTR_UNUSED,
+ uint64_t *bytes_limit_r ATTR_UNUSED,
+ uint64_t *count_value_r ATTR_UNUSED,
+ uint64_t *count_limit_r ATTR_UNUSED,
+ const char **error_r)
+{
+#if defined(EXT_RQUOTAVERS) && defined(GRPQUOTA)
+ struct getquota_rslt result;
+ ext_getquota_args args;
+ struct timeval timeout;
+ enum clnt_stat call_status;
+ CLIENT *cl;
+ struct fs_quota_mountpoint *mount = root->mount;
+ const char *host;
+ char *path;
+
+ path = strchr(mount->device_path, ':');
+ i_assert(path != NULL);
+
+ host = t_strdup_until(mount->device_path, path);
+ path++;
+
+ e_debug(root->root.backend.event, "host=%s, path=%s, gid=%s",
+ host, path, dec2str(root->gid));
+
+ /* clnt_create() polls for a while to establish a connection */
+ cl = clnt_create(host, RQUOTAPROG, EXT_RQUOTAVERS, "udp");
+ if (cl == NULL) {
+ *error_r = t_strdup_printf(
+ "could not contact RPC service on %s (group)", host);
+ return -1;
+ }
+
+ /* Establish some RPC credentials */
+ auth_destroy(cl->cl_auth);
+ cl->cl_auth = authunix_create_default();
+
+ /* make the rquota call on the remote host */
+ args.gqa_pathp = path;
+ args.gqa_id = root->gid;
+ args.gqa_type = GRPQUOTA;
+ timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS;
+ timeout.tv_usec = 0;
+
+ call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA,
+ (xdrproc_t)xdr_ext_getquota_args, (char *)&args,
+ (xdrproc_t)xdr_getquota_rslt, (char *)&result,
+ timeout);
+
+ /* the result has been deserialized, let the client go */
+ auth_destroy(cl->cl_auth);
+ clnt_destroy(cl);
+
+ if (call_status != RPC_SUCCESS) {
+ const char *rpc_error_msg = clnt_sperrno(call_status);
+
+ *error_r = t_strdup_printf(
+ "remote ext rquota call failed: %s", rpc_error_msg);
+ return -1;
+ }
+
+ switch (result.status) {
+ case Q_OK: {
+ rquota_get_result(&result.getquota_rslt_u.gqr_rquota,
+ bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r);
+ e_debug(root->root.backend.event, "gid=%s, bytes=%"PRIu64"/%"PRIu64" "
+ "files=%"PRIu64"/%"PRIu64,
+ dec2str(root->gid),
+ *bytes_value_r, *bytes_limit_r,
+ *count_value_r, *count_limit_r);
+ return 1;
+ }
+ case Q_NOQUOTA:
+ e_debug(root->root.backend.event, "gid=%s, limit=unlimited",
+ dec2str(root->gid));
+ fs_quota_root_disable(root, TRUE);
+ return 0;
+ case Q_EPERM:
+ *error_r = "permission denied to ext rquota service";
+ return -1;
+ default:
+ *error_r = t_strdup_printf(
+ "unrecognized status code (%d) from ext rquota service",
+ result.status);
+ return -1;
+ }
+#else
+ *error_r = "rquota not compiled with group support";
+ return -1;
+#endif
+}
+#endif
+
+#ifdef FS_QUOTA_LINUX
+static int
+fs_quota_get_linux(struct fs_quota_root *root, bool group,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct dqblk dqblk;
+ int type, id;
+
+ type = group ? GRPQUOTA : USRQUOTA;
+ id = group ? root->gid : root->uid;
+
+#ifdef HAVE_XFS_QUOTA
+ if (strcmp(root->mount->type, "xfs") == 0) {
+ struct fs_disk_quota xdqblk;
+
+ if (quotactl(QCMD(Q_XGETQUOTA, type),
+ root->mount->device_path,
+ id, (caddr_t)&xdqblk) < 0) {
+ if (errno == ESRCH) {
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "errno=%d, quotactl(Q_XGETQUOTA, %s) failed: %m",
+ errno, root->mount->device_path);
+ return -1;
+ }
+
+ /* values always returned in 512 byte blocks */
+ *bytes_value_r = xdqblk.d_bcount * 512ULL;
+ *bytes_limit_r = xdqblk.d_blk_softlimit * 512ULL;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = xdqblk.d_blk_hardlimit * 512ULL;
+ }
+ *count_value_r = xdqblk.d_icount;
+ *count_limit_r = xdqblk.d_ino_softlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = xdqblk.d_ino_hardlimit;
+ }
+ } else
+#endif
+ {
+ /* ext2, ext3 */
+ if (quotactl(QCMD(Q_GETQUOTA, type),
+ root->mount->device_path,
+ id, (caddr_t)&dqblk) < 0) {
+ if (errno == ESRCH) {
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "quotactl(Q_GETQUOTA, %s) failed: %m",
+ root->mount->device_path);
+ if (errno == EINVAL) {
+ *error_r = t_strdup_printf("%s, "
+ "Dovecot was compiled with Linux quota "
+ "v%d support, try changing it "
+ "(CPPFLAGS=-D_LINUX_QUOTA_VERSION=%d configure)",
+ *error_r,
+ _LINUX_QUOTA_VERSION,
+ _LINUX_QUOTA_VERSION == 1 ? 2 : 1);
+ }
+ return -1;
+ }
+
+#if _LINUX_QUOTA_VERSION == 1
+ *bytes_value_r = dqblk.dqb_curblocks * 1024ULL;
+#else
+ *bytes_value_r = dqblk.dqb_curblocks;
+#endif
+ *bytes_limit_r = dqblk.dqb_bsoftlimit * 1024ULL;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = dqblk.dqb_bhardlimit * 1024ULL;
+ }
+ *count_value_r = dqblk.dqb_curinodes;
+ *count_limit_r = dqblk.dqb_isoftlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = dqblk.dqb_ihardlimit;
+ }
+ }
+ return 1;
+}
+#endif
+
+#ifdef FS_QUOTA_BSDAIX
+static int
+fs_quota_get_bsdaix(struct fs_quota_root *root, bool group,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct dqblk dqblk;
+ int type, id;
+
+ type = group ? GRPQUOTA : USRQUOTA;
+ id = group ? root->gid : root->uid;
+
+ if (quotactl(root->mount->mount_path, QCMD(Q_GETQUOTA, type),
+ id, (void *)&dqblk) < 0) {
+ if (errno == ESRCH) {
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "quotactl(Q_GETQUOTA, %s) failed: %m",
+ root->mount->mount_path);
+ return -1;
+ }
+ *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * DEV_BSIZE;
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * DEV_BSIZE;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * DEV_BSIZE;
+ }
+ *count_value_r = dqblk.dqb_curinodes;
+ *count_limit_r = dqblk.dqb_isoftlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = dqblk.dqb_ihardlimit;
+ }
+ return 1;
+}
+#endif
+
+#ifdef FS_QUOTA_NETBSD
+static int
+fs_quota_get_netbsd(struct fs_quota_root *root, bool group,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct quotakey qk;
+ struct quotaval qv;
+ struct quotahandle *qh;
+ int ret;
+
+ if ((qh = quota_open(root->mount->mount_path)) == NULL) {
+ *error_r = t_strdup_printf("cannot open quota for %s: %m",
+ root->mount->mount_path);
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+
+ qk.qk_idtype = group ? QUOTA_IDTYPE_GROUP : QUOTA_IDTYPE_USER;
+ qk.qk_id = group ? root->gid : root->uid;
+
+ for (int i = 0; i < 2; i++) {
+ qk.qk_objtype = i == 0 ? QUOTA_OBJTYPE_BLOCKS : QUOTA_OBJTYPE_FILES;
+
+ if (quota_get(qh, &qk, &qv) != 0) {
+ if (errno == ESRCH) {
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "quotactl(Q_GETQUOTA, %s) failed: %m",
+ root->mount->mount_path);
+ ret = -1;
+ break;
+ }
+ if (i == 0) {
+ *bytes_value_r = qv.qv_usage * DEV_BSIZE;
+ *bytes_limit_r = qv.qv_softlimit * DEV_BSIZE;
+ } else {
+ *count_value_r = qv.qv_usage;
+ *count_limit_r = qv.qv_softlimit;
+ }
+ ret = 1;
+ }
+ quota_close(qh);
+ return ret;
+}
+#endif
+
+#ifdef FS_QUOTA_HPUX
+static int
+fs_quota_get_hpux(struct fs_quota_root *root,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct dqblk dqblk;
+
+ if (quotactl(Q_GETQUOTA, root->mount->device_path,
+ root->uid, &dqblk) < 0) {
+ if (errno == ESRCH) {
+ root->user_disabled = TRUE;
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "quotactl(Q_GETQUOTA, %s) failed: %m",
+ root->mount->device_path);
+ return -1;
+ }
+
+ *bytes_value_r = (uint64_t)dqblk.dqb_curblocks *
+ root->mount->block_size;
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit *
+ root->mount->block_size;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit *
+ root->mount->block_size;
+ }
+ *count_value_r = dqblk.dqb_curfiles;
+ *count_limit_r = dqblk.dqb_fsoftlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = dqblk.dqb_fhardlimit;
+ }
+ return 1;
+}
+#endif
+
+#ifdef FS_QUOTA_SOLARIS
+static int
+fs_quota_get_solaris(struct fs_quota_root *root,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct dqblk dqblk;
+ struct quotctl ctl;
+
+ if (root->mount->fd == -1)
+ return 0;
+
+ ctl.op = Q_GETQUOTA;
+ ctl.uid = root->uid;
+ ctl.addr = (caddr_t)&dqblk;
+ if (ioctl(root->mount->fd, Q_QUOTACTL, &ctl) < 0) {
+ *error_r = t_strdup_printf(
+ "ioctl(%s, Q_QUOTACTL) failed: %m",
+ root->mount->path);
+ return -1;
+ }
+ *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * DEV_BSIZE;
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * DEV_BSIZE;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * DEV_BSIZE;
+ }
+ *count_value_r = dqblk.dqb_curfiles;
+ *count_limit_r = dqblk.dqb_fsoftlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = dqblk.dqb_fhardlimit;
+ }
+ return 1;
+}
+#endif
+
+static int
+fs_quota_get_resources(struct fs_quota_root *root, bool group,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ if (group) {
+ if (root->group_disabled)
+ return 0;
+ } else {
+ if (root->user_disabled)
+ return 0;
+ }
+#ifdef FS_QUOTA_LINUX
+ return fs_quota_get_linux(root, group, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#elif defined (FS_QUOTA_NETBSD)
+ return fs_quota_get_netbsd(root, group, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#elif defined (FS_QUOTA_BSDAIX)
+ return fs_quota_get_bsdaix(root, group, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#else
+ if (group) {
+ /* not supported */
+ return 0;
+ }
+#ifdef FS_QUOTA_HPUX
+ return fs_quota_get_hpux(root, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#else
+ return fs_quota_get_solaris(root, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#endif
+#endif
+}
+
+static bool fs_quota_match_box(struct quota_root *_root, struct mailbox *box)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+ struct stat mst, rst;
+ const char *mailbox_path;
+ bool match;
+
+ if (root->storage_mount_path == NULL)
+ return TRUE;
+
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &mailbox_path) <= 0)
+ return FALSE;
+ if (stat(mailbox_path, &mst) < 0) {
+ if (errno != ENOENT)
+ e_error(_root->backend.event,
+ "stat(%s) failed: %m", mailbox_path);
+ return FALSE;
+ }
+ if (stat(root->storage_mount_path, &rst) < 0) {
+ e_debug(_root->backend.event, "stat(%s) failed: %m",
+ root->storage_mount_path);
+ return FALSE;
+ }
+ match = CMP_DEV_T(mst.st_dev, rst.st_dev);
+ e_debug(_root->backend.event, "box=%s mount=%s match=%s", mailbox_path,
+ root->storage_mount_path, match ? "yes" : "no");
+ return match;
+}
+
+static enum quota_get_result
+fs_quota_get_resource(struct quota_root *_root, const char *name,
+ uint64_t *value_r, const char **error_r)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+ uint64_t bytes_value, count_value;
+ uint64_t bytes_limit = 0, count_limit = 0;
+ int ret;
+
+ *value_r = 0;
+
+ if (root->mount == NULL) {
+ if (root->storage_mount_path != NULL)
+ *error_r = t_strdup_printf(
+ "Mount point unknown for path %s",
+ root->storage_mount_path);
+ else
+ *error_r = "Mount point unknown";
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ }
+ if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0 &&
+ strcasecmp(name, QUOTA_NAME_MESSAGES) != 0) {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+
+#ifdef HAVE_RQUOTA
+ if (mount_type_is_nfs(root->mount)) {
+ ret = root->user_disabled ? 0 :
+ do_rquota_user(root, &bytes_value, &bytes_limit,
+ &count_value, &count_limit, error_r);
+ if (ret == 0 && !root->group_disabled)
+ ret = do_rquota_group(root, &bytes_value,
+ &bytes_limit, &count_value,
+ &count_limit, error_r);
+ } else
+#endif
+ {
+ ret = fs_quota_get_resources(root, FALSE, &bytes_value,
+ &bytes_limit, &count_value,
+ &count_limit, error_r);
+ if (ret == 0) {
+ /* fallback to group quota */
+ ret = fs_quota_get_resources(root, TRUE, &bytes_value,
+ &bytes_limit, &count_value,
+ &count_limit, error_r);
+ }
+ }
+ if (ret < 0)
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ else if (ret == 0)
+ return QUOTA_GET_RESULT_LIMITED;
+
+ if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *value_r = bytes_value;
+ else
+ *value_r = count_value;
+ if (_root->bytes_limit != (int64_t)bytes_limit ||
+ _root->count_limit != (int64_t)count_limit) {
+ /* update limit */
+ _root->bytes_limit = bytes_limit;
+ _root->count_limit = count_limit;
+
+ /* limits have changed, so we'll need to recalculate
+ relative quota rules */
+ quota_root_recalculate_relative_rules(_root->set, bytes_limit, count_limit);
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int
+fs_quota_update(struct quota_root *root ATTR_UNUSED,
+ struct quota_transaction_context *ctx ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ return 0;
+}
+
+struct quota_backend quota_backend_fs = {
+ .name = "fs",
+
+ .v = {
+ .alloc = fs_quota_alloc,
+ .init = fs_quota_init,
+ .deinit = fs_quota_deinit,
+
+ .namespace_added = fs_quota_namespace_added,
+
+ .get_resources = fs_quota_root_get_resources,
+ .get_resource = fs_quota_get_resource,
+ .update = fs_quota_update,
+
+ .match_box = fs_quota_match_box,
+ }
+};
+
+#endif
diff --git a/src/plugins/quota/quota-fs.h b/src/plugins/quota/quota-fs.h
new file mode 100644
index 0000000..8508cdf
--- /dev/null
+++ b/src/plugins/quota/quota-fs.h
@@ -0,0 +1,51 @@
+#ifndef QUOTA_FS_H
+#define QUOTA_FS_H
+
+#if defined (HAVE_STRUCT_DQBLK_CURBLOCKS) || \
+ defined (HAVE_STRUCT_DQBLK_CURSPACE)
+# define HAVE_FS_QUOTA
+#endif
+
+#ifdef HAVE_QUOTA_OPEN
+/* absolute path to avoid confusion with ./quota.h */
+# include "/usr/include/quota.h" /* NetBSD with libquota */
+#endif
+
+#ifdef HAVE_SYS_QUOTA_H
+# include <sys/quota.h> /* Linux, HP-UX */
+#elif defined(HAVE_SYS_FS_UFS_QUOTA_H)
+# include <sys/fs/ufs_quota.h> /* Solaris */
+#elif defined(HAVE_UFS_UFS_QUOTA_H)
+# include <ufs/ufs/quota.h> /* BSDs */
+#elif defined(HAVE_JFS_QUOTA_H)
+# include <jfs/quota.h> /* AIX */
+# ifdef HAVE_SYS_FS_QUOTA_COMMON_H
+# include <sys/fs/quota_common.h> /* quotactl() */
+# endif
+#else
+# undef HAVE_FS_QUOTA
+#endif
+
+#ifdef HAVE_QUOTACTL
+# ifdef HAVE_SYS_QUOTA_H
+# ifndef _HPUX_SOURCE
+# define FS_QUOTA_LINUX
+# else
+# define FS_QUOTA_HPUX
+# endif
+# else
+# define FS_QUOTA_BSDAIX
+# endif
+#elif defined (HAVE_Q_QUOTACTL)
+# define FS_QUOTA_SOLARIS
+#else
+# undef HAVE_FS_QUOTA
+#endif
+
+#ifdef HAVE_QUOTA_OPEN /* NetBSD with libquota */
+# define FS_QUOTA_NETBSD
+# define HAVE_FS_QUOTA
+# undef FS_QUOTA_LINUX /* obtained because we also have <sys/quota.h> */
+#endif
+
+#endif
diff --git a/src/plugins/quota/quota-imapc.c b/src/plugins/quota/quota-imapc.c
new file mode 100644
index 0000000..d5931db
--- /dev/null
+++ b/src/plugins/quota/quota-imapc.c
@@ -0,0 +1,494 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "imap-arg.h"
+#include "imapc-storage.h"
+#include "mailbox-list-private.h"
+#include "quota-private.h"
+
+struct imapc_quota_refresh_root {
+ const char *name;
+ unsigned int order;
+
+ uint64_t bytes_cur, count_cur;
+ uint64_t bytes_limit, count_limit;
+};
+
+struct imapc_quota_refresh {
+ pool_t pool;
+ const char *box_name;
+ ARRAY(struct imapc_quota_refresh_root) roots;
+};
+
+struct imapc_quota_root {
+ struct quota_root root;
+ const char *box_name, *root_name;
+
+ struct mail_namespace *imapc_ns;
+ struct imapc_storage_client *client;
+ bool initialized;
+
+ uint64_t bytes_last, count_last;
+
+ struct timeval last_refresh;
+ struct imapc_quota_refresh refresh;
+};
+
+extern struct quota_backend quota_backend_imapc;
+
+static struct quota_root *imapc_quota_alloc(void)
+{
+ struct imapc_quota_root *root;
+
+ root = i_new(struct imapc_quota_root, 1);
+ return &root->root;
+}
+
+static void handle_box_param(struct quota_root *_root, const char *param_value)
+{
+ ((struct imapc_quota_root *)_root)->box_name = p_strdup(_root->pool, param_value);
+}
+
+static void handle_root_param(struct quota_root *_root, const char *param_value)
+{
+ ((struct imapc_quota_root *)_root)->root_name = p_strdup(_root->pool, param_value);
+}
+
+static int imapc_quota_init(struct quota_root *_root, const char *args,
+ const char **error_r)
+{
+ struct imapc_quota_root *root = (struct imapc_quota_root *)_root;
+ const struct quota_param_parser imapc_params[] = {
+ {.param_name = "box=", .param_handler = handle_box_param},
+ {.param_name = "root=", .param_handler = handle_root_param},
+ quota_param_ns,
+ {.param_name = NULL}
+ };
+
+ _root->auto_updating = TRUE;
+ event_set_append_log_prefix(root->root.backend.event, "quota-imapc: ");
+
+ if (quota_parse_parameters(_root, &args, error_r, imapc_params, TRUE) < 0)
+ return -1;
+
+ if (root->box_name == NULL && root->root_name == NULL)
+ root->box_name = "INBOX";
+ /* we'll never try to enforce the quota - it's just a lot of
+ unnecessary remote GETQUOTA calls. */
+ _root->no_enforcing = TRUE;
+ return 0;
+}
+
+static void imapc_quota_deinit(struct quota_root *_root)
+{
+ i_free(_root);
+}
+
+static void
+imapc_quota_root_namespace_added(struct quota_root *_root,
+ struct mail_namespace *ns)
+{
+ struct imapc_quota_root *root = (struct imapc_quota_root *)_root;
+
+ if (root->imapc_ns == NULL)
+ root->imapc_ns = ns;
+}
+
+static struct imapc_quota_refresh *
+imapc_quota_root_refresh_find(struct imapc_storage_client *client)
+{
+ struct imapc_storage *storage = client->_storage;
+ struct quota *quota;
+ struct quota_root *const *rootp;
+
+ i_assert(storage != NULL);
+ quota = quota_get_mail_user_quota(storage->storage.user);
+ i_assert(quota != NULL);
+
+ /* find the quota root that is being refreshed */
+ array_foreach(&quota->roots, rootp) {
+ if ((*rootp)->backend.name == quota_backend_imapc.name) {
+ struct imapc_quota_root *root =
+ (struct imapc_quota_root *)*rootp;
+
+ if (root->refresh.pool != NULL)
+ return &root->refresh;
+ }
+ }
+ return NULL;
+}
+
+static struct imapc_quota_refresh_root *
+imapc_quota_refresh_root_get(struct imapc_quota_refresh *refresh,
+ const char *root_name)
+{
+ struct imapc_quota_refresh_root *refresh_root;
+
+ array_foreach_modifiable(&refresh->roots, refresh_root) {
+ if (strcmp(refresh_root->name, root_name) == 0)
+ return refresh_root;
+ }
+
+ refresh_root = array_append_space(&refresh->roots);
+ refresh_root->order = UINT_MAX;
+ refresh_root->name = p_strdup(refresh->pool, root_name);
+ refresh_root->bytes_limit = (uint64_t)-1;
+ refresh_root->count_limit = (uint64_t)-1;
+ return refresh_root;
+}
+
+static void imapc_untagged_quotaroot(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ struct imapc_quota_refresh *refresh;
+ struct imapc_quota_refresh_root *refresh_root;
+ const char *mailbox_name, *root_name;
+ unsigned int i;
+
+ if (!imap_arg_get_astring(&reply->args[0], &mailbox_name))
+ return;
+
+ if ((refresh = imapc_quota_root_refresh_find(client)) == NULL ||
+ refresh->box_name == NULL ||
+ strcmp(refresh->box_name, mailbox_name) != 0) {
+ /* unsolicited QUOTAROOT reply - ignore */
+ return;
+ }
+ if (array_count(&refresh->roots) > 0) {
+ /* duplicate QUOTAROOT reply - ignore */
+ return;
+ }
+
+ i = 1;
+ while (imap_arg_get_astring(&reply->args[i], &root_name)) {
+ refresh_root = imapc_quota_refresh_root_get(refresh, root_name);
+ refresh_root->order = i;
+ i++;
+ }
+}
+
+static void imapc_untagged_quota(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ const struct imap_arg *list;
+ struct imapc_quota_refresh *refresh;
+ struct imapc_quota_refresh_root *refresh_root;
+ const char *root_name, *resource, *value_str, *limit_str;
+ uint64_t value, limit;
+ unsigned int i;
+
+ if (!imap_arg_get_astring(&reply->args[0], &root_name) ||
+ !imap_arg_get_list(&reply->args[1], &list))
+ return;
+
+ if ((refresh = imapc_quota_root_refresh_find(client)) == NULL) {
+ /* unsolicited QUOTA reply - ignore */
+ return;
+ }
+ refresh_root = imapc_quota_refresh_root_get(refresh, root_name);
+
+ for (i = 0; list[i].type != IMAP_ARG_EOL; i += 3) {
+ if (!imap_arg_get_atom(&list[i], &resource) ||
+ !imap_arg_get_atom(&list[i+1], &value_str) ||
+ !imap_arg_get_atom(&list[i+2], &limit_str) ||
+ /* RFC2087 uses 32bit number, but be ready for future */
+ str_to_uint64(value_str, &value) < 0 ||
+ str_to_uint64(limit_str, &limit) < 0)
+ return;
+
+ if (strcasecmp(resource, QUOTA_NAME_STORAGE_KILOBYTES) == 0) {
+ refresh_root->bytes_cur = value * 1024;
+ refresh_root->bytes_limit = limit * 1024;
+ } else if (strcasecmp(resource, QUOTA_NAME_MESSAGES) == 0) {
+ refresh_root->count_cur = value;
+ refresh_root->count_limit = limit;
+ }
+ }
+}
+
+static bool imapc_quota_client_init(struct imapc_quota_root *root)
+{
+ struct mailbox_list *list;
+ struct mail_storage *storage;
+
+ if (root->initialized)
+ return root->client != NULL;
+ root->initialized = TRUE;
+
+ list = root->imapc_ns->list;
+ if (mailbox_list_get_storage(&list, "", &storage) == 0 &&
+ strcmp(storage->name, IMAPC_STORAGE_NAME) != 0) {
+ /* non-imapc namespace, skip */
+ if ((storage->class_flags &
+ MAIL_STORAGE_CLASS_FLAG_NOQUOTA) == 0) {
+ e_warning(root->root.backend.event,
+ "Namespace '%s' is not imapc, "
+ "skipping for imapc quota",
+ root->imapc_ns->prefix);
+ }
+ return FALSE;
+ }
+ root->client = ((struct imapc_storage *)storage)->client;
+
+ imapc_storage_client_register_untagged(root->client, "QUOTAROOT",
+ imapc_untagged_quotaroot);
+ imapc_storage_client_register_untagged(root->client, "QUOTA",
+ imapc_untagged_quota);
+ return TRUE;
+}
+
+static void imapc_quota_refresh_init(struct imapc_quota_refresh *refresh)
+{
+ i_assert(refresh->pool == NULL);
+
+ refresh->pool = pool_alloconly_create("imapc quota refresh", 256);
+ p_array_init(&refresh->roots, refresh->pool, 4);
+}
+
+static void
+imapc_quota_refresh_update(struct quota *quota,
+ struct imapc_quota_refresh *refresh)
+{
+ struct quota_root *const *rootp;
+ const struct imapc_quota_refresh_root *refresh_root;
+
+ if (array_count(&refresh->roots) == 0) {
+ e_error(quota_backend_imapc.event,
+ "imapc didn't return any QUOTA results");
+ return;
+ }
+ /* use the first quota root for everything */
+ refresh_root = array_front(&refresh->roots);
+
+ array_foreach(&quota->roots, rootp) {
+ if ((*rootp)->backend.name == quota_backend_imapc.name) {
+ struct imapc_quota_root *root =
+ (struct imapc_quota_root *)*rootp;
+
+ root->bytes_last = refresh_root->bytes_cur;
+ root->count_last = refresh_root->count_cur;
+
+ /* If limits are higher than what dovecot can handle
+ consider them unlimited. */
+ if (refresh_root->bytes_limit > INT64_MAX)
+ root->root.bytes_limit = 0;
+ else
+ root->root.bytes_limit = refresh_root->bytes_limit;
+ if (refresh_root->count_limit > INT64_MAX)
+ root->root.count_limit = 0;
+ else
+ root->root.count_limit = refresh_root->count_limit;
+ }
+ }
+}
+
+static void
+imapc_quota_refresh_deinit(struct quota *quota,
+ struct imapc_quota_refresh *refresh, bool success)
+{
+ if (success)
+ imapc_quota_refresh_update(quota, refresh);
+ pool_unref(&refresh->pool);
+ i_zero(refresh);
+}
+
+static int
+imapc_quota_refresh_root_order_cmp(const struct imapc_quota_refresh_root *root1,
+ const struct imapc_quota_refresh_root *root2)
+{
+ if (root1->order < root2->order)
+ return -1;
+ else if (root1->order > root2->order)
+ return 1;
+ else
+ return 0;
+}
+
+static int imapc_quota_refresh_mailbox(struct imapc_quota_root *root,
+ const char **error_r)
+{
+ struct imapc_simple_context sctx;
+ struct imapc_command *cmd;
+
+ i_assert(root->box_name != NULL);
+
+ /* ask quotas for the configured mailbox */
+ imapc_quota_refresh_init(&root->refresh);
+ root->refresh.box_name = root->box_name;
+
+ imapc_simple_context_init(&sctx, root->client);
+ cmd = imapc_client_cmd(root->client->client,
+ imapc_simple_callback, &sctx);
+ imapc_command_sendf(cmd, "GETQUOTAROOT %s", root->box_name);
+ imapc_simple_run(&sctx, &cmd);
+
+ /* if there are multiple quota roots, use the first one returned by
+ the QUOTAROOT */
+ array_sort(&root->refresh.roots, imapc_quota_refresh_root_order_cmp);
+ imapc_quota_refresh_deinit(root->root.quota, &root->refresh,
+ sctx.ret == 0);
+ if (sctx.ret < 0)
+ *error_r = t_strdup_printf(
+ "GETQUOTAROOT %s failed: %s",
+ root->box_name,
+ mail_storage_get_last_internal_error(
+ &root->client->_storage->storage, NULL));
+
+ return sctx.ret;
+}
+
+static int imapc_quota_refresh_root(struct imapc_quota_root *root,
+ const char **error_r)
+{
+ struct imapc_simple_context sctx;
+ struct imapc_command *cmd;
+
+ i_assert(root->root_name != NULL);
+
+ /* ask quotas for the configured quota root */
+ imapc_quota_refresh_init(&root->refresh);
+
+ imapc_simple_context_init(&sctx, root->client);
+ cmd = imapc_client_cmd(root->client->client,
+ imapc_simple_callback, &sctx);
+ imapc_command_sendf(cmd, "GETQUOTA %s", root->root_name);
+ imapc_simple_run(&sctx, &cmd);
+
+ /* there shouldn't be more than one QUOTA reply, but ignore anyway
+ anything we didn't expect. */
+ while (array_count(&root->refresh.roots) > 0) {
+ const struct imapc_quota_refresh_root *refresh_root =
+ array_front(&root->refresh.roots);
+ if (strcmp(refresh_root->name, root->root_name) == 0)
+ break;
+ array_pop_front(&root->refresh.roots);
+ }
+ imapc_quota_refresh_deinit(root->root.quota, &root->refresh,
+ sctx.ret == 0);
+ if (sctx.ret < 0)
+ *error_r = t_strdup_printf(
+ "GETQUOTA %s failed: %s",
+ root->root_name,
+ mail_storage_get_last_internal_error(
+ &root->client->_storage->storage, NULL));
+ return sctx.ret;
+}
+
+static int imapc_quota_refresh(struct imapc_quota_root *root,
+ const char **error_r)
+{
+ enum imapc_capability capa;
+ int ret;
+
+ if (root->imapc_ns == NULL) {
+ /* imapc namespace is missing - disable this quota backend */
+ return 0;
+ }
+ if (root->last_refresh.tv_sec == ioloop_timeval.tv_sec &&
+ root->last_refresh.tv_usec == ioloop_timeval.tv_usec)
+ return 0;
+ if (!imapc_quota_client_init(root))
+ return 0;
+
+ if (imapc_client_get_capabilities(root->client->client, &capa) < 0) {
+ *error_r = "Failed to get server capabilities";
+ return -1;
+ }
+ if ((capa & IMAPC_CAPABILITY_QUOTA) == 0) {
+ /* no QUOTA capability - disable quota */
+ e_warning(root->root.backend.event,
+ "Remote IMAP server doesn't support QUOTA - disabling");
+ root->client = NULL;
+ return 0;
+ }
+
+ if (root->root_name == NULL)
+ ret = imapc_quota_refresh_mailbox(root, error_r);
+ else
+ ret = imapc_quota_refresh_root(root, error_r);
+
+ /* set the last_refresh only after the refresh, because it changes
+ ioloop_timeval. */
+ root->last_refresh = ioloop_timeval;
+ return ret;
+}
+
+static int imapc_quota_init_limits(struct quota_root *_root,
+ const char **error_r)
+{
+ struct imapc_quota_root *root = (struct imapc_quota_root *)_root;
+
+ return imapc_quota_refresh(root, error_r);
+}
+
+static void
+imapc_quota_namespace_added(struct quota *quota, struct mail_namespace *ns)
+{
+ struct quota_root **roots;
+ unsigned int i, count;
+
+ roots = array_get_modifiable(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (roots[i]->backend.name == quota_backend_imapc.name &&
+ ((roots[i]->ns_prefix == NULL &&
+ ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) ||
+ roots[i]->ns == ns))
+ imapc_quota_root_namespace_added(roots[i], ns);
+ }
+}
+
+static const char *const *
+imapc_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources_both[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES,
+ QUOTA_NAME_MESSAGES,
+ NULL
+ };
+ return resources_both;
+}
+
+static enum quota_get_result
+imapc_quota_get_resource(struct quota_root *_root, const char *name,
+ uint64_t *value_r, const char **error_r)
+{
+ struct imapc_quota_root *root = (struct imapc_quota_root *)_root;
+
+ if (imapc_quota_refresh(root, error_r) < 0)
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *value_r = root->bytes_last;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ *value_r = root->count_last;
+ else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int
+imapc_quota_update(struct quota_root *root ATTR_UNUSED,
+ struct quota_transaction_context *ctx ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ return 0;
+}
+
+struct quota_backend quota_backend_imapc = {
+ .name = "imapc",
+
+ .v = {
+ .alloc = imapc_quota_alloc,
+ .init = imapc_quota_init,
+ .deinit = imapc_quota_deinit,
+ .init_limits = imapc_quota_init_limits,
+ .namespace_added = imapc_quota_namespace_added,
+ .get_resources = imapc_quota_root_get_resources,
+ .get_resource = imapc_quota_get_resource,
+ .update = imapc_quota_update,
+ }
+};
diff --git a/src/plugins/quota/quota-maildir.c b/src/plugins/quota/quota-maildir.c
new file mode 100644
index 0000000..f4fd3a7
--- /dev/null
+++ b/src/plugins/quota/quota-maildir.c
@@ -0,0 +1,953 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "nfs-workarounds.h"
+#include "safe-mkstemp.h"
+#include "mkdir-parents.h"
+#include "read-full.h"
+#include "write-full.h"
+#include "str.h"
+#include "maildir-storage.h"
+#include "mailbox-list-private.h"
+#include "quota-private.h"
+
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#define MAILDIRSIZE_FILENAME "maildirsize"
+#define MAILDIRSIZE_STALE_SECS (60*15)
+
+struct maildir_quota_root {
+ struct quota_root root;
+
+ struct mail_namespace *maildirsize_ns;
+ const char *maildirsize_path;
+
+ uint64_t total_bytes;
+ uint64_t total_count;
+
+ int fd;
+ time_t recalc_last_stamp;
+ off_t last_size;
+
+ bool limits_initialized:1;
+};
+
+struct maildir_list_context {
+ struct mailbox_list *list;
+ struct maildir_quota_root *root;
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+
+ string_t *path;
+ int state;
+};
+
+extern struct quota_backend quota_backend_maildir;
+
+static struct dotlock_settings dotlock_settings = {
+ .timeout = 0,
+ .stale_timeout = 30
+};
+
+static int maildir_sum_dir(const char *dir, uint64_t *total_bytes,
+ uint64_t *total_count, const char **error_r)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ string_t *path;
+ const char *p;
+ size_t len;
+ uoff_t num;
+ int ret = 0;
+
+ dirp = opendir(dir);
+ if (dirp == NULL) {
+ if (errno == ENOENT || errno == ESTALE)
+ return 0;
+ *error_r = t_strdup_printf("opendir(%s) failed: %m", dir);
+ return -1;
+ }
+
+ path = t_str_new(256);
+ str_append(path, dir);
+ str_append_c(path, '/');
+
+ len = str_len(path);
+ while ((dp = readdir(dirp)) != NULL) {
+ if (dp->d_name[0] == '.' &&
+ (dp->d_name[1] == '\0' || dp->d_name[1] == '.'))
+ continue;
+
+ p = strstr(dp->d_name, ",S=");
+ num = UOFF_T_MAX;
+ if (p != NULL) {
+ /* ,S=nnnn[:,] */
+ p += 3;
+ for (num = 0; *p >= '0' && *p <= '9'; p++)
+ num = num * 10 + (*p - '0');
+
+ if (*p != ':' && *p != '\0' && *p != ',') {
+ /* not in expected format, fallback to stat() */
+ num = UOFF_T_MAX;
+ } else {
+ *total_bytes += num;
+ *total_count += 1;
+ }
+ }
+ if (num == UOFF_T_MAX) {
+ struct stat st;
+
+ str_truncate(path, len);
+ str_append(path, dp->d_name);
+ if (stat(str_c(path), &st) == 0) {
+ *total_bytes += st.st_size;
+ *total_count += 1;
+ } else if (errno != ENOENT && errno != ESTALE) {
+ *error_r = t_strdup_printf(
+ "stat(%s) failed: %m", str_c(path));
+ ret = -1;
+ }
+ }
+ }
+
+ if (closedir(dirp) < 0) {
+ *error_r = t_strdup_printf("closedir(%s) failed: %m", dir);
+ return -1;
+ }
+ return ret;
+}
+
+static struct maildir_list_context *
+maildir_list_init(struct maildir_quota_root *root, struct mailbox_list *list)
+{
+ struct maildir_list_context *ctx;
+
+ ctx = i_new(struct maildir_list_context, 1);
+ ctx->root = root;
+ ctx->path = str_new(default_pool, 512);
+ ctx->list = list;
+ ctx->iter = mailbox_list_iter_init(list, "*",
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ return ctx;
+}
+
+static bool maildir_set_next_path(struct maildir_list_context *ctx)
+{
+ const char *path, *storage_name;
+
+ str_truncate(ctx->path, 0);
+
+ storage_name = mailbox_list_get_storage_name(
+ ctx->info->ns->list, ctx->info->vname);
+ if (mailbox_list_get_path(ctx->list, storage_name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path) > 0) {
+ str_append(ctx->path, path);
+ str_append(ctx->path, ctx->state == 0 ?
+ "/new" : "/cur");
+ }
+
+ return str_len(ctx->path) > 0;
+}
+
+static const char *
+maildir_list_next(struct maildir_list_context *ctx, time_t *mtime_r)
+{
+ struct quota_rule *rule;
+ struct stat st;
+
+ for (;;) {
+ if (ctx->state == 0) {
+ ctx->info = mailbox_list_iter_next(ctx->iter);
+ if (ctx->info == NULL)
+ return NULL;
+
+ rule = quota_root_rule_find(ctx->root->root.set,
+ ctx->info->vname);
+ if (rule != NULL && rule->ignore) {
+ /* mailbox not included in quota */
+ continue;
+ }
+ }
+
+ if (!maildir_set_next_path(ctx)) {
+ ctx->state = 0;
+ continue;
+ }
+
+ if (++ctx->state == 2)
+ ctx->state = 0;
+
+ if (stat(str_c(ctx->path), &st) == 0)
+ break;
+ /* ignore if the directory got lost, stale or if it was
+ actually a file and not a directory */
+ if (errno != ENOENT && errno != ESTALE && errno != ENOTDIR) {
+ e_error(ctx->root->root.backend.event,
+ "stat(%s) failed: %m", str_c(ctx->path));
+ ctx->state = 0;
+ }
+ }
+
+ *mtime_r = st.st_mtime;
+ return str_c(ctx->path);
+}
+
+static int maildir_list_deinit(struct maildir_list_context *ctx,
+ const char **error_r)
+{
+ int ret = mailbox_list_iter_deinit(&ctx->iter);
+ if (ret < 0)
+ *error_r = t_strdup_printf(
+ "Listing mailboxes failed: %s",
+ mailbox_list_get_last_internal_error(ctx->list, NULL));
+
+ str_free(&ctx->path);
+ i_free(ctx);
+ return ret;
+}
+
+static int
+maildirs_check_have_changed(struct maildir_quota_root *root,
+ struct mail_namespace *ns, time_t latest_mtime,
+ const char **error_r)
+{
+ struct maildir_list_context *ctx;
+ time_t mtime;
+ int ret = 0;
+
+ ctx = maildir_list_init(root, ns->list);
+ while (maildir_list_next(ctx, &mtime) != NULL) {
+ if (mtime > latest_mtime) {
+ ret = 1;
+ break;
+ }
+ }
+ if (maildir_list_deinit(ctx, error_r) < 0)
+ return -1;
+ return ret;
+}
+
+static int maildirsize_write(struct maildir_quota_root *root, const char *path)
+{
+ const struct mail_storage_settings *set =
+ root->maildirsize_ns->mail_set;
+ struct quota_root *_root = &root->root;
+ struct mail_namespace *const *namespaces;
+ unsigned int i, count;
+ struct mailbox_permissions perm;
+ const char *p, *dir;
+ string_t *str, *temp_path;
+ int fd;
+
+ i_assert(root->fd == -1);
+
+ /* figure out what permissions we should use for maildirsize.
+ use the inbox namespace's permissions if possible. */
+ perm.file_create_mode = 0600; perm.dir_create_mode = 0700;
+ perm.file_create_gid = (gid_t)-1;
+ perm.file_create_gid_origin = "default";
+ namespaces = array_get(&root->root.quota->namespaces, &count);
+ i_assert(count > 0);
+ for (i = 0; i < count; i++) {
+ if ((namespaces[i]->flags & NAMESPACE_FLAG_INBOX_USER) == 0)
+ continue;
+
+ mailbox_list_get_root_permissions(namespaces[i]->list,
+ &perm);
+ break;
+ }
+
+ dotlock_settings.use_excl_lock = set->dotlock_use_excl;
+ dotlock_settings.nfs_flush = set->mail_nfs_storage;
+
+ temp_path = t_str_new(128);
+ str_append(temp_path, path);
+ fd = safe_mkstemp_hostpid_group(temp_path, perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin);
+ if (fd == -1 && errno == ENOENT) {
+ /* the control directory doesn't exist yet? create it */
+ p = strrchr(path, '/');
+ dir = t_strdup_until(path, p);
+ if (mkdir_parents_chgrp(dir, perm.dir_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin) < 0 &&
+ errno != EEXIST) {
+ e_error(root->root.backend.event,
+ "mkdir_parents(%s) failed: %m", dir);
+ return -1;
+ }
+ fd = safe_mkstemp_hostpid_group(temp_path,
+ perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin);
+ }
+ if (fd == -1) {
+ e_error(root->root.backend.event,
+ "safe_mkstemp(%s) failed: %m", path);
+ return -1;
+ }
+
+ str = t_str_new(128);
+ /* if we have no limits, write 0S instead of an empty line */
+ if (_root->bytes_limit != 0 || _root->count_limit == 0) {
+ str_printfa(str, "%"PRId64"S", _root->bytes_limit);
+ }
+ if (_root->count_limit != 0) {
+ if (str_len(str) > 0)
+ str_append_c(str, ',');
+ str_printfa(str, "%"PRIu64"C", _root->count_limit);
+ }
+ str_printfa(str, "\n%"PRIu64" %"PRIu64"\n",
+ root->total_bytes, root->total_count);
+ if (write_full(fd, str_data(str), str_len(str)) < 0) {
+ e_error(root->root.backend.event,
+ "write_full(%s) failed: %m", str_c(temp_path));
+ i_close_fd(&fd);
+ i_unlink(str_c(temp_path));
+ return -1;
+ }
+ i_close_fd(&fd);
+
+ if (rename(str_c(temp_path), path) < 0) {
+ e_error(root->root.backend.event,
+ "rename(%s, %s) failed: %m", str_c(temp_path), path);
+ i_unlink_if_exists(str_c(temp_path));
+ return -1;
+ }
+ return 0;
+}
+
+static void maildirsize_recalculate_init(struct maildir_quota_root *root)
+{
+ root->total_bytes = root->total_count = 0;
+ root->recalc_last_stamp = 0;
+}
+
+static int maildirsize_recalculate_namespace(struct maildir_quota_root *root,
+ struct mail_namespace *ns,
+ const char **error_r)
+{
+ struct maildir_list_context *ctx;
+ const char *dir;
+ time_t mtime;
+ int ret = 0;
+
+ ctx = maildir_list_init(root, ns->list);
+ while ((dir = maildir_list_next(ctx, &mtime)) != NULL) {
+ if (mtime > root->recalc_last_stamp)
+ root->recalc_last_stamp = mtime;
+
+ if (maildir_sum_dir(dir, &root->total_bytes,
+ &root->total_count, error_r) < 0)
+ ret = -1;
+ }
+ if (maildir_list_deinit(ctx, error_r) < 0)
+ ret = -1;
+
+ return ret;
+}
+
+static void maildirsize_rebuild_later(struct maildir_quota_root *root)
+{
+ if (!root->root.set->force_default_rule) {
+ /* FIXME: can't unlink(), because the limits would be lost. */
+ return;
+ }
+
+ if (unlink(root->maildirsize_path) < 0 &&
+ errno != ENOENT && errno != ESTALE)
+ e_error(root->root.backend.event,
+ "unlink(%s) failed: %m", root->maildirsize_path);
+}
+
+static int maildirsize_recalculate_finish(struct maildir_quota_root *root,
+ int ret, const char **error_r)
+{
+ if (ret == 0) {
+ /* maildir didn't change, we can write the maildirsize file */
+ if ((ret = maildirsize_write(root, root->maildirsize_path)) < 0)
+ *error_r = "failed to write maildirsize";
+ }
+ if (ret != 0)
+ maildirsize_rebuild_later(root);
+
+ return ret;
+}
+
+static int maildirsize_recalculate(struct maildir_quota_root *root,
+ const char **error_r)
+{
+ struct mail_namespace *const *namespaces;
+ struct event_reason *reason;
+ unsigned int i, count;
+ int ret = 0;
+
+ reason = event_reason_begin("quota:recalculate");
+ maildirsize_recalculate_init(root);
+
+ /* count mails from all namespaces */
+ namespaces = array_get(&root->root.quota->namespaces, &count);
+ for (i = 0; i < count; i++) {
+ if (!quota_root_is_namespace_visible(&root->root, namespaces[i]))
+ continue;
+
+ if (maildirsize_recalculate_namespace(root, namespaces[i], error_r) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+
+ if (ret == 0) {
+ /* check if any of the directories have changed */
+ for (i = 0; i < count; i++) {
+ if (!quota_root_is_namespace_visible(&root->root,
+ namespaces[i]))
+ continue;
+
+ ret = maildirs_check_have_changed(root, namespaces[i],
+ root->recalc_last_stamp,
+ error_r);
+ if (ret != 0)
+ break;
+ }
+ }
+
+ ret = maildirsize_recalculate_finish(root, ret, error_r);
+ event_reason_end(&reason);
+ return ret;
+}
+
+static bool
+maildir_parse_limit(const char *str, uint64_t *bytes_r, uint64_t *count_r)
+{
+ const char *const *limit;
+ unsigned long long value;
+ const char *pos;
+ bool ret = TRUE;
+
+ *bytes_r = 0;
+ *count_r = 0;
+
+ /* 0 values mean unlimited */
+ for (limit = t_strsplit(str, ","); *limit != NULL; limit++) {
+ if (str_parse_ullong(*limit, &value, &pos) < 0) {
+ ret = FALSE;
+ continue;
+ }
+ if (pos[0] != '\0' && pos[1] == '\0') {
+ switch (pos[0]) {
+ case 'C':
+ if (value != 0)
+ *count_r = value;
+ break;
+ case 'S':
+ if (value != 0)
+ *bytes_r = value;
+ break;
+ default:
+ ret = FALSE;
+ break;
+ }
+ } else {
+ ret = FALSE;
+ }
+ }
+ return ret;
+}
+
+static int maildirsize_parse(struct maildir_quota_root *root,
+ int fd, const char *const *lines)
+{
+ struct quota_root *_root = &root->root;
+ uint64_t message_bytes_limit, message_count_limit;
+ long long bytes_diff, total_bytes;
+ int count_diff, total_count;
+ unsigned int line_count = 0;
+
+ if (*lines == NULL)
+ return -1;
+
+ /* first line contains the limits */
+ (void)maildir_parse_limit(lines[0], &message_bytes_limit,
+ &message_count_limit);
+
+ /* truncate too high limits to signed 64bit int range */
+ if (message_bytes_limit >= (1ULL << 63))
+ message_bytes_limit = (1ULL << 63) - 1;
+ if (message_count_limit >= (1ULL << 63))
+ message_count_limit = (1ULL << 63) - 1;
+
+ if (root->root.bytes_limit == (int64_t)message_bytes_limit &&
+ root->root.count_limit == (int64_t)message_count_limit) {
+ /* limits haven't changed */
+ } else if (root->root.set->force_default_rule) {
+ /* we know the limits and they've changed.
+ the file must be rewritten. */
+ return 0;
+ } else {
+ /* we're using limits from the file. */
+ root->root.bytes_limit = message_bytes_limit;
+ root->root.count_limit = message_count_limit;
+ quota_root_recalculate_relative_rules(root->root.set,
+ message_bytes_limit,
+ message_count_limit);
+ }
+
+ if (*lines == NULL) {
+ /* no quota lines. rebuild it. */
+ return 0;
+ }
+
+ /* rest of the lines contains <bytes> <count> diffs */
+ total_bytes = 0; total_count = 0;
+ for (lines++; *lines != NULL; lines++, line_count++) {
+ if (sscanf(*lines, "%lld %d", &bytes_diff, &count_diff) != 2)
+ return -1;
+
+ total_bytes += bytes_diff;
+ total_count += count_diff;
+ }
+
+ if (total_bytes < 0 || total_count < 0) {
+ /* corrupted */
+ return -1;
+ }
+
+ if ((total_bytes > _root->bytes_limit && _root->bytes_limit != 0) ||
+ (total_count > _root->count_limit && _root->count_limit != 0)) {
+ /* we're over quota. don't trust these values if the file
+ contains more than the initial summary line, or if the file
+ is older than 15 minutes. */
+ struct stat st;
+
+ if (line_count > 1)
+ return 0;
+
+ if (fstat(fd, &st) < 0 ||
+ st.st_mtime < ioloop_time - MAILDIRSIZE_STALE_SECS)
+ return 0;
+ }
+ root->total_bytes = (uint64_t)total_bytes;
+ root->total_count = (uint64_t)total_count;
+ return 1;
+}
+
+static int maildirsize_open(struct maildir_quota_root *root,
+ const char **error_r)
+{
+ i_close_fd_path(&root->fd, root->maildirsize_path);
+
+ root->fd = nfs_safe_open(root->maildirsize_path, O_RDWR | O_APPEND);
+ if (root->fd == -1) {
+ if (errno == ENOENT)
+ return 0;
+ *error_r = t_strdup_printf(
+ "open(%s) failed: %m", root->maildirsize_path);
+ return -1;
+ }
+ return 1;
+}
+
+static bool maildirsize_has_changed(struct maildir_quota_root *root)
+{
+ struct stat st1, st2;
+
+ if (dotlock_settings.nfs_flush) {
+ nfs_flush_file_handle_cache(root->maildirsize_path);
+ nfs_flush_attr_cache_unlocked(root->maildirsize_path);
+ }
+
+ if (root->fd == -1)
+ return TRUE;
+
+ if (stat(root->maildirsize_path, &st1) < 0)
+ return TRUE;
+ if (fstat(root->fd, &st2) < 0)
+ return TRUE;
+
+ return root->last_size != st2.st_size || st1.st_ino != st2.st_ino ||
+ !CMP_DEV_T(st1.st_dev, st2.st_dev);
+}
+
+static int maildirsize_read(struct maildir_quota_root *root, bool *retry,
+ const char **error_r)
+{
+ char buf[5120+1];
+ unsigned int i, size;
+ bool retry_estale = *retry;
+ int ret;
+
+ *retry = FALSE;
+
+ if (!maildirsize_has_changed(root))
+ return 1;
+
+ if ((ret = maildirsize_open(root, error_r)) <= 0)
+ return ret;
+
+ /* @UNSAFE */
+ size = 0;
+ while ((ret = read(root->fd, buf + size, sizeof(buf)-1 - size)) != 0) {
+ if (ret < 0) {
+ if (errno == ESTALE && retry_estale) {
+ *retry = TRUE;
+ break;
+ }
+ *error_r = t_strdup_printf(
+ "read(%s) failed: %m", root->maildirsize_path);
+ break;
+ }
+ size += ret;
+ if (size >= sizeof(buf)-1) {
+ /* we'll need to recalculate the quota */
+ break;
+ }
+ }
+
+ /* try to use the file even if we ran into some error. if we don't have
+ forced limits, we'll need to read the header to get them */
+ root->total_bytes = root->total_count = 0;
+ root->last_size = size;
+
+ /* skip the last line if there's no LF at the end. Remove the last LF
+ so we don't get one empty line in the strsplit. */
+ while (size > 0 && buf[size-1] != '\n') size--;
+ if (size > 0) size--;
+ buf[size] = '\0';
+
+ if (ret < 0 && size == 0) {
+ /* the read failed and there's no usable header, fail. */
+ i_close_fd(&root->fd);
+ return -1;
+ }
+
+ /* If there are any NUL bytes, the file is broken. */
+ for (i = 0; i < size; i++) {
+ if (buf[i] == '\0')
+ break;
+ }
+
+ if (i == size &&
+ maildirsize_parse(root, root->fd, t_strsplit(buf, "\n")) > 0 &&
+ ret == 0)
+ ret = 1;
+ else {
+ /* broken file / need recalculation */
+ i_close_fd(&root->fd);
+ ret = 0;
+ }
+ return ret;
+}
+
+static bool maildirquota_limits_init(struct maildir_quota_root *root)
+{
+ struct mailbox_list *list;
+ struct mail_storage *storage;
+ const char *control_dir;
+
+ if (root->limits_initialized)
+ return root->maildirsize_path != NULL;
+ root->limits_initialized = TRUE;
+
+ if (root->maildirsize_ns == NULL) {
+ i_assert(root->maildirsize_path == NULL);
+ return FALSE;
+ }
+
+ list = root->maildirsize_ns->list;
+ if (mailbox_list_get_storage(&list, "", &storage) == 0 &&
+ strcmp(storage->name, MAILDIR_STORAGE_NAME) != 0) {
+ /* non-maildir namespace, skip */
+ if ((storage->class_flags &
+ MAIL_STORAGE_CLASS_FLAG_NOQUOTA) == 0) {
+ e_warning(root->root.backend.event,
+ "Namespace '%s' is not Maildir, "
+ "skipping for Maildir++ quota",
+ root->maildirsize_ns->prefix);
+ }
+ root->maildirsize_path = NULL;
+ return FALSE;
+ }
+ if (root->maildirsize_path == NULL) {
+ if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_CONTROL,
+ &control_dir))
+ i_unreached();
+ root->maildirsize_path =
+ p_strconcat(root->root.pool, control_dir,
+ "/"MAILDIRSIZE_FILENAME, NULL);
+ }
+ return TRUE;
+}
+
+static int maildirquota_read_limits(struct maildir_quota_root *root,
+ const char **error_r)
+{
+ bool retry = TRUE;
+ int ret, n = 0;
+
+ if (!maildirquota_limits_init(root))
+ return 1;
+
+ do {
+ if (n == NFS_ESTALE_RETRY_COUNT)
+ retry = FALSE;
+ ret = maildirsize_read(root, &retry, error_r);
+ n++;
+ } while (ret == -1 && retry);
+ return ret;
+}
+
+static int
+maildirquota_refresh(struct maildir_quota_root *root, bool *recalculated_r,
+ const char **error_r)
+{
+ int ret;
+
+ *recalculated_r = FALSE;
+
+ ret = maildirquota_read_limits(root, error_r);
+ if (ret == 0) {
+ if (root->root.bytes_limit == 0 &&
+ root->root.count_limit == 0 &&
+ root->root.set->default_rule.bytes_limit == 0 &&
+ root->root.set->default_rule.count_limit == 0) {
+ /* no quota */
+ if (!root->root.set->force_default_rule)
+ return 0;
+ /* explicitly specified 0 as quota. keep the quota
+ updated even if it's not enforced. */
+ }
+
+ ret = maildirsize_recalculate(root, error_r);
+ if (ret == 0)
+ *recalculated_r = TRUE;
+ }
+ return ret < 0 ? -1 : 0;
+}
+
+static int maildirsize_update(struct maildir_quota_root *root,
+ int count_diff, int64_t bytes_diff)
+{
+ char str[MAX_INT_STRLEN * 2 + 2];
+ int ret = 0;
+
+ if (count_diff == 0 && bytes_diff == 0)
+ return 0;
+
+ /* We rely on O_APPEND working in here. That isn't NFS-safe, but it
+ isn't necessarily that bad because the file is recreated once in
+ a while, and sooner if corruption causes calculations to go
+ over quota. This is also how Maildir++ spec specifies it should be
+ done.. */
+ if (i_snprintf(str, sizeof(str), "%lld %d\n",
+ (long long)bytes_diff, count_diff) < 0)
+ i_unreached();
+ if (write_full(root->fd, str, strlen(str)) < 0) {
+ ret = -1;
+ if (errno == ESTALE) {
+ /* deleted/replaced already, ignore */
+ } else {
+ e_error(root->root.backend.event,
+ "write_full(%s) failed: %m",
+ root->maildirsize_path);
+ }
+ } else {
+ /* close the file to force a flush with NFS */
+ if (close(root->fd) < 0) {
+ ret = -1;
+ if (errno != ESTALE)
+ e_error(root->root.backend.event,
+ "close(%s) failed: %m", root->maildirsize_path);
+ }
+ root->fd = -1;
+ }
+ return ret;
+}
+
+static struct quota_root *maildir_quota_alloc(void)
+{
+ struct maildir_quota_root *root;
+
+ root = i_new(struct maildir_quota_root, 1);
+ root->fd = -1;
+ return &root->root;
+}
+
+static int maildir_quota_init(struct quota_root *_root, const char *args,
+ const char **error_r)
+{
+ event_set_append_log_prefix(_root->backend.event, "quota-maildir: ");
+ return quota_root_default_init(_root, args, error_r);
+}
+
+static void maildir_quota_deinit(struct quota_root *_root)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+
+ i_close_fd(&root->fd);
+ i_free(root);
+}
+
+static bool
+maildir_quota_parse_rule(struct quota_root_settings *root_set ATTR_UNUSED,
+ struct quota_rule *rule,
+ const char *str, const char **error_r)
+{
+ uint64_t bytes, count;
+
+ if (strcmp(str, "NOQUOTA") == 0) {
+ bytes = 0;
+ count = 0;
+ } else if (!maildir_parse_limit(str, &bytes, &count)) {
+ *error_r = t_strdup_printf(
+ "quota-maildir: Invalid Maildir++ quota rule \"%s\"",
+ str);
+ return FALSE;
+ }
+
+ rule->bytes_limit = bytes;
+ rule->count_limit = count;
+ return TRUE;
+}
+
+static int maildir_quota_init_limits(struct quota_root *_root,
+ const char **error_r)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+ const char *error;
+
+ if (maildirquota_read_limits(root, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "quota-maildir: Failed to read limits: %s", error);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+maildir_quota_root_namespace_added(struct quota_root *_root,
+ struct mail_namespace *ns)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+
+ if (root->maildirsize_ns == NULL)
+ root->maildirsize_ns = ns;
+}
+
+static void
+maildir_quota_namespace_added(struct quota *quota, struct mail_namespace *ns)
+{
+ struct quota_root **roots;
+ unsigned int i, count;
+
+ roots = array_get_modifiable(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (roots[i]->backend.name == quota_backend_maildir.name &&
+ ((roots[i]->ns_prefix == NULL &&
+ ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) ||
+ roots[i]->ns == ns))
+ maildir_quota_root_namespace_added(roots[i], ns);
+ }
+}
+
+static const char *const *
+maildir_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources_both[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES,
+ QUOTA_NAME_MESSAGES,
+ NULL
+ };
+
+ return resources_both;
+}
+
+static enum quota_get_result
+maildir_quota_get_resource(struct quota_root *_root, const char *name,
+ uint64_t *value_r, const char **error_r)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+ bool recalculated;
+ const char *error;
+
+ if (maildirquota_refresh(root, &recalculated, &error) < 0) {
+ *error_r = t_strdup_printf("Failed to get %s: %s", name, error);
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ }
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) {
+ *value_r = root->total_bytes;
+ } else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) {
+ *value_r = root->total_count;
+ } else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int
+maildir_quota_update(struct quota_root *_root,
+ struct quota_transaction_context *ctx,
+ const char **error_r)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+ bool recalculated;
+ const char *error;
+
+ if (!maildirquota_limits_init(root)) {
+ /* no limits */
+ return 0;
+ }
+
+ /* even though we don't really care about the limits in here ourself,
+ we do want to make sure the header gets updated if the limits have
+ changed. also this makes sure the maildirsize file is created if
+ it doesn't exist. */
+ if (maildirquota_refresh(root, &recalculated, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Could not update storage usage data: %s",
+ error);
+ return -1;
+ }
+
+ if (recalculated) {
+ /* quota was just recalculated and it already contains the changes
+ we wanted to do. */
+ } else if (root->fd == -1) {
+ if (maildirsize_recalculate(root, &error) < 0)
+ e_error(root->root.backend.event, "%s", error);
+ } else if (ctx->recalculate != QUOTA_RECALCULATE_DONT) {
+ i_close_fd(&root->fd);
+ if (maildirsize_recalculate(root, &error) < 0)
+ e_error(root->root.backend.event, "%s", error);
+ } else if (maildirsize_update(root, ctx->count_used, ctx->bytes_used) < 0) {
+ i_close_fd(&root->fd);
+ maildirsize_rebuild_later(root);
+ }
+
+ return 0;
+}
+
+struct quota_backend quota_backend_maildir = {
+ .name = "maildir",
+
+ .v = {
+ .alloc = maildir_quota_alloc,
+ .init = maildir_quota_init,
+ .deinit = maildir_quota_deinit,
+ .parse_rule = maildir_quota_parse_rule,
+ .init_limits = maildir_quota_init_limits,
+ .namespace_added = maildir_quota_namespace_added,
+ .get_resources = maildir_quota_root_get_resources,
+ .get_resource = maildir_quota_get_resource,
+ .update = maildir_quota_update,
+ }
+};
diff --git a/src/plugins/quota/quota-plugin.c b/src/plugins/quota/quota-plugin.c
new file mode 100644
index 0000000..507a329
--- /dev/null
+++ b/src/plugins/quota/quota-plugin.c
@@ -0,0 +1,31 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-user.h"
+#include "mail-storage-hooks.h"
+#include "quota-plugin.h"
+
+void quota_backends_register(void);
+void quota_backends_unregister(void);
+
+const char *quota_plugin_version = DOVECOT_ABI_VERSION;
+
+static struct mail_storage_hooks quota_mail_storage_hooks = {
+ .mail_user_created = quota_mail_user_created,
+ .mail_namespaces_created = quota_mail_namespaces_created,
+ .mailbox_list_created = quota_mailbox_list_created,
+ .mailbox_allocated = quota_mailbox_allocated,
+ .mail_allocated = quota_mail_allocated
+};
+
+void quota_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &quota_mail_storage_hooks);
+ quota_backends_register();
+}
+
+void quota_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&quota_mail_storage_hooks);
+ quota_backends_unregister();
+}
diff --git a/src/plugins/quota/quota-plugin.h b/src/plugins/quota/quota-plugin.h
new file mode 100644
index 0000000..927d428
--- /dev/null
+++ b/src/plugins/quota/quota-plugin.h
@@ -0,0 +1,36 @@
+#ifndef QUOTA_PLUGIN_H
+#define QUOTA_PLUGIN_H
+
+#include "module-context.h"
+#include "mail-user.h"
+
+struct module;
+struct mailbox;
+struct mailbox_list;
+struct mail;
+
+#define QUOTA_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, quota_user_module)
+#define QUOTA_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, quota_user_module)
+
+struct quota_user {
+ union mail_user_module_context module_ctx;
+
+ struct quota *quota;
+};
+
+struct mail_storage;
+
+extern MODULE_CONTEXT_DEFINE(quota_user_module, &mail_user_module_register);
+
+void quota_mail_user_created(struct mail_user *user);
+void quota_mailbox_list_created(struct mailbox_list *list);
+void quota_mail_namespaces_created(struct mail_namespace *namespaces);
+void quota_mailbox_allocated(struct mailbox *box);
+void quota_mail_allocated(struct mail *mail);
+
+void quota_plugin_init(struct module *module);
+void quota_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/quota/quota-private.h b/src/plugins/quota/quota-private.h
new file mode 100644
index 0000000..ac091d3
--- /dev/null
+++ b/src/plugins/quota/quota-private.h
@@ -0,0 +1,230 @@
+#ifndef QUOTA_PRIVATE_H
+#define QUOTA_PRIVATE_H
+
+#include "mail-storage-private.h"
+#include "mail-namespace.h"
+#include "quota.h"
+
+/* Modules should use do "my_id = quota_module_id++" and
+ use quota_module_contexts[id] for their own purposes. */
+extern unsigned int quota_module_id;
+
+struct quota {
+ struct mail_user *user;
+ struct quota_settings *set;
+ struct event *event;
+
+ ARRAY(struct quota_root *) roots;
+ ARRAY(struct mail_namespace *) namespaces;
+ struct mail_namespace *unwanted_ns;
+};
+
+struct quota_settings {
+ pool_t pool;
+
+ ARRAY(struct quota_root_settings *) root_sets;
+ struct event *event;
+ enum quota_alloc_result (*test_alloc)(
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r);
+
+ uoff_t max_mail_size;
+ const char *quota_exceeded_msg;
+ bool debug:1;
+ bool initialized:1;
+ bool vsizes:1;
+};
+
+struct quota_rule {
+ const char *mailbox_mask;
+
+ int64_t bytes_limit, count_limit;
+ /* relative to default_rule */
+ int bytes_percent, count_percent;
+
+ /* Don't include this mailbox in quota */
+ bool ignore:1;
+};
+
+struct quota_warning_rule {
+ struct quota_rule rule;
+ const char *command;
+ bool reverse:1;
+};
+
+struct quota_backend_vfuncs {
+ struct quota_root *(*alloc)(void);
+ int (*init)(struct quota_root *root, const char *args,
+ const char **error_r);
+ void (*deinit)(struct quota_root *root);
+
+ bool (*parse_rule)(struct quota_root_settings *root_set,
+ struct quota_rule *rule,
+ const char *str, const char **error_r);
+ int (*init_limits)(struct quota_root *root, const char **error_r);
+
+ /* called once for each namespace */
+ void (*namespace_added)(struct quota *quota,
+ struct mail_namespace *ns);
+
+ const char *const *(*get_resources)(struct quota_root *root);
+ /* Backends return success as QUOTA_GET_RESULT_LIMITED, and returning
+ QUOTA_GET_RESULT_UNLIMITED is prohibited by quota_get_resource(),
+ which is the only caller of this vfunc. */
+ enum quota_get_result (*get_resource)(struct quota_root *root,
+ const char *name,
+ uint64_t *value_r,
+ const char **error_r);
+
+ int (*update)(struct quota_root *root,
+ struct quota_transaction_context *ctx,
+ const char **error_r);
+ bool (*match_box)(struct quota_root *root, struct mailbox *box);
+ void (*flush)(struct quota_root *root);
+};
+
+struct quota_backend {
+ /* quota backends equal if backend1.name == backend2.name */
+ const char *name;
+ struct event *event;
+ struct quota_backend_vfuncs v;
+};
+
+struct quota_root_settings {
+ /* Unique quota root name. */
+ const char *name;
+ /* Name in settings, e.g. "quota", "quota2", .. */
+ const char *set_name;
+
+ struct quota_settings *set;
+ const char *args;
+
+ const struct quota_backend *backend;
+ struct quota_rule default_rule;
+ ARRAY(struct quota_rule) rules;
+ ARRAY(struct quota_warning_rule) warning_rules;
+ const char *limit_set;
+
+ /* If user is under quota before saving a mail, allow the last mail to
+ bring the user over quota by this many bytes. */
+ uint64_t last_mail_max_extra_bytes;
+ struct quota_rule grace_rule;
+
+ /* Limits in default_rule override backend's quota limits */
+ bool force_default_rule:1;
+ /* TRUE if any of the warning_rules have reverse==TRUE */
+ bool have_reverse_warnings:1;
+};
+
+struct quota_root {
+ pool_t pool;
+
+ struct quota_root_settings *set;
+ struct quota *quota;
+ struct quota_backend backend;
+ struct dict *limit_set_dict;
+
+ /* this quota root applies only to this namespace. it may also be
+ a public namespace without an owner. */
+ struct mail_namespace *ns;
+ /* this is set in quota init(), because namespaces aren't known yet.
+ when accessing shared users the ns_prefix may be non-NULL but
+ ns=NULL, so when checking if quota root applies only to a specific
+ namespace use the ns_prefix!=NULL check. */
+ const char *ns_prefix;
+
+ /* initially the same as set->default_rule.*_limit, but some backends
+ may change these by reading the limits elsewhere (e.g. Maildir++,
+ FS quota) */
+ int64_t bytes_limit, count_limit;
+
+ /* Module-specific contexts. See quota_module_id. */
+ ARRAY(void) quota_module_contexts;
+
+ /* don't enforce quota when saving */
+ bool no_enforcing:1;
+ /* quota is automatically updated. update() should be called but the
+ bytes won't be changed. count is still changed, because it's cheap
+ to do and it's internally used to figure out whether there have
+ been some changes and that quota_warnings should be checked. */
+ bool auto_updating:1;
+ /* If user has unlimited quota, disable quota tracking */
+ bool disable_unlimited_tracking:1;
+ /* Set while quota is being recalculated to avoid recursion. */
+ bool recounting:1;
+ /* Quota root is hidden (to e.g. IMAP GETQUOTAROOT) */
+ bool hidden:1;
+ /* Did we already check quota_over_flag correctness? */
+ bool quota_over_flag_checked:1;
+};
+
+struct quota_transaction_context {
+ union mailbox_transaction_module_context module_ctx;
+
+ struct quota *quota;
+ struct mailbox *box;
+
+ int64_t bytes_used, count_used;
+ /* how many bytes/mails can be saved until limit is reached.
+ (set once, not updated by bytes_used/count_used).
+
+ if last_mail_max_extra_bytes>0, the bytes_ceil is initially
+ increased by that much, while bytes_ceil2 contains the real ceiling.
+ after the first allocation is done, bytes_ceil is set to
+ bytes_ceil2. */
+ uint64_t bytes_ceil, bytes_ceil2, count_ceil;
+ /* How many bytes/mails we are over quota. Like *_ceil, these are set
+ only once and not updated by bytes_used/count_used. (Either *_ceil
+ or *_over is always zero.) */
+ uint64_t bytes_over, count_over;
+
+ struct mail *tmp_mail;
+ enum quota_recalculate recalculate;
+
+ bool limits_set:1;
+ bool failed:1;
+ bool sync_transaction:1;
+ /* TRUE if all roots have auto_updating=TRUE */
+ bool auto_updating:1;
+ /* Quota doesn't need to be updated within this transaction. */
+ bool no_quota_updates:1;
+};
+
+/* Register storage to all user's quota roots. */
+void quota_add_user_namespace(struct quota *quota, struct mail_namespace *ns);
+void quota_remove_user_namespace(struct mail_namespace *ns);
+
+int quota_root_default_init(struct quota_root *root, const char *args,
+ const char **error_r);
+struct quota *quota_get_mail_user_quota(struct mail_user *user);
+
+bool quota_root_is_namespace_visible(struct quota_root *root,
+ struct mail_namespace *ns);
+struct quota_rule *
+quota_root_rule_find(struct quota_root_settings *root_set, const char *name);
+
+void quota_root_recalculate_relative_rules(struct quota_root_settings *root_set,
+ int64_t bytes_limit,
+ int64_t count_limit);
+/* Returns 1 if values were returned successfully, 0 if we're recursing into
+ the same function, -1 if error. */
+int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r,
+ enum quota_get_result *error_result_r, const char **error_r);
+
+int quota_root_parse_grace(struct quota_root_settings *root_set,
+ const char *value, const char **error_r);
+bool quota_warning_match(const struct quota_warning_rule *w,
+ uint64_t bytes_before, uint64_t bytes_current,
+ uint64_t count_before, uint64_t count_current,
+ const char **reason_r);
+bool quota_transaction_is_over(struct quota_transaction_context *ctx, uoff_t size);
+int quota_transaction_set_limits(struct quota_transaction_context *ctx,
+ enum quota_get_result *error_result_r,
+ const char **error_r);
+
+void quota_backend_register(const struct quota_backend *backend);
+void quota_backend_unregister(const struct quota_backend *backend);
+
+#define QUOTA_UNKNOWN_RESOURCE_ERROR_STRING "Unknown quota resource"
+
+#endif
diff --git a/src/plugins/quota/quota-status-settings.c b/src/plugins/quota/quota-status-settings.c
new file mode 100644
index 0000000..e98cc09
--- /dev/null
+++ b/src/plugins/quota/quota-status-settings.c
@@ -0,0 +1,37 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "mail-storage-settings.h"
+#include "quota-status-settings.h"
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct quota_status_settings)
+
+static const struct setting_define quota_status_setting_defines[] = {
+ DEF(STR, recipient_delimiter),
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct quota_status_settings quota_status_default_settings = {
+ .recipient_delimiter = "+",
+};
+
+static const struct setting_parser_info *quota_status_setting_dependencies[] = {
+ NULL
+};
+
+const struct setting_parser_info quota_status_setting_parser_info = {
+ .module_name = "mail",
+ .defines = quota_status_setting_defines,
+ .defaults = &quota_status_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct quota_status_settings),
+
+ .parent_offset = SIZE_MAX,
+ .dependencies = quota_status_setting_dependencies
+};
diff --git a/src/plugins/quota/quota-status-settings.h b/src/plugins/quota/quota-status-settings.h
new file mode 100644
index 0000000..e69a0aa
--- /dev/null
+++ b/src/plugins/quota/quota-status-settings.h
@@ -0,0 +1,10 @@
+#ifndef QUOTA_STATUS_SETTINGS_H
+#define QUOTA_STATUS_SETTINGS_H 1
+
+struct quota_status_settings {
+ const char *recipient_delimiter;
+};
+
+extern const struct setting_parser_info quota_status_setting_parser_info;
+
+#endif
diff --git a/src/plugins/quota/quota-status.c b/src/plugins/quota/quota-status.c
new file mode 100644
index 0000000..7db0cb4
--- /dev/null
+++ b/src/plugins/quota/quota-status.c
@@ -0,0 +1,360 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "connection.h"
+#include "restrict-access.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-storage-settings.h"
+#include "mail-storage-service.h"
+#include "smtp-address.h"
+#include "quota-private.h"
+#include "quota-plugin.h"
+#include "quota-status-settings.h"
+
+enum quota_protocol {
+ QUOTA_PROTOCOL_UNKNOWN = 0,
+ QUOTA_PROTOCOL_POSTFIX
+};
+
+struct quota_client {
+ struct connection conn;
+
+ struct event *event;
+
+ char *state;
+ char *recipient;
+ uoff_t size;
+
+ bool warned_bad_state:1;
+};
+
+static struct event_category event_category_quota_status = {
+ .name = "quota-status"
+};
+
+static struct quota_status_settings *quota_status_settings;
+static pool_t quota_status_pool;
+static enum quota_protocol protocol;
+static struct mail_storage_service_ctx *storage_service;
+static struct connection_list *clients;
+static char *nouser_reply;
+
+static void client_connected(struct master_service_connection *conn)
+{
+ struct quota_client *client;
+
+ client = i_new(struct quota_client, 1);
+
+ client->event = event_create(NULL);
+ client->conn.event_parent = client->event;
+ event_add_category(client->event, &event_category_quota_status);
+ connection_init_server(clients, &client->conn,
+ "quota-client", conn->fd, conn->fd);
+ master_service_client_connection_accept(conn);
+
+ e_debug(client->event, "Client connected");
+}
+
+static void client_reset(struct quota_client *client)
+{
+ i_free(client->state);
+ i_free(client->recipient);
+}
+
+static enum quota_alloc_result
+quota_check(struct mail_user *user, uoff_t mail_size, const char **error_r)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ struct quota_transaction_context *ctx;
+ enum quota_alloc_result ret;
+
+ if (quser == NULL) {
+ /* no quota for user */
+ e_debug(user->event, "User has no quota");
+ return QUOTA_ALLOC_RESULT_OK;
+ }
+
+ ns = mail_namespace_find_inbox(user->namespaces);
+ box = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_POST_SESSION);
+
+ ctx = quota_transaction_begin(box);
+ const char *internal_error;
+ ret = quota_test_alloc(ctx, I_MAX(1, mail_size), &internal_error);
+ if (ret == QUOTA_ALLOC_RESULT_TEMPFAIL)
+ e_error(user->event, "quota check failed: %s", internal_error);
+ *error_r = quota_alloc_result_errstr(ret, ctx);
+ quota_transaction_rollback(&ctx);
+
+ mailbox_free(&box);
+ return ret;
+}
+
+static int client_check_mta_state(struct quota_client *client)
+{
+ if (client->state == NULL || strcasecmp(client->state, "RCPT") == 0)
+ return 0;
+
+ if (!client->warned_bad_state) {
+ e_warning(client->event,
+ "Received policy query from MTA in unexpected state %s "
+ "(service can only be used for recipient restrictions)",
+ client->state);
+ }
+ client->warned_bad_state = TRUE;
+ return -1;
+}
+
+static void client_handle_request(struct quota_client *client)
+{
+ struct mail_storage_service_input input;
+ struct mail_storage_service_user *service_user;
+ struct mail_user *user;
+ struct smtp_address *rcpt;
+ const char *value = NULL, *error;
+ const char *detail ATTR_UNUSED;
+ char delim ATTR_UNUSED;
+ string_t *resp;
+ int ret;
+
+ if (client_check_mta_state(client) < 0 || client->recipient == NULL) {
+ e_debug(client->event, "Response: action=DUNNO");
+ o_stream_nsend_str(client->conn.output, "action=DUNNO\n\n");
+ return;
+ }
+
+ if (smtp_address_parse_path(pool_datastack_create(), client->recipient,
+ SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
+ SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
+ SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART,
+ &rcpt, &error) < 0) {
+ e_error(client->event,
+ "Client sent invalid recipient address `%s': "
+ "%s", str_sanitize(client->recipient, 256), error);
+ e_debug(client->event, "Response: action=DUNNO");
+ o_stream_nsend_str(client->conn.output, "action=DUNNO\n\n");
+ return;
+ }
+
+ i_zero(&input);
+ input.event_parent = client->event;
+ smtp_address_detail_parse_temp(quota_status_settings->recipient_delimiter,
+ rcpt, &input.username, &delim,
+ &detail);
+ ret = mail_storage_service_lookup_next(storage_service, &input,
+ &service_user, &user, &error);
+ restrict_access_allow_coredumps(TRUE);
+ if (ret == 0) {
+ e_debug(client->event, "User `%s' not found", input.username);
+ value = nouser_reply;
+ } else if (ret > 0) {
+ enum quota_alloc_result qret = quota_check(user, client->size,
+ &error);
+ if (qret == QUOTA_ALLOC_RESULT_OK) {
+ e_debug(client->event,
+ "Message is acceptable");
+ } else {
+ e_debug(client->event,
+ "Quota check failed: %s", error);
+ }
+
+ switch (qret) {
+ case QUOTA_ALLOC_RESULT_OK: /* under quota */
+ value = mail_user_plugin_getenv(user,
+ "quota_status_success");
+ if (value == NULL)
+ value = "OK";
+ break;
+ case QUOTA_ALLOC_RESULT_OVER_MAXSIZE:
+ /* even over maximum quota */
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
+ value = mail_user_plugin_getenv(user,
+ "quota_status_toolarge");
+ /* fall through */
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA:
+ if (value == NULL)
+ value = mail_user_plugin_getenv(user,
+ "quota_status_overquota");
+ if (value == NULL)
+ value = t_strdup_printf("554 5.2.2 %s", error);
+ break;
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ ret = -1;
+ break;
+ }
+ value = t_strdup(value); /* user's pool is being freed */
+ mail_user_deinit(&user);
+ mail_storage_service_user_unref(&service_user);
+ } else {
+ e_error(client->event,
+ "Failed to lookup user %s: %s", input.username, error);
+ error = "Temporary internal error";
+ }
+
+ resp = t_str_new(256);
+ if (ret < 0) {
+ /* temporary failure */
+ str_append(resp, "action=DEFER_IF_PERMIT ");
+ str_append(resp, error);
+ } else {
+ str_append(resp, "action=");
+ str_append(resp, value);
+ }
+
+ e_debug(client->event, "Response: %s", str_c(resp));
+ str_append(resp, "\n\n");
+ o_stream_nsend_str(client->conn.output, str_c(resp));
+}
+
+static int client_input_line(struct connection *conn, const char *line)
+{
+ struct quota_client *client = (struct quota_client *)conn;
+
+ e_debug(client->event, "Request: %s", str_sanitize(line, 1024));
+
+ if (*line == '\0') {
+ o_stream_cork(conn->output);
+ client_handle_request(client);
+ o_stream_uncork(conn->output);
+ client_reset(client);
+ return 1;
+ }
+ if (str_begins(line, "recipient=")) {
+ if (client->recipient == NULL)
+ client->recipient = i_strdup(line + 10);
+ } else if (str_begins(line, "size=")) {
+ if (str_to_uoff(line+5, &client->size) < 0)
+ client->size = 0;
+ } else if (str_begins(line, "protocol_state=")) {
+ if (client->state == NULL)
+ client->state = i_strdup(line + 15);
+ }
+ return 1;
+}
+
+static void client_destroy(struct connection *conn)
+{
+ struct quota_client *client = (struct quota_client *)conn;
+
+ e_debug(client->event, "Client disconnected");
+
+ connection_deinit(&client->conn);
+ client_reset(client);
+ event_unref(&client->event);
+ i_free(client);
+
+ master_service_client_connection_destroyed(master_service);
+}
+
+static struct connection_settings client_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = client_destroy,
+ .input_line = client_input_line
+};
+
+static void main_preinit(void)
+{
+ restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL);
+ restrict_access_allow_coredumps(TRUE);
+}
+
+static void main_init(void)
+{
+ static const struct setting_parser_info *set_roots[] = {
+ &quota_status_setting_parser_info,
+ NULL
+ };
+ struct mail_storage_service_input input;
+ const struct setting_parser_info *user_info;
+ const struct setting_parser_context *set_parser;
+ const struct mail_user_settings *user_set;
+ const struct quota_status_settings *set;
+ const char *value, *error;
+ pool_t pool;
+
+ clients = connection_list_init(&client_set, &client_vfuncs);
+ storage_service = mail_storage_service_init(master_service, set_roots,
+ MAIL_STORAGE_SERVICE_FLAG_ALLOW_ROOT |
+ MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP |
+ MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP |
+ MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS |
+ MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR);
+
+ i_zero(&input);
+ input.service = "quota-status";
+ input.module = "mail";
+ input.username = "";
+
+ quota_status_pool = pool_alloconly_create("quota status settings", 512);
+ pool = pool_alloconly_create("service all settings", 4096);
+ if (mail_storage_service_read_settings(storage_service, &input, pool,
+ &user_info, &set_parser,
+ &error) < 0)
+ i_fatal("%s", error);
+ user_set = master_service_settings_parser_get_others(master_service,
+ set_parser)[0];
+ set = master_service_settings_get_others(master_service)[1];
+
+ quota_status_settings = settings_dup(&quota_status_setting_parser_info, set,
+ quota_status_pool);
+ value = mail_user_set_plugin_getenv(user_set, "quota_status_nouser");
+ nouser_reply = p_strdup(quota_status_pool,
+ value != NULL ? value : "REJECT Unknown user");
+ pool_unref(&pool);
+}
+
+static void main_deinit(void)
+{
+ pool_unref(&quota_status_pool);
+ connection_list_deinit(&clients);
+ mail_storage_service_deinit(&storage_service);
+}
+
+int main(int argc, char *argv[])
+{
+ enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
+ int c;
+
+ protocol = QUOTA_PROTOCOL_UNKNOWN;
+ master_service = master_service_init("quota-status", service_flags,
+ &argc, &argv, "p:");
+ while ((c = master_getopt(master_service)) > 0) {
+ switch (c) {
+ case 'p':
+ if (strcmp(optarg, "postfix") == 0)
+ protocol = QUOTA_PROTOCOL_POSTFIX;
+ else
+ i_fatal("Unknown -p parameter: '%s'", optarg);
+ break;
+ default:
+ return FATAL_DEFAULT;
+ }
+ }
+ if (protocol == QUOTA_PROTOCOL_UNKNOWN)
+ i_fatal("Missing -p parameter");
+
+ master_service_init_log(master_service);
+ main_preinit();
+
+ main_init();
+ master_service_init_finish(master_service);
+ master_service_run(master_service, client_connected);
+ main_deinit();
+ master_service_deinit(&master_service);
+ return 0;
+}
diff --git a/src/plugins/quota/quota-storage.c b/src/plugins/quota/quota-storage.c
new file mode 100644
index 0000000..a1d08ee
--- /dev/null
+++ b/src/plugins/quota/quota-storage.c
@@ -0,0 +1,780 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "mail-search-build.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "maildir-storage.h"
+#include "index-mailbox-size.h"
+#include "quota-private.h"
+#include "quota-plugin.h"
+
+#include <sys/stat.h>
+
+#define QUOTA_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, quota_storage_module)
+#define QUOTA_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, quota_storage_module)
+#define QUOTA_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, quota_mail_module)
+#define QUOTA_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, quota_mailbox_list_module)
+
+struct quota_mailbox_list {
+ union mailbox_list_module_context module_ctx;
+};
+
+struct quota_mailbox {
+ union mailbox_module_context module_ctx;
+
+ struct mailbox_transaction_context *expunge_trans;
+ struct quota_transaction_context *expunge_qt;
+ ARRAY(uint32_t) expunge_uids;
+ ARRAY(uoff_t) expunge_sizes;
+ unsigned int prev_idx;
+
+ bool recalculate:1;
+ bool sync_transaction_expunge:1;
+};
+
+struct quota_user_module quota_user_module =
+ MODULE_CONTEXT_INIT(&mail_user_module_register);
+
+static MODULE_CONTEXT_DEFINE_INIT(quota_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(quota_mail_module, &mail_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(quota_mailbox_list_module,
+ &mailbox_list_module_register);
+
+static void quota_set_storage_error(struct quota_transaction_context *qt,
+ struct mailbox *box,
+ enum quota_alloc_result res,
+ const char *internal_err)
+{
+ const char *errstr = quota_alloc_result_errstr(res, qt);
+ struct mail_storage *storage = box->storage;
+ switch (res) {
+ case QUOTA_ALLOC_RESULT_OVER_MAXSIZE:
+ mail_storage_set_error(storage, MAIL_ERROR_LIMIT, errstr);
+ break;
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA:
+ mail_storage_set_error(storage, MAIL_ERROR_NOQUOTA, errstr);
+ break;
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ mailbox_set_critical(box, "quota: %s", internal_err);
+ break;
+ case QUOTA_ALLOC_RESULT_OK:
+ i_unreached();
+ }
+}
+
+static void quota_mail_expunge(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(_mail->box);
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(_mail->box->storage->user);
+ union mail_module_context *qmail = QUOTA_MAIL_CONTEXT(mail);
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(_mail->transaction);
+ uoff_t size;
+ int ret;
+
+ if (qt->auto_updating) {
+ qmail->super.expunge(_mail);
+ return;
+ }
+
+ /* We need to handle the situation where multiple transactions expunged
+ the mail at the same time. In here we'll just save the message's
+ physical size and do the quota freeing later when the message was
+ known to be expunged. */
+ if (quser->quota->set->vsizes)
+ ret = mail_get_virtual_size(_mail, &size);
+ else
+ ret = mail_get_physical_size(_mail, &size);
+ if (ret == 0) {
+ if (!array_is_created(&qbox->expunge_uids)) {
+ i_array_init(&qbox->expunge_uids, 64);
+ i_array_init(&qbox->expunge_sizes, 64);
+ }
+ array_push_back(&qbox->expunge_uids, &_mail->uid);
+ array_push_back(&qbox->expunge_sizes, &size);
+ if ((_mail->transaction->flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0) {
+ /* we're running dsync. if this brings the quota below
+ a negative quota warning, don't execute it, because
+ it probably was already executed by the replica. */
+ qbox->sync_transaction_expunge = TRUE;
+ } else {
+ qbox->sync_transaction_expunge = FALSE;
+ }
+ }
+
+ qmail->super.expunge(_mail);
+}
+
+static int
+quota_get_status(struct mailbox *box, enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+ struct quota_transaction_context *qt;
+ int ret = 0;
+
+ if ((items & STATUS_CHECK_OVER_QUOTA) != 0) {
+ qt = quota_transaction_begin(box);
+ const char *error;
+ enum quota_alloc_result qret = quota_test_alloc(qt, 0, &error);
+ if (qret != QUOTA_ALLOC_RESULT_OK) {
+ quota_set_storage_error(qt, box, qret, error);
+ ret = -1;
+ }
+ quota_transaction_rollback(&qt);
+
+ if ((items & ENUM_NEGATE(STATUS_CHECK_OVER_QUOTA)) == 0) {
+ /* don't bother calling parent, it may unnecessarily
+ try to open the mailbox */
+ return ret < 0 ? -1 : 0;
+ }
+ }
+
+ if (qbox->module_ctx.super.get_status(box, items, status_r) < 0)
+ ret = -1;
+ return ret < 0 ? -1 : 0;
+}
+
+static struct mailbox_transaction_context *
+quota_mailbox_transaction_begin(struct mailbox *box,
+ enum mailbox_transaction_flags flags,
+ const char *reason)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+ struct mailbox_transaction_context *t;
+ struct quota_transaction_context *qt;
+
+ t = qbox->module_ctx.super.transaction_begin(box, flags, reason);
+ qt = quota_transaction_begin(box);
+ qt->sync_transaction = (flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0;
+
+ MODULE_CONTEXT_SET(t, quota_storage_module, qt);
+ return t;
+}
+
+static int
+quota_mailbox_transaction_commit(struct mailbox_transaction_context *ctx,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box);
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(ctx);
+
+ i_assert(qt->tmp_mail == NULL);
+
+ if (qbox->module_ctx.super.transaction_commit(ctx, changes_r) < 0) {
+ quota_transaction_rollback(&qt);
+ return -1;
+ } else {
+ (void)quota_transaction_commit(&qt);
+ return 0;
+ }
+}
+
+static void
+quota_mailbox_transaction_rollback(struct mailbox_transaction_context *ctx)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box);
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(ctx);
+
+ i_assert(qt->tmp_mail == NULL);
+
+ qbox->module_ctx.super.transaction_rollback(ctx);
+ quota_transaction_rollback(&qt);
+}
+
+void quota_mail_allocated(struct mail *_mail)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT(_mail->box);
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ union mail_module_context *qmail;
+
+ if (qbox == NULL)
+ return;
+
+ qmail = p_new(mail->pool, union mail_module_context, 1);
+ qmail->super = *v;
+ mail->vlast = &qmail->super;
+
+ v->expunge = quota_mail_expunge;
+ MODULE_CONTEXT_SET_SELF(mail, quota_mail_module, qmail);
+}
+
+static bool
+quota_move_requires_check(struct mailbox *dest_box, struct mailbox *src_box)
+{
+ struct mail_namespace *src_ns = src_box->list->ns;
+ struct mail_namespace *dest_ns = dest_box->list->ns;
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(src_ns->user);
+ struct quota_root *const *rootp;
+
+ array_foreach(&quser->quota->roots, rootp) {
+ bool have_src_quota, have_dest_quota;
+
+ have_src_quota = quota_root_is_namespace_visible(*rootp, src_ns);
+ have_dest_quota = quota_root_is_namespace_visible(*rootp, dest_ns);
+ if (have_src_quota == have_dest_quota) {
+ /* Both/neither have this quota */
+ } else if (have_dest_quota) {
+ /* Destination mailbox has a quota that doesn't exist
+ in source. We'll need to check if it's being
+ exceeded. */
+ return TRUE;
+ } else {
+ /* Source mailbox has a quota root that doesn't exist
+ in destination. We're not increasing the source
+ quota, so ignore it. */
+ }
+ }
+ return FALSE;
+}
+
+static int quota_check(struct mail_save_context *ctx, struct mailbox *src_box)
+{
+ struct mailbox_transaction_context *t = ctx->transaction;
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t);
+ enum quota_alloc_result ret;
+
+ i_assert(!ctx->moving || src_box != NULL);
+
+ if (ctx->moving &&
+ !quota_move_requires_check(ctx->transaction->box, src_box)) {
+ /* the mail is being moved. the quota won't increase (after
+ the following expunge), so allow this even if user is
+ currently over quota */
+ quota_alloc(qt, ctx->dest_mail);
+ return 0;
+ } else if (qt->failed) {
+ return 0;
+ }
+
+ const char *error;
+ ret = quota_try_alloc(qt, ctx->dest_mail, &error);
+ switch (ret) {
+ case QUOTA_ALLOC_RESULT_OK:
+ return 0;
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ /* Log the error, but allow saving anyway. */
+ e_error(qt->quota->event,
+ "Failed to check if user is under quota: %s - "
+ "saving mail anyway", error);
+ return 0;
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ /* Could not determine if there is enough space due to ongoing
+ background quota calculation, allow saving anyway. */
+ e_warning(qt->quota->event,
+ "Failed to check if user is under quota: %s - "
+ "saving mail anyway", error);
+ return 0;
+ default:
+ quota_set_storage_error(qt, t->box, ret, error);
+ return -1;
+ }
+}
+
+static int
+quota_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+ struct mailbox_transaction_context *t = ctx->transaction;
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t);
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(t->box);
+
+ /* we always want to know the mail size */
+ mail_add_temp_wanted_fields(ctx->dest_mail, MAIL_FETCH_PHYSICAL_SIZE, NULL);
+
+ /* get quota before copying any mails. this avoids dovecot-vsize.lock
+ deadlocks with backends that lock mails for expunging/copying. */
+ enum quota_get_result error_res;
+ const char *error;
+ if (quota_transaction_set_limits(qt, &error_res, &error) < 0) {
+ if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC) {
+ e_warning(qt->quota->event,
+ "%s - copying mail anyway", error);
+ } else {
+ e_error(qt->quota->event,
+ "%s - copying mail anyway", error);
+ }
+ }
+
+ if (qbox->module_ctx.super.copy(ctx, mail) < 0)
+ return -1;
+
+ if (ctx->copying_via_save) {
+ /* copying used saving internally, we already checked the
+ quota */
+ return 0;
+ }
+ return quota_check(ctx, mail->box);
+}
+
+static int
+quota_save_begin(struct mail_save_context *ctx, struct istream *input)
+{
+ struct mailbox_transaction_context *t = ctx->transaction;
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t);
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(t->box);
+ const char *error;
+ uoff_t size;
+
+ if (!ctx->moving && i_stream_get_size(input, TRUE, &size) > 0 &&
+ !qt->failed) {
+ /* Input size is known, check for quota immediately. This
+ check isn't perfect, especially because input stream's
+ linefeeds may contain CR+LFs while physical message would
+ only contain LFs. With mbox some headers might be skipped
+ entirely.
+
+ I think these don't really matter though compared to the
+ benefit of giving "out of quota" error before sending the
+ full mail. */
+
+ enum quota_alloc_result qret = quota_test_alloc(qt, size, &error);
+ switch (qret) {
+ case QUOTA_ALLOC_RESULT_OK:
+ /* Great, there is space. */
+ break;
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ /* Log the error, but allow saving anyway. */
+ e_error(qt->quota->event,
+ "Failed to check if user is under quota: %s - "
+ "saving mail anyway", error);
+ break;
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ /* Could not determine if there is enough space due to
+ * ongoing background quota calculation, allow saving
+ * anyway. */
+ e_warning(qt->quota->event,
+ "Failed to check if user is under quota: %s - "
+ "saving mail anyway", error);
+ break;
+ default:
+ quota_set_storage_error(qt, t->box, qret, error);
+ return -1;
+ }
+ }
+
+ /* we always want to know the mail size */
+ mail_add_temp_wanted_fields(ctx->dest_mail, MAIL_FETCH_PHYSICAL_SIZE, NULL);
+
+ /* get quota before copying any mails. this avoids dovecot-vsize.lock
+ deadlocks with backends that lock mails for expunging/copying. */
+ enum quota_get_result error_res;
+ if (quota_transaction_set_limits(qt, &error_res, &error) < 0) {
+ if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC)
+ e_warning(qt->quota->event,
+ "%s - saving mail anyway", error);
+ else
+ e_error(qt->quota->event,
+ "%s - saving mail anyway", error);
+ }
+
+ return qbox->module_ctx.super.save_begin(ctx, input);
+}
+
+static int quota_save_finish(struct mail_save_context *ctx)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->transaction->box);
+ struct mailbox *src_box;
+
+ if (qbox->module_ctx.super.save_finish(ctx) < 0)
+ return -1;
+
+ src_box = ctx->copy_src_mail == NULL ? NULL : ctx->copy_src_mail->box;
+ return quota_check(ctx, src_box);
+}
+
+static void quota_mailbox_sync_cleanup(struct quota_mailbox *qbox)
+{
+ if (array_is_created(&qbox->expunge_uids)) {
+ array_clear(&qbox->expunge_uids);
+ array_clear(&qbox->expunge_sizes);
+ }
+
+ if (qbox->expunge_qt != NULL && qbox->expunge_qt->tmp_mail != NULL) {
+ mail_free(&qbox->expunge_qt->tmp_mail);
+ (void)mailbox_transaction_commit(&qbox->expunge_trans);
+ }
+ qbox->sync_transaction_expunge = FALSE;
+}
+
+static void quota_mailbox_sync_commit(struct quota_mailbox *qbox)
+{
+ quota_mailbox_sync_cleanup(qbox);
+ if (qbox->expunge_qt != NULL)
+ (void)quota_transaction_commit(&qbox->expunge_qt);
+ qbox->recalculate = FALSE;
+}
+
+static void quota_mailbox_sync_notify(struct mailbox *box, uint32_t uid,
+ enum mailbox_sync_type sync_type)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(box->storage->user);
+ const uint32_t *uids;
+ const uoff_t *sizep;
+ unsigned int i, count;
+ uoff_t size;
+
+ if (qbox->module_ctx.super.sync_notify != NULL)
+ qbox->module_ctx.super.sync_notify(box, uid, sync_type);
+
+ if (sync_type != MAILBOX_SYNC_TYPE_EXPUNGE || qbox->recalculate ||
+ (box->flags & MAILBOX_FLAG_DELETE_UNSAFE) != 0) {
+ if (uid == 0) {
+ /* free the transaction before view syncing begins,
+ otherwise it'll crash. */
+ quota_mailbox_sync_cleanup(qbox);
+ }
+ return;
+ }
+
+ if (qbox->expunge_qt == NULL) {
+ qbox->expunge_qt = quota_transaction_begin(box);
+ qbox->expunge_qt->sync_transaction =
+ qbox->sync_transaction_expunge;
+ }
+ if (qbox->expunge_qt->auto_updating) {
+ /* even though backend doesn't care about size/count changes,
+ make sure count_used changes so quota_warnings are
+ executed */
+ quota_free_bytes(qbox->expunge_qt, 0);
+ return;
+ }
+
+ /* we're in the middle of syncing the mailbox, so it's a bad idea to
+ try and get the message sizes at this point. Rely on sizes that
+ we saved earlier, or recalculate the whole quota if we don't know
+ the size. */
+ if (!array_is_created(&qbox->expunge_uids) ||
+ array_is_empty(&qbox->expunge_uids)) {
+ i = count = 0;
+ } else {
+ uids = array_get(&qbox->expunge_uids, &count);
+ for (i = qbox->prev_idx; i < count; i++) {
+ if (uids[i] == uid)
+ break;
+ }
+ if (i >= count) {
+ for (i = 0; i < qbox->prev_idx; i++) {
+ if (uids[i] == uid)
+ break;
+ }
+ if (i == qbox->prev_idx)
+ i = count;
+ }
+ qbox->prev_idx = i;
+ }
+
+ if (i != count) {
+ /* we already know the size */
+ sizep = array_idx(&qbox->expunge_sizes, i);
+ quota_free_bytes(qbox->expunge_qt, *sizep);
+ /* FIXME: it's not ideal that we do the vsize update here, but
+ this is the easiest place for it for now.. maybe the mail
+ size checking code could be moved to lib-storage */
+ if (ibox->vsize_update != NULL && quser->quota->set->vsizes)
+ index_mailbox_vsize_hdr_expunge(ibox->vsize_update, uid, *sizep);
+ return;
+ }
+
+ /* try to look up the size. this works only if it's cached. */
+ if (qbox->expunge_qt->tmp_mail == NULL) {
+ /* FIXME: ugly kludge to open the transaction for sync_view.
+ box->view may not have all the new messages that
+ sync_notify() notifies about, and those messages would
+ cause a quota recalculation. */
+ struct mail_index_view *box_view = box->view;
+ if (box->tmp_sync_view != NULL)
+ box->view = box->tmp_sync_view;
+ qbox->expunge_trans = mailbox_transaction_begin(box, 0, "quota");
+ box->view = box_view;
+ qbox->expunge_qt->tmp_mail =
+ mail_alloc(qbox->expunge_trans,
+ MAIL_FETCH_PHYSICAL_SIZE, NULL);
+ }
+ if (!mail_set_uid(qbox->expunge_qt->tmp_mail, uid))
+ ;
+ else if (!quser->quota->set->vsizes) {
+ if (mail_get_physical_size(qbox->expunge_qt->tmp_mail, &size) == 0) {
+ quota_free_bytes(qbox->expunge_qt, size);
+ return;
+ }
+ } else if (mail_get_virtual_size(qbox->expunge_qt->tmp_mail, &size) == 0) {
+ quota_free_bytes(qbox->expunge_qt, size);
+ if (ibox->vsize_update != NULL)
+ index_mailbox_vsize_hdr_expunge(ibox->vsize_update, uid, size);
+ } else {
+ /* there's no way to get the size. recalculate the quota. */
+ quota_recalculate(qbox->expunge_qt, QUOTA_RECALCULATE_MISSING_FREES);
+ qbox->recalculate = TRUE;
+ }
+}
+
+static int quota_mailbox_sync_deinit(struct mailbox_sync_context *ctx,
+ struct mailbox_sync_status *status_r)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box);
+ int ret;
+
+ ret = qbox->module_ctx.super.sync_deinit(ctx, status_r);
+ /* update quota only after syncing is finished. the quota commit may
+ recalculate the quota and cause all mailboxes to be synced,
+ including the one we're already syncing. */
+ quota_mailbox_sync_commit(qbox);
+ return ret;
+}
+
+static void quota_roots_flush(struct quota *quota)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (roots[i]->backend.v.flush != NULL)
+ roots[i]->backend.v.flush(roots[i]);
+ }
+}
+
+static void quota_mailbox_close(struct mailbox *box)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(box->storage->user);
+
+ /* sync_notify() may be called outside sync_begin()..sync_deinit().
+ make sure we apply changes at close time at latest. */
+ quota_mailbox_sync_commit(qbox);
+
+ /* make sure quota backend flushes all data. this could also be done
+ somewhat later, but user.deinit() is too late, since the flushing
+ can trigger quota recalculation which isn't safe to do anymore
+ at user.deinit() when most of the loaded plugins have already been
+ deinitialized. */
+ quota_roots_flush(quser->quota);
+
+ qbox->module_ctx.super.close(box);
+}
+
+static void quota_mailbox_free(struct mailbox *box)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+
+ if (array_is_created(&qbox->expunge_uids)) {
+ array_free(&qbox->expunge_uids);
+ array_free(&qbox->expunge_sizes);
+ }
+ i_assert(qbox->expunge_qt == NULL ||
+ qbox->expunge_qt->tmp_mail == NULL);
+
+ qbox->module_ctx.super.free(box);
+}
+
+void quota_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct quota_mailbox *qbox;
+
+ if (QUOTA_LIST_CONTEXT(box->list) == NULL)
+ return;
+
+ if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0)
+ return;
+
+ qbox = p_new(box->pool, struct quota_mailbox, 1);
+ qbox->module_ctx.super = *v;
+ box->vlast = &qbox->module_ctx.super;
+
+ v->get_status = quota_get_status;
+ v->transaction_begin = quota_mailbox_transaction_begin;
+ v->transaction_commit = quota_mailbox_transaction_commit;
+ v->transaction_rollback = quota_mailbox_transaction_rollback;
+ v->save_begin = quota_save_begin;
+ v->save_finish = quota_save_finish;
+ v->copy = quota_copy;
+ v->sync_notify = quota_mailbox_sync_notify;
+ v->sync_deinit = quota_mailbox_sync_deinit;
+ v->close = quota_mailbox_close;
+ v->free = quota_mailbox_free;
+ MODULE_CONTEXT_SET(box, quota_storage_module, qbox);
+}
+
+static void quota_mailbox_list_deinit(struct mailbox_list *list)
+{
+ struct quota_mailbox_list *qlist = QUOTA_LIST_CONTEXT(list);
+
+ i_assert(qlist != NULL);
+ quota_remove_user_namespace(list->ns);
+ qlist->module_ctx.super.deinit(list);
+}
+
+struct quota *quota_get_mail_user_quota(struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+
+ return quser == NULL ? NULL : quser->quota;
+}
+
+static void quota_user_deinit(struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(user);
+ struct quota_settings *quota_set = quser->quota->set;
+
+ quota_deinit(&quser->quota);
+ quser->module_ctx.super.deinit(user);
+
+ quota_settings_deinit(&quota_set);
+}
+
+void quota_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct quota_user *quser;
+ struct quota_settings *set;
+ struct quota *quota;
+ const char *error;
+ int ret;
+
+ if ((ret = quota_user_read_settings(user, &set, &error)) > 0) {
+ if (quota_init(set, user, &quota, &error) < 0) {
+ quota_settings_deinit(&set);
+ ret = -1;
+ }
+ }
+
+ if (ret < 0) {
+ user->error = p_strdup_printf(user->pool,
+ "Failed to initialize quota: %s", error);
+ return;
+ }
+ if (ret > 0) {
+ quser = p_new(user->pool, struct quota_user, 1);
+ quser->module_ctx.super = *v;
+ user->vlast = &quser->module_ctx.super;
+ v->deinit = quota_user_deinit;
+ quser->quota = quota;
+
+ MODULE_CONTEXT_SET(user, quota_user_module, quser);
+ } else {
+ e_debug(user->event, "quota: No quota setting - plugin disabled");
+ }
+}
+
+static struct quota_root *
+quota_find_root_for_ns(struct quota *quota, struct mail_namespace *ns)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (roots[i]->ns_prefix != NULL &&
+ strcmp(roots[i]->ns_prefix, ns->prefix) == 0)
+ return roots[i];
+ }
+ return NULL;
+}
+
+void quota_mailbox_list_created(struct mailbox_list *list)
+{
+ struct quota_mailbox_list *qlist;
+ struct quota *quota = NULL;
+ struct quota_root *root;
+ struct mail_user *quota_user;
+ bool add;
+
+ /* see if we have a quota explicitly defined for this namespace */
+ quota = quota_get_mail_user_quota(list->ns->user);
+ if (quota == NULL)
+ return;
+ root = quota_find_root_for_ns(quota, list->ns);
+ if (root != NULL) {
+ /* explicit quota root */
+ root->ns = list->ns;
+ quota_user = list->ns->user;
+ } else {
+ quota_user = list->ns->owner != NULL ?
+ list->ns->owner : list->ns->user;
+ }
+
+ if ((list->ns->flags & NAMESPACE_FLAG_NOQUOTA) != 0)
+ add = FALSE;
+ else if (list->ns->owner == NULL) {
+ /* public namespace - add quota only if namespace is
+ explicitly defined for it */
+ add = root != NULL;
+ } else {
+ /* for shared namespaces add only if the owner has quota
+ enabled */
+ add = QUOTA_USER_CONTEXT(quota_user) != NULL;
+ }
+
+ if (add) {
+ struct mailbox_list_vfuncs *v = list->vlast;
+
+ qlist = p_new(list->pool, struct quota_mailbox_list, 1);
+ qlist->module_ctx.super = *v;
+ list->vlast = &qlist->module_ctx.super;
+ v->deinit = quota_mailbox_list_deinit;
+ MODULE_CONTEXT_SET(list, quota_mailbox_list_module, qlist);
+
+ quota = quota_get_mail_user_quota(quota_user);
+ i_assert(quota != NULL);
+ quota_add_user_namespace(quota, list->ns);
+ }
+}
+
+static void quota_root_set_namespace(struct quota_root *root,
+ struct mail_namespace *namespaces)
+{
+ const struct quota_rule *rule;
+ const char *name;
+ struct mail_namespace *ns;
+ /* silence errors for autocreated (shared) users */
+ bool silent_errors = namespaces->user->autocreated;
+
+ if (root->ns_prefix != NULL && root->ns == NULL) {
+ root->ns = mail_namespace_find_prefix(namespaces,
+ root->ns_prefix);
+ if (root->ns == NULL && !silent_errors) {
+ e_error(root->quota->event,
+ "Unknown namespace: %s",
+ root->ns_prefix);
+ }
+ }
+
+ array_foreach(&root->set->rules, rule) {
+ name = rule->mailbox_mask;
+ ns = mail_namespace_find(namespaces, name);
+ if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0 &&
+ !silent_errors)
+ e_error(root->quota->event,
+ "Unknown namespace: %s", name);
+ }
+}
+
+void quota_mail_namespaces_created(struct mail_namespace *namespaces)
+{
+ struct quota *quota;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ quota = quota_get_mail_user_quota(namespaces->user);
+ if (quota == NULL)
+ return;
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++)
+ quota_root_set_namespace(roots[i], namespaces);
+
+ quota_over_flag_check_startup(quota);
+}
diff --git a/src/plugins/quota/quota-util.c b/src/plugins/quota/quota-util.c
new file mode 100644
index 0000000..95ce369
--- /dev/null
+++ b/src/plugins/quota/quota-util.c
@@ -0,0 +1,465 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "wildcard-match.h"
+#include "quota-private.h"
+
+#include <ctype.h>
+
+#define QUOTA_DEFAULT_GRACE "10%"
+
+#define RULE_NAME_DEFAULT_FORCE "*"
+#define RULE_NAME_DEFAULT_NONFORCE "?"
+
+struct quota_rule *
+quota_root_rule_find(struct quota_root_settings *root_set, const char *name)
+{
+ struct quota_rule *rule;
+
+ array_foreach_modifiable(&root_set->rules, rule) {
+ if (wildcard_match(name, rule->mailbox_mask))
+ return rule;
+ }
+ return NULL;
+}
+
+static struct quota_rule *
+quota_root_rule_find_exact(struct quota_root_settings *root_set,
+ const char *name)
+{
+ struct quota_rule *rule;
+
+ array_foreach_modifiable(&root_set->rules, rule) {
+ if (strcmp(rule->mailbox_mask, name) == 0)
+ return rule;
+ }
+ return NULL;
+}
+
+static int
+quota_rule_parse_percentage(struct quota_root_settings *root_set,
+ struct quota_rule *rule,
+ int64_t *limit, const char **error_r)
+{
+ int64_t percentage = *limit;
+
+ if (percentage <= -100 || percentage >= UINT_MAX) {
+ *error_r = "Invalid percentage";
+ return -1;
+ }
+
+ if (rule == &root_set->default_rule) {
+ *error_r = "Default rule can't be a percentage";
+ return -1;
+ }
+
+ if (limit == &rule->bytes_limit)
+ rule->bytes_percent = percentage;
+ else if (limit == &rule->count_limit)
+ rule->count_percent = percentage;
+ else
+ i_unreached();
+ return 0;
+}
+
+static int quota_limit_parse(struct quota_root_settings *root_set,
+ struct quota_rule *rule, const char *unit,
+ uint64_t multiply, int64_t *limit,
+ const char **error_r)
+{
+ switch (i_toupper(*unit)) {
+ case '\0':
+ /* default */
+ break;
+ case 'B':
+ multiply = 1;
+ break;
+ case 'K':
+ multiply = 1024;
+ break;
+ case 'M':
+ multiply = 1024*1024;
+ break;
+ case 'G':
+ multiply = 1024*1024*1024;
+ break;
+ case 'T':
+ multiply = 1024ULL*1024*1024*1024;
+ break;
+ case '%':
+ multiply = 0;
+ if (quota_rule_parse_percentage(root_set, rule, limit,
+ error_r) < 0)
+ return -1;
+ break;
+ default:
+ *error_r = t_strdup_printf("Unknown unit: %s", unit);
+ return -1;
+ }
+ *limit *= multiply;
+ return 0;
+}
+
+static void
+quota_rule_recalculate_relative_rules(struct quota_rule *rule,
+ int64_t bytes_limit, int64_t count_limit)
+{
+ if (rule->bytes_percent != 0)
+ rule->bytes_limit = bytes_limit * rule->bytes_percent / 100;
+ if (rule->count_percent != 0)
+ rule->count_limit = count_limit * rule->count_percent / 100;
+}
+
+void quota_root_recalculate_relative_rules(struct quota_root_settings *root_set,
+ int64_t bytes_limit,
+ int64_t count_limit)
+{
+ struct quota_rule *rule;
+ struct quota_warning_rule *warning_rule;
+
+ array_foreach_modifiable(&root_set->rules, rule) {
+ quota_rule_recalculate_relative_rules(rule, bytes_limit,
+ count_limit);
+ }
+
+ array_foreach_modifiable(&root_set->warning_rules, warning_rule) {
+ quota_rule_recalculate_relative_rules(&warning_rule->rule,
+ bytes_limit, count_limit);
+ }
+ quota_rule_recalculate_relative_rules(&root_set->grace_rule,
+ bytes_limit, 0);
+ root_set->last_mail_max_extra_bytes = root_set->grace_rule.bytes_limit;
+
+ if (root_set->set->initialized) {
+ e_debug(root_set->set->event,
+ "Quota root %s: Recalculated relative rules with "
+ "bytes=%lld count=%lld. Now grace=%"PRIu64, root_set->name,
+ (long long)bytes_limit, (long long)count_limit,
+ root_set->last_mail_max_extra_bytes);
+ }
+}
+
+static int
+quota_rule_parse_limits(struct quota_root_settings *root_set,
+ struct quota_rule *rule, const char *limits,
+ const char *full_rule_def,
+ bool relative_rule, const char **error_r)
+{
+ const char **args, *key, *value, *error, *p;
+ uint64_t multiply;
+ int64_t *limit;
+
+ args = t_strsplit(limits, ":");
+ for (; *args != NULL; args++) {
+ multiply = 1;
+ limit = NULL;
+
+ key = *args;
+ value = strchr(key, '=');
+ if (value == NULL)
+ value = "";
+ else
+ key = t_strdup_until(key, value++);
+
+ if (*value == '+') {
+ if (!relative_rule) {
+ *error_r = "Rule limit cannot have '+'";
+ return -1;
+ }
+ value++;
+ } else if (*value != '-' && relative_rule) {
+ e_warning(root_set->set->event, "quota root %s rule %s: "
+ "obsolete configuration for rule '%s' "
+ "should be changed to '%s=+%s'",
+ root_set->name, full_rule_def,
+ *args, key, value);
+ }
+
+ if (strcmp(key, "storage") == 0) {
+ multiply = 1024;
+ limit = &rule->bytes_limit;
+ if (str_parse_int64(value, limit, &p) < 0) {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Invalid storage limit: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "bytes") == 0) {
+ limit = &rule->bytes_limit;
+ if (str_parse_int64(value, limit, &p) < 0) {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Invalid bytes limit: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "messages") == 0) {
+ limit = &rule->count_limit;
+ if (str_parse_int64(value, limit, &p) < 0) {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Invalid bytes messages: %s", value);
+ return -1;
+ }
+ } else {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Unknown rule limit name: %s", key);
+ return -1;
+ }
+
+ if (quota_limit_parse(root_set, rule, p, multiply,
+ limit, &error) < 0) {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Invalid rule limit value '%s': %s",
+ *args, error);
+ return -1;
+ }
+ }
+ if (!relative_rule) {
+ if (rule->bytes_limit < 0) {
+ *error_r = "Bytes limit can't be negative";
+ return -1;
+ }
+ if (rule->count_limit < 0) {
+ *error_r = "Count limit can't be negative";
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int quota_root_add_rule(struct quota_root_settings *root_set,
+ const char *rule_def, const char **error_r)
+{
+ struct quota_rule *rule;
+ const char *p, *mailbox_mask;
+ int ret = 0;
+
+ p = strchr(rule_def, ':');
+ if (p == NULL) {
+ *error_r = "Invalid rule";
+ return -1;
+ }
+
+ /* <mailbox mask>:<quota limits> */
+ mailbox_mask = t_strdup_until(rule_def, p++);
+
+ rule = quota_root_rule_find_exact(root_set, mailbox_mask);
+ if (rule == NULL) {
+ if (strcmp(mailbox_mask, RULE_NAME_DEFAULT_NONFORCE) == 0)
+ rule = &root_set->default_rule;
+ else if (strcmp(mailbox_mask, RULE_NAME_DEFAULT_FORCE) == 0) {
+ rule = &root_set->default_rule;
+ root_set->force_default_rule = TRUE;
+ } else {
+ rule = array_append_space(&root_set->rules);
+ rule->mailbox_mask = strcasecmp(mailbox_mask, "INBOX") == 0 ? "INBOX" :
+ p_strdup(root_set->set->pool, mailbox_mask);
+ }
+ }
+
+ if (strcmp(p, "ignore") == 0) {
+ rule->ignore = TRUE;
+ e_debug(root_set->set->event,
+ "Quota rule: root=%s mailbox=%s ignored",
+ root_set->name, mailbox_mask);
+ return 0;
+ }
+
+ if (str_begins(p, "backend=")) {
+ if (root_set->backend->v.parse_rule == NULL) {
+ *error_r = "backend rule not supported";
+ ret = -1;
+ } else if (!root_set->backend->v.parse_rule(root_set, rule,
+ p + 8, error_r))
+ ret = -1;
+ } else {
+ bool relative_rule = rule != &root_set->default_rule;
+
+ if (quota_rule_parse_limits(root_set, rule, p, rule_def,
+ relative_rule, error_r) < 0)
+ ret = -1;
+ }
+
+ quota_root_recalculate_relative_rules(root_set,
+ root_set->default_rule.bytes_limit,
+ root_set->default_rule.count_limit);
+ const char *rule_plus =
+ rule == &root_set->default_rule ? "" : "+";
+
+ e_debug(root_set->set->event, "Quota rule: root=%s mailbox=%s "
+ "bytes=%s%lld%s messages=%s%lld%s",
+ root_set->name, mailbox_mask,
+ rule->bytes_limit > 0 ? rule_plus : "",
+ (long long)rule->bytes_limit,
+ rule->bytes_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", rule->bytes_percent),
+ rule->count_limit > 0 ? rule_plus : "",
+ (long long)rule->count_limit,
+ rule->count_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", rule->count_percent));
+ return ret;
+}
+
+int quota_root_add_warning_rule(struct quota_root_settings *root_set,
+ const char *rule_def, const char **error_r)
+{
+ struct quota_warning_rule *warning;
+ struct quota_rule rule;
+ const char *p, *q;
+ int ret;
+ bool reverse = FALSE;
+
+ p = strchr(rule_def, ' ');
+ if (p == NULL || p[1] == '\0') {
+ *error_r = "No command specified";
+ return -1;
+ }
+
+ if (*rule_def == '+') {
+ /* warn when exceeding quota */
+ q = rule_def+1;
+ } else if (*rule_def == '-') {
+ /* warn when going below quota */
+ q = rule_def+1;
+ reverse = TRUE;
+ } else {
+ /* default: same as '+' */
+ q = rule_def;
+ }
+
+ i_zero(&rule);
+ ret = quota_rule_parse_limits(root_set, &rule, t_strdup_until(q, p),
+ rule_def, FALSE, error_r);
+ if (ret < 0)
+ return -1;
+
+ warning = array_append_space(&root_set->warning_rules);
+ warning->command = p_strdup(root_set->set->pool, p+1);
+ warning->rule = rule;
+ warning->reverse = reverse;
+ if (reverse)
+ root_set->have_reverse_warnings = TRUE;
+
+ quota_root_recalculate_relative_rules(root_set,
+ root_set->default_rule.bytes_limit,
+ root_set->default_rule.count_limit);
+ e_debug(root_set->set->event, "Quota warning: bytes=%"PRId64"%s "
+ "messages=%"PRId64"%s reverse=%s command=%s",
+ warning->rule.bytes_limit,
+ warning->rule.bytes_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", warning->rule.bytes_percent),
+ warning->rule.count_limit,
+ warning->rule.count_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", warning->rule.count_percent),
+ warning->reverse ? "yes" : "no",
+ warning->command);
+ return 0;
+}
+
+int quota_root_parse_grace(struct quota_root_settings *root_set,
+ const char *value, const char **error_r)
+{
+ const char *p;
+
+ if (value == NULL) {
+ /* default */
+ value = QUOTA_DEFAULT_GRACE;
+ }
+
+ if (str_parse_int64(value, &root_set->grace_rule.bytes_limit, &p) < 0)
+ return -1;
+ if (quota_limit_parse(root_set, &root_set->grace_rule, p, 1,
+ &root_set->grace_rule.bytes_limit, error_r) < 0)
+ return -1;
+ quota_rule_recalculate_relative_rules(&root_set->grace_rule,
+ root_set->default_rule.bytes_limit, 0);
+ root_set->last_mail_max_extra_bytes = root_set->grace_rule.bytes_limit;
+ e_debug(root_set->set->event, "Quota grace: root=%s bytes=%lld%s",
+ root_set->name, (long long)root_set->grace_rule.bytes_limit,
+ root_set->grace_rule.bytes_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", root_set->grace_rule.bytes_percent));
+ return 0;
+}
+
+bool quota_warning_match(const struct quota_warning_rule *w,
+ uint64_t bytes_before, uint64_t bytes_current,
+ uint64_t count_before, uint64_t count_current,
+ const char **reason_r)
+{
+#define QUOTA_EXCEEDED(before, current, limit) \
+ ((before) < (uint64_t)(limit) && (current) >= (uint64_t)(limit))
+ if (!w->reverse) {
+ /* over quota (default) */
+ if (QUOTA_EXCEEDED(bytes_before, bytes_current, w->rule.bytes_limit)) {
+ *reason_r = t_strdup_printf("bytes=%"PRIu64" -> %"PRIu64" over limit %"PRId64,
+ bytes_before, bytes_current, w->rule.bytes_limit);
+ return TRUE;
+ }
+ if (QUOTA_EXCEEDED(count_before, count_current, w->rule.count_limit)) {
+ *reason_r = t_strdup_printf("count=%"PRIu64" -> %"PRIu64" over limit %"PRId64,
+ count_before, count_current, w->rule.count_limit);
+ return TRUE;
+ }
+ } else {
+ if (QUOTA_EXCEEDED(bytes_current, bytes_before, w->rule.bytes_limit)) {
+ *reason_r = t_strdup_printf("bytes=%"PRIu64" -> %"PRIu64" below limit %"PRId64,
+ bytes_before, bytes_current, w->rule.bytes_limit);
+ return TRUE;
+ }
+ if (QUOTA_EXCEEDED(count_current, count_before, w->rule.count_limit)) {
+ *reason_r = t_strdup_printf("count=%"PRIu64" -> %"PRIu64" below limit %"PRId64,
+ count_before, count_current, w->rule.count_limit);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+bool quota_transaction_is_over(struct quota_transaction_context *ctx,
+ uoff_t size)
+{
+ if (ctx->count_used < 0) {
+ /* we've deleted some messages. we should be ok, unless we
+ were already over quota and still are after these
+ deletions. */
+ const uint64_t count_deleted = (uint64_t)-ctx->count_used;
+
+ if (ctx->count_over > 0) {
+ if (count_deleted - 1 < ctx->count_over)
+ return TRUE;
+ }
+ } else {
+ if (ctx->count_ceil < 1 ||
+ ctx->count_ceil - 1 < (uint64_t)ctx->count_used) {
+ /* count limit reached */
+ return TRUE;
+ }
+ }
+
+ if (ctx->bytes_used < 0) {
+ const uint64_t bytes_deleted = (uint64_t)-ctx->bytes_used;
+
+ /* we've deleted some messages. same logic as above. */
+ if (ctx->bytes_over > 0) {
+ if (ctx->bytes_over > bytes_deleted) {
+ /* even after deletions we're over quota */
+ return TRUE;
+ }
+ if (size > bytes_deleted - ctx->bytes_over)
+ return TRUE;
+ } else {
+ if (size > bytes_deleted &&
+ size - bytes_deleted < ctx->bytes_ceil)
+ return TRUE;
+ }
+ } else if (size == 0) {
+ /* we need to explicitly test this case, since the generic
+ check would fail if user is already over quota */
+ if (ctx->bytes_over > 0)
+ return TRUE;
+ } else {
+ if (ctx->bytes_ceil < size ||
+ ctx->bytes_ceil - size < (uint64_t)ctx->bytes_used) {
+ /* bytes limit reached */
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
diff --git a/src/plugins/quota/quota.c b/src/plugins/quota/quota.c
new file mode 100644
index 0000000..3d6d8e5
--- /dev/null
+++ b/src/plugins/quota/quota.c
@@ -0,0 +1,1543 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "str.h"
+#include "ioloop.h"
+#include "net.h"
+#include "write-full.h"
+#include "eacces-error.h"
+#include "wildcard-match.h"
+#include "dict.h"
+#include "mailbox-list-private.h"
+#include "quota-private.h"
+#include "quota-fs.h"
+#include "llist.h"
+#include "program-client.h"
+#include "settings-parser.h"
+
+#include <sys/wait.h>
+
+#define DEFAULT_QUOTA_EXCEEDED_MSG \
+ "Quota exceeded (mailbox for user is full)"
+#define QUOTA_LIMIT_SET_PATH DICT_PATH_PRIVATE"quota/limit/"
+
+/* How many seconds after the userdb lookup do we still want to execute the
+ quota_over_script. This applies to quota_over_flag_lazy_check=yes and also
+ after unhibernating IMAP connections. */
+#define QUOTA_OVER_FLAG_MAX_DELAY_SECS 10
+
+struct quota_root_iter {
+ struct quota *quota;
+ struct mailbox *box;
+
+ unsigned int i;
+};
+
+unsigned int quota_module_id = 0;
+
+extern struct quota_backend quota_backend_count;
+extern struct quota_backend quota_backend_dict;
+extern struct quota_backend quota_backend_dirsize;
+extern struct quota_backend quota_backend_fs;
+extern struct quota_backend quota_backend_imapc;
+extern struct quota_backend quota_backend_maildir;
+
+static const struct quota_backend *quota_internal_backends[] = {
+#ifdef HAVE_FS_QUOTA
+ &quota_backend_fs,
+#endif
+ &quota_backend_count,
+ &quota_backend_dict,
+ &quota_backend_dirsize,
+ &quota_backend_imapc,
+ &quota_backend_maildir
+};
+
+static ARRAY(const struct quota_backend*) quota_backends;
+
+static void hidden_param_handler(struct quota_root *_root, const char *param_value);
+static void ignoreunlim_param_handler(struct quota_root *_root, const char *param_value);
+static void noenforcing_param_handler(struct quota_root *_root, const char *param_value);
+static void ns_param_handler(struct quota_root *_root, const char *param_value);
+
+struct quota_param_parser quota_param_hidden = {.param_name = "hidden", .param_handler = hidden_param_handler};
+struct quota_param_parser quota_param_ignoreunlimited = {.param_name = "ignoreunlimited", .param_handler = ignoreunlim_param_handler};
+struct quota_param_parser quota_param_noenforcing = {.param_name = "noenforcing", .param_handler = noenforcing_param_handler};
+struct quota_param_parser quota_param_ns = {.param_name = "ns=", .param_handler = ns_param_handler};
+
+static enum quota_alloc_result quota_default_test_alloc(
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r);
+static void quota_over_flag_check_root(struct quota_root *root);
+
+static const struct quota_backend *quota_backend_find(const char *name)
+{
+ const struct quota_backend *const *backend;
+
+ array_foreach(&quota_backends, backend) {
+ if (strcmp((*backend)->name, name) == 0)
+ return *backend;
+ }
+
+ return NULL;
+}
+
+void quota_backend_register(const struct quota_backend *backend)
+{
+ i_assert(quota_backend_find(backend->name) == NULL);
+ array_push_back(&quota_backends, &backend);
+}
+
+void quota_backend_unregister(const struct quota_backend *backend)
+{
+ for(unsigned int i = 0; i < array_count(&quota_backends); i++) {
+ const struct quota_backend *be =
+ array_idx_elem(&quota_backends, i);
+ if (strcmp(be->name, backend->name) == 0) {
+ array_delete(&quota_backends, i, 1);
+ return;
+ }
+ }
+
+ i_unreached();
+}
+
+void quota_backends_register(void);
+void quota_backends_unregister(void);
+
+void quota_backends_register(void)
+{
+ i_array_init(&quota_backends, 8);
+ array_append(&quota_backends, quota_internal_backends,
+ N_ELEMENTS(quota_internal_backends));
+}
+
+void quota_backends_unregister(void)
+{
+ for(size_t i = 0; i < N_ELEMENTS(quota_internal_backends); i++) {
+ quota_backend_unregister(quota_internal_backends[i]);
+ }
+
+ i_assert(array_count(&quota_backends) == 0);
+ array_free(&quota_backends);
+
+}
+
+static int quota_root_add_rules(struct mail_user *user, const char *root_name,
+ struct quota_root_settings *root_set,
+ const char **error_r)
+{
+ const char *rule_name, *rule, *error;
+ unsigned int i;
+
+ rule_name = t_strconcat(root_name, "_rule", NULL);
+ for (i = 2;; i++) {
+ rule = mail_user_plugin_getenv(user, rule_name);
+ if (rule == NULL)
+ break;
+
+ if (quota_root_add_rule(root_set, rule, &error) < 0) {
+ *error_r = t_strdup_printf("Invalid rule %s: %s",
+ rule, error);
+ return -1;
+ }
+ rule_name = t_strdup_printf("%s_rule%d", root_name, i);
+ }
+ return 0;
+}
+
+static int
+quota_root_add_warning_rules(struct mail_user *user, const char *root_name,
+ struct quota_root_settings *root_set,
+ const char **error_r)
+{
+ const char *rule_name, *rule, *error;
+ unsigned int i;
+
+ rule_name = t_strconcat(root_name, "_warning", NULL);
+ for (i = 2;; i++) {
+ rule = mail_user_plugin_getenv(user, rule_name);
+ if (rule == NULL)
+ break;
+
+ if (quota_root_add_warning_rule(root_set, rule, &error) < 0) {
+ *error_r = t_strdup_printf("Invalid warning rule: %s",
+ rule);
+ return -1;
+ }
+ rule_name = t_strdup_printf("%s_warning%d", root_name, i);
+ }
+ return 0;
+}
+
+static int
+quota_root_parse_set(struct mail_user *user, const char *root_name,
+ struct quota_root_settings *root_set,
+ const char **error_r)
+{
+ const char *name, *value;
+
+ name = t_strconcat(root_name, "_set", NULL);
+ value = mail_user_plugin_getenv(user, name);
+ if (value == NULL)
+ return 0;
+
+ if (!str_begins(value, "dict:")) {
+ *error_r = t_strdup_printf("%s supports only dict backend", name);
+ return -1;
+ }
+ root_set->limit_set = p_strdup(root_set->set->pool, value+5);
+ return 0;
+}
+
+static int
+quota_root_settings_init(struct quota_settings *quota_set, const char *root_def,
+ struct quota_root_settings **set_r,
+ const char **error_r)
+{
+ struct quota_root_settings *root_set;
+ const struct quota_backend *backend;
+ const char *p, *args, *backend_name;
+
+ /* <backend>[:<quota root name>[:<backend args>]] */
+ p = strchr(root_def, ':');
+ if (p == NULL) {
+ backend_name = root_def;
+ args = NULL;
+ } else {
+ backend_name = t_strdup_until(root_def, p);
+ args = p + 1;
+ }
+
+ backend = quota_backend_find(backend_name);
+ if (backend == NULL) {
+ *error_r = t_strdup_printf("Unknown quota backend: %s",
+ backend_name);
+ return -1;
+ }
+
+ root_set = p_new(quota_set->pool, struct quota_root_settings, 1);
+ root_set->set = quota_set;
+ root_set->backend = backend;
+
+ if (args != NULL) {
+ /* save root's name */
+ p = strchr(args, ':');
+ if (p == NULL) {
+ root_set->name = p_strdup(quota_set->pool, args);
+ args = NULL;
+ } else {
+ root_set->name =
+ p_strdup_until(quota_set->pool, args, p);
+ args = p + 1;
+ }
+ } else {
+ root_set->name = "";
+ }
+ root_set->args = p_strdup(quota_set->pool, args);
+
+ e_debug(quota_set->event, "Quota root: name=%s backend=%s args=%s",
+ root_set->name, backend_name, args == NULL ? "" : args);
+
+ p_array_init(&root_set->rules, quota_set->pool, 4);
+ p_array_init(&root_set->warning_rules, quota_set->pool, 4);
+ array_push_back(&quota_set->root_sets, &root_set);
+ *set_r = root_set;
+ return 0;
+}
+
+static int
+quota_root_add(struct quota_settings *quota_set, struct mail_user *user,
+ const char *env, const char *root_name, const char **error_r)
+{
+ struct quota_root_settings *root_set;
+ const char *set_name, *value;
+
+ if (quota_root_settings_init(quota_set, env, &root_set, error_r) < 0)
+ return -1;
+ root_set->set_name = p_strdup(quota_set->pool, root_name);
+ if (quota_root_add_rules(user, root_name, root_set, error_r) < 0)
+ return -1;
+ if (quota_root_add_warning_rules(user, root_name, root_set, error_r) < 0)
+ return -1;
+ if (quota_root_parse_set(user, root_name, root_set, error_r) < 0)
+ return -1;
+
+ set_name = t_strconcat(root_name, "_grace", NULL);
+ value = mail_user_plugin_getenv(user, set_name);
+ if (quota_root_parse_grace(root_set, value, error_r) < 0) {
+ *error_r = t_strdup_printf("Invalid %s value '%s': %s",
+ set_name, value, *error_r);
+ return -1;
+ }
+ return 0;
+}
+
+const char *quota_alloc_result_errstr(enum quota_alloc_result res,
+ struct quota_transaction_context *qt)
+{
+ switch (res) {
+ case QUOTA_ALLOC_RESULT_OK:
+ return "OK";
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ return "Blocked by an ongoing background quota calculation";
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ return "Internal quota calculation error";
+ case QUOTA_ALLOC_RESULT_OVER_MAXSIZE:
+ return "Mail size is larger than the maximum size allowed by "
+ "server configuration";
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA:
+ return qt->quota->set->quota_exceeded_msg;
+ }
+ i_unreached();
+}
+
+int quota_user_read_settings(struct mail_user *user,
+ struct quota_settings **set_r,
+ const char **error_r)
+{
+ struct quota_settings *quota_set;
+ char root_name[5 + MAX_INT_STRLEN];
+ const char *env, *error;
+ unsigned int i;
+ pool_t pool;
+
+ pool = pool_alloconly_create("quota settings", 2048);
+ quota_set = p_new(pool, struct quota_settings, 1);
+ quota_set->pool = pool;
+ quota_set->event = event_create(user->event);
+ quota_set->test_alloc = quota_default_test_alloc;
+ quota_set->debug = user->mail_debug;
+ quota_set->quota_exceeded_msg =
+ mail_user_plugin_getenv(user, "quota_exceeded_message");
+ if (quota_set->quota_exceeded_msg == NULL)
+ quota_set->quota_exceeded_msg = DEFAULT_QUOTA_EXCEEDED_MSG;
+ quota_set->vsizes = mail_user_plugin_getenv_bool(user, "quota_vsizes");
+
+ const char *max_size = mail_user_plugin_getenv(user,
+ "quota_max_mail_size");
+ if (max_size != NULL) {
+ const char *error = NULL;
+ if (settings_get_size(max_size, &quota_set->max_mail_size,
+ &error) < 0) {
+ *error_r = t_strdup_printf("quota_max_mail_size: %s",
+ error);
+ return -1;
+ }
+ }
+
+ p_array_init(&quota_set->root_sets, pool, 4);
+ if (i_strocpy(root_name, "quota", sizeof(root_name)) < 0)
+ i_unreached();
+ for (i = 2;; i++) {
+ env = mail_user_plugin_getenv(user, root_name);
+ if (env == NULL || *env == '\0')
+ break;
+
+ if (quota_root_add(quota_set, user, env, root_name,
+ &error) < 0) {
+ *error_r = t_strdup_printf("Invalid quota root %s: %s",
+ root_name, error);
+ event_unref(&quota_set->event);
+ pool_unref(&pool);
+ return -1;
+ }
+ if (i_snprintf(root_name, sizeof(root_name), "quota%d", i) < 0)
+ i_unreached();
+ }
+ if (quota_set->max_mail_size == 0 &&
+ array_count(&quota_set->root_sets) == 0) {
+ event_unref(&quota_set->event);
+ pool_unref(&pool);
+ return 0;
+ }
+
+ quota_set->initialized = TRUE;
+ *set_r = quota_set;
+ return 1;
+}
+
+void quota_settings_deinit(struct quota_settings **_quota_set)
+{
+ struct quota_settings *quota_set = *_quota_set;
+
+ *_quota_set = NULL;
+
+ event_unref(&quota_set->event);
+ pool_unref(&quota_set->pool);
+}
+
+static void quota_root_deinit(struct quota_root *root)
+{
+ pool_t pool = root->pool;
+
+ if (root->limit_set_dict != NULL)
+ dict_deinit(&root->limit_set_dict);
+ event_unref(&root->backend.event);
+ root->backend.v.deinit(root);
+ pool_unref(&pool);
+}
+
+int quota_root_default_init(struct quota_root *root, const char *args,
+ const char **error_r)
+{
+ const struct quota_param_parser default_params[] = {
+ quota_param_hidden,
+ quota_param_ignoreunlimited,
+ quota_param_noenforcing,
+ quota_param_ns,
+ {.param_name = NULL}
+ };
+ return quota_parse_parameters(root, &args, error_r, default_params, TRUE);
+}
+
+static int
+quota_root_init(struct quota_root_settings *root_set, struct quota *quota,
+ struct quota_root **root_r, const char **error_r)
+{
+ struct quota_root *root;
+
+ root = root_set->backend->v.alloc();
+ root->pool = pool_alloconly_create("quota root", 512);
+ root->set = root_set;
+ root->quota = quota;
+ root->backend = *root_set->backend;
+ root->bytes_limit = root_set->default_rule.bytes_limit;
+ root->count_limit = root_set->default_rule.count_limit;
+
+ array_create(&root->quota_module_contexts, root->pool,
+ sizeof(void *), 10);
+
+ if (root->backend.v.init != NULL) {
+ root->backend.event = event_create(quota->event);
+ event_drop_parent_log_prefixes(root->backend.event, 1);
+ event_set_forced_debug(root->backend.event, root->quota->set->debug);
+
+ if (root->backend.v.init(root, root_set->args, error_r) < 0) {
+ *error_r = t_strdup_printf("%s quota init failed: %s",
+ root->backend.name, *error_r);
+
+ event_unref(&root->backend.event);
+ return -1;
+ }
+ } else {
+ if (quota_root_default_init(root, root_set->args, error_r) < 0)
+ return -1;
+ }
+ if (root_set->default_rule.bytes_limit == 0 &&
+ root_set->default_rule.count_limit == 0 &&
+ root->disable_unlimited_tracking) {
+ quota_root_deinit(root);
+ return 0;
+ }
+ *root_r = root;
+ return 1;
+}
+
+int quota_init(struct quota_settings *quota_set, struct mail_user *user,
+ struct quota **quota_r, const char **error_r)
+{
+ struct quota *quota;
+ struct quota_root *root;
+ struct quota_root_settings *const *root_sets;
+ unsigned int i, count;
+ const char *error;
+ int ret;
+
+ quota = i_new(struct quota, 1);
+ quota->event = event_create(user->event);
+ event_set_forced_debug(quota->event, quota_set->debug);
+ event_set_append_log_prefix(quota->event, "quota: ");
+ quota->user = user;
+ quota->set = quota_set;
+ i_array_init(&quota->roots, 8);
+
+ root_sets = array_get(&quota_set->root_sets, &count);
+ i_array_init(&quota->namespaces, count);
+ for (i = 0; i < count; i++) {
+ ret = quota_root_init(root_sets[i], quota, &root, &error);
+ if (ret < 0) {
+ *error_r = t_strdup_printf("Quota root %s: %s",
+ root_sets[i]->name, error);
+ quota_deinit(&quota);
+ return -1;
+ }
+ if (ret > 0)
+ array_push_back(&quota->roots, &root);
+ }
+ *quota_r = quota;
+ return 0;
+}
+
+void quota_deinit(struct quota **_quota)
+{
+ struct quota *quota = *_quota;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++)
+ quota_root_deinit(roots[i]);
+
+ /* deinit quota roots before setting quser->quota=NULL */
+ *_quota = NULL;
+
+ array_free(&quota->roots);
+ array_free(&quota->namespaces);
+ event_unref(&quota->event);
+ i_free(quota);
+}
+
+static int quota_root_get_rule_limits(struct quota_root *root,
+ const char *mailbox_name,
+ uint64_t *bytes_limit_r,
+ uint64_t *count_limit_r,
+ bool *ignored_r,
+ const char **error_r)
+{
+ struct quota_rule *rule;
+ int64_t bytes_limit, count_limit;
+ int ret;
+
+ *ignored_r = FALSE;
+
+ if (!root->set->force_default_rule) {
+ if (root->backend.v.init_limits != NULL) {
+ const char *error;
+ if (root->backend.v.init_limits(root, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Initializing limits failed for quota backend: %s",
+ error);
+ return -1;
+ }
+ }
+ }
+
+ bytes_limit = root->bytes_limit;
+ count_limit = root->count_limit;
+
+ /* if default rule limits are 0, user has unlimited quota.
+ ignore any specific quota rules */
+ if (bytes_limit != 0 || count_limit != 0) {
+ (void)mail_namespace_find_unalias(root->quota->user->namespaces,
+ &mailbox_name);
+ rule = quota_root_rule_find(root->set, mailbox_name);
+ ret = 1;
+ } else {
+ rule = NULL;
+ ret = 0;
+ }
+
+ if (rule != NULL) {
+ if (!rule->ignore) {
+ bytes_limit += rule->bytes_limit;
+ count_limit += rule->count_limit;
+ } else {
+ bytes_limit = 0;
+ count_limit = 0;
+ *ignored_r = TRUE;
+ }
+ }
+
+ *bytes_limit_r = bytes_limit <= 0 ? 0 : bytes_limit;
+ *count_limit_r = count_limit <= 0 ? 0 : count_limit;
+ return ret;
+}
+
+static bool
+quota_is_duplicate_namespace(struct quota *quota, struct mail_namespace *ns)
+{
+ struct mail_namespace *const *namespaces;
+ unsigned int i, count;
+ const char *path, *path2;
+
+ if (!mailbox_list_get_root_path(ns->list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &path))
+ path = NULL;
+
+ namespaces = array_get(&quota->namespaces, &count);
+ for (i = 0; i < count; i++) {
+ /* count namespace aliases only once. don't rely only on
+ alias_for != NULL, because the alias might have been
+ explicitly added as the wanted quota namespace. */
+ if (ns->alias_for == namespaces[i] ||
+ namespaces[i]->alias_for == ns)
+ continue;
+
+ if (path != NULL &&
+ mailbox_list_get_root_path(namespaces[i]->list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &path2) &&
+ strcmp(path, path2) == 0) {
+ /* duplicate path */
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) == 0)
+ return TRUE;
+
+ /* this is inbox=yes namespace, but the earlier one
+ that had the same location was inbox=no. we need to
+ include the INBOX also in quota calculations, so we
+ can't just ignore this namespace. but since we've
+ already called backend's namespace_added(), we can't
+ just remove it either. so just mark the old one as
+ unwanted namespace.
+
+ an alternative would be to do a bit larger change so
+ namespaces wouldn't be added until
+ mail_namespaces_created() hook is called */
+ i_assert(quota->unwanted_ns == NULL);
+ quota->unwanted_ns = namespaces[i];
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+void quota_add_user_namespace(struct quota *quota, struct mail_namespace *ns)
+{
+ struct quota_root *const *roots;
+ struct quota_backend **backends;
+ unsigned int i, j, count;
+
+ /* first check if there already exists a namespace with the exact same
+ path. we don't want to count them twice. */
+ if (quota_is_duplicate_namespace(quota, ns))
+ return;
+
+ array_push_back(&quota->namespaces, &ns);
+
+ roots = array_get(&quota->roots, &count);
+ /* @UNSAFE: get different backends into one array */
+ backends = t_new(struct quota_backend *, count + 1);
+ for (i = 0; i < count; i++) {
+ for (j = 0; backends[j] != NULL; j++) {
+ if (backends[j]->name == roots[i]->backend.name)
+ break;
+ }
+ if (backends[j] == NULL)
+ backends[j] = &roots[i]->backend;
+ }
+
+ for (i = 0; backends[i] != NULL; i++) {
+ if (backends[i]->v.namespace_added != NULL)
+ backends[i]->v.namespace_added(quota, ns);
+ }
+}
+
+void quota_remove_user_namespace(struct mail_namespace *ns)
+{
+ struct quota *quota;
+ struct mail_namespace *const *namespaces;
+ unsigned int i, count;
+
+ quota = ns->owner != NULL ?
+ quota_get_mail_user_quota(ns->owner) :
+ quota_get_mail_user_quota(ns->user);
+ if (quota == NULL) {
+ /* no quota for this namespace */
+ return;
+ }
+
+ namespaces = array_get(&quota->namespaces, &count);
+ for (i = 0; i < count; i++) {
+ if (namespaces[i] == ns) {
+ array_delete(&quota->namespaces, i, 1);
+ break;
+ }
+ }
+}
+
+struct quota_root_iter *
+quota_root_iter_init_user(struct mail_user *user)
+{
+ struct quota_root_iter *iter;
+
+ iter = i_new(struct quota_root_iter, 1);
+ iter->quota = quota_get_mail_user_quota(user);
+ return iter;
+}
+
+struct quota_root_iter *
+quota_root_iter_init(struct mailbox *box)
+{
+ struct quota_root_iter *iter;
+ struct mail_user *user;
+
+ user = box->list->ns->owner != NULL ?
+ box->list->ns->owner : box->list->ns->user;
+ iter = quota_root_iter_init_user(user);
+ iter->box = box;
+ return iter;
+}
+
+bool quota_root_is_namespace_visible(struct quota_root *root,
+ struct mail_namespace *ns)
+{
+ struct mailbox_list *list = ns->list;
+ struct mail_storage *storage;
+
+ /* this check works as long as there is only one storage per list */
+ if (mailbox_list_get_storage(&list, "", &storage) == 0 &&
+ (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0)
+ return FALSE;
+ if (root->quota->unwanted_ns == ns)
+ return FALSE;
+
+ if (root->ns_prefix != NULL) {
+ if (root->ns != ns)
+ return FALSE;
+ } else {
+ if (ns->owner == NULL)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+quota_root_is_visible(struct quota_root *root, struct mailbox *box)
+{
+ if (!quota_root_is_namespace_visible(root, box->list->ns))
+ return FALSE;
+ if (array_count(&root->quota->roots) == 1) {
+ /* a single quota root: don't bother checking further */
+ return TRUE;
+ }
+ return root->backend.v.match_box == NULL ? TRUE :
+ root->backend.v.match_box(root, box);
+}
+
+struct quota_root *quota_root_iter_next(struct quota_root_iter *iter)
+{
+ struct quota_root *const *roots, *root = NULL;
+ unsigned int count;
+
+ if (iter->quota == NULL)
+ return NULL;
+
+ roots = array_get(&iter->quota->roots, &count);
+ if (iter->i >= count)
+ return NULL;
+
+ for (; iter->i < count; iter->i++) {
+ if (iter->box != NULL &&
+ !quota_root_is_visible(roots[iter->i], iter->box))
+ continue;
+
+ root = roots[iter->i];
+ break;
+ }
+
+ iter->i++;
+ return root;
+}
+
+void quota_root_iter_deinit(struct quota_root_iter **_iter)
+{
+ struct quota_root_iter *iter = *_iter;
+
+ *_iter = NULL;
+ i_free(iter);
+}
+
+struct quota_root *quota_root_lookup(struct mail_user *user, const char *name)
+{
+ struct quota *quota;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ quota = quota_get_mail_user_quota(user);
+ if (quota == NULL)
+ return NULL;
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(roots[i]->set->name, name) == 0)
+ return roots[i];
+ }
+ return NULL;
+}
+
+const char *quota_root_get_name(struct quota_root *root)
+{
+ return root->set->name;
+}
+
+const char *const *quota_root_get_resources(struct quota_root *root)
+{
+ /* if we haven't checked the quota_over_flag yet, do it now */
+ quota_over_flag_check_root(root);
+
+ return root->backend.v.get_resources(root);
+}
+
+bool quota_root_is_hidden(struct quota_root *root)
+{
+ return root->hidden;
+}
+
+enum quota_get_result
+quota_get_resource(struct quota_root *root, const char *mailbox_name,
+ const char *name, uint64_t *value_r, uint64_t *limit_r,
+ const char **error_r)
+{
+ const char *error;
+ uint64_t bytes_limit, count_limit;
+ bool ignored, kilobytes = FALSE;
+ enum quota_get_result ret;
+
+ *value_r = *limit_r = 0;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_KILOBYTES) == 0) {
+ name = QUOTA_NAME_STORAGE_BYTES;
+ kilobytes = TRUE;
+ }
+
+ /* Get the value first. This call may also update quota limits if
+ they're defined externally. */
+ ret = root->backend.v.get_resource(root, name, value_r, &error);
+ if (ret == QUOTA_GET_RESULT_UNLIMITED)
+ i_panic("Quota backend %s returned QUOTA_GET_RESULT_UNLIMITED "
+ "while getting resource %s from box %s",
+ root->backend.name, name, mailbox_name);
+ else if (ret != QUOTA_GET_RESULT_LIMITED) {
+ *error_r = t_strdup_printf(
+ "quota-%s: %s", root->set->backend->name, error);
+ return ret;
+ }
+
+ if (quota_root_get_rule_limits(root, mailbox_name,
+ &bytes_limit, &count_limit,
+ &ignored, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Failed to get quota root rule limits for mailbox %s: %s",
+ mailbox_name, error);
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ }
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *limit_r = bytes_limit;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ *limit_r = count_limit;
+ else
+ *limit_r = 0;
+
+ if (kilobytes) {
+ *value_r = (*value_r + 1023) / 1024;
+ *limit_r = (*limit_r + 1023) / 1024;
+ }
+ return *limit_r == 0 ? QUOTA_GET_RESULT_UNLIMITED : QUOTA_GET_RESULT_LIMITED;
+}
+
+int quota_set_resource(struct quota_root *root, const char *name,
+ uint64_t value, const char **client_error_r)
+{
+ struct dict_transaction_context *trans;
+ const char *key, *error;
+ const struct dict_op_settings *set;
+
+ if (root->set->limit_set == NULL) {
+ *client_error_r = MAIL_ERRSTR_NO_PERMISSION;
+ return -1;
+ }
+ if (strcasecmp(name, QUOTA_NAME_STORAGE_KILOBYTES) == 0)
+ key = "storage";
+ else if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ key = "bytes";
+ else if (strcasecmp(name, QUOTA_NAME_MESSAGES) == 0)
+ key = "messages";
+ else {
+ *client_error_r = t_strdup_printf(
+ "Unsupported resource name: %s", name);
+ return -1;
+ }
+
+ if (root->limit_set_dict == NULL) {
+ struct dict_settings set;
+
+ i_zero(&set);
+ set.base_dir = root->quota->user->set->base_dir;
+ set.event_parent = root->quota->user->event;
+ if (dict_init(root->set->limit_set, &set,
+ &root->limit_set_dict, &error) < 0) {
+ e_error(root->quota->event,
+ "dict_init() failed: %s", error);
+ *client_error_r = "Internal quota limit update error";
+ return -1;
+ }
+ }
+
+ set = mail_user_get_dict_op_settings(root->ns->user);
+ trans = dict_transaction_begin(root->limit_set_dict, set);
+ key = t_strdup_printf(QUOTA_LIMIT_SET_PATH"%s", key);
+ dict_set(trans, key, dec2str(value));
+ if (dict_transaction_commit(&trans, &error) < 0) {
+ e_error(root->quota->event,
+ "dict_transaction_commit() failed: %s", error);
+ *client_error_r = "Internal quota limit update error";
+ return -1;
+ }
+ return 0;
+}
+
+struct quota_transaction_context *quota_transaction_begin(struct mailbox *box)
+{
+ struct quota_transaction_context *ctx;
+ struct quota_root *const *rootp;
+ const struct quota_rule *rule;
+ const char *mailbox_name;
+
+ ctx = i_new(struct quota_transaction_context, 1);
+ ctx->quota = box->list->ns->owner != NULL ?
+ quota_get_mail_user_quota(box->list->ns->owner) :
+ quota_get_mail_user_quota(box->list->ns->user);
+ i_assert(ctx->quota != NULL);
+
+ ctx->box = box;
+ ctx->bytes_ceil = (uint64_t)-1;
+ ctx->bytes_ceil2 = (uint64_t)-1;
+ ctx->count_ceil = (uint64_t)-1;
+
+ mailbox_name = mailbox_get_vname(box);
+ (void)mail_namespace_find_unalias(box->storage->user->namespaces,
+ &mailbox_name);
+
+ ctx->auto_updating = TRUE;
+ array_foreach(&ctx->quota->roots, rootp) {
+ if (!quota_root_is_visible(*rootp, ctx->box))
+ continue;
+
+ rule = quota_root_rule_find((*rootp)->set, mailbox_name);
+ if (rule != NULL && rule->ignore) {
+ /* This mailbox isn't included in quota. This means
+ it's also not included in quota_warnings, so make
+ sure it's fully ignored. */
+ continue;
+ }
+
+ /* If there are reverse quota_warnings, we'll need to track
+ how many bytes were expunged even with auto_updating roots.
+ (An alternative could be to get the current quota usage
+ before and after the expunges, but that's more complicated
+ and probably isn't any better.) */
+ if (!(*rootp)->auto_updating ||
+ (*rootp)->set->have_reverse_warnings)
+ ctx->auto_updating = FALSE;
+ }
+
+ if (box->storage->user->dsyncing) {
+ /* ignore quota for dsync */
+ ctx->limits_set = TRUE;
+ }
+ return ctx;
+}
+
+int quota_transaction_set_limits(struct quota_transaction_context *ctx,
+ enum quota_get_result *error_result_r,
+ const char **error_r)
+{
+ struct quota_root *const *roots;
+ const char *mailbox_name, *error;
+ unsigned int i, count;
+ uint64_t bytes_limit, count_limit, current, limit, diff;
+ bool use_grace, ignored;
+ enum quota_get_result ret;
+
+ if (ctx->limits_set)
+ return 0;
+ ctx->limits_set = TRUE;
+ mailbox_name = mailbox_get_vname(ctx->box);
+ /* use quota_grace only for LDA/LMTP */
+ use_grace = (ctx->box->flags & MAILBOX_FLAG_POST_SESSION) != 0;
+ ctx->no_quota_updates = TRUE;
+
+ /* find the lowest quota limits from all roots and use them */
+ roots = array_get(&ctx->quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ /* make sure variables get initialized */
+ bytes_limit = count_limit = 0;
+ if (!quota_root_is_visible(roots[i], ctx->box))
+ continue;
+ else if (roots[i]->no_enforcing) {
+ ignored = FALSE;
+ } else if (quota_root_get_rule_limits(roots[i], mailbox_name,
+ &bytes_limit, &count_limit,
+ &ignored, &error) < 0) {
+ ctx->failed = TRUE;
+ *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR;
+ *error_r = t_strdup_printf(
+ "Failed to get quota root rule limits for %s: %s",
+ mailbox_name, error);
+ return -1;
+ }
+ if (!ignored)
+ ctx->no_quota_updates = FALSE;
+
+ if (bytes_limit > 0) {
+ ret = quota_get_resource(roots[i], mailbox_name,
+ QUOTA_NAME_STORAGE_BYTES,
+ &current, &limit, &error);
+ if (ret == QUOTA_GET_RESULT_LIMITED) {
+ if (limit <= current) {
+ /* over quota */
+ ctx->bytes_ceil = 0;
+ ctx->bytes_ceil2 = 0;
+ diff = current - limit;
+ if (ctx->bytes_over < diff)
+ ctx->bytes_over = diff;
+ } else {
+ diff = limit - current;
+ if (ctx->bytes_ceil2 > diff)
+ ctx->bytes_ceil2 = diff;
+ diff += !use_grace ? 0 :
+ roots[i]->set->last_mail_max_extra_bytes;
+ if (ctx->bytes_ceil > diff)
+ ctx->bytes_ceil = diff;
+ }
+ } else if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ ctx->failed = TRUE;
+ *error_result_r = ret;
+ *error_r = t_strdup_printf(
+ "Failed to get quota resource "
+ QUOTA_NAME_STORAGE_BYTES" for %s: %s",
+ mailbox_name, error);
+ return -1;
+ }
+ }
+
+ if (count_limit > 0) {
+ ret = quota_get_resource(roots[i], mailbox_name,
+ QUOTA_NAME_MESSAGES,
+ &current, &limit, &error);
+ if (ret == QUOTA_GET_RESULT_LIMITED) {
+ if (limit <= current) {
+ /* over quota */
+ ctx->count_ceil = 0;
+ diff = current - limit;
+ if (ctx->count_over < diff)
+ ctx->count_over = diff;
+ } else {
+ diff = limit - current;
+ if (ctx->count_ceil > diff)
+ ctx->count_ceil = diff;
+ }
+ } else if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ ctx->failed = TRUE;
+ *error_result_r = ret;
+ *error_r = t_strdup_printf(
+ "Failed to get quota resource "
+ QUOTA_NAME_MESSAGES" for %s: %s",
+ mailbox_name, error);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+static void quota_warning_execute(struct quota_root *root, const char *cmd,
+ const char *last_arg, const char *reason)
+{
+ const char *socket_path, *const *args, *error, *scheme, *ptr;
+
+ struct program_client_settings set = {
+ .client_connect_timeout_msecs = 1000,
+ .debug = root->quota->user->mail_debug,
+ };
+ struct program_client *pc;
+
+ restrict_access_init(&set.restrict_set);
+
+ e_debug(root->quota->event, "Executing warning: %s (because %s)", cmd, reason);
+
+ args = t_strsplit_spaces(cmd, " ");
+ if (last_arg != NULL) {
+ unsigned int count = str_array_length(args);
+ const char **new_args = t_new(const char *, count + 2);
+
+ memcpy(new_args, args, sizeof(const char *) * count);
+ new_args[count] = last_arg;
+ args = new_args;
+ }
+ socket_path = args[0];
+
+ if ((ptr = strchr(socket_path, ':')) != NULL) {
+ scheme = t_strcut(socket_path, ':');
+ socket_path = ptr+1;
+ } else {
+ scheme = "unix";
+ }
+
+ if (*socket_path != '/' &&
+ strcmp(scheme, "unix") == 0)
+ socket_path =
+ t_strconcat(root->quota->user->set->base_dir,
+ "/", socket_path, NULL);
+
+ socket_path = t_strdup_printf("%s:%s", scheme, socket_path);
+
+ args++;
+
+ if (program_client_create(socket_path, args, &set, TRUE,
+ &pc, &error) < 0) {
+ e_error(root->quota->event,
+ "program_client_create(%s) failed: %s", socket_path,
+ error);
+ return;
+ }
+
+ (void)program_client_run(pc);
+
+ program_client_destroy(&pc);
+}
+
+static void quota_warnings_execute(struct quota_transaction_context *ctx,
+ struct quota_root *root)
+{
+ struct quota_warning_rule *warnings;
+ unsigned int i, count;
+ uint64_t bytes_current, bytes_before, bytes_limit;
+ uint64_t count_current, count_before, count_limit;
+ const char *reason, *error;
+
+ warnings = array_get_modifiable(&root->set->warning_rules, &count);
+ if (count == 0)
+ return;
+
+ if (quota_get_resource(root, "", QUOTA_NAME_STORAGE_BYTES,
+ &bytes_current, &bytes_limit, &error) == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ e_error(root->quota->event,
+ "Failed to get quota resource "QUOTA_NAME_STORAGE_BYTES
+ ": %s", error);
+ return;
+ }
+ if (quota_get_resource(root, "", QUOTA_NAME_MESSAGES,
+ &count_current, &count_limit, &error) == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ e_error(root->quota->event,
+ "Failed to get quota resource "QUOTA_NAME_MESSAGES
+ ": %s", error);
+ return;
+ }
+
+ if (ctx->bytes_used > 0 && bytes_current < (uint64_t)ctx->bytes_used)
+ bytes_before = 0;
+ else
+ bytes_before = (int64_t)bytes_current - ctx->bytes_used;
+
+ if (ctx->count_used > 0 && count_current < (uint64_t)ctx->count_used)
+ count_before = 0;
+ else
+ count_before = (int64_t)count_current - ctx->count_used;
+ for (i = 0; i < count; i++) {
+ if (quota_warning_match(&warnings[i],
+ bytes_before, bytes_current,
+ count_before, count_current,
+ &reason)) {
+ quota_warning_execute(root, warnings[i].command,
+ NULL, reason);
+ break;
+ }
+ }
+}
+
+int quota_transaction_commit(struct quota_transaction_context **_ctx)
+{
+ struct quota_transaction_context *ctx = *_ctx;
+ struct quota_rule *rule;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+ const char *mailbox_name;
+ int ret = 0;
+
+ *_ctx = NULL;
+
+ if (ctx->failed)
+ ret = -1;
+ else if (ctx->bytes_used != 0 || ctx->count_used != 0 ||
+ ctx->recalculate != QUOTA_RECALCULATE_DONT) T_BEGIN {
+ ARRAY(struct quota_root *) warn_roots;
+
+ mailbox_name = mailbox_get_vname(ctx->box);
+ (void)mail_namespace_find_unalias(
+ ctx->box->storage->user->namespaces, &mailbox_name);
+
+ roots = array_get(&ctx->quota->roots, &count);
+ t_array_init(&warn_roots, count);
+ for (i = 0; i < count; i++) {
+ if (!quota_root_is_visible(roots[i], ctx->box))
+ continue;
+
+ rule = quota_root_rule_find(roots[i]->set,
+ mailbox_name);
+ if (rule != NULL && rule->ignore) {
+ /* mailbox not included in quota */
+ continue;
+ }
+
+ const char *error;
+ if (roots[i]->backend.v.update(roots[i], ctx, &error) < 0) {
+ e_error(ctx->quota->event,
+ "Failed to update quota for %s: %s",
+ mailbox_name, error);
+ ret = -1;
+ }
+ else if (!ctx->sync_transaction)
+ array_push_back(&warn_roots, &roots[i]);
+ }
+ /* execute quota warnings after all updates. this makes it
+ work correctly regardless of whether backend.get_resource()
+ returns updated values before backend.update() or not.
+ warnings aren't executed when dsync bring the user over,
+ because the user probably already got the warning on the
+ other replica. */
+ array_foreach(&warn_roots, roots)
+ quota_warnings_execute(ctx, *roots);
+ } T_END;
+
+ i_free(ctx);
+ return ret;
+}
+
+static bool quota_over_flag_init_root(struct quota_root *root,
+ const char **quota_over_script_r,
+ const char **quota_over_flag_r,
+ bool *status_r)
+{
+ const char *name, *flag_mask;
+
+ *quota_over_flag_r = NULL;
+ *status_r = FALSE;
+
+ name = t_strconcat(root->set->set_name, "_over_script", NULL);
+ *quota_over_script_r = mail_user_plugin_getenv(root->quota->user, name);
+ if (*quota_over_script_r == NULL) {
+ e_debug(root->quota->event, "quota_over_flag check: "
+ "%s unset - skipping", name);
+ return FALSE;
+ }
+
+ /* e.g.: quota_over_flag_value=TRUE or quota_over_flag_value=* */
+ name = t_strconcat(root->set->set_name, "_over_flag_value", NULL);
+ flag_mask = mail_user_plugin_getenv(root->quota->user, name);
+ if (flag_mask == NULL) {
+ e_debug(root->quota->event, "quota_over_flag check: "
+ "%s unset - skipping", name);
+ return FALSE;
+ }
+
+ /* compare quota_over_flag's value (that comes from userdb) to
+ quota_over_flag_value and save the result. */
+ name = t_strconcat(root->set->set_name, "_over_flag", NULL);
+ *quota_over_flag_r = mail_user_plugin_getenv(root->quota->user, name);
+ *status_r = *quota_over_flag_r != NULL &&
+ wildcard_match_icase(*quota_over_flag_r, flag_mask);
+ return TRUE;
+}
+
+static void quota_over_flag_check_root(struct quota_root *root)
+{
+ const char *quota_over_script, *quota_over_flag, *error;
+ const char *const *resources;
+ unsigned int i;
+ uint64_t value, limit;
+ bool cur_overquota = FALSE;
+ bool quota_over_status;
+ enum quota_get_result ret;
+
+ if (root->quota_over_flag_checked)
+ return;
+ if (root->quota->user->session_create_time +
+ QUOTA_OVER_FLAG_MAX_DELAY_SECS < ioloop_time) {
+ /* userdb's quota_over_flag lookup is too old. */
+ e_debug(root->quota->event, "quota_over_flag check: "
+ "Flag lookup time is too old - skipping");
+ return;
+ }
+ if (root->quota->user->session_restored) {
+ /* we don't know whether the quota_over_script was executed
+ before hibernation. just assume that it was, so we don't
+ unnecessarily call it too often. */
+ e_debug(root->quota->event, "quota_over_flag check: "
+ "Session was already hibernated - skipping");
+ return;
+ }
+ root->quota_over_flag_checked = TRUE;
+ if (!quota_over_flag_init_root(root, &quota_over_script,
+ &quota_over_flag, &quota_over_status))
+ return;
+
+ resources = quota_root_get_resources(root);
+ for (i = 0; resources[i] != NULL; i++) {
+ ret = quota_get_resource(root, "", resources[i], &value,
+ &limit, &error);
+ if (ret == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ /* can't reliably verify this */
+ e_error(root->quota->event, "Quota %s lookup failed -"
+ "can't verify quota_over_flag: %s",
+ resources[i], error);
+ return;
+ }
+ e_debug(root->quota->event, "quota_over_flag check: %s ret=%d"
+ "value=%"PRIu64" limit=%"PRIu64, resources[i], ret,
+ value, limit);
+ if (ret == QUOTA_GET_RESULT_LIMITED && value >= limit)
+ cur_overquota = TRUE;
+ }
+ e_debug(root->quota->event, "quota_over_flag=%d(%s) vs currently overquota=%d",
+ quota_over_status ? 1 : 0,
+ quota_over_flag == NULL ? "(null)" : quota_over_flag,
+ cur_overquota ? 1 : 0);
+ if (cur_overquota != quota_over_status) {
+ quota_warning_execute(root, quota_over_script, quota_over_flag,
+ "quota_over_flag mismatch");
+ }
+}
+
+void quota_over_flag_check_startup(struct quota *quota)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+ const char *name;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ name = t_strconcat(roots[i]->set->set_name, "_over_flag_lazy_check", NULL);
+ if (!mail_user_plugin_getenv_bool(roots[i]->quota->user, name))
+ quota_over_flag_check_root(roots[i]);
+ }
+}
+
+void quota_transaction_rollback(struct quota_transaction_context **_ctx)
+{
+ struct quota_transaction_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+ i_free(ctx);
+}
+
+static int quota_get_mail_size(struct quota_transaction_context *ctx,
+ struct mail *mail, uoff_t *size_r)
+{
+ if (ctx->quota->set->vsizes)
+ return mail_get_virtual_size(mail, size_r);
+ else
+ return mail_get_physical_size(mail, size_r);
+}
+
+enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
+ struct mail *mail, const char **error_r)
+{
+ uoff_t size;
+ const char *error;
+ enum quota_get_result error_res;
+
+ if (quota_transaction_set_limits(ctx, &error_res, error_r) < 0) {
+ if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC)
+ return QUOTA_ALLOC_RESULT_BACKGROUND_CALC;
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ if (ctx->no_quota_updates)
+ return QUOTA_ALLOC_RESULT_OK;
+
+ if (quota_get_mail_size(ctx, mail, &size) < 0) {
+ enum mail_error err;
+ error = mailbox_get_last_internal_error(mail->box, &err);
+
+ if (err == MAIL_ERROR_EXPUNGED) {
+ /* mail being copied was already expunged. it'll fail,
+ so just return success for the quota allocated. */
+ return QUOTA_ALLOC_RESULT_OK;
+ }
+ *error_r = t_strdup_printf(
+ "Failed to get mail size (box=%s, uid=%u): %s",
+ mail->box->vname, mail->uid, error);
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ enum quota_alloc_result ret = quota_test_alloc(ctx, size, error_r);
+ if (ret != QUOTA_ALLOC_RESULT_OK)
+ return ret;
+ /* with quota_try_alloc() we want to keep track of how many bytes
+ we've been adding/removing, so disable auto_updating=TRUE
+ optimization. this of course doesn't work perfectly if
+ quota_alloc() or quota_free_bytes() was already used within the same
+ transaction, but that doesn't normally happen. */
+ ctx->auto_updating = FALSE;
+ quota_alloc(ctx, mail);
+ return QUOTA_ALLOC_RESULT_OK;
+}
+
+enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
+ uoff_t size, const char **error_r)
+{
+ if (ctx->failed) {
+ *error_r = "Quota transaction has failed earlier";
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ enum quota_get_result error_res;
+ if (quota_transaction_set_limits(ctx, &error_res, error_r) < 0) {
+ if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC)
+ return QUOTA_ALLOC_RESULT_BACKGROUND_CALC;
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ uoff_t max_size = ctx->quota->set->max_mail_size;
+ if (max_size > 0 && size > max_size) {
+ *error_r = t_strdup_printf(
+ "Requested allocation size %"PRIuUOFF_T" exceeds max "
+ "mail size %"PRIuUOFF_T, size, max_size);
+ return QUOTA_ALLOC_RESULT_OVER_MAXSIZE;
+ }
+
+ if (ctx->no_quota_updates)
+ return QUOTA_ALLOC_RESULT_OK;
+ /* this is a virtual function mainly for trash plugin and similar,
+ which may automatically delete mails to stay under quota. */
+ return ctx->quota->set->test_alloc(ctx, size, error_r);
+}
+
+static enum quota_alloc_result quota_default_test_alloc(
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+ bool ignore;
+ int ret;
+
+ if (!quota_transaction_is_over(ctx, size))
+ return QUOTA_ALLOC_RESULT_OK;
+
+ /* limit reached. */
+ roots = array_get(&ctx->quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ uint64_t bytes_limit, count_limit;
+
+ if (!quota_root_is_visible(roots[i], ctx->box) ||
+ roots[i]->no_enforcing)
+ continue;
+
+ const char *error;
+ ret = quota_root_get_rule_limits(roots[i],
+ mailbox_get_vname(ctx->box),
+ &bytes_limit, &count_limit,
+ &ignore, &error);
+ if (ret < 0) {
+ *error_r = t_strdup_printf(
+ "Failed to get quota root rule limits: %s",
+ error);
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ /* if size is bigger than any limit, then
+ it is bigger than the lowest limit */
+ if (bytes_limit > 0 && size > bytes_limit) {
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota limit",
+ size);
+ return QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT;
+ }
+ }
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota", size);
+ return QUOTA_ALLOC_RESULT_OVER_QUOTA;
+}
+
+void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail)
+{
+ uoff_t size;
+
+ if (!ctx->auto_updating) {
+ if (quota_get_mail_size(ctx, mail, &size) == 0)
+ ctx->bytes_used += size;
+ }
+
+ ctx->bytes_ceil = ctx->bytes_ceil2;
+ ctx->count_used++;
+}
+
+void quota_free_bytes(struct quota_transaction_context *ctx,
+ uoff_t physical_size)
+{
+ i_assert(physical_size <= INT64_MAX);
+ ctx->bytes_used -= (int64_t)physical_size;
+ ctx->count_used--;
+}
+
+void quota_recalculate(struct quota_transaction_context *ctx,
+ enum quota_recalculate recalculate)
+{
+ ctx->recalculate = recalculate;
+}
+
+static void hidden_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ _root->hidden = TRUE;
+}
+
+static void ignoreunlim_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ _root->disable_unlimited_tracking = TRUE;
+}
+
+static void noenforcing_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ _root->no_enforcing = TRUE;
+}
+
+static void ns_param_handler(struct quota_root *_root, const char *param_value)
+{
+ _root->ns_prefix = p_strdup(_root->pool, param_value);
+}
+
+int quota_parse_parameters(struct quota_root *root, const char **args, const char **error_r,
+ const struct quota_param_parser *valid_params, bool fail_on_unknown)
+{
+ const char *tmp_param_name, *tmp_param_val;
+ size_t tmp_param_len;
+
+ while (*args != NULL && (*args)[0] != '\0') {
+ for (; valid_params->param_name != NULL; ++valid_params) {
+ tmp_param_name = valid_params->param_name;
+ tmp_param_len = strlen(valid_params->param_name);
+ i_assert(*args != NULL);
+ if (strncmp(*args, tmp_param_name, tmp_param_len) == 0) {
+ tmp_param_val = NULL;
+ *args += tmp_param_len;
+ if (tmp_param_name[tmp_param_len - 1] == '=') {
+ const char *next_colon = strchr(*args, ':');
+ tmp_param_val = (next_colon == NULL)?
+ t_strdup(*args):
+ t_strdup_until(*args, next_colon);
+ *args = (next_colon == NULL) ? NULL : next_colon + 1;
+ }
+ else if ((*args)[0] == '\0' ||
+ (*args)[0] == ':') {
+ *args = ((*args)[0] == ':') ? *args + 1 : NULL;
+ /* in case parameter is a boolean second parameter
+ * string parameter value will be ignored by param_handler
+ * we just need some non-NULL value
+ * to indicate that argument is to be processed */
+ tmp_param_val = "";
+ }
+ if (tmp_param_val != NULL) {
+ valid_params->param_handler(root, tmp_param_val);
+ break;
+ }
+ }
+ }
+ if (valid_params->param_name == NULL) {
+ if (fail_on_unknown) {
+ *error_r = t_strdup_printf(
+ "Unknown parameter for backend %s: %s",
+ root->backend.name, *args);
+ return -1;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/plugins/quota/quota.h b/src/plugins/quota/quota.h
new file mode 100644
index 0000000..8de2d8e
--- /dev/null
+++ b/src/plugins/quota/quota.h
@@ -0,0 +1,147 @@
+#ifndef QUOTA_H
+#define QUOTA_H
+
+struct mail;
+struct mailbox;
+struct mail_user;
+
+/* Message storage size kilobytes. */
+#define QUOTA_NAME_STORAGE_KILOBYTES "STORAGE"
+/* Message storage size bytes. This is used only internally. */
+#define QUOTA_NAME_STORAGE_BYTES "STORAGE_BYTES"
+/* Number of messages. */
+#define QUOTA_NAME_MESSAGES "MESSAGE"
+
+struct quota;
+struct quota_settings;
+struct quota_root_settings;
+struct quota_root;
+struct quota_root_iter;
+struct quota_transaction_context;
+
+struct quota_param_parser {
+ char *param_name;
+ void (* param_handler)(struct quota_root *_root, const char *param_value);
+};
+
+extern struct quota_param_parser quota_param_hidden;
+extern struct quota_param_parser quota_param_ignoreunlimited;
+extern struct quota_param_parser quota_param_noenforcing;
+extern struct quota_param_parser quota_param_ns;
+
+enum quota_recalculate {
+ QUOTA_RECALCULATE_DONT = 0,
+ /* We may want to recalculate quota because we weren't able to call
+ quota_free*() correctly for all mails. Quota needs to be
+ recalculated unless the backend does the quota tracking
+ internally. */
+ QUOTA_RECALCULATE_MISSING_FREES,
+ /* doveadm quota recalc called - make sure the quota is correct */
+ QUOTA_RECALCULATE_FORCED
+};
+
+enum quota_alloc_result {
+ QUOTA_ALLOC_RESULT_OK,
+ QUOTA_ALLOC_RESULT_TEMPFAIL,
+ QUOTA_ALLOC_RESULT_OVER_MAXSIZE,
+ QUOTA_ALLOC_RESULT_OVER_QUOTA,
+ /* Mail size is larger than even the maximum allowed quota. */
+ QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT,
+ /* Blocked by ongoing background quota calculation. */
+ QUOTA_ALLOC_RESULT_BACKGROUND_CALC,
+};
+
+/* Anything <= QUOTA_GET_RESULT_INTERNAL_ERROR is an error. */
+enum quota_get_result {
+ /* Ongoing background quota calculation */
+ QUOTA_GET_RESULT_BACKGROUND_CALC,
+ /* Quota resource name doesn't exist */
+ QUOTA_GET_RESULT_UNKNOWN_RESOURCE,
+ /* Internal error */
+ QUOTA_GET_RESULT_INTERNAL_ERROR,
+
+ /* Quota limit exists and was returned successfully */
+ QUOTA_GET_RESULT_LIMITED,
+ /* Quota is unlimited, but its value was returned */
+ QUOTA_GET_RESULT_UNLIMITED,
+};
+
+const char *quota_alloc_result_errstr(enum quota_alloc_result res,
+ struct quota_transaction_context *qt);
+
+int quota_user_read_settings(struct mail_user *user,
+ struct quota_settings **set_r,
+ const char **error_r);
+void quota_settings_deinit(struct quota_settings **quota_set);
+
+/* Add a new rule too the quota root. Returns 0 if ok, -1 if rule is invalid. */
+int quota_root_add_rule(struct quota_root_settings *root_set,
+ const char *rule_def, const char **error_r);
+/* Add a new warning rule for the quota root. Returns 0 if ok, -1 if rule is
+ invalid. */
+int quota_root_add_warning_rule(struct quota_root_settings *root_set,
+ const char *rule_def, const char **error_r);
+
+/* Initialize quota for the given user. Returns 0 and quota_r on success,
+ -1 and error_r on failure. */
+int quota_init(struct quota_settings *quota_set, struct mail_user *user,
+ struct quota **quota_r, const char **error_r);
+void quota_deinit(struct quota **quota);
+
+/* List all visible quota roots. They don't need to be freed. */
+struct quota_root_iter *quota_root_iter_init_user(struct mail_user *user);
+struct quota_root_iter *quota_root_iter_init(struct mailbox *box);
+struct quota_root *quota_root_iter_next(struct quota_root_iter *iter);
+void quota_root_iter_deinit(struct quota_root_iter **iter);
+
+/* Return quota root or NULL. */
+struct quota_root *quota_root_lookup(struct mail_user *user, const char *name);
+
+/* Returns name of the quota root. */
+const char *quota_root_get_name(struct quota_root *root);
+/* Return a list of all resources set for the quota root. */
+const char *const *quota_root_get_resources(struct quota_root *root);
+/* Returns TRUE if quota root is marked as hidden (so it shouldn't be visible
+ to users via IMAP GETQUOTAROOT command). */
+bool quota_root_is_hidden(struct quota_root *root);
+
+/* Returns 1 if values were successfully returned, 0 if resource name doesn't
+ exist or isn't enabled, -1 if error. */
+enum quota_get_result
+quota_get_resource(struct quota_root *root, const char *mailbox_name,
+ const char *name, uint64_t *value_r, uint64_t *limit_r,
+ const char **error_r);
+/* Returns 0 if OK, -1 if error (eg. permission denied, invalid name). */
+int quota_set_resource(struct quota_root *root, const char *name,
+ uint64_t value, const char **client_error_r);
+
+/* Start a new quota transaction. */
+struct quota_transaction_context *quota_transaction_begin(struct mailbox *box);
+/* Commit quota transaction. Returns 0 if ok, -1 if failed. */
+int quota_transaction_commit(struct quota_transaction_context **ctx);
+/* Rollback quota transaction changes. */
+void quota_transaction_rollback(struct quota_transaction_context **ctx);
+
+/* Allocate from quota if there's space. error_r is set when result is not
+ * QUOTA_ALLOC_RESULT_OK. */
+enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
+ struct mail *mail, const char **error_r);
+/* Like quota_try_alloc(), but don't actually allocate anything. */
+enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
+ uoff_t size, const char **error_r);
+/* Update quota by allocating/freeing space used by mail. */
+void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail);
+void quota_free_bytes(struct quota_transaction_context *ctx,
+ uoff_t physical_size);
+/* Mark the quota to be recalculated */
+void quota_recalculate(struct quota_transaction_context *ctx,
+ enum quota_recalculate recalculate);
+
+/* Execute quota_over_scripts if needed. */
+void quota_over_flag_check_startup(struct quota *quota);
+
+/* Common quota parameters parsing loop */
+int quota_parse_parameters(struct quota_root *root, const char **args, const char **error_r,
+ const struct quota_param_parser *valid_params, bool fail_on_unknown);
+
+#endif
diff --git a/src/plugins/quota/rquota.x b/src/plugins/quota/rquota.x
new file mode 100644
index 0000000..3cd5c10
--- /dev/null
+++ b/src/plugins/quota/rquota.x
@@ -0,0 +1,139 @@
+/* @(#)rquota.x 2.1 88/08/01 4.0 RPCSRC */
+/* @(#)rquota.x 1.2 87/09/20 Copyr 1987 Sun Micro */
+
+/*
+ * Remote quota protocol
+ * Requires unix authentication
+ */
+
+const RQ_PATHLEN = 1024;
+
+struct sq_dqblk {
+ unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */
+ unsigned int rq_bsoftlimit; /* preferred limit on disk blks */
+ unsigned int rq_curblocks; /* current block count */
+ unsigned int rq_fhardlimit; /* absolute limit on allocated files */
+ unsigned int rq_fsoftlimit; /* preferred file limit */
+ unsigned int rq_curfiles; /* current # allocated files */
+ unsigned int rq_btimeleft; /* time left for excessive disk use */
+ unsigned int rq_ftimeleft; /* time left for excessive files */
+};
+
+struct getquota_args {
+ string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int gqa_uid; /* Inquire about quota for uid */
+};
+
+struct setquota_args {
+ int sqa_qcmd;
+ string sqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int sqa_id; /* Set quota for uid */
+ sq_dqblk sqa_dqblk;
+};
+
+struct ext_getquota_args {
+ string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int gqa_type; /* Type of quota info is needed about */
+ int gqa_id; /* Inquire about quota for id */
+};
+
+struct ext_setquota_args {
+ int sqa_qcmd;
+ string sqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int sqa_id; /* Set quota for id */
+ int sqa_type; /* Type of quota to set */
+ sq_dqblk sqa_dqblk;
+};
+
+/*
+ * remote quota structure
+ */
+struct rquota {
+ int rq_bsize; /* block size for block counts */
+ bool rq_active; /* indicates whether quota is active */
+ unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */
+ unsigned int rq_bsoftlimit; /* preferred limit on disk blks */
+ unsigned int rq_curblocks; /* current block count */
+ unsigned int rq_fhardlimit; /* absolute limit on allocated files */
+ unsigned int rq_fsoftlimit; /* preferred file limit */
+ unsigned int rq_curfiles; /* current # allocated files */
+ unsigned int rq_btimeleft; /* time left for excessive disk use */
+ unsigned int rq_ftimeleft; /* time left for excessive files */
+};
+
+enum qr_status {
+ Q_OK = 1, /* quota returned */
+ Q_NOQUOTA = 2, /* noquota for uid */
+ Q_EPERM = 3 /* no permission to access quota */
+};
+
+union getquota_rslt switch (qr_status status) {
+case Q_OK:
+ rquota gqr_rquota; /* valid if status == Q_OK */
+case Q_NOQUOTA:
+ void;
+case Q_EPERM:
+ void;
+};
+
+union setquota_rslt switch (qr_status status) {
+case Q_OK:
+ rquota sqr_rquota; /* valid if status == Q_OK */
+case Q_NOQUOTA:
+ void;
+case Q_EPERM:
+ void;
+};
+
+program RQUOTAPROG {
+ version RQUOTAVERS {
+ /*
+ * Get all quotas
+ */
+ getquota_rslt
+ RQUOTAPROC_GETQUOTA(getquota_args) = 1;
+
+ /*
+ * Get active quotas only
+ */
+ getquota_rslt
+ RQUOTAPROC_GETACTIVEQUOTA(getquota_args) = 2;
+
+ /*
+ * Set all quotas
+ */
+ setquota_rslt
+ RQUOTAPROC_SETQUOTA(setquota_args) = 3;
+
+ /*
+ * Get active quotas only
+ */
+ setquota_rslt
+ RQUOTAPROC_SETACTIVEQUOTA(setquota_args) = 4;
+ } = 1;
+ version EXT_RQUOTAVERS {
+ /*
+ * Get all quotas
+ */
+ getquota_rslt
+ RQUOTAPROC_GETQUOTA(ext_getquota_args) = 1;
+
+ /*
+ * Get active quotas only
+ */
+ getquota_rslt
+ RQUOTAPROC_GETACTIVEQUOTA(ext_getquota_args) = 2;
+
+ /*
+ * Set all quotas
+ */
+ setquota_rslt
+ RQUOTAPROC_SETQUOTA(ext_setquota_args) = 3;
+
+ /*
+ * Set active quotas only
+ */
+ setquota_rslt
+ RQUOTAPROC_SETACTIVEQUOTA(ext_setquota_args) = 4;
+ } = 2;
+} = 100011;
diff --git a/src/plugins/quota/test-quota-util.c b/src/plugins/quota/test-quota-util.c
new file mode 100644
index 0000000..682cffe
--- /dev/null
+++ b/src/plugins/quota/test-quota-util.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "quota-private.h"
+#include "test-common.h"
+
+struct test {
+ uint64_t limit, initial_size;
+ int64_t transaction_diff;
+ uint64_t new_size;
+ bool is_over;
+};
+
+static void test_quota_transaction_is_over(void)
+{
+#define MAXU64 (uint64_t)-1
+#define MAXS64 9223372036854775807LL
+#define MINS64 (-MAXS64 - 1LL)
+ static const struct test tests[] = {
+ /* first test only with new_size=1. these are used for both
+ count and bytes tests: */
+
+ /* limit, init, diff, new */
+ { 1, 0, 0, 1, FALSE },
+ { MAXU64, MAXU64, 0, 1, TRUE },
+ { MAXU64, MAXU64-1, 0, 1, FALSE },
+ { MAXU64, MAXU64-1, 1, 1, TRUE },
+ { MAXU64-1, MAXU64-1, 0, 1, TRUE },
+ { MAXU64-1, MAXU64-1, -1, 1, FALSE },
+ { MAXU64-2, MAXU64-1, -1, 1, TRUE },
+ { MAXU64-2, MAXU64-1, -2, 1, FALSE },
+
+ /* these are for bytes tests: */
+
+ /* limit, init, diff, new */
+ { MAXU64, MAXU64, 0, 0, FALSE },
+ { MAXU64, MAXU64-1, 1, 0, FALSE },
+ { MAXU64-1, MAXU64, 1, 0, TRUE },
+ { MAXU64-1, MAXU64, 0, 0, TRUE },
+ { MAXU64-1, MAXU64, -1, 0, FALSE },
+ { MAXU64, MAXU64, 0, 1, TRUE },
+ { MAXU64, 0, 0, MAXU64, FALSE },
+ { MAXU64, 1, 0, MAXU64, TRUE },
+ { MAXU64, 0, 1, MAXU64, TRUE },
+ { MAXU64-1, 0, 0, MAXU64, TRUE },
+ { MAXU64-1, 0, 0, MAXU64-1, FALSE },
+ { MAXU64-1, 1, 0, MAXU64-1, TRUE },
+ { MAXU64-1, 1, -1, MAXU64-1, FALSE },
+ { MAXU64, MAXU64, 0, MAXU64, TRUE },
+ };
+ struct quota_transaction_context ctx;
+ unsigned int i;
+
+ test_begin("quota transaction is over (count)");
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ if (tests[i].new_size != 1)
+ continue;
+
+ i_zero(&ctx);
+ ctx.count_used = tests[i].transaction_diff;
+ if (tests[i].initial_size > tests[i].limit)
+ ctx.count_over = tests[i].initial_size - tests[i].limit;
+ else {
+ ctx.count_ceil = tests[i].limit - tests[i].initial_size;
+ i_assert(ctx.count_used < 0 ||
+ (uint64_t)ctx.count_used <= ctx.count_ceil); /* test is broken otherwise */
+ }
+ test_assert_idx(quota_transaction_is_over(&ctx, 0) == tests[i].is_over, i);
+ }
+ test_end();
+
+ test_begin("quota transaction is over (bytes)");
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ i_zero(&ctx);
+ ctx.count_ceil = 1;
+ ctx.bytes_used = tests[i].transaction_diff;
+ if (tests[i].initial_size > tests[i].limit)
+ ctx.bytes_over = tests[i].initial_size - tests[i].limit;
+ else {
+ ctx.bytes_ceil = tests[i].limit - tests[i].initial_size;
+ i_assert(ctx.bytes_used < 0 ||
+ (uint64_t)ctx.bytes_used <= ctx.bytes_ceil); /* test is broken otherwise */
+ }
+ test_assert_idx(quota_transaction_is_over(&ctx, tests[i].new_size) == tests[i].is_over, i);
+ }
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_quota_transaction_is_over,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/plugins/replication/Makefile.am b/src/plugins/replication/Makefile.am
new file mode 100644
index 0000000..461935c
--- /dev/null
+++ b/src/plugins/replication/Makefile.am
@@ -0,0 +1,25 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/replication \
+ -I$(top_srcdir)/src/plugins/notify
+
+NOPLUGIN_LDFLAGS =
+lib20_replication_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_replication_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+lib20_replication_plugin_la_LIBADD = \
+ ../notify/lib15_notify_plugin.la
+endif
+
+lib20_replication_plugin_la_SOURCES = \
+ replication-plugin.c
+
+noinst_HEADERS = \
+ replication-plugin.h
diff --git a/src/plugins/replication/Makefile.in b/src/plugins/replication/Makefile.in
new file mode 100644
index 0000000..34a0ce3
--- /dev/null
+++ b/src/plugins/replication/Makefile.in
@@ -0,0 +1,827 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/replication
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+@DOVECOT_PLUGIN_DEPS_TRUE@lib20_replication_plugin_la_DEPENDENCIES = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../notify/lib15_notify_plugin.la
+am_lib20_replication_plugin_la_OBJECTS = replication-plugin.lo
+lib20_replication_plugin_la_OBJECTS = \
+ $(am_lib20_replication_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_replication_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_replication_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/replication-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_replication_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_replication_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/replication \
+ -I$(top_srcdir)/src/plugins/notify
+
+lib20_replication_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_replication_plugin.la
+
+@DOVECOT_PLUGIN_DEPS_TRUE@lib20_replication_plugin_la_LIBADD = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../notify/lib15_notify_plugin.la
+
+lib20_replication_plugin_la_SOURCES = \
+ replication-plugin.c
+
+noinst_HEADERS = \
+ replication-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/replication/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/replication/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_replication_plugin.la: $(lib20_replication_plugin_la_OBJECTS) $(lib20_replication_plugin_la_DEPENDENCIES) $(EXTRA_lib20_replication_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_replication_plugin_la_LINK) -rpath $(moduledir) $(lib20_replication_plugin_la_OBJECTS) $(lib20_replication_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replication-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/replication-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/replication-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/replication/replication-plugin.c b/src/plugins/replication/replication-plugin.c
new file mode 100644
index 0000000..9b4bb08
--- /dev/null
+++ b/src/plugins/replication/replication-plugin.c
@@ -0,0 +1,404 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "ioloop.h"
+#include "net.h"
+#include "write-full.h"
+#include "mail-user.h"
+#include "mail-namespace.h"
+#include "mail-storage-private.h"
+#include "notify-plugin.h"
+#include "replication-common.h"
+#include "replication-plugin.h"
+
+
+#define REPLICATION_SOCKET_NAME "replication-notify"
+#define REPLICATION_FIFO_NAME "replication-notify-fifo"
+#define REPLICATION_NOTIFY_DELAY_MSECS 500
+#define REPLICATION_SYNC_TIMEOUT_SECS 10
+
+#define REPLICATION_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, replication_user_module)
+
+struct replication_user {
+ union mail_user_module_context module_ctx;
+
+ const char *socket_path;
+
+ struct timeout *to;
+ enum replication_priority priority;
+ unsigned int sync_secs;
+};
+
+struct replication_mail_txn_context {
+ struct mail_namespace *ns;
+ bool new_messages;
+ bool sync_trans;
+ char *reason;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(replication_user_module,
+ &mail_user_module_register);
+static int fifo_fd;
+static bool fifo_failed;
+static char *fifo_path;
+
+static int
+replication_fifo_notify(struct mail_user *user,
+ enum replication_priority priority)
+{
+ string_t *str;
+ ssize_t ret;
+
+ if (fifo_failed)
+ return -1;
+ if (fifo_fd == -1) {
+ fifo_fd = open(fifo_path, O_WRONLY | O_NONBLOCK);
+ if (fifo_fd == -1) {
+ i_error("open(%s) failed: %m", fifo_path);
+ fifo_failed = TRUE;
+ return -1;
+ }
+ }
+ /* <username> \t <priority> */
+ str = t_str_new(256);
+ str_append_tabescaped(str, user->username);
+ str_append_c(str, '\t');
+ switch (priority) {
+ case REPLICATION_PRIORITY_NONE:
+ case REPLICATION_PRIORITY_SYNC:
+ i_unreached();
+ case REPLICATION_PRIORITY_LOW:
+ str_append(str, "low");
+ break;
+ case REPLICATION_PRIORITY_HIGH:
+ str_append(str, "high");
+ break;
+ }
+ str_append_c(str, '\n');
+ ret = write(fifo_fd, str_data(str), str_len(str));
+ i_assert(ret != 0);
+ if (ret != (ssize_t)str_len(str)) {
+ if (ret > 0)
+ i_error("write(%s) wrote partial data", fifo_path);
+ else if (errno == EAGAIN) {
+ /* busy, try again later */
+ return 0;
+ } else if (errno != EPIPE) {
+ i_error("write(%s) failed: %m", fifo_path);
+ } else {
+ /* server was probably restarted, don't bother logging
+ this. */
+ }
+ if (close(fifo_fd) < 0)
+ i_error("close(%s) failed: %m", fifo_path);
+ fifo_fd = -1;
+ return -1;
+ }
+ return 1;
+}
+
+static void replication_notify_now(struct mail_user *user)
+{
+ struct replication_user *ruser = REPLICATION_USER_CONTEXT(user);
+ int ret;
+
+ i_assert(ruser != NULL);
+ i_assert(ruser->priority != REPLICATION_PRIORITY_NONE);
+ i_assert(ruser->priority != REPLICATION_PRIORITY_SYNC);
+
+ if ((ret = replication_fifo_notify(user, ruser->priority)) < 0 &&
+ !fifo_failed) {
+ /* retry once, in case replication server was restarted */
+ ret = replication_fifo_notify(user, ruser->priority);
+ }
+ if (ret != 0) {
+ timeout_remove(&ruser->to);
+ ruser->priority = REPLICATION_PRIORITY_NONE;
+ }
+}
+
+static int replication_notify_sync(struct mail_user *user)
+{
+ struct replication_user *ruser = REPLICATION_USER_CONTEXT(user);
+ string_t *str;
+ char buf[1024];
+ int fd;
+ ssize_t ret;
+ bool success = FALSE;
+
+ i_assert(ruser != NULL);
+
+ fd = net_connect_unix(ruser->socket_path);
+ if (fd == -1) {
+ i_error("net_connect_unix(%s) failed: %m", ruser->socket_path);
+ return -1;
+ }
+ net_set_nonblock(fd, FALSE);
+
+ /* <username> \t "sync" */
+ str = t_str_new(256);
+ str_append_tabescaped(str, user->username);
+ str_append(str, "\tsync\n");
+ alarm(ruser->sync_secs);
+ if (write_full(fd, str_data(str), str_len(str)) < 0) {
+ i_error("write(%s) failed: %m", ruser->socket_path);
+ } else {
+ /* + | - */
+ ret = read(fd, buf, sizeof(buf));
+ if (ret < 0) {
+ if (errno != EINTR) {
+ i_error("read(%s) failed: %m",
+ ruser->socket_path);
+ } else {
+ i_warning("replication(%s): Sync failure: "
+ "Timeout in %u secs",
+ user->username, ruser->sync_secs);
+ }
+ } else if (ret == 0) {
+ i_error("read(%s) failed: EOF", ruser->socket_path);
+ } else if (buf[0] == '+') {
+ /* success */
+ success = TRUE;
+ } else if (buf[0] == '-') {
+ /* failure */
+ if (buf[ret-1] == '\n') ret--;
+ i_warning("replication(%s): Sync failure: %s",
+ user->username, t_strndup(buf+1, ret-1));
+ i_warning("replication(%s): "
+ "Remote sent invalid input: %s",
+ user->username, t_strndup(buf, ret));
+ }
+ }
+ alarm(0);
+ if (close(fd) < 0)
+ i_error("close(%s) failed: %m", ruser->socket_path);
+ return success ? 0 : -1;
+}
+
+static void replication_notify(struct mail_namespace *ns,
+ enum replication_priority priority,
+ const char *event)
+{
+ struct replication_user *ruser;
+
+ ruser = REPLICATION_USER_CONTEXT(ns->user);
+ if (ruser == NULL)
+ return;
+
+ e_debug(ns->user->event,
+ "replication: Replication requested by '%s', priority=%d",
+ event, priority);
+
+ if (priority == REPLICATION_PRIORITY_SYNC) {
+ if (replication_notify_sync(ns->user) == 0) {
+ timeout_remove(&ruser->to);
+ ruser->priority = REPLICATION_PRIORITY_NONE;
+ return;
+ }
+ /* sync replication failed, try as "high" via fifo */
+ priority = REPLICATION_PRIORITY_HIGH;
+ }
+
+ if (ruser->priority < priority)
+ ruser->priority = priority;
+ if (ruser->to == NULL) {
+ ruser->to = timeout_add_short(REPLICATION_NOTIFY_DELAY_MSECS,
+ replication_notify_now, ns->user);
+ }
+}
+
+static void *
+replication_mail_transaction_begin(struct mailbox_transaction_context *t)
+{
+ struct replication_mail_txn_context *ctx;
+
+ ctx = i_new(struct replication_mail_txn_context, 1);
+ ctx->ns = mailbox_get_namespace(t->box);
+ ctx->reason = i_strdup(t->reason);
+ if ((t->flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0) {
+ /* Transaction is from dsync. Don't trigger replication back. */
+ ctx->sync_trans = TRUE;
+ }
+ return ctx;
+}
+
+static void replication_mail_save(void *txn, struct mail *mail ATTR_UNUSED)
+{
+ struct replication_mail_txn_context *ctx =
+ (struct replication_mail_txn_context *)txn;
+
+ ctx->new_messages = TRUE;
+}
+
+static void replication_mail_copy(void *txn, struct mail *src,
+ struct mail *dst)
+{
+ struct replication_mail_txn_context *ctx =
+ (struct replication_mail_txn_context *)txn;
+
+ if (src->box->storage != dst->box->storage) {
+ /* copy between storages, e.g. new mail delivery */
+ ctx->new_messages = TRUE;
+ } else {
+ /* copy within storage, which isn't as high priority since the
+ mail already exists. and especially copies to Trash or to
+ lazy-expunge namespace is pretty low priority. */
+ }
+}
+
+static bool
+replication_want_sync_changes(const struct mail_transaction_commit_changes *changes)
+{
+ /* Replication needs to be triggered on all the user-visible changes,
+ but not e.g. due to writes to cache file. */
+ return (changes->changes_mask &
+ ENUM_NEGATE(MAIL_INDEX_TRANSACTION_CHANGE_OTHERS)) != 0;
+}
+
+static void
+replication_mail_transaction_commit(void *txn,
+ struct mail_transaction_commit_changes *changes)
+{
+ struct replication_mail_txn_context *ctx =
+ (struct replication_mail_txn_context *)txn;
+ struct replication_user *ruser =
+ REPLICATION_USER_CONTEXT(ctx->ns->user);
+ enum replication_priority priority;
+
+ if (ruser != NULL && !ctx->sync_trans &&
+ (ctx->new_messages || replication_want_sync_changes(changes))) {
+ priority = !ctx->new_messages ? REPLICATION_PRIORITY_LOW :
+ ruser->sync_secs == 0 ? REPLICATION_PRIORITY_HIGH :
+ REPLICATION_PRIORITY_SYNC;
+ replication_notify(ctx->ns, priority, ctx->reason);
+ }
+ i_free(ctx->reason);
+ i_free(ctx);
+}
+
+static void replication_mailbox_create(struct mailbox *box)
+{
+ replication_notify(mailbox_get_namespace(box),
+ REPLICATION_PRIORITY_LOW, "mailbox create");
+}
+
+static void
+replication_mailbox_delete_commit(void *txn ATTR_UNUSED,
+ struct mailbox *box)
+{
+ replication_notify(mailbox_get_namespace(box),
+ REPLICATION_PRIORITY_LOW, "mailbox delete");
+}
+
+static void
+replication_mailbox_rename(struct mailbox *src ATTR_UNUSED,
+ struct mailbox *dest)
+{
+ replication_notify(mailbox_get_namespace(dest),
+ REPLICATION_PRIORITY_LOW, "mailbox rename");
+}
+
+static void replication_mailbox_set_subscribed(struct mailbox *box,
+ bool subscribed ATTR_UNUSED)
+{
+ replication_notify(mailbox_get_namespace(box),
+ REPLICATION_PRIORITY_LOW, "mailbox subscribe");
+}
+
+static void replication_user_deinit(struct mail_user *user)
+{
+ struct replication_user *ruser = REPLICATION_USER_CONTEXT(user);
+
+ i_assert(ruser != NULL);
+
+ if (ruser->to != NULL) {
+ replication_notify_now(user);
+ if (ruser->to != NULL) {
+ i_warning("%s: Couldn't send final notification "
+ "due to fifo being busy", fifo_path);
+ timeout_remove(&ruser->to);
+ }
+ }
+
+ ruser->module_ctx.super.deinit(user);
+}
+
+static void replication_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct replication_user *ruser;
+ const char *value;
+
+ value = mail_user_plugin_getenv(user, "mail_replica");
+ if (value == NULL || value[0] == '\0') {
+ e_debug(user->event, "replication: No mail_replica setting - replication disabled");
+ return;
+ }
+
+ if (user->dsyncing) {
+ /* we're running dsync, which means that the remote is telling
+ us about a change. don't trigger a replication back to it */
+ e_debug(user->event, "replication: We're running dsync - replication disabled");
+ return;
+ }
+
+ ruser = p_new(user->pool, struct replication_user, 1);
+ ruser->module_ctx.super = *v;
+ user->vlast = &ruser->module_ctx.super;
+ v->deinit = replication_user_deinit;
+ MODULE_CONTEXT_SET(user, replication_user_module, ruser);
+
+ if (fifo_path == NULL) {
+ /* we'll assume that all users have the same base_dir.
+ they really should. */
+ fifo_path = i_strconcat(user->set->base_dir,
+ "/"REPLICATION_FIFO_NAME, NULL);
+ }
+ ruser->socket_path = p_strconcat(user->pool, user->set->base_dir,
+ "/"REPLICATION_SOCKET_NAME, NULL);
+ value = mail_user_plugin_getenv(user, "replication_sync_timeout");
+ if (value != NULL && str_to_uint(value, &ruser->sync_secs) < 0) {
+ i_error("replication(%s): "
+ "Invalid replication_sync_timeout value: %s",
+ user->username, value);
+ }
+}
+
+static const struct notify_vfuncs replication_vfuncs = {
+ .mail_transaction_begin = replication_mail_transaction_begin,
+ .mail_save = replication_mail_save,
+ .mail_copy = replication_mail_copy,
+ .mail_transaction_commit = replication_mail_transaction_commit,
+ .mailbox_create = replication_mailbox_create,
+ .mailbox_delete_commit = replication_mailbox_delete_commit,
+ .mailbox_rename = replication_mailbox_rename,
+ .mailbox_set_subscribed = replication_mailbox_set_subscribed
+};
+
+static struct notify_context *replication_ctx;
+
+static struct mail_storage_hooks replication_mail_storage_hooks = {
+ .mail_user_created = replication_user_created
+};
+
+void replication_plugin_init(struct module *module)
+{
+ fifo_fd = -1;
+ replication_ctx = notify_register(&replication_vfuncs);
+ mail_storage_hooks_add(module, &replication_mail_storage_hooks);
+}
+
+void replication_plugin_deinit(void)
+{
+ i_close_fd_path(&fifo_fd, fifo_path);
+ i_free_and_null(fifo_path);
+
+ mail_storage_hooks_remove(&replication_mail_storage_hooks);
+ notify_unregister(replication_ctx);
+}
+
+const char *replication_plugin_dependencies[] = { "notify", NULL };
diff --git a/src/plugins/replication/replication-plugin.h b/src/plugins/replication/replication-plugin.h
new file mode 100644
index 0000000..7fa344f
--- /dev/null
+++ b/src/plugins/replication/replication-plugin.h
@@ -0,0 +1,9 @@
+#ifndef REPLICATION_PLUGIN_H
+#define REPLICATION_PLUGIN_H
+
+extern const char *replication_plugin_dependencies[];
+
+void replication_plugin_init(struct module *module);
+void replication_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/trash/Makefile.am b/src/plugins/trash/Makefile.am
new file mode 100644
index 0000000..af15d13
--- /dev/null
+++ b/src/plugins/trash/Makefile.am
@@ -0,0 +1,23 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/quota
+
+NOPLUGIN_LDFLAGS =
+lib11_trash_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib11_trash_plugin.la
+
+if DOVECOT_PLUGIN_DEPS
+lib11_trash_plugin_la_LIBADD = \
+ ../quota/lib10_quota_plugin.la
+endif
+
+lib11_trash_plugin_la_SOURCES = \
+ trash-plugin.c
+
+noinst_HEADERS = \
+ trash-plugin.h
diff --git a/src/plugins/trash/Makefile.in b/src/plugins/trash/Makefile.in
new file mode 100644
index 0000000..8ebd77e
--- /dev/null
+++ b/src/plugins/trash/Makefile.in
@@ -0,0 +1,824 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/trash
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+@DOVECOT_PLUGIN_DEPS_TRUE@lib11_trash_plugin_la_DEPENDENCIES = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../quota/lib10_quota_plugin.la
+am_lib11_trash_plugin_la_OBJECTS = trash-plugin.lo
+lib11_trash_plugin_la_OBJECTS = $(am_lib11_trash_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib11_trash_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib11_trash_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/trash-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib11_trash_plugin_la_SOURCES)
+DIST_SOURCES = $(lib11_trash_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/plugins/quota
+
+lib11_trash_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib11_trash_plugin.la
+
+@DOVECOT_PLUGIN_DEPS_TRUE@lib11_trash_plugin_la_LIBADD = \
+@DOVECOT_PLUGIN_DEPS_TRUE@ ../quota/lib10_quota_plugin.la
+
+lib11_trash_plugin_la_SOURCES = \
+ trash-plugin.c
+
+noinst_HEADERS = \
+ trash-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/trash/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/trash/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib11_trash_plugin.la: $(lib11_trash_plugin_la_OBJECTS) $(lib11_trash_plugin_la_DEPENDENCIES) $(EXTRA_lib11_trash_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib11_trash_plugin_la_LINK) -rpath $(moduledir) $(lib11_trash_plugin_la_OBJECTS) $(lib11_trash_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trash-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/trash-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/trash-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/trash/trash-plugin.c b/src/plugins/trash/trash-plugin.c
new file mode 100644
index 0000000..d918484
--- /dev/null
+++ b/src/plugins/trash/trash-plugin.c
@@ -0,0 +1,392 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "unichar.h"
+#include "istream.h"
+#include "mail-namespace.h"
+#include "mail-search-build.h"
+#include "quota-private.h"
+#include "quota-plugin.h"
+#include "trash-plugin.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#define INIT_TRASH_MAILBOX_COUNT 4
+#define MAX_RETRY_COUNT 3
+
+#define TRASH_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, trash_user_module)
+#define TRASH_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, trash_user_module)
+
+struct trash_mailbox {
+ const char *name;
+ int priority; /* lower number = higher priority */
+
+ struct mail_namespace *ns;
+
+ /* temporarily set while cleaning: */
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+};
+
+struct trash_user {
+ union mail_user_module_context module_ctx;
+
+ const char *config_file;
+ /* ordered by priority, highest first */
+ ARRAY(struct trash_mailbox) trash_boxes;
+};
+
+const char *trash_plugin_version = DOVECOT_ABI_VERSION;
+
+static MODULE_CONTEXT_DEFINE_INIT(trash_user_module,
+ &mail_user_module_register);
+static enum quota_alloc_result (*trash_next_quota_test_alloc)(
+ struct quota_transaction_context *, uoff_t,
+ const char **error_r);
+
+static int trash_clean_mailbox_open(struct trash_mailbox *trash)
+{
+ struct mail_search_args *search_args;
+
+ trash->box = mailbox_alloc(trash->ns->list, trash->name, 0);
+ if (mailbox_open(trash->box) < 0) {
+ mailbox_free(&trash->box);
+ return 0;
+ }
+
+ if (mailbox_sync(trash->box, MAILBOX_SYNC_FLAG_FULL_READ) < 0)
+ return -1;
+
+ trash->trans = mailbox_transaction_begin(trash->box, 0, __func__);
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+ trash->search_ctx = mailbox_search_init(trash->trans,
+ search_args, NULL,
+ MAIL_FETCH_PHYSICAL_SIZE |
+ MAIL_FETCH_RECEIVED_DATE, NULL);
+ mail_search_args_unref(&search_args);
+
+ return mailbox_search_next(trash->search_ctx, &trash->mail) ? 1 : 0;
+}
+
+static int trash_clean_mailbox_get_next(struct trash_mailbox *trash,
+ time_t *received_time_r)
+{
+ int ret;
+
+ if (trash->mail == NULL) {
+ if (trash->box == NULL)
+ ret = trash_clean_mailbox_open(trash);
+ else {
+ ret = mailbox_search_next(trash->search_ctx,
+ &trash->mail) ? 1 : 0;
+ }
+ if (ret <= 0) {
+ *received_time_r = 0;
+ return ret;
+ }
+ }
+
+ if (mail_get_received_date(trash->mail, received_time_r) < 0)
+ return -1;
+ return 1;
+}
+
+static int trash_try_clean_mails(struct quota_transaction_context *ctx,
+ uint64_t size_needed,
+ unsigned int count_needed)
+{
+ struct trash_user *tuser = TRASH_USER_CONTEXT_REQUIRE(ctx->quota->user);
+ struct trash_mailbox *trashes;
+ struct event_reason *reason;
+ unsigned int i, j, count, oldest_idx;
+ time_t oldest, received = 0;
+ uint64_t size, size_expunged = 0;
+ unsigned int expunged_count = 0;
+ int ret = 0;
+
+ reason = event_reason_begin("trash:clean");
+
+ trashes = array_get_modifiable(&tuser->trash_boxes, &count);
+ for (i = 0; i < count; ) {
+ /* expunge oldest mails first in all trash boxes with
+ same priority */
+ oldest_idx = count;
+ oldest = (time_t)-1;
+ for (j = i; j < count; j++) {
+ if (trashes[j].priority != trashes[i].priority)
+ break;
+
+ ret = trash_clean_mailbox_get_next(&trashes[j],
+ &received);
+ if (ret < 0)
+ goto err;
+ if (ret > 0) {
+ if (oldest == (time_t)-1 || received < oldest) {
+ oldest = received;
+ oldest_idx = j;
+ }
+ }
+ }
+
+ if (oldest_idx < count) {
+ if (mail_get_physical_size(trashes[oldest_idx].mail,
+ &size) < 0) {
+ /* maybe expunged already? */
+ trashes[oldest_idx].mail = NULL;
+ continue;
+ }
+
+ mail_expunge(trashes[oldest_idx].mail);
+ expunged_count++;
+ size_expunged += size;
+ if (size_expunged >= size_needed &&
+ expunged_count >= count_needed)
+ break;
+ trashes[oldest_idx].mail = NULL;
+ } else {
+ /* find more mails from next priority's mailbox */
+ i = j;
+ }
+ }
+
+err:
+ for (i = 0; i < count; i++) {
+ struct trash_mailbox *trash = &trashes[i];
+
+ if (trash->box == NULL)
+ continue;
+
+ trash->mail = NULL;
+ (void)mailbox_search_deinit(&trash->search_ctx);
+
+ if (size_expunged >= size_needed &&
+ expunged_count >= count_needed) {
+ (void)mailbox_transaction_commit(&trash->trans);
+ (void)mailbox_sync(trash->box, 0);
+ } else {
+ /* couldn't get enough space, don't expunge anything */
+ mailbox_transaction_rollback(&trash->trans);
+ }
+
+ mailbox_free(&trash->box);
+ }
+ event_reason_end(&reason);
+
+ if (size_expunged < size_needed) {
+ e_debug(ctx->quota->user->event,
+ "trash plugin: Failed to remove enough messages "
+ "(needed %"PRIu64" bytes, expunged only %"PRIu64" bytes)",
+ size_needed, size_expunged);
+ return 0;
+ }
+ if (expunged_count < count_needed) {
+ e_debug(ctx->quota->user->event,
+ "trash plugin: Failed to remove enough messages "
+ "(needed %u messages, expunged only %u messages)",
+ count_needed, expunged_count);
+ return 0;
+ }
+
+ if (ctx->bytes_over > 0) {
+ /* user is over quota. drop the over-bytes first. */
+ i_assert(ctx->bytes_over <= size_expunged);
+ size_expunged -= ctx->bytes_over;
+ ctx->bytes_over = 0;
+ }
+ if (ctx->count_over > 0) {
+ /* user is over quota. drop the over-count first. */
+ i_assert(ctx->count_over <= expunged_count);
+ expunged_count -= ctx->count_over;
+ ctx->count_over = 0;
+ }
+
+ if (ctx->bytes_ceil > ((uint64_t)-1 - size_expunged)) {
+ ctx->bytes_ceil = (uint64_t)-1;
+ } else {
+ ctx->bytes_ceil += size_expunged;
+ }
+ if (ctx->count_ceil < ((uint64_t)-1 - expunged_count)) {
+ ctx->count_ceil = (uint64_t)-1;
+ } else {
+ ctx->count_ceil += expunged_count;
+ }
+ return 1;
+}
+
+static enum quota_alloc_result
+trash_quota_test_alloc(struct quota_transaction_context *ctx,
+ uoff_t size, const char **error_r)
+{
+ int i;
+ uint64_t size_needed = 0;
+ unsigned int count_needed = 0;
+
+ for (i = 0; ; i++) {
+ enum quota_alloc_result ret;
+ ret = trash_next_quota_test_alloc(ctx, size, error_r);
+ if (ret != QUOTA_ALLOC_RESULT_OVER_QUOTA) {
+ if (ret == QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT &&
+ ctx->quota->user->mail_debug)
+ i_debug("trash plugin: Mail is larger than "
+ "quota, won't even try to handle");
+ return ret;
+ }
+
+ if (i == MAX_RETRY_COUNT) {
+ /* trash_try_clean_mails() should have returned 0 if
+ it couldn't get enough space, but allow retrying
+ it a couple of times if there was some extra space
+ that was needed.. */
+ break;
+ }
+
+ if (ctx->bytes_ceil != (uint64_t)-1 &&
+ ctx->bytes_ceil < size + ctx->bytes_over)
+ size_needed = size + ctx->bytes_over - ctx->bytes_ceil;
+ if (ctx->count_ceil != (uint64_t)-1 &&
+ ctx->count_ceil < 1 + ctx->count_over)
+ count_needed = 1 + ctx->count_over - ctx->count_ceil;
+
+ /* not enough space. try deleting some from mailbox. */
+ if (trash_try_clean_mails(ctx, size_needed, count_needed) <= 0) {
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota", size);
+ return QUOTA_ALLOC_RESULT_OVER_QUOTA;
+ }
+ }
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota", size);
+ return QUOTA_ALLOC_RESULT_OVER_QUOTA;
+}
+
+static bool trash_find_storage(struct mail_user *user,
+ struct trash_mailbox *trash)
+{
+ struct mail_namespace *ns;
+
+ ns = mail_namespace_find(user->namespaces, trash->name);
+ if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0)
+ return FALSE;
+
+ trash->ns = ns;
+ return TRUE;
+}
+
+static int trash_mailbox_priority_cmp(const struct trash_mailbox *t1,
+ const struct trash_mailbox *t2)
+{
+ return t1->priority - t2->priority;
+}
+
+static int read_configuration(struct mail_user *user, const char *path)
+{
+ struct trash_user *tuser = TRASH_USER_CONTEXT_REQUIRE(user);
+ struct istream *input;
+ const char *line, *name;
+ struct trash_mailbox *trash;
+ int fd, ret = 0;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ i_error("trash plugin: open(%s) failed: %m", path);
+ return -1;
+ }
+
+ p_array_init(&tuser->trash_boxes, user->pool, INIT_TRASH_MAILBOX_COUNT);
+
+ input = i_stream_create_fd(fd, SIZE_MAX);
+ i_stream_set_return_partial_line(input, TRUE);
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ /* <priority> <mailbox name> */
+ name = strchr(line, ' ');
+ if (name == NULL || name[1] == '\0' || *line == '#')
+ continue;
+
+ trash = array_append_space(&tuser->trash_boxes);
+ trash->name = p_strdup(user->pool, name+1);
+ if (str_to_int(t_strdup_until(line, name),
+ &trash->priority) < 0) {
+ i_error("trash: Invalid priority for mailbox '%s'",
+ trash->name);
+ ret = -1;
+ }
+
+ if (!uni_utf8_str_is_valid(trash->name)) {
+ i_error("trash: Mailbox name not UTF-8: %s",
+ trash->name);
+ ret = -1;
+ }
+ if (!trash_find_storage(user, trash)) {
+ i_error("trash: Namespace not found for mailbox '%s'",
+ trash->name);
+ ret = -1;
+ }
+
+ e_debug(user->event, "trash plugin: Added '%s' with priority %d",
+ trash->name, trash->priority);
+ }
+ i_stream_destroy(&input);
+ i_close_fd(&fd);
+
+ array_sort(&tuser->trash_boxes, trash_mailbox_priority_cmp);
+ return ret;
+}
+
+static void
+trash_mail_user_created(struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+ struct trash_user *tuser;
+ const char *env;
+
+ env = mail_user_plugin_getenv(user, "trash");
+ if (env == NULL) {
+ e_debug(user->event, "trash: No trash setting - plugin disabled");
+ } else if (quser == NULL) {
+ i_error("trash plugin: quota plugin not initialized");
+ } else {
+ tuser = p_new(user->pool, struct trash_user, 1);
+ tuser->config_file = env;
+ MODULE_CONTEXT_SET(user, trash_user_module, tuser);
+ }
+}
+
+static void
+trash_mail_namespaces_created(struct mail_namespace *namespaces)
+{
+ struct mail_user *user = namespaces->user;
+ struct trash_user *tuser = TRASH_USER_CONTEXT(user);
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+
+ if (tuser != NULL && read_configuration(user, tuser->config_file) == 0) {
+ i_assert(quser != NULL);
+ trash_next_quota_test_alloc =
+ quser->quota->set->test_alloc;
+ quser->quota->set->test_alloc = trash_quota_test_alloc;
+ }
+}
+
+static struct mail_storage_hooks trash_mail_storage_hooks = {
+ .mail_user_created = trash_mail_user_created,
+ .mail_namespaces_created = trash_mail_namespaces_created,
+};
+
+void trash_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &trash_mail_storage_hooks);
+}
+
+void trash_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&trash_mail_storage_hooks);
+}
+
+const char *trash_plugin_dependencies[] = { "quota", NULL };
diff --git a/src/plugins/trash/trash-plugin.h b/src/plugins/trash/trash-plugin.h
new file mode 100644
index 0000000..bd6db6b
--- /dev/null
+++ b/src/plugins/trash/trash-plugin.h
@@ -0,0 +1,9 @@
+#ifndef TRASH_PLUGIN_H
+#define TRASH_PLUGIN_H
+
+extern const char *trash_plugin_dependencies[];
+
+void trash_plugin_init(struct module *module);
+void trash_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/var-expand-crypt/Makefile.am b/src/plugins/var-expand-crypt/Makefile.am
new file mode 100644
index 0000000..6feb1a7
--- /dev/null
+++ b/src/plugins/var-expand-crypt/Makefile.am
@@ -0,0 +1,39 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dcrypt
+
+NOPLUGIN_LDFLAGS =
+lib20_var_expand_crypt_la_LDFLAGS = -module -avoid-version
+lib20_auth_var_expand_crypt_la_LDFLAGS = -module -avoid-version
+
+auth_moduledir = $(moduledir)/auth
+
+module_LTLIBRARIES = \
+ lib20_var_expand_crypt.la
+
+auth_module_LTLIBRARIES = \
+ lib20_auth_var_expand_crypt.la
+
+lib20_auth_var_expand_crypt_la_SOURCES = \
+ var-expand-crypt-plugin.c
+
+lib20_var_expand_crypt_la_SOURCES = \
+ var-expand-crypt-plugin.c
+
+test_programs = test-var-expand-crypt
+
+test_var_expand_crypt_CFLAGS = \
+ -DDCRYPT_BUILD_DIR=\"$(top_builddir)/src/lib-dcrypt\"
+test_var_expand_crypt_SOURCES = \
+ test-var-expand-crypt.c
+test_var_expand_crypt_LDADD = \
+ ../../lib-dovecot/libdovecot.la \
+ lib20_auth_var_expand_crypt.la
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+noinst_PROGRAMS = $(test_programs)
diff --git a/src/plugins/var-expand-crypt/Makefile.in b/src/plugins/var-expand-crypt/Makefile.in
new file mode 100644
index 0000000..e2b9ba0
--- /dev/null
+++ b/src/plugins/var-expand-crypt/Makefile.in
@@ -0,0 +1,938 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/plugins/var-expand-crypt
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-var-expand-crypt$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(auth_moduledir)" \
+ "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(auth_module_LTLIBRARIES) $(module_LTLIBRARIES)
+lib20_auth_var_expand_crypt_la_LIBADD =
+am_lib20_auth_var_expand_crypt_la_OBJECTS = \
+ var-expand-crypt-plugin.lo
+lib20_auth_var_expand_crypt_la_OBJECTS = \
+ $(am_lib20_auth_var_expand_crypt_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_auth_var_expand_crypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib20_auth_var_expand_crypt_la_LDFLAGS) $(LDFLAGS) -o $@
+lib20_var_expand_crypt_la_LIBADD =
+am_lib20_var_expand_crypt_la_OBJECTS = var-expand-crypt-plugin.lo
+lib20_var_expand_crypt_la_OBJECTS = \
+ $(am_lib20_var_expand_crypt_la_OBJECTS)
+lib20_var_expand_crypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_var_expand_crypt_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_test_var_expand_crypt_OBJECTS = \
+ test_var_expand_crypt-test-var-expand-crypt.$(OBJEXT)
+test_var_expand_crypt_OBJECTS = $(am_test_var_expand_crypt_OBJECTS)
+test_var_expand_crypt_DEPENDENCIES = ../../lib-dovecot/libdovecot.la \
+ lib20_auth_var_expand_crypt.la
+test_var_expand_crypt_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(test_var_expand_crypt_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po \
+ ./$(DEPDIR)/var-expand-crypt-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_auth_var_expand_crypt_la_SOURCES) \
+ $(lib20_var_expand_crypt_la_SOURCES) \
+ $(test_var_expand_crypt_SOURCES)
+DIST_SOURCES = $(lib20_auth_var_expand_crypt_la_SOURCES) \
+ $(lib20_var_expand_crypt_la_SOURCES) \
+ $(test_var_expand_crypt_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dcrypt
+
+lib20_var_expand_crypt_la_LDFLAGS = -module -avoid-version
+lib20_auth_var_expand_crypt_la_LDFLAGS = -module -avoid-version
+auth_moduledir = $(moduledir)/auth
+module_LTLIBRARIES = \
+ lib20_var_expand_crypt.la
+
+auth_module_LTLIBRARIES = \
+ lib20_auth_var_expand_crypt.la
+
+lib20_auth_var_expand_crypt_la_SOURCES = \
+ var-expand-crypt-plugin.c
+
+lib20_var_expand_crypt_la_SOURCES = \
+ var-expand-crypt-plugin.c
+
+test_programs = test-var-expand-crypt
+test_var_expand_crypt_CFLAGS = \
+ -DDCRYPT_BUILD_DIR=\"$(top_builddir)/src/lib-dcrypt\"
+
+test_var_expand_crypt_SOURCES = \
+ test-var-expand-crypt.c
+
+test_var_expand_crypt_LDADD = \
+ ../../lib-dovecot/libdovecot.la \
+ lib20_auth_var_expand_crypt.la
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/var-expand-crypt/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/var-expand-crypt/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-auth_moduleLTLIBRARIES: $(auth_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(auth_module_LTLIBRARIES)'; test -n "$(auth_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(auth_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(auth_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(auth_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(auth_moduledir)"; \
+ }
+
+uninstall-auth_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(auth_module_LTLIBRARIES)'; test -n "$(auth_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(auth_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(auth_moduledir)/$$f"; \
+ done
+
+clean-auth_moduleLTLIBRARIES:
+ -test -z "$(auth_module_LTLIBRARIES)" || rm -f $(auth_module_LTLIBRARIES)
+ @list='$(auth_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_auth_var_expand_crypt.la: $(lib20_auth_var_expand_crypt_la_OBJECTS) $(lib20_auth_var_expand_crypt_la_DEPENDENCIES) $(EXTRA_lib20_auth_var_expand_crypt_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_auth_var_expand_crypt_la_LINK) -rpath $(auth_moduledir) $(lib20_auth_var_expand_crypt_la_OBJECTS) $(lib20_auth_var_expand_crypt_la_LIBADD) $(LIBS)
+
+lib20_var_expand_crypt.la: $(lib20_var_expand_crypt_la_OBJECTS) $(lib20_var_expand_crypt_la_DEPENDENCIES) $(EXTRA_lib20_var_expand_crypt_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_var_expand_crypt_la_LINK) -rpath $(moduledir) $(lib20_var_expand_crypt_la_OBJECTS) $(lib20_var_expand_crypt_la_LIBADD) $(LIBS)
+
+test-var-expand-crypt$(EXEEXT): $(test_var_expand_crypt_OBJECTS) $(test_var_expand_crypt_DEPENDENCIES) $(EXTRA_test_var_expand_crypt_DEPENDENCIES)
+ @rm -f test-var-expand-crypt$(EXEEXT)
+ $(AM_V_CCLD)$(test_var_expand_crypt_LINK) $(test_var_expand_crypt_OBJECTS) $(test_var_expand_crypt_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/var-expand-crypt-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+test_var_expand_crypt-test-var-expand-crypt.o: test-var-expand-crypt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_var_expand_crypt_CFLAGS) $(CFLAGS) -MT test_var_expand_crypt-test-var-expand-crypt.o -MD -MP -MF $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Tpo -c -o test_var_expand_crypt-test-var-expand-crypt.o `test -f 'test-var-expand-crypt.c' || echo '$(srcdir)/'`test-var-expand-crypt.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Tpo $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-var-expand-crypt.c' object='test_var_expand_crypt-test-var-expand-crypt.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_var_expand_crypt_CFLAGS) $(CFLAGS) -c -o test_var_expand_crypt-test-var-expand-crypt.o `test -f 'test-var-expand-crypt.c' || echo '$(srcdir)/'`test-var-expand-crypt.c
+
+test_var_expand_crypt-test-var-expand-crypt.obj: test-var-expand-crypt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_var_expand_crypt_CFLAGS) $(CFLAGS) -MT test_var_expand_crypt-test-var-expand-crypt.obj -MD -MP -MF $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Tpo -c -o test_var_expand_crypt-test-var-expand-crypt.obj `if test -f 'test-var-expand-crypt.c'; then $(CYGPATH_W) 'test-var-expand-crypt.c'; else $(CYGPATH_W) '$(srcdir)/test-var-expand-crypt.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Tpo $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-var-expand-crypt.c' object='test_var_expand_crypt-test-var-expand-crypt.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_var_expand_crypt_CFLAGS) $(CFLAGS) -c -o test_var_expand_crypt-test-var-expand-crypt.obj `if test -f 'test-var-expand-crypt.c'; then $(CYGPATH_W) 'test-var-expand-crypt.c'; else $(CYGPATH_W) '$(srcdir)/test-var-expand-crypt.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(auth_moduledir)" "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-auth_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po
+ -rm -f ./$(DEPDIR)/var-expand-crypt-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-auth_moduleLTLIBRARIES \
+ install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po
+ -rm -f ./$(DEPDIR)/var-expand-crypt-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-auth_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-auth_moduleLTLIBRARIES clean-generic \
+ clean-libtool clean-moduleLTLIBRARIES clean-noinstPROGRAMS \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-auth_moduleLTLIBRARIES install-data install-data-am \
+ install-dvi install-dvi-am install-exec install-exec-am \
+ install-html install-html-am install-info install-info-am \
+ install-man install-moduleLTLIBRARIES install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-auth_moduleLTLIBRARIES uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/var-expand-crypt/test-var-expand-crypt.c b/src/plugins/var-expand-crypt/test-var-expand-crypt.c
new file mode 100644
index 0000000..e12a040
--- /dev/null
+++ b/src/plugins/var-expand-crypt/test-var-expand-crypt.c
@@ -0,0 +1,102 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "str.h"
+#include "var-expand.h"
+#include "randgen.h"
+#include "dcrypt.h"
+
+struct module;
+
+extern void var_expand_crypt_init(struct module *module);
+extern void var_expand_crypt_deinit(void);
+
+static void test_var_expand_crypt(void)
+{
+ struct var_expand_table table[] = {
+ { '\0', "98b3b40a48ca40f998b3b40a48ca40f9", "iv" },
+ { '\0', "cc2981c8f38aea59cc2981c8f38aea59", "key" },
+ { '\0', "46b58741763fe22598014be26331a082", "encrypted_noiv" },
+ { '\0', "98b3b40a48ca40f998b3b40a48ca40f9$46b58741763fe22598014be26331a082$", "encrypted" },
+ { '\0', "hello, world", "decrypted" },
+ { '\0', NULL, "encrypted2" },
+ { '\0', NULL, NULL }
+ };
+
+ static struct {
+ const char *input;
+ const char *output;
+ int expect_ret;
+ } test_cases[] = {
+ { "%{encrypt;algo=null:decrypted}", "", -1 },
+ { "%{encrypt;algo=aes-128-cbc,iv=98b3b40a48ca40f998b3b40a48ca40f9,key=cc2981c8f38aea59cc2981c8f38aea59:decrypted}", "98b3b40a48ca40f998b3b40a48ca40f9$46b58741763fe22598014be26331a082$", 1 },
+ { "%{encrypt;noiv=yes,algo=aes-128-cbc,iv=98b3b40a48ca40f998b3b40a48ca40f9,key=cc2981c8f38aea59cc2981c8f38aea59:decrypted}", "46b58741763fe22598014be26331a082", 1 },
+ { "%{encrypt;algo=aes-128-cbc,iv=%{iv},key=%{key}:decrypted}", "98b3b40a48ca40f998b3b40a48ca40f9$46b58741763fe22598014be26331a082$", 1 },
+ { "%{decrypt;algo=null:encrypted}", "", -1 },
+ { "%{decrypt;algo=aes-128-cbc,key=%{key}:encrypted}", "hello, world", 1 },
+ { "%{decrypt;algo=aes-128-cbc,iv=%{iv},key=%{key}:encrypted_noiv}", "hello, world", 1 },
+ { "%{decrypt;algo=aes-128-cbc,iv=98b3b40a48ca40f998b3b40a48ca40f9,key=cc2981c8f38aea59cc2981c8f38aea59:encrypted_noiv}", "hello, world", 1 },
+ };
+
+ unsigned int i;
+
+ test_begin("var_expand_crypt");
+ var_expand_crypt_init(NULL);
+
+ for(i=0; i < N_ELEMENTS(test_cases); i++) T_BEGIN {
+ const char *error;
+ string_t *dest = t_str_new(32);
+ int ret = var_expand(dest, test_cases[i].input, table, &error);
+ if (ret < 0) {
+ if (test_cases[i].expect_ret == -1)
+ i_info("Expected: var_expand(%s): %s", test_cases[i].input, error);
+ else
+ i_error("var_expand(%s): %s", test_cases[i].input, error);
+ }
+ test_assert_idx(strcmp(str_c(dest), test_cases[i].output)==0, i);
+ test_assert_idx(ret == test_cases[i].expect_ret, i);
+ } T_END;
+
+ test_end();
+
+ test_begin("var_expand_crypt_random");
+
+ string_t *input = t_str_new(32);
+ string_t *output = t_str_new(32);
+
+ for(i=0;i<1000;i++) {
+ const char *error;
+ str_truncate(input, 0);
+ str_truncate(output, 0);
+
+ test_assert_idx(var_expand(input, "%{encrypt;algo=aes-128-cbc,key=%{key}:decrypted}", table, &error) == 1, i);
+ table[5].value = str_c(input);
+ test_assert_idx(var_expand(output, "%{decrypt;algo=aes-128-cbc,key=%{key}:encrypted2}", table, &error) == 1, i);
+ test_assert_idx(strcmp(str_c(output), table[4].value)==0, i);
+ };
+
+ var_expand_crypt_deinit();
+ test_end();
+}
+
+int main(void)
+{
+ int ret = 0;
+ static void (*const test_functions[])(void) = {
+ test_var_expand_crypt,
+ NULL
+ };
+ struct dcrypt_settings set = {
+ .module_dir = DCRYPT_BUILD_DIR"/.libs"
+ };
+
+ if (!dcrypt_initialize(NULL, &set, NULL))
+ return 0;
+
+ ret = test_run(test_functions);
+
+ dcrypt_deinitialize();
+
+ return ret;
+}
diff --git a/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c
new file mode 100644
index 0000000..1f6cce7
--- /dev/null
+++ b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c
@@ -0,0 +1,335 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hex-binary.h"
+#include "base64.h"
+#include "str.h"
+#include "strescape.h"
+#include "var-expand.h"
+#include "var-expand-private.h"
+#include "dcrypt.h"
+
+#define VAR_EXPAND_CRYPT_DEFAULT_ALGO "AES-256-CBC"
+
+struct module;
+
+enum crypt_field_format {
+ FORMAT_HEX,
+ FORMAT_BASE64
+};
+
+struct var_expand_crypt_context {
+ struct var_expand_context *ctx;
+ const char *algo;
+ string_t *iv;
+ string_t *enckey;
+ enum crypt_field_format format;
+ bool enc_result_only:1;
+};
+
+static bool var_expand_crypt_initialize(const char **error_r);
+
+void var_expand_crypt_init(struct module *module);
+void var_expand_crypt_deinit(void);
+void auth_var_expand_crypt_init(struct module *module);
+void auth_var_expand_crypt_deinit(void);
+
+static int
+var_expand_crypt_settings(struct var_expand_crypt_context *ctx,
+ const char *const *args, const char **error_r)
+{
+ while(args != NULL && *args != NULL) {
+ const char *k = t_strcut(*args, '=');
+ const char *value = strchr(*args, '=');
+ if (value == NULL) {
+ args++;
+ continue;
+ } else {
+ value++;
+ }
+
+ if (strcmp(k, "iv") == 0) {
+ str_truncate(ctx->iv, 0);
+ if (var_expand_with_funcs(ctx->iv, value, ctx->ctx->table,
+ ctx->ctx->func_table,
+ ctx->ctx->context, error_r) < 0) {
+ return -1;
+ }
+ const char *hexiv = t_strdup(str_c(ctx->iv));
+ /* try to decode IV */
+ str_truncate(ctx->iv, 0);
+ hex_to_binary(hexiv, ctx->iv);
+ } if (strcmp(k, "noiv") == 0) {
+ ctx->enc_result_only = strcasecmp(value, "yes")==0;
+ } if (strcmp(k, "algo") == 0) {
+ ctx->algo = value;
+ } else if (strcmp(k, "key") == 0) {
+ str_truncate(ctx->enckey, 0);
+ if (var_expand_with_funcs(ctx->enckey, value,
+ ctx->ctx->table,
+ ctx->ctx->func_table,
+ ctx->ctx->context,
+ error_r) < 0) {
+ return -1;
+ }
+ const char *hexkey = t_strdup(str_c(ctx->enckey));
+ str_truncate(ctx->enckey, 0);
+ hex_to_binary(hexkey, ctx->enckey);
+ } else if (strcmp(k, "format") == 0) {
+ if (strcmp(value, "hex") == 0) {
+ ctx->format = FORMAT_HEX;
+ } else if (strcmp(value, "base64") == 0) {
+ ctx->format = FORMAT_BASE64;
+ } else {
+ *error_r = t_strdup_printf(
+ "Cannot parse hash arguments:"
+ "'%s' is not supported format",
+ value);
+ return -1;
+ }
+ }
+ args++;
+ }
+
+ if (ctx->algo == NULL) {
+ ctx->algo = "AES-256-CBC";
+ }
+
+ return 0;
+}
+
+static int
+var_expand_crypt(struct dcrypt_context_symmetric *dctx, buffer_t *key, buffer_t *iv,
+ const buffer_t *input, buffer_t *output, const char **error_r)
+{
+ /* make sure IV is correct */
+ if (iv->used == 0) {
+ dcrypt_ctx_sym_set_key_iv_random(dctx);
+ /* acquire IV */
+ dcrypt_ctx_sym_get_iv(dctx, iv);
+ } else if (dcrypt_ctx_sym_get_iv_length(dctx) != iv->used) {
+ *error_r = t_strdup_printf("crypt: IV length invalid (%zu != %u)",
+ iv->used,
+ dcrypt_ctx_sym_get_iv_length(dctx));
+ return -1;
+ } else {
+ dcrypt_ctx_sym_set_iv(dctx, iv->data, iv->used);
+ }
+
+ if (dcrypt_ctx_sym_get_key_length(dctx) != key->used) {
+ *error_r = t_strdup_printf("crypt: Key length invalid (%zu != %u)",
+ key->used,
+ dcrypt_ctx_sym_get_key_length(dctx));
+ return -1;
+ } else {
+ dcrypt_ctx_sym_set_key(dctx, key->data, key->used);
+ }
+
+ if (!dcrypt_ctx_sym_init(dctx, error_r) ||
+ !dcrypt_ctx_sym_update(dctx, input->data,
+ input->used, output, error_r) ||
+ !dcrypt_ctx_sym_final(dctx, output, error_r))
+ return -1;
+ return 0;
+}
+
+static int
+var_expand_encrypt(struct var_expand_context *_ctx,
+ const char *key, const char *field,
+ const char **result_r, const char **error_r)
+{
+ if (!var_expand_crypt_initialize(error_r))
+ return -1;
+
+ const char *p = strchr(key, ';');
+ const char *const *args = NULL;
+ const char *value;
+ struct var_expand_crypt_context ctx;
+ string_t *dest;
+ int ret = 0;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.ctx = _ctx;
+ ctx.format = FORMAT_HEX;
+
+ if (p != NULL) {
+ args = t_strsplit(p+1, ",");
+ }
+
+ string_t *field_value = t_str_new(64);
+ ctx.iv = t_str_new(64);
+ ctx.enckey = t_str_new(64);
+ string_t *tmp = t_str_new(128);
+
+ if ((ret = var_expand_long(_ctx, field, strlen(field),
+ &value, error_r)) < 1) {
+ return ret;
+ }
+
+ if (*value == '\0') {
+ *result_r = value;
+ return ret;
+ }
+
+ if (var_expand_crypt_settings(&ctx, args, error_r) < 0)
+ return -1;
+
+ str_append(field_value, value);
+
+ struct dcrypt_context_symmetric *dctx;
+ if (!dcrypt_ctx_sym_create(ctx.algo, DCRYPT_MODE_ENCRYPT, &dctx, error_r))
+ return -1;
+
+ ret = var_expand_crypt(dctx, ctx.enckey, ctx.iv, field_value, tmp, error_r);
+ dcrypt_ctx_sym_destroy(&dctx);
+
+ if (ret == 0) {
+ /* makes compiler happy */
+ const char *enciv = "";
+ const char *res = "";
+
+ switch(ctx.format) {
+ case FORMAT_HEX:
+ enciv = binary_to_hex(ctx.iv->data, ctx.iv->used);
+ res = binary_to_hex(tmp->data, tmp->used);
+ break;
+ case FORMAT_BASE64:
+ dest = t_str_new(32);
+ base64_encode(ctx.iv->data, ctx.iv->used, dest);
+ enciv = str_c(dest);
+ dest = t_str_new(32);
+ base64_encode(tmp->data, tmp->used, dest);
+ res = str_c(dest);
+ break;
+ default:
+ i_unreached();
+ }
+ if (ctx.enc_result_only)
+ *result_r = t_strdup(res);
+ else
+ *result_r = t_strdup_printf("%s$%s$", enciv, res);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int
+var_expand_decrypt(struct var_expand_context *_ctx,
+ const char *key, const char *field,
+ const char **result_r, const char **error_r)
+{
+ if (!var_expand_crypt_initialize(error_r))
+ return -1;
+
+ const char *p = strchr(key, ';');
+ const char *const *args = NULL;
+ const char *value;
+ struct var_expand_crypt_context ctx;
+ int ret = 0;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.ctx = _ctx;
+ ctx.format = FORMAT_HEX;
+
+ if (p != NULL) {
+ args = t_strsplit(p+1, ",");
+ }
+
+ string_t *field_value = t_str_new(64);
+ ctx.iv = t_str_new(64);
+ ctx.enckey = t_str_new(64);
+ string_t *tmp = t_str_new(128);
+
+ if ((ret = var_expand_long(_ctx, field, strlen(field),
+ &value, error_r)) < 1) {
+ return ret;
+ }
+
+ if (*value == '\0') {
+ *result_r = value;
+ return ret;
+ }
+
+ if (var_expand_crypt_settings(&ctx, args, error_r) < 0)
+ return -1;
+
+ const char *encdata = value;
+ const char *enciv = "";
+
+ /* make sure IV is correct */
+ if (ctx.iv->used == 0 && (p = strchr(encdata, '$')) != NULL) {
+ /* see if IV can be taken from data */
+ enciv = t_strcut(encdata, '$');
+ encdata = t_strcut(p+1,'$');
+ }
+
+ str_truncate(field_value, 0);
+
+ /* try to decode iv and encdata */
+ switch(ctx.format) {
+ case FORMAT_HEX:
+ if (ctx.iv->used == 0)
+ hex_to_binary(enciv, ctx.iv);
+ hex_to_binary(encdata, field_value);
+ break;
+ case FORMAT_BASE64:
+ if (ctx.iv->used == 0)
+ str_append_str(ctx.iv, t_base64_decode_str(enciv));
+ str_append_str(field_value, t_base64_decode_str(encdata));
+ break;
+ }
+
+ if (ctx.iv->used == 0) {
+ *error_r = t_strdup_printf("decrypt: IV missing");
+ return -1;
+ }
+
+ struct dcrypt_context_symmetric *dctx;
+ if (!dcrypt_ctx_sym_create(ctx.algo, DCRYPT_MODE_DECRYPT, &dctx, error_r))
+ return -1;
+ ret = var_expand_crypt(dctx, ctx.enckey, ctx.iv, field_value, tmp, error_r);
+ dcrypt_ctx_sym_destroy(&dctx);
+
+ if (ret == 0) {
+ *result_r = str_c(tmp);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static const struct var_expand_extension_func_table funcs[] = {
+ { "encrypt", var_expand_encrypt },
+ { "decrypt", var_expand_decrypt },
+ { NULL, NULL, }
+};
+
+static bool var_expand_crypt_initialize(const char **error_r)
+{
+ return dcrypt_initialize(NULL, NULL, error_r);
+}
+
+void var_expand_crypt_init(struct module *module ATTR_UNUSED)
+{
+ var_expand_register_func_array(funcs);
+ /* do not initialize dcrypt here - saves alot of memory
+ to not load openssl every time. Only load it if
+ needed */
+}
+
+void var_expand_crypt_deinit(void)
+{
+ var_expand_unregister_func_array(funcs);
+}
+
+void auth_var_expand_crypt_init(struct module *module)
+{
+ var_expand_crypt_init(module);
+}
+
+void auth_var_expand_crypt_deinit(void)
+{
+ var_expand_crypt_deinit();
+}
diff --git a/src/plugins/virtual/Makefile.am b/src/plugins/virtual/Makefile.am
new file mode 100644
index 0000000..9ba873d
--- /dev/null
+++ b/src/plugins/virtual/Makefile.am
@@ -0,0 +1,29 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-imap-storage
+
+NOPLUGIN_LDFLAGS =
+lib20_virtual_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_virtual_plugin.la
+
+lib20_virtual_plugin_la_SOURCES = \
+ virtual-config.c \
+ virtual-mail.c \
+ virtual-plugin.c \
+ virtual-search.c \
+ virtual-storage.c \
+ virtual-save.c \
+ virtual-sync.c \
+ virtual-transaction.c
+
+noinst_HEADERS = \
+ virtual-plugin.h \
+ virtual-storage.h \
+ virtual-transaction.h
diff --git a/src/plugins/virtual/Makefile.in b/src/plugins/virtual/Makefile.in
new file mode 100644
index 0000000..aba6783
--- /dev/null
+++ b/src/plugins/virtual/Makefile.in
@@ -0,0 +1,859 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/virtual
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib20_virtual_plugin_la_LIBADD =
+am_lib20_virtual_plugin_la_OBJECTS = virtual-config.lo virtual-mail.lo \
+ virtual-plugin.lo virtual-search.lo virtual-storage.lo \
+ virtual-save.lo virtual-sync.lo virtual-transaction.lo
+lib20_virtual_plugin_la_OBJECTS = \
+ $(am_lib20_virtual_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_virtual_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_virtual_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/virtual-config.Plo \
+ ./$(DEPDIR)/virtual-mail.Plo ./$(DEPDIR)/virtual-plugin.Plo \
+ ./$(DEPDIR)/virtual-save.Plo ./$(DEPDIR)/virtual-search.Plo \
+ ./$(DEPDIR)/virtual-storage.Plo ./$(DEPDIR)/virtual-sync.Plo \
+ ./$(DEPDIR)/virtual-transaction.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_virtual_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_virtual_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-imap-storage
+
+lib20_virtual_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_virtual_plugin.la
+
+lib20_virtual_plugin_la_SOURCES = \
+ virtual-config.c \
+ virtual-mail.c \
+ virtual-plugin.c \
+ virtual-search.c \
+ virtual-storage.c \
+ virtual-save.c \
+ virtual-sync.c \
+ virtual-transaction.c
+
+noinst_HEADERS = \
+ virtual-plugin.h \
+ virtual-storage.h \
+ virtual-transaction.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/virtual/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/virtual/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_virtual_plugin.la: $(lib20_virtual_plugin_la_OBJECTS) $(lib20_virtual_plugin_la_DEPENDENCIES) $(EXTRA_lib20_virtual_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_virtual_plugin_la_LINK) -rpath $(moduledir) $(lib20_virtual_plugin_la_OBJECTS) $(lib20_virtual_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-mail.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-save.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-search.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-sync.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-transaction.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/virtual-config.Plo
+ -rm -f ./$(DEPDIR)/virtual-mail.Plo
+ -rm -f ./$(DEPDIR)/virtual-plugin.Plo
+ -rm -f ./$(DEPDIR)/virtual-save.Plo
+ -rm -f ./$(DEPDIR)/virtual-search.Plo
+ -rm -f ./$(DEPDIR)/virtual-storage.Plo
+ -rm -f ./$(DEPDIR)/virtual-sync.Plo
+ -rm -f ./$(DEPDIR)/virtual-transaction.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/virtual-config.Plo
+ -rm -f ./$(DEPDIR)/virtual-mail.Plo
+ -rm -f ./$(DEPDIR)/virtual-plugin.Plo
+ -rm -f ./$(DEPDIR)/virtual-save.Plo
+ -rm -f ./$(DEPDIR)/virtual-search.Plo
+ -rm -f ./$(DEPDIR)/virtual-storage.Plo
+ -rm -f ./$(DEPDIR)/virtual-sync.Plo
+ -rm -f ./$(DEPDIR)/virtual-transaction.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/virtual/virtual-config.c b/src/plugins/virtual/virtual-config.c
new file mode 100644
index 0000000..05a6305
--- /dev/null
+++ b/src/plugins/virtual/virtual-config.c
@@ -0,0 +1,567 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "crc32.h"
+#include "istream.h"
+#include "str.h"
+#include "unichar.h"
+#include "wildcard-match.h"
+#include "imap-parser.h"
+#include "imap-match.h"
+#include "mail-namespace.h"
+#include "mail-search-build.h"
+#include "mail-search-parser.h"
+#include "mailbox-attribute.h"
+#include "mailbox-list-iter.h"
+#include "imap-metadata.h"
+#include "virtual-storage.h"
+#include "virtual-plugin.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+struct virtual_parse_context {
+ struct virtual_mailbox *mbox;
+ struct istream *input;
+
+ pool_t pool;
+ string_t *rule;
+ unsigned int rule_idx;
+
+ char sep;
+ bool have_wildcards;
+ bool have_mailbox_defines;
+};
+
+static struct mail_search_args *
+virtual_search_args_parse(const string_t *rule, const char **error_r)
+{
+ struct istream *input;
+ struct imap_parser *imap_parser;
+ const struct imap_arg *args;
+ struct mail_search_parser *parser;
+ struct mail_search_args *sargs;
+ const char *charset = "UTF-8";
+ int ret;
+
+ if (str_len(rule) == 0) {
+ sargs = mail_search_build_init();
+ mail_search_build_add_all(sargs);
+ return sargs;
+ }
+
+ input = i_stream_create_from_data(str_data(rule), str_len(rule));
+ (void)i_stream_read(input);
+
+ imap_parser = imap_parser_create(input, NULL, SIZE_MAX);
+ ret = imap_parser_finish_line(imap_parser, 0, 0, &args);
+ if (ret < 0) {
+ sargs = NULL;
+ *error_r = t_strdup(imap_parser_get_error(imap_parser, NULL));
+ } else {
+ parser = mail_search_parser_init_imap(args);
+ if (mail_search_build(mail_search_register_get_imap(),
+ parser, &charset, &sargs, error_r) < 0)
+ sargs = NULL;
+ mail_search_parser_deinit(&parser);
+ }
+
+ imap_parser_unref(&imap_parser);
+ i_stream_destroy(&input);
+ return sargs;
+}
+
+static int
+virtual_config_add_rule(struct virtual_parse_context *ctx, const char **error_r)
+{
+ struct virtual_backend_box *const *bboxes;
+ struct mail_search_args *search_args;
+ unsigned int i, count;
+
+ *error_r = NULL;
+
+ if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) {
+ i_assert(str_len(ctx->rule) == 0);
+ return 0;
+ }
+
+ ctx->mbox->search_args_crc32 =
+ crc32_str_more(ctx->mbox->search_args_crc32, str_c(ctx->rule));
+ search_args = virtual_search_args_parse(ctx->rule, error_r);
+ str_truncate(ctx->rule, 0);
+ if (search_args == NULL) {
+ i_assert(*error_r != NULL);
+ *error_r = t_strconcat("Previous search rule is invalid: ",
+ *error_r, NULL);
+ return -1;
+ }
+
+ /* update at all the mailboxes that were introduced since the previous
+ rule. */
+ bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+ i_assert(ctx->rule_idx < count);
+ for (i = ctx->rule_idx; i < count; i++) {
+ i_assert(bboxes[i]->search_args == NULL);
+ mail_search_args_ref(search_args);
+ bboxes[i]->search_args = search_args;
+ }
+ mail_search_args_unref(&search_args);
+
+ ctx->rule_idx = array_count(&ctx->mbox->backend_boxes);
+ return 0;
+}
+
+static int
+virtual_config_parse_line(struct virtual_parse_context *ctx, const char *line,
+ const char **error_r)
+{
+ struct mail_user *user = ctx->mbox->storage->storage.user;
+ struct virtual_backend_box *bbox;
+ const char *p;
+ bool no_wildcards = FALSE;
+
+ if (*line == ' ' || *line == '\t') {
+ /* continues the previous search rule */
+ if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) {
+ *error_r = "Search rule without a mailbox";
+ return -1;
+ }
+ while (*line == ' ' || *line == '\t') line++;
+ str_append_c(ctx->rule, ' ');
+ str_append(ctx->rule, line);
+ return 0;
+ }
+ /* if there is no rule yet, it means we want the previous mailboxes
+ to use the rule that comes later */
+ if (str_len(ctx->rule) > 0) {
+ if (virtual_config_add_rule(ctx, error_r) < 0)
+ return -1;
+ }
+ if (!uni_utf8_str_is_valid(line)) {
+ *error_r = t_strdup_printf("Mailbox name not UTF-8: %s",
+ line);
+ return -1;
+ }
+
+ /* new mailbox. the search args are added to it later. */
+ bbox = p_new(ctx->pool, struct virtual_backend_box, 1);
+ bbox->virtual_mbox = ctx->mbox;
+ if (strcasecmp(line, "INBOX") == 0)
+ line = "INBOX";
+ bbox->name = p_strdup(ctx->pool, line);
+ switch (bbox->name[0]) {
+ case '+':
+ bbox->name++;
+ bbox->clear_recent = TRUE;
+ break;
+ case '-':
+ bbox->name++;
+ bbox->negative_match = TRUE;
+ break;
+ case '!':
+ /* save messages here */
+ if (ctx->mbox->save_bbox != NULL) {
+ *error_r = "Multiple save mailboxes defined";
+ return -1;
+ }
+ bbox->name++;
+ ctx->mbox->save_bbox = bbox;
+ no_wildcards = TRUE;
+ break;
+ }
+ if (bbox->name[0] == '/') {
+ /* [+-!]/metadata entry:value */
+ if ((p = strchr(bbox->name, ':')) == NULL) {
+ *error_r = "':' separator missing between metadata entry name and value";
+ return -1;
+ }
+ bbox->metadata_entry = p_strdup_until(ctx->pool, bbox->name, p++);
+ bbox->metadata_value = p;
+ if (!imap_metadata_verify_entry_name(bbox->metadata_entry, error_r))
+ return -1;
+ no_wildcards = TRUE;
+ }
+
+ if (!no_wildcards &&
+ (strchr(bbox->name, '*') != NULL ||
+ strchr(bbox->name, '%') != NULL)) {
+ bbox->glob = imap_match_init(ctx->pool, bbox->name, TRUE, ctx->sep);
+ ctx->have_wildcards = TRUE;
+ }
+ if (bbox->metadata_entry == NULL) {
+ /* now that the prefix characters have been processed,
+ find the namespace */
+ bbox->ns = strcasecmp(bbox->name, "INBOX") == 0 ?
+ mail_namespace_find_inbox(user->namespaces) :
+ mail_namespace_find(user->namespaces, bbox->name);
+ if (bbox->ns == NULL) {
+ *error_r = t_strdup_printf("Namespace not found for %s",
+ bbox->name);
+ return -1;
+ }
+ if (strcmp(bbox->name, ctx->mbox->box.vname) == 0) {
+ *error_r = "Virtual mailbox can't point to itself";
+ return -1;
+ }
+ ctx->have_mailbox_defines = TRUE;
+ }
+
+ array_push_back(&ctx->mbox->backend_boxes, &bbox);
+ return 0;
+}
+
+static void
+virtual_mailbox_get_list_patterns(struct virtual_parse_context *ctx)
+{
+ struct virtual_mailbox *mbox = ctx->mbox;
+ ARRAY_TYPE(mailbox_virtual_patterns) *dest;
+ struct mailbox_virtual_pattern pattern;
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ i_zero(&pattern);
+ bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
+ p_array_init(&mbox->list_include_patterns, ctx->pool, count);
+ p_array_init(&mbox->list_exclude_patterns, ctx->pool, count);
+ for (i = 0; i < count; i++) {
+ if (bboxes[i]->metadata_entry == NULL)
+ continue;
+ pattern.ns = bboxes[i]->ns;
+ pattern.pattern = bboxes[i]->name;
+ if (bboxes[i]->negative_match)
+ dest = &mbox->list_include_patterns;
+ else {
+ dest = &mbox->list_exclude_patterns;
+ pattern.pattern++;
+ }
+ array_push_back(dest, &pattern);
+ }
+}
+
+static void
+separate_wildcard_mailboxes(struct virtual_mailbox *mbox,
+ ARRAY_TYPE(virtual_backend_box) *wildcard_boxes,
+ ARRAY_TYPE(virtual_backend_box) *neg_boxes,
+ ARRAY_TYPE(virtual_backend_box) *metadata_boxes)
+{
+ struct virtual_backend_box *const *bboxes;
+ ARRAY_TYPE(virtual_backend_box) *dest;
+ unsigned int i, count;
+
+ bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
+ t_array_init(wildcard_boxes, I_MIN(16, count));
+ t_array_init(neg_boxes, 4);
+ t_array_init(metadata_boxes, 4);
+ for (i = 0; i < count;) {
+ if (bboxes[i]->metadata_entry != NULL)
+ dest = metadata_boxes;
+ else if (bboxes[i]->negative_match)
+ dest = neg_boxes;
+ else if (bboxes[i]->glob != NULL)
+ dest = wildcard_boxes;
+ else {
+ dest = NULL;
+ i++;
+ }
+
+ if (dest != NULL) {
+ array_push_back(dest, &bboxes[i]);
+ array_delete(&mbox->backend_boxes, i, 1);
+ bboxes = array_get_modifiable(&mbox->backend_boxes,
+ &count);
+ }
+ }
+}
+
+static void virtual_config_copy_expanded(struct virtual_parse_context *ctx,
+ struct virtual_backend_box *wbox,
+ const char *name)
+{
+ struct virtual_backend_box *bbox;
+
+ bbox = p_new(ctx->pool, struct virtual_backend_box, 1);
+ *bbox = *wbox;
+ bbox->name = p_strdup(ctx->pool, name);
+ bbox->glob = NULL;
+ bbox->wildcard = TRUE;
+ mail_search_args_ref(bbox->search_args);
+ array_push_back(&ctx->mbox->backend_boxes, &bbox);
+}
+
+static bool virtual_ns_match(struct mail_namespace *config_ns,
+ struct mail_namespace *iter_ns)
+{
+ /* we match only one namespace for each pattern, except with shared
+ namespaces match also autocreated children */
+ if (config_ns == iter_ns)
+ return TRUE;
+ if (config_ns->type == iter_ns->type &&
+ (config_ns->flags & NAMESPACE_FLAG_AUTOCREATED) == 0 &&
+ (iter_ns->flags & NAMESPACE_FLAG_AUTOCREATED) != 0)
+ return TRUE;
+ if ((iter_ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
+ (config_ns->flags & NAMESPACE_FLAG_AUTOCREATED) != 0 &&
+ config_ns->prefix_len == 0) {
+ /* prefix="" namespace was autocreated, so e.g. "*" would match
+ only that empty namespace. but we want "*" to also match
+ the inbox=yes namespace, so check it here separately. */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool virtual_config_match(const struct mailbox_info *info,
+ ARRAY_TYPE(virtual_backend_box) *boxes_arr,
+ unsigned int *idx_r)
+{
+ struct virtual_backend_box *const *boxes;
+ unsigned int i, count;
+
+ boxes = array_get_modifiable(boxes_arr, &count);
+ for (i = 0; i < count; i++) {
+ if (boxes[i]->glob != NULL) {
+ if (virtual_ns_match(boxes[i]->ns, info->ns) &&
+ imap_match(boxes[i]->glob,
+ info->vname) == IMAP_MATCH_YES) {
+ *idx_r = i;
+ return TRUE;
+ }
+ } else {
+ if (strcmp(boxes[i]->name, info->vname) == 0) {
+ *idx_r = i;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+static int virtual_config_box_metadata_match(struct mailbox *box,
+ struct virtual_backend_box *bbox,
+ const char **error_r)
+{
+ struct imap_metadata_transaction *imtrans;
+ struct mail_attribute_value value;
+ int ret;
+
+ imtrans = imap_metadata_transaction_begin(box);
+ ret = imap_metadata_get(imtrans, bbox->metadata_entry, &value);
+ if (ret < 0)
+ *error_r = t_strdup(imap_metadata_transaction_get_last_error(imtrans, NULL));
+ if (ret > 0)
+ ret = wildcard_match(value.value, bbox->metadata_value) ? 1 : 0;
+ if (ret >= 0 && bbox->negative_match)
+ ret = ret > 0 ? 0 : 1;
+ (void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
+ return ret;
+}
+
+static int
+virtual_config_metadata_match(const struct mailbox_info *info,
+ ARRAY_TYPE(virtual_backend_box) *boxes_arr,
+ const char **error_r)
+{
+ struct virtual_backend_box *const *boxes;
+ struct mailbox *box;
+ unsigned int i, count;
+ int ret = 1;
+
+ boxes = array_get_modifiable(boxes_arr, &count);
+ if (count == 0)
+ return 1;
+
+ box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY);
+ for (i = 0; i < count; i++) {
+ /* break on error or match */
+ if ((ret = virtual_config_box_metadata_match(box, boxes[i], error_r)) < 0 || ret > 0)
+ break;
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static int virtual_config_expand_wildcards(struct virtual_parse_context *ctx,
+ const char **error_r)
+{
+ const enum mail_namespace_type iter_ns_types =
+ MAIL_NAMESPACE_TYPE_MASK_ALL;
+ const enum mailbox_list_iter_flags iter_flags =
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
+ struct mail_user *user = ctx->mbox->storage->storage.user;
+ ARRAY_TYPE(virtual_backend_box) wildcard_boxes, neg_boxes, metadata_boxes;
+ struct mailbox_list_iterate_context *iter;
+ struct virtual_backend_box *const *wboxes, *const *boxp;
+ const char **patterns;
+ const struct mailbox_info *info;
+ unsigned int i, j, count;
+ int ret = 0;
+
+ separate_wildcard_mailboxes(ctx->mbox, &wildcard_boxes,
+ &neg_boxes, &metadata_boxes);
+
+ /* get patterns we want to list */
+ wboxes = array_get_modifiable(&wildcard_boxes, &count);
+ if (count == 0) {
+ /* only negative wildcards - doesn't really make sense.
+ just ignore. */
+ return 0;
+ }
+ patterns = t_new(const char *, count + 1);
+ for (i = 0; i < count; i++)
+ patterns[i] = wboxes[i]->name;
+
+ /* match listed mailboxes to wildcards */
+ iter = mailbox_list_iter_init_namespaces(user->namespaces, patterns,
+ iter_ns_types, iter_flags);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ /* skip non-selectable mailboxes (especially mbox
+ directories) */
+ if ((info->flags & MAILBOX_NOSELECT) != 0)
+ continue;
+ if (strcmp(info->vname, ctx->mbox->box.vname) == 0) {
+ /* don't allow virtual folder to point to itself */
+ continue;
+ }
+
+ if (virtual_config_match(info, &wildcard_boxes, &i) &&
+ !virtual_config_match(info, &neg_boxes, &j) &&
+ virtual_backend_box_lookup_name(ctx->mbox,
+ info->vname) == NULL) {
+ ret = virtual_config_metadata_match(info, &metadata_boxes, error_r);
+ if (ret < 0)
+ break;
+ if (ret > 0) {
+ virtual_config_copy_expanded(ctx, wboxes[i],
+ info->vname);
+ }
+ }
+ }
+ for (i = 0; i < count; i++)
+ mail_search_args_unref(&wboxes[i]->search_args);
+ array_foreach(&neg_boxes, boxp)
+ mail_search_args_unref(&(*boxp)->search_args);
+ array_foreach(&metadata_boxes, boxp)
+ mail_search_args_unref(&(*boxp)->search_args);
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ *error_r = mailbox_list_get_last_internal_error(user->namespaces->list, NULL);
+ return -1;
+ }
+ return ret < 0 ? -1 : 0;
+}
+
+static void virtual_config_search_args_dup(struct virtual_mailbox *mbox)
+{
+ struct virtual_backend_box *const *bboxes;
+ struct mail_search_args *old_args;
+ unsigned int i, count;
+
+ bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ old_args = bboxes[i]->search_args;
+ bboxes[i]->search_args = mail_search_args_dup(old_args);
+ mail_search_args_unref(&old_args);
+ }
+}
+
+int virtual_config_read(struct virtual_mailbox *mbox)
+{
+ struct mail_storage *storage = mbox->box.storage;
+ struct virtual_parse_context ctx;
+ const char *box_path, *path, *line, *error;
+ unsigned int linenum = 0;
+ int fd, ret = 0;
+
+ i_array_init(&mbox->backend_boxes, 8);
+ mbox->search_args_crc32 = (uint32_t)-1;
+
+ box_path = mailbox_get_path(&mbox->box);
+ path = t_strconcat(box_path, "/"VIRTUAL_CONFIG_FNAME, NULL);
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ if (errno == EACCES) {
+ mailbox_set_critical(&mbox->box, "%s",
+ mail_error_eacces_msg("open", path));
+ } else if (errno != ENOENT) {
+ mailbox_set_critical(&mbox->box,
+ "open(%s) failed: %m", path);
+ } else if (errno == ENOENT) {
+ mail_storage_set_error(storage, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(mbox->box.vname));
+ } else {
+ mailbox_set_critical(&mbox->box,
+ "stat(%s) failed: %m", box_path);
+ }
+ return -1;
+ }
+
+ i_zero(&ctx);
+ ctx.sep = mail_namespaces_get_root_sep(storage->user->namespaces);
+ ctx.mbox = mbox;
+ ctx.pool = mbox->box.pool;
+ ctx.rule = t_str_new(256);
+ ctx.input = i_stream_create_fd(fd, SIZE_MAX);
+ i_stream_set_return_partial_line(ctx.input, TRUE);
+ while ((line = i_stream_read_next_line(ctx.input)) != NULL) {
+ linenum++;
+ if (*line == '#')
+ continue;
+ if (*line == '\0')
+ ret = virtual_config_add_rule(&ctx, &error);
+ else
+ ret = virtual_config_parse_line(&ctx, line, &error);
+ if (ret < 0) {
+ mailbox_set_critical(&mbox->box,
+ "%s: Error at line %u: %s",
+ path, linenum, error);
+ break;
+ }
+ }
+ if (ret == 0) {
+ ret = virtual_config_add_rule(&ctx, &error);
+ if (ret < 0) {
+ mailbox_set_critical(&mbox->box,
+ "%s: Error at line %u: %s",
+ path, linenum, error);
+ }
+ }
+
+ virtual_mailbox_get_list_patterns(&ctx);
+ if (ret == 0 && ctx.have_wildcards) {
+ struct event_reason *reason =
+ event_reason_begin("virtual:config_read");
+ ret = virtual_config_expand_wildcards(&ctx, &error);
+ if (ret < 0)
+ mailbox_set_critical(&mbox->box, "%s: %s", path, error);
+ event_reason_end(&reason);
+ }
+
+ if (ret == 0 && !ctx.have_mailbox_defines) {
+ mailbox_set_critical(&mbox->box,
+ "%s: No mailboxes defined", path);
+ ret = -1;
+ }
+ if (ret == 0)
+ virtual_config_search_args_dup(mbox);
+ i_stream_unref(&ctx.input);
+ i_close_fd(&fd);
+ return ret;
+}
+
+void virtual_config_free(struct virtual_mailbox *mbox)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ if (!array_is_created(&mbox->backend_boxes)) {
+ /* mailbox wasn't opened */
+ return;
+ }
+
+ bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (bboxes[i]->search_args != NULL)
+ mail_search_args_unref(&bboxes[i]->search_args);
+ }
+ array_free(&mbox->backend_boxes);
+}
diff --git a/src/plugins/virtual/virtual-mail.c b/src/plugins/virtual/virtual-mail.c
new file mode 100644
index 0000000..21459ba
--- /dev/null
+++ b/src/plugins/virtual/virtual-mail.c
@@ -0,0 +1,583 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "index-mail.h"
+#include "virtual-storage.h"
+#include "virtual-transaction.h"
+
+struct virtual_mail {
+ struct index_mail imail;
+
+ enum mail_fetch_field wanted_fields;
+ struct mailbox_header_lookup_ctx *wanted_headers;
+
+ /* temp_wanted_fields for this mail. Used only when mail doesn't have
+ a backend mail yet. */
+ enum mail_fetch_field delayed_temp_fields;
+ struct mailbox_header_lookup_ctx *delayed_temp_headers;
+
+ /* currently active mail */
+ struct mail *cur_backend_mail;
+ struct virtual_mail_index_record cur_vrec;
+
+ /* all allocated mails */
+ ARRAY(struct mail *) backend_mails;
+
+ /* mail is lost if backend_mail doesn't point to correct mail */
+ bool cur_lost:1;
+};
+
+struct mail *
+virtual_mail_alloc(struct mailbox_transaction_context *t,
+ enum mail_fetch_field wanted_fields,
+ struct mailbox_header_lookup_ctx *wanted_headers)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)t->box;
+ struct virtual_mail *vmail;
+ pool_t mail_pool, data_pool;
+
+ mail_pool = pool_alloconly_create("vmail", 1024);
+ data_pool = pool_alloconly_create("virtual index_mail", 512);
+ vmail = p_new(mail_pool, struct virtual_mail, 1);
+ vmail->wanted_fields = wanted_fields;
+ vmail->wanted_headers = wanted_headers;
+ if (vmail->wanted_headers != NULL)
+ mailbox_header_lookup_ref(vmail->wanted_headers);
+ /* Do not pass wanted_fields or wanted_headers to index_mail_init.
+ It will just cause unwanted behaviour, as we only want these
+ to be passed to backend mails. */
+ index_mail_init(&vmail->imail, t, 0, NULL, mail_pool, data_pool);
+ vmail->imail.mail.v = virtual_mail_vfuncs;
+ i_array_init(&vmail->backend_mails, array_count(&mbox->backend_boxes));
+ return &vmail->imail.mail.mail;
+}
+
+static void virtual_mail_close(struct mail *mail)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail **mails;
+ unsigned int i, count;
+
+ if (mail->seq != 0) {
+ mailbox_header_lookup_unref(&vmail->delayed_temp_headers);
+ vmail->delayed_temp_fields = 0;
+ }
+
+ mails = array_get_modifiable(&vmail->backend_mails, &count);
+ for (i = 0; i < count; i++) {
+ struct mail_private *p = (struct mail_private *)mails[i];
+
+ if (vmail->imail.freeing)
+ mail_free(&mails[i]);
+ else
+ p->v.close(mails[i]);
+ }
+ if (vmail->imail.freeing) {
+ array_free(&vmail->backend_mails);
+ mailbox_header_lookup_unref(&vmail->wanted_headers);
+ }
+ index_mail_close(mail);
+}
+
+static struct mail *
+backend_mail_find(struct virtual_mail *vmail, struct mailbox *box)
+{
+ struct mail *const *mails;
+ unsigned int i, count;
+
+ mails = array_get(&vmail->backend_mails, &count);
+ for (i = 0; i < count; i++) {
+ if (mails[i]->box == box)
+ return mails[i];
+ }
+ return NULL;
+}
+
+static int backend_mail_get(struct virtual_mail *vmail,
+ struct mail **backend_mail_r)
+{
+ struct mail *mail = &vmail->imail.mail.mail;
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box;
+ struct virtual_backend_box *bbox;
+
+ *backend_mail_r = NULL;
+
+ if (vmail->cur_backend_mail != NULL) {
+ if (vmail->cur_lost) {
+ mail_set_expunged(&vmail->imail.mail.mail);
+ return -1;
+ }
+ *backend_mail_r = vmail->cur_backend_mail;
+ return 0;
+ }
+
+ bbox = virtual_backend_box_lookup(mbox, vmail->cur_vrec.mailbox_id);
+ i_assert(bbox != NULL);
+
+ vmail->cur_backend_mail = backend_mail_find(vmail, bbox->box);
+ if (vmail->cur_backend_mail == NULL) {
+ if (!bbox->box->opened &&
+ virtual_backend_box_open(mbox, bbox) < 0) {
+ virtual_box_copy_error(mail->box, bbox->box);
+ return -1;
+ }
+ (void)virtual_mail_set_backend_mail(mail, bbox);
+ i_assert(vmail->cur_backend_mail != NULL);
+ }
+ virtual_backend_box_accessed(mbox, bbox);
+ vmail->cur_lost = !mail_set_uid(vmail->cur_backend_mail,
+ vmail->cur_vrec.real_uid);
+ mail->expunged = vmail->cur_lost || vmail->cur_backend_mail->expunged;
+ if (vmail->cur_lost) {
+ mail_set_expunged(&vmail->imail.mail.mail);
+ return -1;
+ }
+ /* headers need to be converted to backend-headers, so go through
+ the virtual add_temp_wanted_fields() again. */
+ mail_add_temp_wanted_fields(mail, vmail->delayed_temp_fields,
+ vmail->delayed_temp_headers);
+ *backend_mail_r = vmail->cur_backend_mail;
+ return 0;
+}
+
+struct mail *
+virtual_mail_set_backend_mail(struct mail *mail,
+ struct virtual_backend_box *bbox)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail_private *backend_pmail;
+ struct mailbox_transaction_context *backend_trans;
+ struct mailbox_header_lookup_ctx *backend_headers;
+
+ i_assert(bbox->box->opened);
+
+ backend_trans = virtual_transaction_get(mail->transaction, bbox->box);
+
+ backend_headers = vmail->wanted_headers == NULL ? NULL :
+ mailbox_header_lookup_init(bbox->box,
+ vmail->wanted_headers->name);
+ vmail->cur_backend_mail =
+ mail_alloc(backend_trans, vmail->wanted_fields, backend_headers);
+ mailbox_header_lookup_unref(&backend_headers);
+
+ backend_pmail = (struct mail_private *)vmail->cur_backend_mail;
+ backend_pmail->vmail = mail;
+ array_push_back(&vmail->backend_mails, &vmail->cur_backend_mail);
+ return vmail->cur_backend_mail;
+}
+
+void virtual_mail_set_unattached_backend_mail(struct mail *mail,
+ struct mail *backend_mail)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail_private *backend_pmail;
+
+ vmail->cur_backend_mail = backend_mail;
+
+ backend_pmail = (struct mail_private *)backend_mail;
+ backend_pmail->vmail = mail;
+}
+
+static void virtual_mail_set_seq(struct mail *mail, uint32_t seq, bool saving)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box;
+ const void *data;
+
+ i_assert(!saving);
+
+ mail_index_lookup_ext(mail->transaction->view, seq,
+ mbox->virtual_ext_id, &data, NULL);
+ memcpy(&vmail->cur_vrec, data, sizeof(vmail->cur_vrec));
+
+ index_mail_set_seq(mail, seq, saving);
+
+ vmail->cur_backend_mail = NULL;
+}
+
+static bool virtual_mail_set_uid(struct mail *mail, uint32_t uid)
+{
+ uint32_t seq;
+
+ if (!mail_index_lookup_seq(mail->transaction->view, uid, &seq))
+ return FALSE;
+
+ virtual_mail_set_seq(mail, seq, FALSE);
+ return TRUE;
+}
+
+static void virtual_mail_set_uid_cache_updates(struct mail *mail, bool set)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ struct mail_private *p;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return;
+ p = (struct mail_private *)backend_mail;
+ p->v.set_uid_cache_updates(backend_mail, set);
+}
+
+static bool virtual_mail_prefetch(struct mail *mail)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ struct mail_private *p;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return TRUE;
+ p = (struct mail_private *)backend_mail;
+ return p->v.prefetch(backend_mail);
+}
+
+static int virtual_mail_precache(struct mail *mail)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ struct mail_private *p;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ p = (struct mail_private *)backend_mail;
+ return p->v.precache(backend_mail);
+}
+
+static void
+virtual_mail_add_temp_wanted_fields(struct mail *mail,
+ enum mail_fetch_field fields,
+ struct mailbox_header_lookup_ctx *headers)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ struct mailbox_header_lookup_ctx *backend_headers, *new_headers;
+
+ if (mail->seq == 0) {
+ /* No mail set yet. Delay until it is set. */
+ vmail->delayed_temp_fields |= fields;
+ if (vmail->delayed_temp_headers == NULL)
+ vmail->delayed_temp_headers = headers;
+ else {
+ new_headers = mailbox_header_lookup_merge(
+ vmail->delayed_temp_headers, headers);
+ mailbox_header_lookup_unref(&vmail->delayed_temp_headers);
+ vmail->delayed_temp_headers = new_headers;
+ }
+ return;
+ }
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return;
+ /* convert header indexes to backend mailbox's header indexes */
+ backend_headers = headers == NULL ? NULL :
+ mailbox_header_lookup_init(backend_mail->box, headers->name);
+ mail_add_temp_wanted_fields(backend_mail, fields, backend_headers);
+ mailbox_header_lookup_unref(&backend_headers);
+}
+
+static int
+virtual_mail_get_parts(struct mail *mail, struct message_part **parts_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ if (mail_get_parts(backend_mail, parts_r) < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+virtual_mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ int tz;
+
+ if (timezone_r == NULL)
+ timezone_r = &tz;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ if (mail_get_date(backend_mail, date_r, timezone_r) < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int virtual_mail_get_received_date(struct mail *mail, time_t *date_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ if (mail_get_received_date(backend_mail, date_r) < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int virtual_mail_get_save_date(struct mail *mail, time_t *date_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ int ret;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ ret = mail_get_save_date(backend_mail, date_r);
+ if (ret < 0)
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return ret;
+}
+
+static int virtual_mail_get_virtual_mail_size(struct mail *mail, uoff_t *size_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ if (mail_get_virtual_size(backend_mail, size_r) < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int virtual_mail_get_physical_size(struct mail *mail, uoff_t *size_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ if (mail_get_physical_size(backend_mail, size_r) < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+virtual_mail_get_first_header(struct mail *mail, const char *field,
+ bool decode_to_utf8, const char **value_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ struct mail_private *p;
+ int ret;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ p = (struct mail_private *)backend_mail;
+ ret = p->v.get_first_header(backend_mail, field,
+ decode_to_utf8, value_r);
+ if (ret < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return ret;
+}
+
+static int
+virtual_mail_get_headers(struct mail *mail, const char *field,
+ bool decode_to_utf8, const char *const **value_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ struct mail_private *p;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ p = (struct mail_private *)backend_mail;
+ if (p->v.get_headers(backend_mail, field, decode_to_utf8, value_r) < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+virtual_mail_get_header_stream(struct mail *mail,
+ struct mailbox_header_lookup_ctx *headers,
+ struct istream **stream_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+ struct mailbox_header_lookup_ctx *backend_headers;
+ int ret;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+
+ backend_headers = mailbox_header_lookup_init(backend_mail->box,
+ headers->name);
+ ret = mail_get_header_stream(backend_mail, backend_headers, stream_r);
+ mailbox_header_lookup_unref(&backend_headers);
+ if (ret < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+virtual_mail_get_stream(struct mail *mail, bool get_body,
+ struct message_size *hdr_size,
+ struct message_size *body_size,
+ struct istream **stream_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail_private *vp = (struct mail_private *)mail;
+ struct mail *backend_mail;
+ const char *reason = t_strdup_printf("virtual mailbox %s: Opened mail UID=%u: %s",
+ mailbox_get_vname(mail->box), mail->uid, vp->get_stream_reason);
+ int ret;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+
+ if (get_body) {
+ ret = mail_get_stream_because(backend_mail, hdr_size, body_size,
+ reason, stream_r);
+ } else {
+ ret = mail_get_hdr_stream_because(backend_mail, hdr_size,
+ reason, stream_r);
+ }
+
+ if (ret < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+virtual_mail_get_binary_stream(struct mail *mail,
+ const struct message_part *part,
+ bool include_hdr, uoff_t *size_r,
+ unsigned int *lines_r, bool *binary_r,
+ struct istream **stream_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+
+ struct mail_private *p = (struct mail_private *)backend_mail;
+ if (p->v.get_binary_stream(backend_mail, part, include_hdr,
+ size_r, lines_r, binary_r, stream_r) < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+virtual_mail_get_special(struct mail *mail, enum mail_fetch_field field,
+ const char **value_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+ if (mail_get_special(backend_mail, field, value_r) < 0) {
+ virtual_box_copy_error(mail->box, backend_mail->box);
+ return -1;
+ }
+ return 0;
+}
+
+static int virtual_mail_get_backend_mail(struct mail *mail,
+ struct mail **real_mail_r)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return -1;
+
+ if (mail_get_backend_mail(backend_mail, real_mail_r) < 0)
+ return -1;
+ return 0;
+}
+
+static void virtual_mail_update_pop3_uidl(struct mail *mail, const char *uidl)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return;
+ mail_update_pop3_uidl(backend_mail, uidl);
+}
+
+static void virtual_mail_expunge(struct mail *mail)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return;
+ mail_expunge(backend_mail);
+}
+
+static void
+virtual_mail_set_cache_corrupted(struct mail *mail,
+ enum mail_fetch_field field,
+ const char *reason)
+{
+ struct virtual_mail *vmail = (struct virtual_mail *)mail;
+ struct mail *backend_mail;
+
+ if (backend_mail_get(vmail, &backend_mail) < 0)
+ return;
+ mail_set_cache_corrupted(backend_mail, field, reason);
+}
+
+struct mail_vfuncs virtual_mail_vfuncs = {
+ virtual_mail_close,
+ index_mail_free,
+ virtual_mail_set_seq,
+ virtual_mail_set_uid,
+ virtual_mail_set_uid_cache_updates,
+ virtual_mail_prefetch,
+ virtual_mail_precache,
+ virtual_mail_add_temp_wanted_fields,
+
+ index_mail_get_flags,
+ index_mail_get_keywords,
+ index_mail_get_keyword_indexes,
+ index_mail_get_modseq,
+ index_mail_get_pvt_modseq,
+ virtual_mail_get_parts,
+ virtual_mail_get_date,
+ virtual_mail_get_received_date,
+ virtual_mail_get_save_date,
+ virtual_mail_get_virtual_mail_size,
+ virtual_mail_get_physical_size,
+ virtual_mail_get_first_header,
+ virtual_mail_get_headers,
+ virtual_mail_get_header_stream,
+ virtual_mail_get_stream,
+ virtual_mail_get_binary_stream,
+ virtual_mail_get_special,
+ virtual_mail_get_backend_mail,
+ index_mail_update_flags,
+ index_mail_update_keywords,
+ index_mail_update_modseq,
+ index_mail_update_pvt_modseq,
+ virtual_mail_update_pop3_uidl,
+ virtual_mail_expunge,
+ virtual_mail_set_cache_corrupted,
+ NULL,
+};
diff --git a/src/plugins/virtual/virtual-plugin.c b/src/plugins/virtual/virtual-plugin.c
new file mode 100644
index 0000000..43db7cb
--- /dev/null
+++ b/src/plugins/virtual/virtual-plugin.c
@@ -0,0 +1,25 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-namespace.h"
+#include "virtual-storage.h"
+#include "virtual-plugin.h"
+
+const char *virtual_plugin_version = DOVECOT_ABI_VERSION;
+
+static struct mail_storage_hooks acl_mail_storage_hooks = {
+ .mailbox_allocated = virtual_backend_mailbox_allocated,
+ .mailbox_opened = virtual_backend_mailbox_opened
+};
+
+void virtual_plugin_init(struct module *module ATTR_UNUSED)
+{
+ mail_storage_class_register(&virtual_storage);
+ mail_storage_hooks_add(module, &acl_mail_storage_hooks);
+}
+
+void virtual_plugin_deinit(void)
+{
+ mail_storage_class_unregister(&virtual_storage);
+ mail_storage_hooks_remove(&acl_mail_storage_hooks);
+}
diff --git a/src/plugins/virtual/virtual-plugin.h b/src/plugins/virtual/virtual-plugin.h
new file mode 100644
index 0000000..39f04bb
--- /dev/null
+++ b/src/plugins/virtual/virtual-plugin.h
@@ -0,0 +1,7 @@
+#ifndef VIRTUAL_PLUGIN_H
+#define VIRTUAL_PLUGIN_H
+
+void virtual_plugin_init(struct module *module);
+void virtual_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/virtual/virtual-save.c b/src/plugins/virtual/virtual-save.c
new file mode 100644
index 0000000..9499cd3
--- /dev/null
+++ b/src/plugins/virtual/virtual-save.c
@@ -0,0 +1,153 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "virtual-transaction.h"
+#include "virtual-storage.h"
+
+struct virtual_save_context {
+ struct mail_save_context ctx;
+ struct mail_save_context *backend_save_ctx;
+ struct mailbox *backend_box;
+ char *open_errstr;
+ enum mail_error open_error;
+};
+
+struct mail_save_context *
+virtual_save_alloc(struct mailbox_transaction_context *_t)
+{
+ struct virtual_transaction_context *t =
+ (struct virtual_transaction_context *)_t;
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)_t->box;
+ struct mailbox_transaction_context *backend_trans;
+ struct virtual_save_context *ctx;
+ const char *errstr;
+
+ if (_t->save_ctx == NULL) {
+ ctx = i_new(struct virtual_save_context, 1);
+ ctx->ctx.transaction = &t->t;
+ _t->save_ctx = &ctx->ctx;
+ } else {
+ ctx = (struct virtual_save_context *)_t->save_ctx;
+ }
+
+ if (mbox->save_bbox != NULL) {
+ i_assert(ctx->backend_save_ctx == NULL);
+ i_assert(ctx->open_errstr == NULL);
+
+ if (!mbox->save_bbox->box->opened &&
+ virtual_backend_box_open(mbox, mbox->save_bbox) < 0) {
+ errstr = mailbox_get_last_error(mbox->save_bbox->box,
+ &ctx->open_error);
+ ctx->open_errstr = i_strdup(errstr);
+ } else {
+ backend_trans =
+ virtual_transaction_get(_t, mbox->save_bbox->box);
+ ctx->backend_save_ctx = mailbox_save_alloc(backend_trans);
+ }
+ virtual_backend_box_accessed(mbox, mbox->save_bbox);
+ }
+ return _t->save_ctx;
+}
+
+static struct mail_keywords *
+virtual_copy_keywords(struct mailbox *src_box,
+ const struct mail_keywords *src_keywords,
+ struct mailbox *dest_box)
+{
+ struct mailbox_status status;
+ ARRAY_TYPE(keywords) kw_strings;
+ const char *kw;
+ unsigned int i;
+
+ if (src_keywords == NULL || src_keywords->count == 0)
+ return NULL;
+
+ t_array_init(&kw_strings, src_keywords->count + 1);
+ mailbox_get_open_status(src_box, STATUS_KEYWORDS, &status);
+
+ for (i = 0; i < src_keywords->count; i++) {
+ kw = array_idx_elem(status.keywords, src_keywords->idx[i]);
+ array_push_back(&kw_strings, &kw);
+ }
+ array_append_zero(&kw_strings);
+ return mailbox_keywords_create_valid(dest_box,
+ array_front(&kw_strings));
+}
+
+int virtual_save_begin(struct mail_save_context *_ctx, struct istream *input)
+{
+ struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx;
+ struct mail_save_data *mdata = &_ctx->data;
+ struct mail_keywords *keywords;
+
+ if (ctx->backend_save_ctx == NULL) {
+ if (ctx->open_errstr != NULL) {
+ /* mailbox_open() failed */
+ mail_storage_set_error(_ctx->transaction->box->storage,
+ ctx->open_error, ctx->open_errstr);
+ } else {
+ mail_storage_set_error(_ctx->transaction->box->storage,
+ MAIL_ERROR_NOTPOSSIBLE,
+ "Can't save messages to this virtual mailbox");
+ }
+ return -1;
+ }
+
+ ctx->backend_box = ctx->backend_save_ctx->transaction->box;
+ keywords = virtual_copy_keywords(_ctx->transaction->box, mdata->keywords,
+ ctx->backend_box);
+ mailbox_save_set_flags(ctx->backend_save_ctx,
+ mdata->flags | mdata->pvt_flags,
+ keywords);
+ if (keywords != NULL)
+ mail_index_keywords_unref(&keywords);
+
+ mailbox_save_set_received_date(ctx->backend_save_ctx,
+ mdata->received_date,
+ mdata->received_tz_offset);
+ mailbox_save_set_from_envelope(ctx->backend_save_ctx,
+ mdata->from_envelope);
+ mailbox_save_set_guid(ctx->backend_save_ctx, mdata->guid);
+ mailbox_save_set_min_modseq(ctx->backend_save_ctx, mdata->min_modseq);
+
+ virtual_mail_set_unattached_backend_mail(_ctx->dest_mail,
+ ctx->backend_save_ctx->dest_mail);
+ return mailbox_save_begin(&ctx->backend_save_ctx, input);
+}
+
+int virtual_save_continue(struct mail_save_context *_ctx)
+{
+ struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx;
+
+ return mailbox_save_continue(ctx->backend_save_ctx);
+}
+
+int virtual_save_finish(struct mail_save_context *_ctx)
+{
+ struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx;
+ int ret;
+ ret = mailbox_save_finish(&ctx->backend_save_ctx);
+ index_save_context_free(_ctx);
+
+ return ret;
+}
+
+void virtual_save_cancel(struct mail_save_context *_ctx)
+{
+ struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx;
+
+ if (ctx->backend_save_ctx != NULL)
+ mailbox_save_cancel(&ctx->backend_save_ctx);
+ i_free_and_null(ctx->open_errstr);
+ _ctx->unfinished = FALSE;
+}
+
+void virtual_save_free(struct mail_save_context *_ctx)
+{
+ struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx;
+
+ virtual_save_cancel(_ctx);
+ mailbox_save_context_deinit(_ctx);
+ i_free(ctx);
+}
diff --git a/src/plugins/virtual/virtual-search.c b/src/plugins/virtual/virtual-search.c
new file mode 100644
index 0000000..ee04a74
--- /dev/null
+++ b/src/plugins/virtual/virtual-search.c
@@ -0,0 +1,206 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-search.h"
+#include "index-search-private.h"
+#include "virtual-storage.h"
+
+
+enum virtual_search_state {
+ VIRTUAL_SEARCH_STATE_BUILD,
+ VIRTUAL_SEARCH_STATE_RETURN,
+ VIRTUAL_SEARCH_STATE_SORT,
+ VIRTUAL_SEARCH_STATE_SORT_DONE
+};
+
+struct virtual_search_record {
+ uint32_t mailbox_id;
+ uint32_t real_uid;
+ uint32_t virtual_seq;
+};
+
+struct virtual_search_context {
+ union mail_search_module_context module_ctx;
+
+ ARRAY_TYPE(seq_range) result;
+ struct seq_range_iter result_iter;
+ ARRAY(struct virtual_search_record) records;
+
+ enum virtual_search_state search_state;
+ unsigned int next_result_n;
+ unsigned int next_record_idx;
+};
+
+static int virtual_search_record_cmp(const struct virtual_search_record *r1,
+ const struct virtual_search_record *r2)
+{
+ if (r1->mailbox_id < r2->mailbox_id)
+ return -1;
+ if (r1->mailbox_id > r2->mailbox_id)
+ return 1;
+
+ if (r1->real_uid < r2->real_uid)
+ return -1;
+ if (r1->real_uid > r2->real_uid)
+ return 1;
+ return 0;
+}
+
+static int mail_search_get_result(struct mail_search_context *ctx)
+{
+ const struct mail_search_arg *arg;
+ int ret = 1;
+
+ for (arg = ctx->args->args; arg != NULL; arg = arg->next) {
+ if (arg->result < 0)
+ return -1;
+ if (arg->result == 0)
+ ret = 0;
+ }
+ return ret;
+}
+
+static void virtual_search_get_records(struct mail_search_context *ctx,
+ struct virtual_search_context *vctx)
+{
+ struct virtual_mailbox *mbox =
+ (struct virtual_mailbox *)ctx->transaction->box;
+ const struct virtual_mail_index_record *vrec;
+ struct virtual_search_record srec;
+ const void *data;
+ int result;
+
+ i_zero(&srec);
+ while (index_storage_search_next_update_seq(ctx)) {
+ result = mail_search_get_result(ctx);
+ i_assert(result != 0);
+ if (result > 0) {
+ /* full match, no need to check this any further */
+ seq_range_array_add(&vctx->result, ctx->seq);
+ } else {
+ /* possible match, save and check later */
+ mail_index_lookup_ext(mbox->box.view, ctx->seq,
+ mbox->virtual_ext_id,
+ &data, NULL);
+ vrec = data;
+
+ srec.mailbox_id = vrec->mailbox_id;
+ srec.real_uid = vrec->real_uid;
+ srec.virtual_seq = ctx->seq;
+ array_push_back(&vctx->records, &srec);
+ }
+ mail_search_args_reset(ctx->args->args, FALSE);
+ }
+ array_sort(&vctx->records, virtual_search_record_cmp);
+
+ ctx->progress_max = array_count(&vctx->records);
+}
+
+struct mail_search_context *
+virtual_search_init(struct mailbox_transaction_context *t,
+ struct mail_search_args *args,
+ const enum mail_sort_type *sort_program,
+ enum mail_fetch_field wanted_fields,
+ struct mailbox_header_lookup_ctx *wanted_headers)
+{
+ struct mail_search_context *ctx;
+ struct virtual_search_context *vctx;
+
+ ctx = index_storage_search_init(t, args, sort_program,
+ wanted_fields, wanted_headers);
+
+ vctx = i_new(struct virtual_search_context, 1);
+ vctx->search_state = VIRTUAL_SEARCH_STATE_BUILD;
+ i_array_init(&vctx->result, 64);
+ i_array_init(&vctx->records, 64);
+ MODULE_CONTEXT_SET(ctx, virtual_storage_module, vctx);
+
+ virtual_search_get_records(ctx, vctx);
+ seq_range_array_iter_init(&vctx->result_iter, &vctx->result);
+ return ctx;
+}
+
+int virtual_search_deinit(struct mail_search_context *ctx)
+{
+ struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx);
+
+ array_free(&vctx->result);
+ array_free(&vctx->records);
+ i_free(vctx);
+ return index_storage_search_deinit(ctx);
+}
+
+bool virtual_search_next_nonblock(struct mail_search_context *ctx,
+ struct mail **mail_r, bool *tryagain_r)
+{
+ struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx);
+ struct index_search_context *ictx = (struct index_search_context *)ctx;
+ uint32_t seq;
+
+ switch (vctx->search_state) {
+ case VIRTUAL_SEARCH_STATE_BUILD:
+ if (ctx->sort_program == NULL)
+ vctx->search_state = VIRTUAL_SEARCH_STATE_SORT;
+ else
+ vctx->search_state = VIRTUAL_SEARCH_STATE_RETURN;
+ return virtual_search_next_nonblock(ctx, mail_r, tryagain_r);
+ case VIRTUAL_SEARCH_STATE_RETURN:
+ return index_storage_search_next_nonblock(ctx, mail_r, tryagain_r);
+ case VIRTUAL_SEARCH_STATE_SORT:
+ /* the messages won't be returned sorted, so we'll have to
+ do it ourself */
+ while (index_storage_search_next_nonblock(ctx, mail_r, tryagain_r))
+ seq_range_array_add(&vctx->result, (*mail_r)->seq);
+ if (*tryagain_r)
+ return FALSE;
+
+ vctx->next_result_n = 0;
+ vctx->search_state = VIRTUAL_SEARCH_STATE_SORT_DONE;
+ /* fall through */
+ case VIRTUAL_SEARCH_STATE_SORT_DONE:
+ *tryagain_r = FALSE;
+ if (!seq_range_array_iter_nth(&vctx->result_iter,
+ vctx->next_result_n, &seq))
+ return FALSE;
+ vctx->next_result_n++;
+ *mail_r = index_search_get_mail(ictx);
+ i_assert(*mail_r != NULL);
+ mail_set_seq(*mail_r, seq);
+ return TRUE;
+ }
+ i_unreached();
+}
+
+static void search_args_set_full_match(struct mail_search_arg *args)
+{
+ for (; args != NULL; args = args->next)
+ args->result = 1;
+}
+
+bool virtual_search_next_update_seq(struct mail_search_context *ctx)
+{
+ struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx);
+ const struct virtual_search_record *recs;
+ unsigned int count;
+
+ recs = array_get(&vctx->records, &count);
+ if (vctx->next_record_idx < count) {
+ /* go through potential results first */
+ ctx->seq = recs[vctx->next_record_idx++].virtual_seq - 1;
+ if (!index_storage_search_next_update_seq(ctx))
+ i_unreached();
+ ctx->progress_cur = vctx->next_record_idx;
+ return TRUE;
+ }
+
+ if (ctx->sort_program != NULL &&
+ seq_range_array_iter_nth(&vctx->result_iter,
+ vctx->next_result_n, &ctx->seq)) {
+ /* this is known to match fully */
+ search_args_set_full_match(ctx->args->args);
+ vctx->next_result_n++;
+ return TRUE;
+ }
+ return FALSE;
+}
diff --git a/src/plugins/virtual/virtual-storage.c b/src/plugins/virtual/virtual-storage.c
new file mode 100644
index 0000000..f88a279
--- /dev/null
+++ b/src/plugins/virtual/virtual-storage.c
@@ -0,0 +1,950 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "str.h"
+#include "llist.h"
+#include "mkdir-parents.h"
+#include "unlink-directory.h"
+#include "index-mail.h"
+#include "mail-copy.h"
+#include "mail-search.h"
+#include "mailbox-list-private.h"
+#include "virtual-plugin.h"
+#include "virtual-transaction.h"
+#include "virtual-storage.h"
+#include "mailbox-list-notify.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#define VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES 64
+
+#define VIRTUAL_BACKEND_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, virtual_backend_storage_module)
+
+struct virtual_backend_mailbox {
+ union mailbox_module_context module_ctx;
+};
+
+extern struct mail_storage virtual_storage;
+extern struct mailbox virtual_mailbox;
+extern struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs;
+
+struct virtual_storage_module virtual_storage_module =
+ MODULE_CONTEXT_INIT(&mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(virtual_backend_storage_module,
+ &mail_storage_module_register);
+
+static bool ns_is_visible(struct mail_namespace *ns)
+{
+ return (ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0 ||
+ (ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0 ||
+ (ns->flags & NAMESPACE_FLAG_HIDDEN) == 0;
+}
+
+static const char *get_user_visible_mailbox_name(struct mailbox *box)
+{
+ if (ns_is_visible(box->list->ns))
+ return box->vname;
+ else {
+ return t_strdup_printf("<hidden>%c%s",
+ mail_namespace_get_sep(box->list->ns),
+ box->vname);
+ }
+}
+
+void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src)
+{
+ const char *name, *str;
+ enum mail_error error;
+
+ name = get_user_visible_mailbox_name(src);
+ str = mailbox_get_last_error(src, &error);
+
+ str = t_strdup_printf("%s (for backend mailbox %s)", str, name);
+ mail_storage_set_error(dest->storage, error, str);
+}
+
+static struct mail_storage *virtual_storage_alloc(void)
+{
+ struct virtual_storage *storage;
+ pool_t pool;
+
+ pool = pool_alloconly_create("virtual storage", 1024);
+ storage = p_new(pool, struct virtual_storage, 1);
+ storage->storage = virtual_storage;
+ storage->storage.pool = pool;
+ p_array_init(&storage->open_stack, pool, 8);
+ return &storage->storage;
+}
+
+static int
+virtual_storage_create(struct mail_storage *_storage,
+ struct mail_namespace *ns ATTR_UNUSED,
+ const char **error_r)
+{
+ struct virtual_storage *storage = (struct virtual_storage *)_storage;
+ const char *value;
+
+ value = mail_user_plugin_getenv(_storage->user, "virtual_max_open_mailboxes");
+ if (value == NULL)
+ storage->max_open_mailboxes = VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES;
+ else if (str_to_uint(value, &storage->max_open_mailboxes) < 0) {
+ *error_r = "Invalid virtual_max_open_mailboxes setting";
+ return -1;
+ }
+ return 0;
+}
+
+static void
+virtual_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED,
+ struct mailbox_list_settings *set)
+{
+ if (set->layout == NULL)
+ set->layout = MAILBOX_LIST_NAME_FS;
+ if (set->subscription_fname == NULL)
+ set->subscription_fname = VIRTUAL_SUBSCRIPTION_FILE_NAME;
+}
+
+struct virtual_backend_box *
+virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(bboxes[i]->name, name) == 0)
+ return bboxes[i];
+ }
+ return NULL;
+}
+
+struct virtual_backend_box *
+virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ if (mailbox_id == 0)
+ return NULL;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (bboxes[i]->mailbox_id == mailbox_id)
+ return bboxes[i];
+ }
+ return NULL;
+}
+
+static bool virtual_mailbox_is_in_open_stack(struct virtual_storage *storage,
+ const char *name)
+{
+ const char *const *names;
+ unsigned int i, count;
+
+ names = array_get(&storage->open_stack, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(names[i], name) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int virtual_backend_box_open_failed(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox)
+{
+ enum mail_error error;
+ const char *str;
+
+ str = t_strdup_printf(
+ "Virtual mailbox open failed because of mailbox %s: %s",
+ get_user_visible_mailbox_name(bbox->box),
+ mailbox_get_last_error(bbox->box, &error));
+ mail_storage_set_error(mbox->box.storage, error, str);
+ mailbox_free(&bbox->box);
+
+ if (error == MAIL_ERROR_PERM && bbox->wildcard) {
+ /* this mailbox wasn't explicitly specified. just skip it. */
+ return 0;
+ }
+ return -1;
+}
+
+static int virtual_backend_box_alloc(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox,
+ enum mailbox_flags flags)
+{
+ struct mail_user *user = mbox->storage->storage.user;
+ struct mail_namespace *ns;
+ const char *mailbox;
+ enum mailbox_existence existence;
+
+ i_assert(bbox->box == NULL);
+
+ if (!bbox->clear_recent)
+ flags &= ENUM_NEGATE(MAILBOX_FLAG_DROP_RECENT);
+
+ mailbox = bbox->name;
+ ns = mail_namespace_find(user->namespaces, mailbox);
+ bbox->box = mailbox_alloc(ns->list, mailbox, flags);
+ MODULE_CONTEXT_SET(bbox->box, virtual_storage_module, bbox);
+
+ if (bbox == mbox->save_bbox) {
+ /* Assume that the save_bbox exists, whether or not it truly
+ does. This at least gives a better error message than crash
+ later on. */
+ existence = MAILBOX_EXISTENCE_SELECT;
+ } else {
+ if (mailbox_exists(bbox->box, TRUE, &existence) < 0)
+ return virtual_backend_box_open_failed(mbox, bbox);
+ }
+ if (existence != MAILBOX_EXISTENCE_SELECT) {
+ /* ignore this. it could be intentional. */
+ e_debug(mbox->box.event,
+ "Skipping non-existing mailbox %s",
+ bbox->box->vname);
+ mailbox_free(&bbox->box);
+ return 0;
+ }
+
+ i_array_init(&bbox->uids, 64);
+ i_array_init(&bbox->sync_pending_removes, 64);
+ /* we use modseqs for being able to check quickly if backend mailboxes
+ have changed. make sure the backend has them enabled. */
+ (void)mailbox_enable(bbox->box, MAILBOX_FEATURE_CONDSTORE);
+ return 1;
+}
+
+static int virtual_mailboxes_open(struct virtual_mailbox *mbox,
+ enum mailbox_flags flags)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+ int ret;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; ) {
+ ret = virtual_backend_box_alloc(mbox, bboxes[i], flags);
+ if (ret <= 0) {
+ if (ret < 0)
+ break;
+ array_delete(&mbox->backend_boxes, i, 1);
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ } else {
+ i++;
+ }
+ }
+ if (i == count)
+ return 0;
+ else {
+ /* failed */
+ for (; i > 0; i--) {
+ mailbox_free(&bboxes[i-1]->box);
+ array_free(&bboxes[i-1]->uids);
+ }
+ return -1;
+ }
+}
+
+static struct mailbox *
+virtual_mailbox_alloc(struct mail_storage *_storage, struct mailbox_list *list,
+ const char *vname, enum mailbox_flags flags)
+{
+ struct virtual_storage *storage = (struct virtual_storage *)_storage;
+ struct virtual_mailbox *mbox;
+ pool_t pool;
+
+ pool = pool_alloconly_create("virtual mailbox", 2048);
+ mbox = p_new(pool, struct virtual_mailbox, 1);
+ mbox->box = virtual_mailbox;
+ mbox->box.pool = pool;
+ mbox->box.storage = _storage;
+ mbox->box.list = list;
+ mbox->box.mail_vfuncs = &virtual_mail_vfuncs;
+ mbox->box.virtual_vfuncs = &virtual_mailbox_vfuncs;
+
+ index_storage_mailbox_alloc(&mbox->box, vname, flags, MAIL_INDEX_PREFIX);
+
+ mbox->storage = storage;
+ mbox->virtual_ext_id = (uint32_t)-1;
+ mbox->virtual_ext2_id = (uint32_t)-1;
+ mbox->virtual_guid_ext_id = (uint32_t)-1;
+ return &mbox->box;
+}
+
+void virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox)
+{
+ struct mailbox_transaction_context *trans;
+
+ if (bbox->sync_mail != NULL) {
+ trans = bbox->sync_mail->transaction;
+ mail_free(&bbox->sync_mail);
+ (void)mailbox_transaction_commit(&trans);
+ }
+}
+
+static bool virtual_backend_box_can_close(struct virtual_backend_box *bbox)
+{
+ if (bbox->box->notify_callback != NULL) {
+ /* we can close it if notify is set
+ because we have no need to keep it open
+ for tracking changes */
+ return bbox->notify != NULL;
+ }
+ if (array_count(&bbox->sync_pending_removes) > 0) {
+ /* FIXME: we could probably close this by making
+ syncing support it? */
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+virtual_backend_box_close_any_except(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *except_bbox)
+{
+ struct virtual_backend_box *bbox;
+
+ /* first try to close a mailbox without any transactions.
+ we'll also skip any mailbox that has notifications enabled (ideally
+ these would be handled by mailbox list index) */
+ for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) {
+ i_assert(bbox->box->opened);
+
+ if (bbox != except_bbox &&
+ bbox->box->transaction_count == 0 &&
+ virtual_backend_box_can_close(bbox)) {
+ i_assert(bbox->sync_mail == NULL);
+ virtual_backend_box_close(mbox, bbox);
+ return TRUE;
+ }
+ }
+
+ /* next try to close a mailbox that has sync_mail, but no
+ other transactions */
+ for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) {
+ if (bbox != except_bbox &&
+ bbox->sync_mail != NULL &&
+ bbox->box->transaction_count == 1 &&
+ virtual_backend_box_can_close(bbox)) {
+ virtual_backend_box_sync_mail_unset(bbox);
+ i_assert(bbox->box->transaction_count == 0);
+ virtual_backend_box_close(mbox, bbox);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void virtual_backend_mailbox_close(struct mailbox *box)
+{
+ struct virtual_backend_box *bbox = VIRTUAL_CONTEXT(box);
+ struct virtual_backend_mailbox *vbox = VIRTUAL_BACKEND_CONTEXT(box);
+
+ if (bbox != NULL && bbox->open_tracked) {
+ /* we could have gotten here from e.g. mailbox_autocreate()
+ without going through virtual_mailbox_close() */
+ virtual_backend_box_close(bbox->virtual_mbox, bbox);
+ }
+ vbox->module_ctx.super.close(box);
+}
+
+void virtual_backend_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct virtual_backend_mailbox *vbox;
+
+ vbox = p_new(box->pool, struct virtual_backend_mailbox, 1);
+ vbox->module_ctx.super = *v;
+ box->vlast = &vbox->module_ctx.super;
+ v->close = virtual_backend_mailbox_close;
+ MODULE_CONTEXT_SET(box, virtual_backend_storage_module, vbox);
+}
+
+void virtual_backend_mailbox_opened(struct mailbox *box)
+{
+ struct virtual_backend_box *bbox = VIRTUAL_CONTEXT(box);
+ struct virtual_mailbox *mbox;
+
+ if (bbox == NULL) {
+ /* not a backend for a virtual mailbox */
+ return;
+ }
+ i_assert(!bbox->open_tracked);
+ mbox = bbox->virtual_mbox;
+
+ /* the backend mailbox was already opened. if we didn't get here
+ from virtual_backend_box_open() we may need to close a mailbox */
+ while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes &&
+ virtual_backend_box_close_any_except(mbox, bbox))
+ ;
+
+ bbox->open_tracked = TRUE;
+ mbox->backends_open_count++;
+ DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head,
+ &mbox->open_backend_boxes_tail, bbox,
+ prev_open, next_open);
+}
+
+int virtual_backend_box_open(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox)
+{
+ i_assert(!bbox->box->opened);
+
+ /* try to keep the number of open mailboxes below the threshold
+ before opening the mailbox */
+ while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes &&
+ virtual_backend_box_close_any_except(mbox, bbox))
+ ;
+
+ return mailbox_open(bbox->box);
+}
+
+void virtual_backend_box_close(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox)
+{
+ i_assert(bbox->box->opened);
+ i_assert(bbox->open_tracked);
+
+ if (bbox->search_result != NULL)
+ mailbox_search_result_free(&bbox->search_result);
+
+ if (bbox->search_args != NULL &&
+ bbox->search_args_initialized) {
+ mail_search_args_deinit(bbox->search_args);
+ bbox->search_args_initialized = FALSE;
+ }
+ i_assert(mbox->backends_open_count > 0);
+ mbox->backends_open_count--;
+ bbox->open_tracked = FALSE;
+
+ DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head,
+ &mbox->open_backend_boxes_tail, bbox,
+ prev_open, next_open);
+
+ /* stop receiving notifications */
+ if (bbox->notify_changes_started)
+ mailbox_notify_changes_stop(bbox->box);
+ bbox->notify_changes_started = FALSE;
+
+ mailbox_close(bbox->box);
+}
+
+void virtual_backend_box_accessed(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox)
+{
+ DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head,
+ &mbox->open_backend_boxes_tail, bbox,
+ prev_open, next_open);
+ DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head,
+ &mbox->open_backend_boxes_tail, bbox,
+ prev_open, next_open);
+}
+
+static void virtual_mailbox_close_internal(struct virtual_mailbox *mbox)
+{
+ struct virtual_backend_box **bboxes;
+ unsigned int i, count;
+
+ bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (bboxes[i]->box == NULL)
+ continue;
+ if (bboxes[i]->notify != NULL)
+ mailbox_list_notify_deinit(&bboxes[i]->notify);
+ if (bboxes[i]->box->opened)
+ virtual_backend_box_close(mbox, bboxes[i]);
+ mailbox_free(&bboxes[i]->box);
+ if (array_is_created(&bboxes[i]->sync_outside_expunges))
+ array_free(&bboxes[i]->sync_outside_expunges);
+ array_free(&bboxes[i]->sync_pending_removes);
+ array_free(&bboxes[i]->uids);
+ }
+ i_assert(mbox->backends_open_count == 0);
+}
+
+static int
+virtual_mailbox_exists(struct mailbox *box, bool auto_boxes ATTR_UNUSED,
+ enum mailbox_existence *existence_r)
+{
+ return index_storage_mailbox_exists_full(box, VIRTUAL_CONFIG_FNAME,
+ existence_r);
+}
+
+static int virtual_mailbox_open(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ bool broken;
+ int ret = 0;
+
+ if (virtual_mailbox_is_in_open_stack(mbox->storage, box->name)) {
+ mailbox_set_critical(box,
+ "Virtual mailbox loops: %s", box->name);
+ return -1;
+ }
+
+ if (!array_is_created(&mbox->backend_boxes))
+ ret = virtual_config_read(mbox);
+ if (ret == 0) {
+ array_push_back(&mbox->storage->open_stack, &box->name);
+ ret = virtual_mailboxes_open(mbox, box->flags);
+ array_pop_back(&mbox->storage->open_stack);
+ }
+ if (ret == 0)
+ ret = index_storage_mailbox_open(box, FALSE);
+ if (ret < 0) {
+ virtual_mailbox_close_internal(mbox);
+ return -1;
+ }
+
+ mbox->virtual_ext_id =
+ mail_index_ext_register(mbox->box.index, "virtual", 0,
+ sizeof(struct virtual_mail_index_record),
+ sizeof(uint32_t));
+ mbox->virtual_ext2_id =
+ mail_index_ext_register(mbox->box.index, "virtual2", 0, 0, 0);
+
+ mbox->virtual_guid_ext_id =
+ mail_index_ext_register(mbox->box.index, "virtual-guid", GUID_128_SIZE,
+ 0, 0);
+
+ if (virtual_mailbox_ext_header_read(mbox, box->view, &broken) < 0) {
+ virtual_mailbox_close_internal(mbox);
+ index_storage_mailbox_close(box);
+ return -1;
+ }
+
+ /* if GUID is missing write it here */
+ if (guid_128_is_empty(mbox->guid)) {
+ guid_128_generate(mbox->guid);
+ struct mail_index_transaction *t =
+ mail_index_transaction_begin(box->view, 0);
+ mail_index_update_header_ext(t, mbox->virtual_guid_ext_id,
+ 0, mbox->guid, GUID_128_SIZE);
+ if (mail_index_transaction_commit(&t) < 0) {
+ mailbox_set_critical(box,
+ "Cannot write GUID for virtual mailbox to index");
+ virtual_mailbox_close_internal(mbox);
+ index_storage_mailbox_close(box);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void virtual_mailbox_close(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+ virtual_mailbox_close_internal(mbox);
+ index_storage_mailbox_close(box);
+}
+
+static void virtual_mailbox_free(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+ virtual_config_free(mbox);
+ index_storage_mailbox_free(box);
+}
+
+static int
+virtual_mailbox_create(struct mailbox *box,
+ const struct mailbox_update *update ATTR_UNUSED,
+ bool directory ATTR_UNUSED)
+{
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't create virtual mailboxes");
+ return -1;
+}
+
+static int
+virtual_mailbox_update(struct mailbox *box,
+ const struct mailbox_update *update ATTR_UNUSED)
+{
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't update virtual mailboxes");
+ return -1;
+}
+
+static int virtual_storage_set_have_guid_flags(struct virtual_mailbox *mbox)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+ struct mailbox_status status;
+
+ if (!mbox->box.opened) {
+ if (mailbox_open(&mbox->box) < 0)
+ return -1;
+ }
+
+ mbox->have_guids = TRUE;
+ mbox->have_save_guids = TRUE;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (mailbox_get_status(bboxes[i]->box, 0, &status) < 0) {
+ const char *errstr;
+ enum mail_error error;
+
+ errstr = mailbox_get_last_error(bboxes[i]->box, &error);
+ if (error == MAIL_ERROR_NOTFOUND) {
+ /* backend mailbox was just lost - skip it */
+ continue;
+ }
+ /* Not expected to happen, but we can't return failure
+ since this could be called from
+ mailbox_get_open_status() and it would panic.
+ So just log the error and skip the mailbox. */
+ mailbox_set_critical(&mbox->box,
+ "Virtual mailbox: Failed to get have_guid existence for backend mailbox %s: %s",
+ mailbox_get_vname(bboxes[i]->box), errstr);
+ continue;
+ }
+ if (!status.have_guids)
+ mbox->have_guids = FALSE;
+ if (!status.have_save_guids)
+ mbox->have_save_guids = FALSE;
+ }
+ return 0;
+}
+
+static int
+virtual_storage_get_status(struct mailbox *box,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+ if ((items & STATUS_LAST_CACHED_SEQ) != 0)
+ items |= STATUS_MESSAGES;
+
+ if (index_storage_get_status(box, items, status_r) < 0)
+ return -1;
+
+ if ((items & STATUS_LAST_CACHED_SEQ) != 0) {
+ /* Virtual mailboxes have no cached data of their own, so the
+ current value is always 0. The most important use for this
+ functionality is for "doveadm index" to do FTS indexing and
+ it doesn't really matter there if we set this value
+ correctly or not. So for now just assume that everything is
+ indexed. */
+ status_r->last_cached_seq = status_r->messages;
+ }
+ if (!mbox->have_guid_flags_set) {
+ if (virtual_storage_set_have_guid_flags(mbox) < 0)
+ return -1;
+ mbox->have_guid_flags_set = TRUE;
+ }
+
+ if (mbox->have_guids)
+ status_r->have_guids = TRUE;
+ if (mbox->have_save_guids)
+ status_r->have_save_guids = TRUE;
+ return 0;
+}
+
+static int
+virtual_mailbox_get_metadata(struct mailbox *box,
+ enum mailbox_metadata_items items,
+ struct mailbox_metadata *metadata_r)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ if (index_mailbox_get_metadata(box, items, metadata_r) < 0)
+ return -1;
+ i_assert(box->opened);
+ if ((items & MAILBOX_METADATA_GUID) != 0) {
+ if (guid_128_is_empty(mbox->guid)) {
+ mailbox_set_critical(box, "GUID missing for virtual folder");
+ return -1;
+ }
+ guid_128_copy(metadata_r->guid, mbox->guid);
+ }
+ return 0;
+}
+
+static void
+virtual_notify_callback(struct mailbox *bbox ATTR_UNUSED, struct mailbox *box)
+{
+ box->notify_callback(box, box->notify_context);
+}
+
+static void virtual_backend_box_changed(struct virtual_backend_box *bbox)
+{
+ virtual_notify_callback(bbox->box, &bbox->virtual_mbox->box);
+}
+
+static int virtual_notify_start(struct virtual_backend_box *bbox)
+{
+ i_assert(bbox->notify == NULL);
+ if (mailbox_list_notify_init(bbox->box->list, MAILBOX_LIST_NOTIFY_STATUS,
+ &bbox->notify) < 0)
+ /* did not support notifications */
+ return -1;
+ mailbox_list_notify_wait(bbox->notify, virtual_backend_box_changed, bbox);
+ return 0;
+}
+
+static void virtual_notify_changes(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_backend_box **bboxp;
+
+ if (box->notify_callback == NULL) {
+ array_foreach_modifiable(&mbox->backend_boxes, bboxp) {
+ if ((*bboxp)->notify_changes_started) {
+ mailbox_notify_changes_stop((*bboxp)->box);
+ (*bboxp)->notify_changes_started = FALSE;
+ }
+ if ((*bboxp)->notify != NULL)
+ mailbox_list_notify_deinit(&(*bboxp)->notify);
+ }
+ return;
+ }
+
+ array_foreach_modifiable(&mbox->backend_boxes, bboxp) {
+ if (array_count(&mbox->backend_boxes) == 1 &&
+ (*bboxp)->box->opened) {
+ /* There's only a single backend mailbox and its
+ indexes are already opened. Might as well use the
+ backend directly for notifications. */
+ } else {
+ /* we are already waiting for notifications */
+ if ((*bboxp)->notify != NULL)
+ continue;
+ /* wait for notifications */
+ if (virtual_notify_start(*bboxp) == 0)
+ continue;
+ /* it did not work, so open the mailbox and use
+ alternative method */
+ }
+
+ if (!(*bboxp)->box->opened &&
+ virtual_backend_box_open(mbox, *bboxp) < 0) {
+ /* we can't report error in here, so do it later */
+ (*bboxp)->open_failed = TRUE;
+ continue;
+ }
+ mailbox_notify_changes((*bboxp)->box,
+ virtual_notify_callback, box);
+ (*bboxp)->notify_changes_started = TRUE;
+ }
+}
+
+static void
+virtual_uidmap_to_uid_array(struct virtual_backend_box *bbox,
+ ARRAY_TYPE(seq_range) *uids_r)
+{
+ const struct virtual_backend_uidmap *uid;
+ array_foreach(&bbox->uids, uid) {
+ seq_range_array_add(uids_r, uid->real_uid);
+ }
+}
+
+static void
+virtual_get_virtual_uids(struct mailbox *box,
+ struct mailbox *backend_mailbox,
+ const ARRAY_TYPE(seq_range) *backend_uids,
+ ARRAY_TYPE(seq_range) *virtual_uids_r)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_backend_box *bbox;
+ const struct virtual_backend_uidmap *uids;
+ ARRAY_TYPE(seq_range) uid_range;
+ struct seq_range_iter iter;
+ unsigned int n, i, count;
+ uint32_t uid;
+
+ if (mbox->lookup_prev_bbox != NULL &&
+ strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0)
+ bbox = mbox->lookup_prev_bbox;
+ else {
+ bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname);
+ mbox->lookup_prev_bbox = bbox;
+ }
+ if (bbox == NULL)
+ return;
+
+ uids = array_get(&bbox->uids, &count); i = 0;
+
+ t_array_init(&uid_range, 8);
+ virtual_uidmap_to_uid_array(bbox, &uid_range);
+ seq_range_array_intersect(&uid_range, backend_uids);
+
+ seq_range_array_iter_init(&iter, &uid_range); n = 0;
+ while (seq_range_array_iter_nth(&iter, n++, &uid)) {
+ while (i < count && uids[i].real_uid < uid) i++;
+ if (i < count && uids[i].real_uid == uid) {
+ i_assert(uids[i].virtual_uid > 0);
+ seq_range_array_add(virtual_uids_r,
+ uids[i].virtual_uid);
+ i++;
+ }
+ }
+}
+
+static void
+virtual_get_virtual_uid_map(struct mailbox *box,
+ struct mailbox *backend_mailbox,
+ const ARRAY_TYPE(seq_range) *backend_uids,
+ ARRAY_TYPE(uint32_t) *virtual_uids_r)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_backend_box *bbox;
+ const struct virtual_backend_uidmap *uids;
+ struct seq_range_iter iter;
+ unsigned int n, i, count;
+ uint32_t uid;
+
+ if (mbox->lookup_prev_bbox != NULL &&
+ strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0)
+ bbox = mbox->lookup_prev_bbox;
+ else {
+ bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname);
+ mbox->lookup_prev_bbox = bbox;
+ }
+ if (bbox == NULL)
+ return;
+
+ uids = array_get(&bbox->uids, &count); i = 0;
+ seq_range_array_iter_init(&iter, backend_uids); n = 0;
+ while (seq_range_array_iter_nth(&iter, n++, &uid)) {
+ while (i < count && uids[i].real_uid < uid) i++;
+ if (i == count || uids[i].real_uid > uid) {
+ uint32_t zero = 0;
+
+ array_push_back(virtual_uids_r, &zero);
+ } else {
+ i_assert(uids[i].virtual_uid > 0);
+ array_push_back(virtual_uids_r, &uids[i].virtual_uid);
+ i++;
+ }
+ }
+}
+
+static void
+virtual_get_virtual_backend_boxes(struct mailbox *box,
+ ARRAY_TYPE(mailboxes) *mailboxes,
+ bool only_with_msgs)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (!only_with_msgs || array_count(&bboxes[i]->uids) > 0)
+ array_push_back(mailboxes, &bboxes[i]->box);
+ }
+}
+
+static bool virtual_is_inconsistent(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+ if (mbox->inconsistent)
+ return TRUE;
+
+ return index_storage_is_inconsistent(box);
+}
+
+static int
+virtual_list_index_has_changed(struct mailbox *box ATTR_UNUSED,
+ struct mail_index_view *list_view ATTR_UNUSED,
+ uint32_t seq ATTR_UNUSED, bool quick ATTR_UNUSED,
+ const char **reason_r)
+{
+ /* we don't have any quick and easy optimizations for tracking
+ virtual folders. ideally we'd completely disable mailbox list
+ indexes for them, but this is the easiest way to do it for now. */
+ *reason_r = "Virtual indexes always change";
+ return 1;
+}
+
+static void
+virtual_list_index_update_sync(struct mailbox *box ATTR_UNUSED,
+ struct mail_index_transaction *trans ATTR_UNUSED,
+ uint32_t seq ATTR_UNUSED)
+{
+}
+
+struct mail_storage virtual_storage = {
+ .name = VIRTUAL_STORAGE_NAME,
+ .class_flags = MAIL_STORAGE_CLASS_FLAG_NOQUOTA |
+ MAIL_STORAGE_CLASS_FLAG_SECONDARY_INDEX,
+
+ .v = {
+ NULL,
+ virtual_storage_alloc,
+ virtual_storage_create,
+ index_storage_destroy,
+ NULL,
+ virtual_storage_get_list_settings,
+ NULL,
+ virtual_mailbox_alloc,
+ NULL,
+ NULL,
+ }
+};
+
+struct mailbox virtual_mailbox = {
+ .v = {
+ index_storage_is_readonly,
+ index_storage_mailbox_enable,
+ virtual_mailbox_exists,
+ virtual_mailbox_open,
+ virtual_mailbox_close,
+ virtual_mailbox_free,
+ virtual_mailbox_create,
+ virtual_mailbox_update,
+ index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
+ virtual_storage_get_status,
+ virtual_mailbox_get_metadata,
+ index_storage_set_subscribed,
+ index_storage_attribute_set,
+ index_storage_attribute_get,
+ index_storage_attribute_iter_init,
+ index_storage_attribute_iter_next,
+ index_storage_attribute_iter_deinit,
+ virtual_list_index_has_changed,
+ virtual_list_index_update_sync,
+ virtual_storage_sync_init,
+ index_mailbox_sync_next,
+ index_mailbox_sync_deinit,
+ NULL,
+ virtual_notify_changes,
+ virtual_transaction_begin,
+ virtual_transaction_commit,
+ virtual_transaction_rollback,
+ NULL,
+ virtual_mail_alloc,
+ virtual_search_init,
+ virtual_search_deinit,
+ virtual_search_next_nonblock,
+ virtual_search_next_update_seq,
+ index_storage_search_next_match_mail,
+ virtual_save_alloc,
+ virtual_save_begin,
+ virtual_save_continue,
+ virtual_save_finish,
+ virtual_save_cancel,
+ mail_storage_copy,
+ NULL,
+ NULL,
+ NULL,
+ virtual_is_inconsistent
+ }
+};
+
+struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs = {
+ virtual_get_virtual_uids,
+ virtual_get_virtual_uid_map,
+ virtual_get_virtual_backend_boxes
+};
diff --git a/src/plugins/virtual/virtual-storage.h b/src/plugins/virtual/virtual-storage.h
new file mode 100644
index 0000000..1f34e39
--- /dev/null
+++ b/src/plugins/virtual/virtual-storage.h
@@ -0,0 +1,251 @@
+#ifndef VIRTUAL_STORAGE_H
+#define VIRTUAL_STORAGE_H
+
+#include "seq-range-array.h"
+#include "index-storage.h"
+
+#define VIRTUAL_STORAGE_NAME "virtual"
+#define VIRTUAL_SUBSCRIPTION_FILE_NAME ".virtual-subscriptions"
+#define VIRTUAL_CONFIG_FNAME "dovecot-virtual"
+
+#define VIRTUAL_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, virtual_storage_module)
+#define VIRTUAL_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, virtual_storage_module)
+
+#define VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION 1
+
+struct virtual_save_context;
+
+struct virtual_mail_index_header {
+ /* Increased by one each time the header is modified */
+ uint32_t change_counter;
+ /* Number of mailbox records following this header. Mailbox names
+ follow the mailbox records - they have neither NUL terminator nor
+ padding. */
+ uint32_t mailbox_count;
+ /* Highest used mailbox ID. IDs are never reused. */
+ uint32_t highest_mailbox_id;
+ /* CRC32 of all the search parameters. If it changes, the mailbox is
+ rebuilt. */
+ uint32_t search_args_crc32;
+};
+
+struct virtual_mail_index_mailbox_record {
+ /* Unique mailbox ID used as mailbox_id in records. */
+ uint32_t id;
+ /* Length of this mailbox's name. */
+ uint32_t name_len;
+ /* Synced UID validity value */
+ uint32_t uid_validity;
+ /* Next unseen UID */
+ uint32_t next_uid;
+ /* Synced highest modseq value */
+ uint64_t highest_modseq;
+};
+
+struct virtual_mail_index_ext2_header {
+ /* Version compatibility number:
+ VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION */
+ uint8_t version;
+ /* Set to sizeof(struct virtual_mail_index_mailbox_ext2_record) when
+ writing. */
+ uint8_t ext_record_size;
+ /* Set to sizeof(struct virtual_mail_index_ext2_header) when writing. */
+ uint16_t hdr_size;
+ /* Must be the same as virtual_mail_index_header.change_counter.
+ If not, it means the header was modified by an older Dovecot version
+ and this extension header should be ignored/rewritten. */
+ uint32_t change_counter;
+};
+
+struct virtual_mail_index_mailbox_ext2_record {
+ /* Synced GUID value */
+ uint8_t guid[16];
+};
+
+struct virtual_mail_index_record {
+ uint32_t mailbox_id;
+ uint32_t real_uid;
+};
+
+struct virtual_storage {
+ struct mail_storage storage;
+
+ /* List of mailboxes while a virtual mailbox is being opened.
+ Used to track loops. */
+ ARRAY_TYPE(const_string) open_stack;
+
+ unsigned int max_open_mailboxes;
+};
+
+struct virtual_backend_uidmap {
+ uint32_t real_uid;
+ /* can be 0 temporarily while syncing before the UID is assigned */
+ uint32_t virtual_uid;
+};
+
+struct virtual_backend_box {
+ union mailbox_module_context module_ctx;
+ struct virtual_mailbox *virtual_mbox;
+
+ /* linked list for virtual_mailbox->open_backend_boxes_{head,tail} */
+ struct virtual_backend_box *prev_open, *next_open;
+
+ /* Initially zero, updated by syncing */
+ uint32_t mailbox_id;
+ const char *name;
+
+ unsigned int sync_mailbox_idx1;
+ uint32_t sync_uid_validity;
+ uint32_t sync_next_uid;
+ uint64_t sync_highest_modseq;
+ guid_128_t sync_guid;
+ /* this value is either 0 or same as sync_highest_modseq. it's kept 0
+ when there are pending removes that have yet to be expunged */
+ uint64_t ondisk_highest_modseq;
+
+ struct mail_search_args *search_args;
+ struct mail_search_result *search_result;
+
+ struct mailbox *box;
+ /* Messages currently included in the virtual mailbox,
+ sorted by real_uid */
+ ARRAY(struct virtual_backend_uidmap) uids;
+
+ /* temporary mail used while syncing */
+ struct mail *sync_mail;
+ /* pending removed UIDs */
+ ARRAY_TYPE(seq_range) sync_pending_removes;
+ /* another process expunged these UIDs. they need to be removed on
+ next sync. */
+ ARRAY_TYPE(seq_range) sync_outside_expunges;
+
+ /* name contains a wildcard, this is a glob for it */
+ struct imap_match_glob *glob;
+ struct mail_namespace *ns;
+ /* mailbox metadata matching */
+ const char *metadata_entry, *metadata_value;
+
+ /* notify context */
+ struct mailbox_list_notify *notify;
+
+ bool open_tracked:1;
+ bool open_failed:1;
+ bool sync_seen:1;
+ bool wildcard:1;
+ bool clear_recent:1;
+ bool negative_match:1;
+ bool uids_nonsorted:1;
+ bool search_args_initialized:1;
+ bool deleted:1;
+ bool notify_changes_started:1; /* if the box was opened for notify_changes */
+ bool first_sync:1; /* if this is the first sync after bbox was (re-)created */
+};
+ARRAY_DEFINE_TYPE(virtual_backend_box, struct virtual_backend_box *);
+
+struct virtual_mailbox {
+ struct mailbox box;
+ struct virtual_storage *storage;
+
+ uint32_t virtual_ext_id;
+ uint32_t virtual_ext2_id;
+ uint32_t virtual_guid_ext_id;
+
+ uint32_t prev_uid_validity;
+ uint32_t prev_change_counter;
+ uint32_t highest_mailbox_id;
+ uint32_t search_args_crc32;
+ guid_128_t guid;
+
+ struct virtual_backend_box *lookup_prev_bbox;
+ uint32_t sync_virtual_next_uid;
+
+ /* Mailboxes this virtual mailbox consists of, sorted by mailbox_id */
+ ARRAY_TYPE(virtual_backend_box) backend_boxes;
+ /* backend mailbox where to save messages when saving to this mailbox */
+ struct virtual_backend_box *save_bbox;
+
+ /* linked list of open backend mailboxes. head will contain the oldest
+ accessed mailbox, tail will contain the newest. */
+ struct virtual_backend_box *open_backend_boxes_head;
+ struct virtual_backend_box *open_backend_boxes_tail;
+ /* number of backend mailboxes that are open currently. */
+ unsigned int backends_open_count;
+
+ ARRAY_TYPE(mailbox_virtual_patterns) list_include_patterns;
+ ARRAY_TYPE(mailbox_virtual_patterns) list_exclude_patterns;
+
+ bool uids_mapped:1;
+ bool sync_initialized:1;
+ bool inconsistent:1;
+ bool have_guid_flags_set:1;
+ bool have_guids:1;
+ bool have_save_guids:1;
+ bool ext_header_rewrite:1;
+};
+
+extern MODULE_CONTEXT_DEFINE(virtual_storage_module,
+ &mail_storage_module_register);
+
+extern struct mail_storage virtual_storage;
+extern struct mail_vfuncs virtual_mail_vfuncs;
+
+int virtual_config_read(struct virtual_mailbox *mbox);
+void virtual_config_free(struct virtual_mailbox *mbox);
+
+int virtual_mailbox_ext_header_read(struct virtual_mailbox *mbox,
+ struct mail_index_view *view,
+ bool *broken_r);
+
+struct virtual_backend_box *
+virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name);
+struct virtual_backend_box *
+virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id);
+
+int virtual_backend_box_open(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox);
+void virtual_backend_box_close(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox);
+void virtual_backend_box_accessed(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox);
+void virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox);
+
+struct mail_search_context *
+virtual_search_init(struct mailbox_transaction_context *t,
+ struct mail_search_args *args,
+ const enum mail_sort_type *sort_program,
+ enum mail_fetch_field wanted_fields,
+ struct mailbox_header_lookup_ctx *wanted_headers);
+int virtual_search_deinit(struct mail_search_context *ctx);
+bool virtual_search_next_nonblock(struct mail_search_context *ctx,
+ struct mail **mail_r, bool *tryagain_r);
+bool virtual_search_next_update_seq(struct mail_search_context *ctx);
+
+struct mail *
+virtual_mail_alloc(struct mailbox_transaction_context *t,
+ enum mail_fetch_field wanted_fields,
+ struct mailbox_header_lookup_ctx *wanted_headers);
+struct mail *
+virtual_mail_set_backend_mail(struct mail *mail,
+ struct virtual_backend_box *bbox);
+void virtual_mail_set_unattached_backend_mail(struct mail *mail,
+ struct mail *backend_mail);
+
+struct mailbox_sync_context *
+virtual_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags);
+
+struct mail_save_context *
+virtual_save_alloc(struct mailbox_transaction_context *t);
+int virtual_save_begin(struct mail_save_context *ctx, struct istream *input);
+int virtual_save_continue(struct mail_save_context *ctx);
+int virtual_save_finish(struct mail_save_context *ctx);
+void virtual_save_cancel(struct mail_save_context *ctx);
+void virtual_save_free(struct mail_save_context *ctx);
+
+void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src);
+
+void virtual_backend_mailbox_allocated(struct mailbox *box);
+void virtual_backend_mailbox_opened(struct mailbox *box);
+
+#endif
diff --git a/src/plugins/virtual/virtual-sync.c b/src/plugins/virtual/virtual-sync.c
new file mode 100644
index 0000000..f8c6989
--- /dev/null
+++ b/src/plugins/virtual/virtual-sync.c
@@ -0,0 +1,1956 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "bsearch-insert-pos.h"
+#include "ioloop.h"
+#include "str.h"
+#include "mail-index-modseq.h"
+#include "mail-search-build.h"
+#include "mailbox-search-result-private.h"
+#include "mailbox-recent-flags.h"
+#include "index-sync-private.h"
+#include "index-search-result.h"
+#include "virtual-storage.h"
+
+
+struct virtual_add_record {
+ struct virtual_mail_index_record rec;
+ time_t received_date;
+};
+
+struct virtual_sync_mail {
+ uint32_t vseq;
+ struct virtual_mail_index_record vrec;
+};
+
+struct virtual_sync_context {
+ struct virtual_mailbox *mbox;
+ struct mail_index_sync_ctx *index_sync_ctx;
+ struct mail_index *index;
+ struct mail_index_view *sync_view;
+ struct mail_index_transaction *trans;
+ const char *const *kw_all;
+
+ /* messages expunged within this sync */
+ ARRAY_TYPE(seq_range) sync_expunges;
+
+ ARRAY(struct virtual_add_record) all_adds;
+
+ /* all messages in this sync, sorted by mailbox_id
+ (but unsorted inside it for now, since it doesn't matter) */
+ ARRAY(struct virtual_sync_mail) all_mails;
+ uint32_t all_mails_idx, all_mails_prev_mailbox_id;
+
+ enum mailbox_sync_flags flags;
+ uint32_t uid_validity;
+
+ bool ext_header_changed:1;
+ bool expunge_removed:1;
+ bool index_broken:1;
+};
+
+static void virtual_sync_backend_box_deleted(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox);
+
+static void virtual_sync_set_uidvalidity(struct virtual_sync_context *ctx)
+{
+ uint32_t uid_validity = ioloop_time;
+
+ mail_index_update_header(ctx->trans,
+ offsetof(struct mail_index_header, uid_validity),
+ &uid_validity, sizeof(uid_validity), TRUE);
+ ctx->uid_validity = uid_validity;
+}
+
+static void virtual_sync_external_flags(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ uint32_t vseq, uint32_t real_uid)
+{
+ enum mail_flags flags;
+ const char *const *kw_names;
+ struct mail_keywords *keywords;
+
+ if (!mail_set_uid(bbox->sync_mail, real_uid)) {
+ /* we may have reopened the mailbox, which could have
+ caused the mail to be expunged already. */
+ return;
+ }
+
+ /* copy flags */
+ flags = mail_get_flags(bbox->sync_mail);
+
+ /* we don't need to keep recent flags here */
+ mail_index_update_flags(ctx->trans, vseq, MODIFY_REPLACE,
+ flags & ENUM_NEGATE(MAIL_RECENT));
+
+ /* copy keywords */
+ kw_names = mail_get_keywords(bbox->sync_mail);
+ keywords = mail_index_keywords_create(ctx->index, kw_names);
+ mail_index_update_keywords(ctx->trans, vseq, MODIFY_REPLACE, keywords);
+ mail_index_keywords_unref(&keywords);
+}
+
+static int virtual_sync_mail_uid_cmp(const void *p1, const void *p2)
+{
+ const struct virtual_sync_mail *m1 = p1, *m2 = p2;
+
+ if (m1->vrec.mailbox_id < m2->vrec.mailbox_id)
+ return -1;
+ if (m1->vrec.mailbox_id > m2->vrec.mailbox_id)
+ return 1;
+
+ if (m1->vrec.real_uid < m2->vrec.real_uid)
+ return -1;
+ if (m1->vrec.real_uid > m2->vrec.real_uid)
+ return 1;
+ /* broken */
+ return 0;
+}
+
+static void
+virtual_backend_box_sync_mail_set(struct virtual_backend_box *bbox)
+{
+ struct mailbox_transaction_context *trans;
+
+ if (bbox->sync_mail == NULL) {
+ trans = mailbox_transaction_begin(bbox->box, 0, __func__);
+ bbox->sync_mail = mail_alloc(trans, 0, NULL);
+ }
+}
+
+static int bbox_mailbox_id_cmp(struct virtual_backend_box *const *b1,
+ struct virtual_backend_box *const *b2)
+{
+ if ((*b1)->mailbox_id < (*b2)->mailbox_id)
+ return -1;
+ if ((*b1)->mailbox_id > (*b2)->mailbox_id)
+ return 1;
+ return 0;
+}
+
+static int
+virtual_sync_get_backend_box(struct virtual_mailbox *mbox, const char *name,
+ struct virtual_backend_box **bbox_r)
+{
+ *bbox_r = virtual_backend_box_lookup_name(mbox, name);
+ if (*bbox_r != NULL || !mbox->sync_initialized)
+ return 0;
+
+ /* another process just added a new mailbox.
+ we can't handle this currently. */
+ mbox->inconsistent = TRUE;
+ mail_storage_set_error(mbox->box.storage, MAIL_ERROR_TEMP, t_strdup_printf(
+ "Backend mailbox '%s' added by another session. "
+ "Reopen the virtual mailbox.", name));
+ return -1;
+}
+
+static bool
+virtual_mailbox_ext2_header_read(struct virtual_mailbox *mbox,
+ struct mail_index_view *view,
+ const struct virtual_mail_index_header *ext_hdr)
+{
+ const char *box_path = mailbox_get_path(&mbox->box);
+ const struct virtual_mail_index_ext2_header *ext2_hdr;
+ const struct virtual_mail_index_mailbox_ext2_record *ext2_rec;
+ const void *ext2_data;
+ size_t ext2_size;
+ struct virtual_backend_box *bbox;
+
+ mail_index_get_header_ext(view, mbox->virtual_ext2_id,
+ &ext2_data, &ext2_size);
+ ext2_hdr = ext2_data;
+ if (ext2_size == 0) {
+ /* ext2 is missing - silently add it */
+ return FALSE;
+ }
+ if (ext2_size < sizeof(*ext2_hdr)) {
+ i_error("virtual index %s: Invalid ext2 header size: %zu",
+ box_path, ext2_size);
+ return FALSE;
+ }
+ if (ext2_hdr->hdr_size > ext2_size) {
+ i_error("virtual index %s: ext2 header size too large: %u > %zu",
+ box_path, ext2_hdr->hdr_size, ext2_size);
+ return FALSE;
+ }
+ if (ext2_hdr->ext_record_size < sizeof(*ext2_rec)) {
+ i_error("virtual index %s: Invalid ext2 record size: %u",
+ box_path, ext2_hdr->ext_record_size);
+ return FALSE;
+ }
+
+ if (ext_hdr->change_counter != ext2_hdr->change_counter) {
+ i_warning("virtual index %s: "
+ "Extension header change_counter mismatch (%u != %u) - "
+ "Index was modified by an older version?",
+ box_path, ext_hdr->change_counter,
+ ext2_hdr->change_counter);
+ return FALSE;
+ }
+ size_t mailboxes_size = ext2_size - ext2_hdr->hdr_size;
+ if (mailboxes_size % ext2_hdr->ext_record_size != 0 ||
+ mailboxes_size / ext2_hdr->ext_record_size != ext_hdr->mailbox_count) {
+ i_error("virtual index %s: Invalid ext2 size: "
+ "hdr_size=%u record_size=%u total_size=%zu mailbox_count=%u",
+ box_path, ext2_hdr->hdr_size, ext2_hdr->ext_record_size,
+ ext2_size, ext_hdr->mailbox_count);
+ return FALSE;
+ }
+
+ ext2_rec = CONST_PTR_OFFSET(ext2_data, ext2_hdr->hdr_size);
+ array_foreach_elem(&mbox->backend_boxes, bbox) {
+ if (bbox->sync_mailbox_idx1 == 0)
+ continue;
+
+ guid_128_copy(bbox->sync_guid,
+ ext2_rec[bbox->sync_mailbox_idx1-1].guid);
+ }
+ return TRUE;
+}
+
+int virtual_mailbox_ext_header_read(struct virtual_mailbox *mbox,
+ struct mail_index_view *view,
+ bool *broken_r)
+{
+ const char *box_path = mailbox_get_path(&mbox->box);
+ const struct virtual_mail_index_header *ext_hdr;
+ const struct mail_index_header *hdr;
+ const struct virtual_mail_index_mailbox_record *mailboxes;
+ struct virtual_backend_box *bbox, **bboxes;
+ const void *ext_data;
+ size_t ext_size;
+ unsigned int i, count, ext_name_offset, ext_mailbox_count;
+ uint32_t prev_mailbox_id;
+ int ret = 1;
+
+ *broken_r = FALSE;
+
+ hdr = mail_index_get_header(view);
+ mail_index_get_header_ext(view, mbox->virtual_ext_id,
+ &ext_data, &ext_size);
+ ext_hdr = ext_data;
+ if (mbox->sync_initialized &&
+ mbox->prev_uid_validity == hdr->uid_validity &&
+ ext_size >= sizeof(*ext_hdr) &&
+ mbox->prev_change_counter == ext_hdr->change_counter) {
+ /* fully refreshed */
+ return 1;
+ }
+
+ mbox->prev_uid_validity = hdr->uid_validity;
+ if (ext_hdr == NULL ||
+ mbox->search_args_crc32 != ext_hdr->search_args_crc32) {
+ mailboxes = NULL;
+ ext_name_offset = 0;
+ ext_mailbox_count = 0;
+ ret = 0;
+ } else {
+ const void *guid_data;
+ size_t guid_size;
+ mail_index_get_header_ext(view, mbox->virtual_guid_ext_id,
+ &guid_data, &guid_size);
+ if (guid_size >= GUID_128_SIZE)
+ guid_128_copy(mbox->guid, guid_data);
+
+ mbox->prev_change_counter = ext_hdr->change_counter;
+ mailboxes = (const void *)(ext_hdr + 1);
+ ext_name_offset = sizeof(*ext_hdr) +
+ ext_hdr->mailbox_count * sizeof(*mailboxes);
+ if (ext_name_offset >= ext_size ||
+ ext_hdr->mailbox_count > INT_MAX/sizeof(*mailboxes)) {
+ i_error("virtual index %s: Broken mailbox_count header",
+ box_path);
+ *broken_r = TRUE;
+ ext_mailbox_count = 0;
+ ret = 0;
+ } else {
+ ext_mailbox_count = ext_hdr->mailbox_count;
+ }
+ }
+
+ /* update mailbox backends */
+ prev_mailbox_id = 0;
+ for (i = 0; i < ext_mailbox_count; i++) {
+ if (mailboxes[i].id > ext_hdr->highest_mailbox_id ||
+ mailboxes[i].id <= prev_mailbox_id) {
+ i_error("virtual index %s: Broken mailbox id",
+ box_path);
+ break;
+ }
+ if (mailboxes[i].name_len == 0 ||
+ mailboxes[i].name_len > ext_size) {
+ i_error("virtual index %s: Broken mailbox name_len",
+ box_path);
+ break;
+ }
+ if (ext_name_offset + mailboxes[i].name_len > ext_size) {
+ i_error("virtual index %s: Broken mailbox list",
+ box_path);
+ break;
+ }
+ T_BEGIN {
+ const unsigned char *nameptr;
+ const char *name;
+
+ nameptr = CONST_PTR_OFFSET(ext_data, ext_name_offset);
+ name = t_strndup(nameptr, mailboxes[i].name_len);
+ if (virtual_sync_get_backend_box(mbox, name, &bbox) < 0)
+ ret = -1;
+ } T_END;
+
+ if (bbox == NULL) {
+ if (ret < 0)
+ return -1;
+ /* mailbox no longer exists. */
+ ret = 0;
+ } else {
+ bbox->mailbox_id = mailboxes[i].id;
+ bbox->sync_uid_validity = mailboxes[i].uid_validity;
+ bbox->ondisk_highest_modseq =
+ bbox->sync_highest_modseq =
+ mailboxes[i].highest_modseq;
+ bbox->sync_next_uid = mailboxes[i].next_uid;
+ bbox->sync_mailbox_idx1 = i+1;
+ }
+ ext_name_offset += mailboxes[i].name_len;
+ prev_mailbox_id = mailboxes[i].id;
+ }
+ if (i < ext_mailbox_count) {
+ *broken_r = TRUE;
+ mbox->ext_header_rewrite = TRUE;
+ ret = 0;
+ }
+ if (!*broken_r && ext_mailbox_count > 0) {
+ if (!virtual_mailbox_ext2_header_read(mbox, view, ext_hdr))
+ mbox->ext_header_rewrite = TRUE;
+ }
+
+ mbox->highest_mailbox_id = ext_hdr == NULL ? 0 :
+ ext_hdr->highest_mailbox_id;
+ /* do not mark it initialized if it's broken */
+ mbox->sync_initialized = !*broken_r;
+
+ /* assign new mailbox IDs if any are missing */
+ bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (bboxes[i]->mailbox_id == 0) {
+ bboxes[i]->mailbox_id = ++mbox->highest_mailbox_id;
+ ret = 0;
+ }
+ }
+ /* sort the backend mailboxes by mailbox_id. */
+ array_sort(&mbox->backend_boxes, bbox_mailbox_id_cmp);
+ if (ret == 0)
+ mbox->ext_header_rewrite = TRUE;
+ return ret;
+}
+
+static void virtual_sync_ext_header_rewrite(struct virtual_sync_context *ctx)
+{
+ struct virtual_mail_index_header ext_hdr;
+ struct virtual_mail_index_mailbox_record mailbox;
+ struct virtual_mail_index_mailbox_ext2_record ext2_rec;
+ struct virtual_backend_box **bboxes;
+ buffer_t *buf, *buf2;
+ const void *ext_data;
+ size_t ext_size;
+ unsigned int i, mailbox_pos, name_pos, count;
+
+ bboxes = array_get_modifiable(&ctx->mbox->backend_boxes, &count);
+ mailbox_pos = sizeof(ext_hdr);
+ name_pos = mailbox_pos + sizeof(mailbox) * count;
+
+ i_zero(&ext_hdr);
+ i_zero(&mailbox);
+ i_zero(&ext2_rec);
+
+ ext_hdr.change_counter = ++ctx->mbox->prev_change_counter;
+ ext_hdr.mailbox_count = count;
+ ext_hdr.highest_mailbox_id = ctx->mbox->highest_mailbox_id;
+ ext_hdr.search_args_crc32 = ctx->mbox->search_args_crc32;
+
+ buf = buffer_create_dynamic(default_pool, name_pos + 256);
+ buffer_append(buf, &ext_hdr, sizeof(ext_hdr));
+
+ struct virtual_mail_index_ext2_header ext2_hdr = {
+ .version = VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION,
+ .ext_record_size = sizeof(struct virtual_mail_index_mailbox_ext2_record),
+ .hdr_size = sizeof(struct virtual_mail_index_ext2_header),
+ .change_counter = ext_hdr.change_counter,
+ };
+ buf2 = buffer_create_dynamic(default_pool, sizeof(ext2_hdr) +
+ sizeof(ext2_rec) * count);
+ buffer_append(buf2, &ext2_hdr, sizeof(ext2_hdr));
+
+ for (i = 0; i < count; i++) {
+ i_assert(i == 0 ||
+ bboxes[i]->mailbox_id > bboxes[i-1]->mailbox_id);
+
+ bboxes[i]->sync_mailbox_idx1 = i+1;
+ mailbox.id = bboxes[i]->mailbox_id;
+ mailbox.name_len = strlen(bboxes[i]->name);
+ mailbox.uid_validity = bboxes[i]->sync_uid_validity;
+ mailbox.highest_modseq = bboxes[i]->ondisk_highest_modseq;
+ mailbox.next_uid = bboxes[i]->sync_next_uid;
+ buffer_write(buf, mailbox_pos, &mailbox, sizeof(mailbox));
+ buffer_write(buf, name_pos, bboxes[i]->name, mailbox.name_len);
+
+ guid_128_copy(ext2_rec.guid, bboxes[i]->sync_guid);
+ buffer_append(buf2, &ext2_rec, sizeof(ext2_rec));
+
+ mailbox_pos += sizeof(mailbox);
+ name_pos += mailbox.name_len;
+
+ /* repair the value */
+ if (ctx->mbox->highest_mailbox_id < mailbox.id)
+ ctx->mbox->highest_mailbox_id = mailbox.id;
+ }
+ if (ctx->mbox->highest_mailbox_id != ext_hdr.highest_mailbox_id) {
+ ext_hdr.highest_mailbox_id = ctx->mbox->highest_mailbox_id;
+ buffer_write(buf, 0, &ext_hdr, sizeof(ext_hdr));
+ }
+ i_assert(buf->used == name_pos);
+
+ /* update base extension */
+ mail_index_get_header_ext(ctx->sync_view, ctx->mbox->virtual_ext_id,
+ &ext_data, &ext_size);
+ if (ext_size < name_pos) {
+ mail_index_ext_resize(ctx->trans, ctx->mbox->virtual_ext_id,
+ name_pos,
+ sizeof(struct virtual_mail_index_record),
+ sizeof(uint32_t));
+ }
+ mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id,
+ 0, buf->data, name_pos);
+
+ /* update ext2 */
+ mail_index_get_header_ext(ctx->sync_view, ctx->mbox->virtual_ext2_id,
+ &ext_data, &ext_size);
+ if (ext_size != buf2->used) {
+ mail_index_ext_resize(ctx->trans, ctx->mbox->virtual_ext2_id,
+ buf2->used, 0, 0);
+ }
+ mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id,
+ 0, buf2->data, buf2->used);
+ buffer_free(&buf);
+ buffer_free(&buf2);
+}
+
+static void virtual_sync_ext_header_update(struct virtual_sync_context *ctx)
+{
+ struct virtual_mail_index_header ext_hdr;
+
+ if (!ctx->ext_header_changed)
+ return;
+
+ /* we changed something - update the change counter in header */
+ ext_hdr.change_counter = ++ctx->mbox->prev_change_counter;
+ mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id,
+ offsetof(struct virtual_mail_index_header, change_counter),
+ &ext_hdr.change_counter, sizeof(ext_hdr.change_counter));
+ mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id,
+ offsetof(struct virtual_mail_index_ext2_header, change_counter),
+ &ext_hdr.change_counter, sizeof(ext_hdr.change_counter));
+}
+
+static int virtual_sync_index_rec(struct virtual_sync_context *ctx,
+ const struct mail_index_sync_rec *sync_rec)
+{
+ uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+ struct virtual_backend_box *bbox;
+ const struct virtual_mail_index_record *vrec;
+ const void *data;
+ enum mail_flags flags;
+ struct mail_keywords *keywords;
+ enum modify_type modify_type;
+ const char *kw_names[2];
+ uint32_t vseq, seq1, seq2;
+
+ switch (sync_rec->type) {
+ case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
+ case MAIL_INDEX_SYNC_TYPE_FLAGS:
+ case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
+ case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
+ break;
+ }
+ if (!mail_index_lookup_seq_range(ctx->sync_view,
+ sync_rec->uid1, sync_rec->uid2,
+ &seq1, &seq2)) {
+ /* already expunged, nothing to do. */
+ return 0;
+ }
+
+ for (vseq = seq1; vseq <= seq2; vseq++) {
+ mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id,
+ &data, NULL);
+ vrec = data;
+
+ bbox = virtual_backend_box_lookup(ctx->mbox, vrec->mailbox_id);
+ if (bbox == NULL)
+ continue;
+ if (!bbox->box->opened) {
+ if (virtual_backend_box_open(ctx->mbox, bbox) < 0) {
+ virtual_box_copy_error(&ctx->mbox->box,
+ bbox->box);
+ return -1;
+ }
+ } else {
+ virtual_backend_box_accessed(ctx->mbox, bbox);
+ }
+
+ virtual_backend_box_sync_mail_set(bbox);
+ if (!mail_set_uid(bbox->sync_mail, vrec->real_uid)) {
+ /* message is already expunged from backend mailbox. */
+ continue;
+ }
+
+ switch (sync_rec->type) {
+ case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
+ mail_expunge(bbox->sync_mail);
+ break;
+ case MAIL_INDEX_SYNC_TYPE_FLAGS:
+ flags = sync_rec->add_flags & MAIL_FLAGS_NONRECENT;
+ if (flags != 0) {
+ mail_update_flags(bbox->sync_mail,
+ MODIFY_ADD, flags);
+ }
+ flags = sync_rec->remove_flags & MAIL_FLAGS_NONRECENT;
+ if (flags != 0) {
+ mail_update_flags(bbox->sync_mail,
+ MODIFY_REMOVE, flags);
+ }
+ break;
+ case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
+ case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
+ kw_names[0] = ctx->kw_all[sync_rec->keyword_idx];
+ kw_names[1] = NULL;
+ keywords = mailbox_keywords_create_valid(bbox->box,
+ kw_names);
+
+ modify_type = sync_rec->type ==
+ MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD ?
+ MODIFY_ADD : MODIFY_REMOVE;
+ mail_update_keywords(bbox->sync_mail,
+ modify_type, keywords);
+ mailbox_keywords_unref(&keywords);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int virtual_sync_index_changes(struct virtual_sync_context *ctx)
+{
+ const ARRAY_TYPE(keywords) *keywords;
+ struct mail_index_sync_rec sync_rec;
+
+ keywords = mail_index_get_keywords(ctx->index);
+ ctx->kw_all = array_count(keywords) == 0 ? NULL :
+ array_front(keywords);
+ while (mail_index_sync_next(ctx->index_sync_ctx, &sync_rec)) {
+ if (virtual_sync_index_rec(ctx, &sync_rec) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void virtual_sync_index_finish(struct virtual_sync_context *ctx)
+{
+ struct mailbox *box = &ctx->mbox->box;
+ const struct mail_index_header *hdr;
+ struct mail_index_view *view;
+ uint32_t seq1, seq2;
+
+ view = mail_index_transaction_open_updated_view(ctx->trans);
+
+ hdr = mail_index_get_header(ctx->sync_view);
+ if (hdr->uid_validity != 0)
+ ctx->uid_validity = hdr->uid_validity;
+ else
+ virtual_sync_set_uidvalidity(ctx);
+
+ /* mark the newly seen messages as recent */
+ if (mail_index_lookup_seq_range(view, hdr->first_recent_uid,
+ (uint32_t)-1, &seq1, &seq2)) {
+ mailbox_recent_flags_set_seqs(&ctx->mbox->box, view,
+ seq1, seq2);
+ }
+
+ mail_index_view_close(&view);
+
+ if (ctx->mbox->ext_header_rewrite) {
+ /* entire mailbox list needs to be rewritten */
+ virtual_sync_ext_header_rewrite(ctx);
+ } else {
+ /* update only changed parts in the header */
+ virtual_sync_ext_header_update(ctx);
+ }
+
+ mailbox_sync_notify(box, 0, 0);
+}
+
+static int virtual_sync_backend_box_init(struct virtual_backend_box *bbox)
+{
+ struct mailbox_transaction_context *trans;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ struct virtual_backend_uidmap uidmap;
+ enum mailbox_search_result_flags result_flags;
+ int ret;
+
+ trans = mailbox_transaction_begin(bbox->box, 0, __func__);
+
+ if (!bbox->search_args_initialized) {
+ mail_search_args_init(bbox->search_args, bbox->box, FALSE, NULL);
+ bbox->search_args_initialized = TRUE;
+ }
+ search_ctx = mailbox_search_init(trans, bbox->search_args, NULL,
+ 0, NULL);
+
+ /* save the result and keep it updated */
+ result_flags = MAILBOX_SEARCH_RESULT_FLAG_UPDATE |
+ MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC;
+ bbox->search_result =
+ mailbox_search_result_save(search_ctx, result_flags);
+
+ /* add the found UIDs to uidmap. virtual_uid gets assigned later. */
+ i_zero(&uidmap);
+ array_clear(&bbox->uids);
+ while (mailbox_search_next(search_ctx, &mail)) {
+ uidmap.real_uid = mail->uid;
+ array_push_back(&bbox->uids, &uidmap);
+ }
+ ret = mailbox_search_deinit(&search_ctx);
+
+ (void)mailbox_transaction_commit(&trans);
+ return ret;
+}
+
+static int
+virtual_backend_uidmap_bsearch_cmp(const uint32_t *uidp,
+ const struct virtual_backend_uidmap *uidmap)
+{
+ return *uidp < uidmap->real_uid ? -1 :
+ (*uidp > uidmap->real_uid ? 1 : 0);
+}
+
+static void
+virtual_sync_mailbox_box_remove(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ const ARRAY_TYPE(seq_range) *removed_uids)
+{
+ const struct seq_range *uids;
+ struct virtual_backend_uidmap *uidmap;
+ unsigned int i, src, dest, uid_count, rec_count;
+ uint32_t uid, vseq;
+
+ uids = array_get(removed_uids, &uid_count);
+ if (uid_count == 0)
+ return;
+
+ /* everything in removed_uids should exist in bbox->uids */
+ uidmap = array_get_modifiable(&bbox->uids, &rec_count);
+ i_assert(rec_count >= uid_count);
+
+ /* find the first uidmap record to be removed */
+ if (!array_bsearch_insert_pos(&bbox->uids, &uids[0].seq1,
+ virtual_backend_uidmap_bsearch_cmp, &src))
+ i_unreached();
+
+ /* remove the unwanted messages */
+ dest = src;
+ for (i = 0; i < uid_count; i++) {
+ uid = uids[i].seq1;
+ while (uidmap[src].real_uid != uid) {
+ uidmap[dest++] = uidmap[src++];
+ i_assert(src < rec_count);
+ }
+
+ for (; uid <= uids[i].seq2; uid++, src++) {
+ i_assert(src < rec_count);
+ i_assert(uidmap[src].real_uid == uid);
+ if (uidmap[src].virtual_uid == 0) {
+ /* has not been assigned yet */
+ continue;
+ }
+ if (mail_index_lookup_seq(ctx->sync_view,
+ uidmap[src].virtual_uid,
+ &vseq))
+ mail_index_expunge(ctx->trans, vseq);
+ }
+ }
+ array_delete(&bbox->uids, dest, src - dest);
+}
+
+static void
+virtual_sync_mailbox_box_add(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ const ARRAY_TYPE(seq_range) *added_uids_arr)
+{
+ const struct seq_range *added_uids;
+ struct virtual_backend_uidmap *uidmap;
+ struct virtual_add_record rec;
+ unsigned int i, src, dest, uid_count, add_count, rec_count;
+ uint32_t add_uid;
+
+ added_uids = array_get(added_uids_arr, &uid_count);
+ if (uid_count == 0)
+ return;
+ add_count = seq_range_count(added_uids_arr);
+
+ /* none of added_uids should exist in bbox->uids. find the position
+ of the first inserted index. */
+ uidmap = array_get_modifiable(&bbox->uids, &rec_count);
+ if (rec_count == 0 ||
+ added_uids[0].seq1 > uidmap[rec_count-1].real_uid) {
+ /* fast path: usually messages are appended */
+ dest = rec_count;
+ } else if (array_bsearch_insert_pos(&bbox->uids, &added_uids[0].seq1,
+ virtual_backend_uidmap_bsearch_cmp,
+ &dest))
+ i_unreached();
+
+ /* make space for all added UIDs. */
+ if (rec_count == dest)
+ array_idx_clear(&bbox->uids, dest + add_count-1);
+ else {
+ array_copy(&bbox->uids.arr, dest + add_count,
+ &bbox->uids.arr, dest, rec_count - dest);
+ }
+ uidmap = array_get_modifiable(&bbox->uids, &rec_count);
+ src = dest + add_count;
+
+ /* add/move the UIDs to their correct positions */
+ i_zero(&rec);
+ rec.rec.mailbox_id = bbox->mailbox_id;
+ for (i = 0; i < uid_count; i++) {
+ add_uid = added_uids[i].seq1;
+ while (src < rec_count && uidmap[src].real_uid < add_uid)
+ uidmap[dest++] = uidmap[src++];
+
+ for (; add_uid <= added_uids[i].seq2; add_uid++, dest++) {
+ i_assert(dest < rec_count);
+
+ uidmap[dest].real_uid = add_uid;
+ uidmap[dest].virtual_uid = 0;
+
+ if (ctx->mbox->uids_mapped) {
+ rec.rec.real_uid = add_uid;
+ array_push_back(&ctx->all_adds, &rec);
+ }
+ }
+ }
+}
+
+static void
+virtual_sync_mailbox_box_update_flags(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ const ARRAY_TYPE(seq_range) *uids_arr)
+{
+ unsigned int i, uid, vseq;
+ struct virtual_backend_uidmap *vuid;
+ struct seq_range_iter iter;
+
+ i = 0;
+ seq_range_array_iter_init(&iter, uids_arr);
+ while(seq_range_array_iter_nth(&iter, i++, &uid)) {
+ vuid = array_bsearch(&bbox->uids, &uid,
+ virtual_backend_uidmap_bsearch_cmp);
+ if (vuid == NULL ||
+ vuid->virtual_uid == 0 ||
+ !mail_index_lookup_seq(ctx->sync_view,
+ vuid->virtual_uid, &vseq)) {
+ /* the entry has been already removed either by
+ us or some other session. doesn't matter,
+ we don't need to update the flags.
+
+ it might also have not yet been assigned a uid
+ so we don't want to update the flags then either.
+ */
+ continue;
+ }
+ virtual_sync_external_flags(ctx, bbox, vseq,
+ vuid->real_uid);
+ }
+}
+
+static int virtual_backend_uidmap_cmp(const struct virtual_backend_uidmap *u1,
+ const struct virtual_backend_uidmap *u2)
+{
+ if (u1->real_uid < u2->real_uid)
+ return -1;
+ if (u1->real_uid > u2->real_uid)
+ return 1;
+ return 0;
+}
+
+static void virtual_sync_bbox_uids_sort(struct virtual_backend_box *bbox)
+{
+ /* the uidmap must be sorted by real_uids */
+ array_sort(&bbox->uids, virtual_backend_uidmap_cmp);
+ bbox->uids_nonsorted = FALSE;
+}
+
+static void virtual_sync_backend_boxes_sort_uids(struct virtual_mailbox *mbox)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (bboxes[i]->uids_nonsorted)
+ virtual_sync_bbox_uids_sort(bboxes[i]);
+ }
+}
+
+static void
+virtual_sync_backend_add_vmsgs_results(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ uint32_t real_uid,
+ struct mail_search_result *result,
+ const uint32_t vseq)
+{
+ struct virtual_backend_uidmap uidmap;
+ uint32_t vuid, seq;
+
+ mail_index_lookup_uid(ctx->sync_view, vseq, &vuid);
+
+ i_zero(&uidmap);
+ uidmap.real_uid = real_uid;
+ uidmap.virtual_uid = vuid;
+ array_push_back(&bbox->uids, &uidmap);
+
+ if (result == NULL)
+ ;
+ else if (mail_index_lookup_seq(bbox->box->view, real_uid, &seq))
+ seq_range_array_add(&result->uids, real_uid);
+ else
+ seq_range_array_add(&result->removed_uids, real_uid);
+}
+
+static void
+virtual_sync_backend_handle_old_vmsgs(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ struct mail_search_result *result)
+{
+ const struct virtual_mail_index_record *vrec;
+ const struct virtual_sync_mail *sync_mail, *sync_mails;
+ const void *data;
+ uint32_t i, vseq, messages;
+
+ /* find the messages that currently exist in virtual index and add them
+ to the backend mailbox's list of uids. */
+ array_clear(&bbox->uids);
+
+ if (array_is_created(&ctx->all_mails)) {
+ i_assert(ctx->all_mails_prev_mailbox_id < bbox->mailbox_id);
+ sync_mails = array_get(&ctx->all_mails, &messages);
+ for (i = ctx->all_mails_idx; i < messages; i++) {
+ sync_mail = &sync_mails[i];
+ if (sync_mail->vrec.mailbox_id != bbox->mailbox_id) {
+ if (sync_mail->vrec.mailbox_id < bbox->mailbox_id) {
+ /* stale mailbox_id, ignore */
+ continue;
+ }
+ /* Should be in mailbox_id order,
+ so skip to next box */
+ break;
+ }
+
+ virtual_sync_backend_add_vmsgs_results(ctx, bbox,
+ sync_mail->vrec.real_uid, result, sync_mail->vseq);
+ }
+ ctx->all_mails_idx = i;
+ ctx->all_mails_prev_mailbox_id = bbox->mailbox_id;
+ } else {
+ /* there should be only a single backend mailbox, but in the
+ existing index there may be stale mailbox_ids that we'll
+ just skip over. */
+ messages = mail_index_view_get_messages_count(ctx->sync_view);
+ for (vseq = 1; vseq <= messages; vseq++) {
+ mail_index_lookup_ext(ctx->sync_view, vseq,
+ ctx->mbox->virtual_ext_id, &data, NULL);
+ vrec = data;
+ if (vrec->mailbox_id == bbox->mailbox_id) {
+ virtual_sync_backend_add_vmsgs_results(ctx,
+ bbox, vrec->real_uid, result, vseq);
+ }
+ }
+ }
+ virtual_sync_bbox_uids_sort(bbox);
+}
+
+static int virtual_sync_backend_box_continue(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox)
+{
+ const enum mailbox_search_result_flags result_flags =
+ MAILBOX_SEARCH_RESULT_FLAG_UPDATE |
+ MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC;
+ struct mail_index_view *view = bbox->box->view;
+ struct mail_search_result *result;
+ ARRAY_TYPE(seq_range) expunged_uids = ARRAY_INIT, removed_uids;
+ ARRAY_TYPE(seq_range) added_uids, flag_update_uids;
+ uint64_t modseq, old_highest_modseq;
+ uint32_t seq, uid, old_msg_count;
+
+ /* initialize the search result from all the existing messages in
+ virtual index. */
+ if (!bbox->search_args_initialized) {
+ mail_search_args_init(bbox->search_args, bbox->box, FALSE, NULL);
+ bbox->search_args_initialized = TRUE;
+ }
+ result = mailbox_search_result_alloc(bbox->box, bbox->search_args,
+ result_flags);
+ mailbox_search_result_initial_done(result);
+ i_assert(array_count(&result->removed_uids) == 0);
+ virtual_sync_backend_handle_old_vmsgs(ctx, bbox, result);
+ if (array_count(&result->removed_uids) > 0) {
+ /* these are all expunged messages. treat them separately from
+ "no longer matching messages" (=removed_uids) */
+ t_array_init(&expunged_uids, array_count(&result->removed_uids));
+ array_append_array(&expunged_uids, &result->removed_uids);
+ array_clear(&result->removed_uids);
+ }
+
+ /* get list of changed old messages (messages already once seen by
+ virtual index), based on modseq changes. (we'll assume all modseq
+ changes are due to flag changes, which may not be true in future) */
+ if (bbox->sync_next_uid <= 1 ||
+ !mail_index_lookup_seq_range(view, 1, bbox->sync_next_uid-1,
+ &seq, &old_msg_count))
+ old_msg_count = 0;
+ old_highest_modseq = mail_index_modseq_get_highest(view);
+
+ t_array_init(&flag_update_uids, I_MIN(128, old_msg_count));
+ if (bbox->sync_highest_modseq < old_highest_modseq) {
+ for (seq = 1; seq <= old_msg_count; seq++) {
+ modseq = mail_index_modseq_lookup(view, seq);
+ if (modseq > bbox->sync_highest_modseq) {
+ mail_index_lookup_uid(view, seq, &uid);
+ seq_range_array_add(&flag_update_uids, uid);
+ }
+ }
+ }
+
+ /* update the search result based on the flag changes and
+ new messages */
+ if (index_search_result_update_flags(result, &flag_update_uids) < 0 ||
+ index_search_result_update_appends(result, old_msg_count) < 0) {
+ mailbox_search_result_free(&result);
+ return -1;
+ }
+
+ t_array_init(&removed_uids, 128);
+ t_array_init(&added_uids, 128);
+ mailbox_search_result_sync(result, &removed_uids, &added_uids);
+ if (array_is_created(&expunged_uids)) {
+ seq_range_array_remove_seq_range(&removed_uids, &expunged_uids);
+ virtual_sync_mailbox_box_remove(ctx, bbox, &expunged_uids);
+ }
+ if (ctx->expunge_removed)
+ virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids);
+ else {
+ /* delayed remove */
+ seq_range_array_merge(&bbox->sync_pending_removes,
+ &removed_uids);
+ }
+ virtual_sync_mailbox_box_add(ctx, bbox, &added_uids);
+ virtual_sync_mailbox_box_update_flags(ctx, bbox, &flag_update_uids);
+
+ bbox->search_result = result;
+ return 0;
+}
+
+static void virtual_sync_drop_existing(struct virtual_backend_box *bbox,
+ ARRAY_TYPE(seq_range) *added_uids)
+{
+ ARRAY_TYPE(seq_range) drop_uids;
+ const struct virtual_backend_uidmap *uidmap;
+ struct seq_range_iter iter;
+ unsigned int i, n = 0, count;
+ uint32_t add_uid;
+
+ seq_range_array_iter_init(&iter, added_uids);
+ if (!seq_range_array_iter_nth(&iter, n++, &add_uid))
+ return;
+
+ (void)array_bsearch_insert_pos(&bbox->uids, &add_uid,
+ virtual_backend_uidmap_bsearch_cmp, &i);
+
+ uidmap = array_get_modifiable(&bbox->uids, &count);
+ if (i == count)
+ return;
+
+ t_array_init(&drop_uids, array_count(added_uids));
+ for (; i < count; ) {
+ if (uidmap[i].real_uid < add_uid) {
+ i++;
+ continue;
+ }
+ if (uidmap[i].real_uid == add_uid) {
+ seq_range_array_add(&drop_uids, add_uid);
+ i++;
+ }
+ if (!seq_range_array_iter_nth(&iter, n++, &add_uid))
+ break;
+ }
+ seq_range_array_remove_seq_range(added_uids, &drop_uids);
+}
+
+static void virtual_sync_drop_nonexistent(struct virtual_backend_box *bbox,
+ ARRAY_TYPE(seq_range) *removed_uids)
+{
+ ARRAY_TYPE(seq_range) drop_uids;
+ const struct virtual_backend_uidmap *uidmap;
+ struct seq_range_iter iter;
+ unsigned int i, n = 0, count;
+ uint32_t remove_uid;
+ bool iter_done = FALSE;
+
+ seq_range_array_iter_init(&iter, removed_uids);
+ if (!seq_range_array_iter_nth(&iter, n++, &remove_uid))
+ return;
+
+ (void)array_bsearch_insert_pos(&bbox->uids, &remove_uid,
+ virtual_backend_uidmap_bsearch_cmp, &i);
+
+ t_array_init(&drop_uids, array_count(removed_uids)); iter_done = FALSE;
+ uidmap = array_get_modifiable(&bbox->uids, &count);
+ for (; i < count; ) {
+ if (uidmap[i].real_uid < remove_uid) {
+ i++;
+ continue;
+ }
+ if (uidmap[i].real_uid != remove_uid)
+ seq_range_array_add(&drop_uids, remove_uid);
+ else
+ i++;
+ if (!seq_range_array_iter_nth(&iter, n++, &remove_uid)) {
+ iter_done = TRUE;
+ break;
+ }
+ }
+ if (!iter_done) {
+ do {
+ seq_range_array_add(&drop_uids, remove_uid);
+ } while (seq_range_array_iter_nth(&iter, n++, &remove_uid));
+ }
+ seq_range_array_remove_seq_range(removed_uids, &drop_uids);
+}
+
+static void virtual_sync_mailbox_box_update(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox)
+{
+ ARRAY_TYPE(seq_range) removed_uids, added_uids, temp_uids;
+ unsigned int count1, count2;
+
+ t_array_init(&removed_uids, 128);
+ t_array_init(&added_uids, 128);
+
+ mailbox_search_result_sync(bbox->search_result,
+ &removed_uids, &added_uids);
+ if (array_is_created(&bbox->sync_outside_expunges)) {
+ seq_range_array_remove_seq_range(&bbox->sync_outside_expunges,
+ &added_uids);
+ seq_range_array_merge(&removed_uids,
+ &bbox->sync_outside_expunges);
+ array_clear(&bbox->sync_outside_expunges);
+ }
+
+ virtual_sync_drop_existing(bbox, &added_uids);
+ virtual_sync_drop_nonexistent(bbox, &removed_uids);
+
+ /* if any of the pending removes came back, we don't want to expunge
+ them anymore. also since they already exist, remove them from
+ added_uids. */
+ count1 = array_count(&bbox->sync_pending_removes);
+ count2 = array_count(&added_uids);
+ if (count1 > 0 && count2 > 0) {
+ t_array_init(&temp_uids, count1);
+ array_append_array(&temp_uids, &bbox->sync_pending_removes);
+ if (seq_range_array_remove_seq_range(
+ &bbox->sync_pending_removes, &added_uids) > 0) {
+ seq_range_array_remove_seq_range(&added_uids,
+ &temp_uids);
+ }
+ }
+
+ if (!ctx->expunge_removed) {
+ /* delay removing messages that don't match the search
+ criteria, but don't delay removing expunged messages */
+ if (array_count(&ctx->sync_expunges) > 0) {
+ seq_range_array_remove_seq_range(&bbox->sync_pending_removes,
+ &ctx->sync_expunges);
+ seq_range_array_remove_seq_range(&removed_uids,
+ &ctx->sync_expunges);
+ virtual_sync_mailbox_box_remove(ctx, bbox,
+ &ctx->sync_expunges);
+ }
+ seq_range_array_merge(&bbox->sync_pending_removes,
+ &removed_uids);
+ } else if (array_count(&bbox->sync_pending_removes) > 0) {
+ /* remove all current and old */
+ seq_range_array_merge(&bbox->sync_pending_removes,
+ &removed_uids);
+ virtual_sync_mailbox_box_remove(ctx, bbox,
+ &bbox->sync_pending_removes);
+ array_clear(&bbox->sync_pending_removes);
+ } else {
+ virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids);
+ }
+ virtual_sync_mailbox_box_add(ctx, bbox, &added_uids);
+}
+
+static bool virtual_sync_find_seqs(struct virtual_backend_box *bbox,
+ const struct mailbox_sync_rec *sync_rec,
+ unsigned int *idx1_r,
+ unsigned int *idx2_r)
+{
+ const struct virtual_backend_uidmap *uidmap;
+ unsigned int idx, count;
+ uint32_t uid1, uid2;
+
+ mail_index_lookup_uid(bbox->box->view, sync_rec->seq1, &uid1);
+ mail_index_lookup_uid(bbox->box->view, sync_rec->seq2, &uid2);
+ (void)array_bsearch_insert_pos(&bbox->uids, &uid1,
+ virtual_backend_uidmap_bsearch_cmp,
+ &idx);
+
+ uidmap = array_get_modifiable(&bbox->uids, &count);
+ if (idx == count || uidmap[idx].real_uid > uid2)
+ return FALSE;
+
+ *idx1_r = idx;
+ while (idx < count && uidmap[idx].real_uid <= uid2) idx++;
+ *idx2_r = idx - 1;
+ return TRUE;
+}
+
+static void virtual_sync_expunge_add(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ const struct mailbox_sync_rec *sync_rec)
+{
+ struct virtual_backend_uidmap *uidmap;
+ uint32_t uid1, uid2;
+ unsigned int i, idx1, count;
+
+ mail_index_lookup_uid(bbox->box->view, sync_rec->seq1, &uid1);
+ mail_index_lookup_uid(bbox->box->view, sync_rec->seq2, &uid2);
+
+ /* remember only the expunges for messages that
+ already exist for this mailbox */
+ (void)array_bsearch_insert_pos(&bbox->uids, &uid1,
+ virtual_backend_uidmap_bsearch_cmp,
+ &idx1);
+ uidmap = array_get_modifiable(&bbox->uids, &count);
+ for (i = idx1; i < count; i++) {
+ if (uidmap[i].real_uid > uid2)
+ break;
+ seq_range_array_add(&ctx->sync_expunges, uidmap[i].real_uid);
+ }
+}
+
+static int virtual_sync_backend_box_sync(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ enum mailbox_sync_flags sync_flags)
+{
+ struct mailbox_sync_context *sync_ctx;
+ const struct virtual_backend_uidmap *uidmap;
+ struct mailbox_sync_rec sync_rec;
+ struct mailbox_sync_status sync_status;
+ unsigned int idx1, idx2;
+ uint32_t vseq, vuid;
+
+ sync_ctx = mailbox_sync_init(bbox->box, sync_flags);
+ virtual_backend_box_sync_mail_set(bbox);
+ while (mailbox_sync_next(sync_ctx, &sync_rec)) {
+ switch (sync_rec.type) {
+ case MAILBOX_SYNC_TYPE_EXPUNGE:
+ if (ctx->expunge_removed) {
+ /* no need to keep track of expunges */
+ break;
+ }
+ virtual_sync_expunge_add(ctx, bbox, &sync_rec);
+ break;
+ case MAILBOX_SYNC_TYPE_FLAGS:
+ if (!virtual_sync_find_seqs(bbox, &sync_rec,
+ &idx1, &idx2))
+ break;
+ uidmap = array_front(&bbox->uids);
+ for (; idx1 <= idx2; idx1++) {
+ vuid = uidmap[idx1].virtual_uid;
+ if (vuid == 0) {
+ /* has not been even assigned yet */
+ continue;
+ }
+ if (!mail_index_lookup_seq(ctx->sync_view,
+ vuid, &vseq)) {
+ /* expunged by another session,
+ but we haven't yet updated
+ bbox->uids. */
+ continue;
+ }
+ virtual_sync_external_flags(ctx, bbox, vseq,
+ uidmap[idx1].real_uid);
+ }
+ break;
+ case MAILBOX_SYNC_TYPE_MODSEQ:
+ break;
+ }
+ }
+ if (mailbox_sync_deinit(&sync_ctx, &sync_status) < 0) {
+ if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND)
+ return -1;
+ /* mailbox was deleted */
+ virtual_sync_backend_box_deleted(ctx, bbox);
+ return 0;
+ }
+ return 0;
+}
+
+static bool
+virtual_bbox_mailbox_equals(struct virtual_backend_box *bbox,
+ const struct mailbox_status *status,
+ struct mailbox_metadata *metadata,
+ const char **reason_r)
+{
+ if (!guid_128_equals(bbox->sync_guid, metadata->guid)) {
+ *reason_r = t_strdup_printf("GUID changed: %s -> %s",
+ guid_128_to_string(bbox->sync_guid),
+ guid_128_to_string(metadata->guid));
+ return FALSE;
+ }
+ if (bbox->sync_uid_validity != status->uidvalidity) {
+ *reason_r = t_strdup_printf("UIDVALIDITY changed: %u -> %u",
+ bbox->sync_uid_validity, status->uidvalidity);
+ return FALSE;
+ }
+ if (bbox->sync_next_uid != status->uidnext) {
+ *reason_r = t_strdup_printf("UIDNEXT changed: %u -> %u",
+ bbox->sync_next_uid, status->uidnext);
+ return FALSE;
+ }
+ if (bbox->sync_highest_modseq != status->highest_modseq) {
+ *reason_r = t_strdup_printf("HIGHESTMODSEQ changed: "
+ "%"PRIu64" -> %"PRIu64,
+ bbox->sync_highest_modseq, status->highest_modseq);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void virtual_sync_backend_ext_header(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox)
+{
+ const unsigned int uidval_pos =
+ offsetof(struct virtual_mail_index_mailbox_record,
+ uid_validity);
+ struct mailbox_status status;
+ struct virtual_mail_index_mailbox_record mailbox;
+ struct virtual_mail_index_mailbox_ext2_record ext2;
+ unsigned int mailbox_offset, ext2_offset;
+ uint64_t wanted_ondisk_highest_modseq;
+ struct mailbox_metadata metadata;
+ const char *reason;
+
+ mailbox_get_open_status(bbox->box, STATUS_UIDVALIDITY |
+ STATUS_HIGHESTMODSEQ, &status);
+ wanted_ondisk_highest_modseq =
+ array_count(&bbox->sync_pending_removes) > 0 ? 0 :
+ status.highest_modseq;
+
+ if (mailbox_get_metadata(bbox->box, MAILBOX_METADATA_GUID,
+ &metadata) < 0) {
+ /* Either a temporary failure or the mailbox was already
+ deleted. Either way, it doesn't really matter at this point.
+ We'll just leave the error handling until the next sync. */
+ return;
+ }
+
+ if (virtual_bbox_mailbox_equals(bbox, &status, &metadata, &reason) &&
+ bbox->ondisk_highest_modseq == wanted_ondisk_highest_modseq)
+ return;
+
+ /* mailbox changed - update extension header */
+ bbox->sync_uid_validity = status.uidvalidity;
+ bbox->sync_highest_modseq = status.highest_modseq;
+ bbox->ondisk_highest_modseq = wanted_ondisk_highest_modseq;
+ bbox->sync_next_uid = status.uidnext;
+ guid_128_copy(bbox->sync_guid, metadata.guid);
+
+ if (ctx->mbox->ext_header_rewrite) {
+ /* we'll rewrite the entire header later */
+ return;
+ }
+
+ i_zero(&mailbox);
+ mailbox.uid_validity = bbox->sync_uid_validity;
+ mailbox.highest_modseq = bbox->ondisk_highest_modseq;
+ mailbox.next_uid = bbox->sync_next_uid;
+
+ i_zero(&ext2);
+ guid_128_copy(ext2.guid, bbox->sync_guid);
+
+ i_assert(bbox->sync_mailbox_idx1 > 0);
+ mailbox_offset = sizeof(struct virtual_mail_index_header) +
+ (bbox->sync_mailbox_idx1-1) * sizeof(mailbox);
+ ext2_offset = sizeof(struct virtual_mail_index_ext2_header) +
+ (bbox->sync_mailbox_idx1-1) * sizeof(ext2);
+ mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id,
+ mailbox_offset + uidval_pos,
+ CONST_PTR_OFFSET(&mailbox, uidval_pos),
+ sizeof(mailbox) - uidval_pos);
+ mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id,
+ ext2_offset, &ext2, sizeof(ext2));
+ ctx->ext_header_changed = TRUE;
+}
+
+static void virtual_sync_backend_box_deleted(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox)
+{
+ ARRAY_TYPE(seq_range) removed_uids;
+ const struct virtual_backend_uidmap *uidmap;
+
+ /* delay its full removal until the next time we open the virtual
+ mailbox. for now just treat it as if it was empty. */
+
+ t_array_init(&removed_uids, 128);
+ array_foreach(&bbox->uids, uidmap)
+ seq_range_array_add(&removed_uids, uidmap->real_uid);
+ virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids);
+
+ bbox->deleted = TRUE;
+}
+
+static int
+virtual_try_open_and_sync_backend_box(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox,
+ enum mailbox_sync_flags sync_flags)
+{
+ int ret = 0;
+
+ if (!bbox->box->opened)
+ ret = virtual_backend_box_open(ctx->mbox, bbox);
+ if (ret == 0)
+ ret = mailbox_sync(bbox->box, sync_flags);
+ if (ret < 0) {
+ if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND)
+ return -1;
+ /* mailbox was deleted */
+ virtual_sync_backend_box_deleted(ctx, bbox);
+ return 0;
+ }
+ return 1;
+}
+
+static int virtual_sync_backend_box(struct virtual_sync_context *ctx,
+ struct virtual_backend_box *bbox)
+{
+ enum mailbox_sync_flags sync_flags;
+ struct mailbox_status status;
+ const char *reason;
+ int ret;
+
+ if (bbox->deleted)
+ return 0;
+
+ /* if we already did some changes to index, commit them before
+ syncing starts. */
+ virtual_backend_box_sync_mail_unset(bbox);
+
+ sync_flags = ctx->flags & (MAILBOX_SYNC_FLAG_FULL_READ |
+ MAILBOX_SYNC_FLAG_FULL_WRITE |
+ MAILBOX_SYNC_FLAG_FAST);
+
+ if (bbox->search_result == NULL) {
+ struct mailbox_metadata metadata;
+
+ /* a) first sync in this process.
+ b) we had auto-closed this backend mailbox.
+
+ first try to quickly check if the mailbox has changed.
+ if we can do that check from mailbox list index, we don't
+ even need to open the mailbox. */
+ i_assert(array_count(&bbox->sync_pending_removes) == 0);
+ if (bbox->box->opened || bbox->open_failed) {
+ /* a) index already opened, refresh it
+ b) delayed error handling for mailbox_open()
+ that failed in virtual_notify_changes() */
+ if ((ret = virtual_try_open_and_sync_backend_box(ctx, bbox, sync_flags)) <= 0)
+ return ret;
+ bbox->open_failed = FALSE;
+ }
+
+ if ((mailbox_get_status(bbox->box, STATUS_UIDVALIDITY |
+ STATUS_UIDNEXT | STATUS_HIGHESTMODSEQ,
+ &status) < 0) ||
+ (mailbox_get_metadata(bbox->box, MAILBOX_METADATA_GUID,
+ &metadata) < 0)) {
+ if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND)
+ return -1;
+ /* mailbox was deleted */
+ virtual_sync_backend_box_deleted(ctx, bbox);
+ return 0;
+ }
+ if (guid_128_is_empty(bbox->sync_guid)) {
+ /* upgrading from old virtual index */
+ guid_128_copy(bbox->sync_guid, metadata.guid);
+ ctx->mbox->ext_header_rewrite = TRUE;
+ }
+ if (virtual_bbox_mailbox_equals(bbox, &status, &metadata, &reason)) {
+ /* mailbox hasn't changed since we last opened it,
+ skip it for now.
+
+ we'll still need to create the bbox->uids mapping
+ using the current index. */
+ if (array_count(&bbox->uids) == 0)
+ virtual_sync_backend_handle_old_vmsgs(ctx, bbox, NULL);
+ return 0;
+ }
+ e_debug(ctx->mbox->box.event, "Backend mailbox %s changed: %s",
+ bbox->box->vname, reason);
+ if (!bbox->box->opened) {
+ /* first time we're opening the index */
+ if ((ret = virtual_try_open_and_sync_backend_box(ctx, bbox, sync_flags)) <= 0)
+ return ret;
+ }
+
+ virtual_backend_box_sync_mail_set(bbox);
+ if ((status.uidvalidity != bbox->sync_uid_validity) ||
+ !guid_128_equals(metadata.guid, bbox->sync_guid)) {
+ /* UID validity or GUID changed since last sync (or
+ this is the first sync), do a full search */
+ bbox->first_sync = TRUE;
+ ret = virtual_sync_backend_box_init(bbox);
+ } else {
+ /* build the initial search using the saved modseq. */
+ ret = virtual_sync_backend_box_continue(ctx, bbox);
+ }
+ i_assert(bbox->search_result != NULL || ret < 0);
+ } else {
+ /* sync using the existing search result */
+ i_assert(bbox->box->opened);
+ i_array_init(&ctx->sync_expunges, 32);
+ ret = virtual_sync_backend_box_sync(ctx, bbox, sync_flags);
+ if (ret == 0) T_BEGIN {
+ virtual_sync_mailbox_box_update(ctx, bbox);
+ } T_END;
+ array_free(&ctx->sync_expunges);
+ }
+
+ virtual_sync_backend_ext_header(ctx, bbox);
+ return ret;
+}
+
+static void virtual_sync_backend_map_uids(struct virtual_sync_context *ctx)
+{
+ uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+ struct virtual_sync_mail *vmails;
+ struct virtual_backend_box *bbox;
+ struct virtual_backend_uidmap *uidmap = NULL;
+ struct virtual_add_record add_rec;
+ const struct virtual_mail_index_record *vrec;
+ const void *data;
+ uint32_t i, vseq, vuid, messages;
+ unsigned int j = 0, uidmap_count = 0;
+
+ messages = mail_index_view_get_messages_count(ctx->sync_view);
+ if (messages == 0)
+ return;
+
+ /* sort the messages in current view by their backend mailbox and
+ real UID */
+ vmails = i_new(struct virtual_sync_mail, messages);
+ for (vseq = 1; vseq <= messages; vseq++) {
+ mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id,
+ &data, NULL);
+ vrec = data;
+ vmails[vseq-1].vseq = vseq;
+ vmails[vseq-1].vrec = *vrec;
+ }
+ qsort(vmails, messages, sizeof(*vmails), virtual_sync_mail_uid_cmp);
+
+ /* create real mailbox uid -> virtual uid mapping and expunge
+ messages no longer matching the search rule */
+ i_zero(&add_rec);
+ bbox = NULL;
+ for (i = 0; i < messages; i++) {
+ vseq = vmails[i].vseq;
+ vrec = &vmails[i].vrec;
+
+ if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+ /* add the rest of the newly seen messages */
+ for (; j < uidmap_count; j++) {
+ add_rec.rec.real_uid = uidmap[j].real_uid;
+ array_push_back(&ctx->all_adds, &add_rec);
+ }
+ bbox = virtual_backend_box_lookup(ctx->mbox,
+ vrec->mailbox_id);
+ if (bbox == NULL || bbox->first_sync) {
+ /* the entire mailbox is lost */
+ mail_index_expunge(ctx->trans, vseq);
+ continue;
+ }
+ uidmap = array_get_modifiable(&bbox->uids,
+ &uidmap_count);
+ j = 0;
+ add_rec.rec.mailbox_id = bbox->mailbox_id;
+ bbox->sync_seen = TRUE;
+ }
+ mail_index_lookup_uid(ctx->sync_view, vseq, &vuid);
+
+ /* if virtual record doesn't exist in uidmap, it's expunged */
+ for (; j < uidmap_count; j++) {
+ if (uidmap[j].real_uid >= vrec->real_uid)
+ break;
+
+ /* newly seen message */
+ add_rec.rec.real_uid = uidmap[j].real_uid;
+ array_push_back(&ctx->all_adds, &add_rec);
+ }
+ if (j == uidmap_count || uidmap[j].real_uid != vrec->real_uid)
+ mail_index_expunge(ctx->trans, vseq);
+ else {
+ /* exists - update uidmap and flags */
+ uidmap[j++].virtual_uid = vuid;
+ if (bbox->search_result == NULL) {
+ /* mailbox is completely unchanged since last
+ sync - no need to sync flags */
+ } else {
+ virtual_sync_external_flags(ctx, bbox, vseq,
+ vrec->real_uid);
+ }
+ }
+ }
+ i_free(vmails);
+
+ /* finish adding messages to the last mailbox */
+ for (; j < uidmap_count; j++) {
+ add_rec.rec.real_uid = uidmap[j].real_uid;
+ array_push_back(&ctx->all_adds, &add_rec);
+ }
+}
+
+static void virtual_sync_new_backend_boxes(struct virtual_sync_context *ctx)
+{
+ struct virtual_backend_box *const *bboxes;
+ struct virtual_add_record add_rec;
+ struct virtual_backend_uidmap *uidmap;
+ unsigned int i, j, count, uidmap_count;
+
+ /* if there are any mailboxes we didn't yet sync, add new messages in
+ them */
+ i_zero(&add_rec);
+ bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ bboxes[i]->first_sync = FALSE; /* this is the end of the sync */
+
+ if (bboxes[i]->sync_seen)
+ continue;
+
+ add_rec.rec.mailbox_id = bboxes[i]->mailbox_id;
+ uidmap = array_get_modifiable(&bboxes[i]->uids, &uidmap_count);
+ for (j = 0; j < uidmap_count; j++) {
+ add_rec.rec.real_uid = uidmap[j].real_uid;
+ array_push_back(&ctx->all_adds, &add_rec);
+ }
+ }
+}
+
+static int virtual_add_record_cmp(const struct virtual_add_record *add1,
+ const struct virtual_add_record *add2)
+{
+ if (add1->received_date < add2->received_date)
+ return -1;
+ if (add1->received_date > add2->received_date)
+ return 1;
+
+ /* if they're in same mailbox, we can order them correctly by the UID.
+ if they're in different mailboxes, ordering by UID doesn't really
+ help but it doesn't really harm either. */
+ if (add1->rec.real_uid < add2->rec.real_uid)
+ return -1;
+ if (add1->rec.real_uid > add2->rec.real_uid)
+ return 1;
+
+ /* two messages in different mailboxes have the same received date
+ and UID. */
+ return 0;
+}
+
+static int virtual_sync_backend_sort_new(struct virtual_sync_context *ctx)
+{
+ struct virtual_backend_box *bbox;
+ struct virtual_add_record *adds;
+ const struct virtual_mail_index_record *vrec;
+ unsigned int i, count;
+
+ /* get all messages' received dates */
+ adds = array_get_modifiable(&ctx->all_adds, &count);
+ for (bbox = NULL, i = 0; i < count; i++) {
+ vrec = &adds[i].rec;
+
+ if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+ bbox = virtual_backend_box_lookup(ctx->mbox,
+ vrec->mailbox_id);
+ if (!bbox->box->opened &&
+ virtual_backend_box_open(ctx->mbox, bbox) < 0)
+ return -1;
+ virtual_backend_box_sync_mail_set(bbox);
+ }
+ if (!mail_set_uid(bbox->sync_mail, vrec->real_uid)) {
+ /* we may have reopened the mailbox, which could have
+ caused the mail to be expunged already. */
+ adds[i].received_date = 0;
+ } else if (mail_get_received_date(bbox->sync_mail,
+ &adds[i].received_date) < 0) {
+ if (!bbox->sync_mail->expunged)
+ return -1;
+ /* expunged already, just add it somewhere */
+ adds[i].received_date = 0;
+ }
+ }
+
+ array_sort(&ctx->all_adds, virtual_add_record_cmp);
+ return 0;
+}
+
+static int virtual_sync_backend_add_new(struct virtual_sync_context *ctx)
+{
+ uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+ struct virtual_add_record *adds;
+ struct virtual_backend_box *bbox;
+ struct virtual_backend_uidmap *uidmap;
+ const struct mail_index_header *hdr;
+ const struct virtual_mail_index_record *vrec;
+ unsigned int i, count, idx;
+ ARRAY_TYPE(seq_range) saved_uids;
+ uint32_t vseq, first_uid;
+
+ hdr = mail_index_get_header(ctx->sync_view);
+ adds = array_get_modifiable(&ctx->all_adds, &count);
+ if (count == 0) {
+ ctx->mbox->sync_virtual_next_uid = hdr->next_uid;
+ return 0;
+ }
+
+ if (adds[0].rec.mailbox_id == adds[count-1].rec.mailbox_id) {
+ /* all messages are from a single mailbox. add them in
+ the same order. */
+ } else {
+ /* sort new messages by received date to get the add order */
+ if (virtual_sync_backend_sort_new(ctx) < 0)
+ return -1;
+ }
+
+ for (bbox = NULL, i = 0; i < count; i++) {
+ vrec = &adds[i].rec;
+ if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+ bbox = virtual_backend_box_lookup(ctx->mbox,
+ vrec->mailbox_id);
+ if (!bbox->box->opened &&
+ virtual_backend_box_open(ctx->mbox, bbox) < 0)
+ return -1;
+ virtual_backend_box_sync_mail_set(bbox);
+ }
+
+ mail_index_append(ctx->trans, 0, &vseq);
+ mail_index_update_ext(ctx->trans, vseq, virtual_ext_id,
+ vrec, NULL);
+ virtual_sync_external_flags(ctx, bbox, vseq, vrec->real_uid);
+ }
+
+ /* assign UIDs to new messages */
+ first_uid = hdr->next_uid;
+ t_array_init(&saved_uids, 1);
+ mail_index_append_finish_uids(ctx->trans, first_uid, &saved_uids);
+ i_assert(seq_range_count(&saved_uids) == count);
+
+ /* update virtual UIDs in uidmap */
+ for (bbox = NULL, i = 0; i < count; i++) {
+ vrec = &adds[i].rec;
+ if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+ bbox = virtual_backend_box_lookup(ctx->mbox,
+ vrec->mailbox_id);
+ }
+
+ if (!array_bsearch_insert_pos(&bbox->uids, &vrec->real_uid,
+ virtual_backend_uidmap_bsearch_cmp,
+ &idx))
+ i_unreached();
+ uidmap = array_idx_modifiable(&bbox->uids, idx);
+ i_assert(uidmap->virtual_uid == 0);
+ uidmap->virtual_uid = first_uid + i;
+ }
+ ctx->mbox->sync_virtual_next_uid = first_uid + i;
+ return 0;
+}
+
+static int
+virtual_sync_apply_existing_appends(struct virtual_sync_context *ctx)
+{
+ uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+ struct virtual_backend_box *bbox = NULL;
+ const struct mail_index_header *hdr;
+ const struct virtual_mail_index_record *vrec;
+ struct virtual_backend_uidmap uidmap;
+ const void *data;
+ uint32_t seq, seq2;
+
+ if (!ctx->mbox->uids_mapped)
+ return 0;
+
+ hdr = mail_index_get_header(ctx->sync_view);
+ if (ctx->mbox->sync_virtual_next_uid >= hdr->next_uid)
+ return 0;
+
+ /* another process added messages to virtual index. get backend boxes'
+ uid lists up-to-date by adding the new messages there. */
+ if (!mail_index_lookup_seq_range(ctx->sync_view,
+ ctx->mbox->sync_virtual_next_uid,
+ (uint32_t)-1, &seq, &seq2))
+ return 0;
+
+ i_zero(&uidmap);
+ for (; seq <= seq2; seq++) {
+ mail_index_lookup_ext(ctx->sync_view, seq, virtual_ext_id,
+ &data, NULL);
+ vrec = data;
+ uidmap.real_uid = vrec->real_uid;
+ mail_index_lookup_uid(ctx->sync_view, seq, &uidmap.virtual_uid);
+
+ if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+ bbox = virtual_backend_box_lookup(ctx->mbox,
+ vrec->mailbox_id);
+ if (bbox == NULL) {
+ mail_index_expunge(ctx->trans, seq);
+ continue;
+ }
+ }
+ array_push_back(&bbox->uids, &uidmap);
+ bbox->uids_nonsorted = TRUE;
+ }
+
+ virtual_sync_backend_boxes_sort_uids(ctx->mbox);
+ return 0;
+}
+
+static void
+virtual_sync_apply_existing_expunges(struct virtual_mailbox *mbox,
+ struct mailbox_sync_context *sync_ctx)
+{
+ struct index_mailbox_sync_context *isync_ctx =
+ (struct index_mailbox_sync_context *)sync_ctx;
+ struct virtual_backend_box *bbox = NULL;
+ struct seq_range_iter iter;
+ const struct virtual_mail_index_record *vrec;
+ const void *data;
+ unsigned int n = 0;
+ uint32_t seq;
+
+ if (isync_ctx->expunges == NULL)
+ return;
+
+ seq_range_array_iter_init(&iter, isync_ctx->expunges);
+ while (seq_range_array_iter_nth(&iter, n++, &seq)) {
+ mail_index_lookup_ext(mbox->box.view, seq,
+ mbox->virtual_ext_id, &data, NULL);
+ vrec = data;
+
+ if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+ bbox = virtual_backend_box_lookup(mbox,
+ vrec->mailbox_id);
+ if (!array_is_created(&bbox->sync_outside_expunges))
+ i_array_init(&bbox->sync_outside_expunges, 32);
+ }
+ seq_range_array_add(&bbox->sync_outside_expunges,
+ vrec->real_uid);
+ }
+}
+
+static int virtual_sync_mail_mailbox_cmp(const struct virtual_sync_mail *m1,
+ const struct virtual_sync_mail *m2)
+{
+ if (m1->vrec.mailbox_id < m2->vrec.mailbox_id)
+ return -1;
+ if (m1->vrec.mailbox_id > m2->vrec.mailbox_id)
+ return 1;
+ return 0;
+}
+
+static void virtual_sync_bboxes_get_mails(struct virtual_sync_context *ctx)
+{
+ uint32_t messages, vseq;
+ const void *mail_data;
+ const struct virtual_mail_index_record *vrec;
+ struct virtual_sync_mail *sync_mail;
+
+ messages = mail_index_view_get_messages_count(ctx->sync_view);
+ i_array_init(&ctx->all_mails, messages);
+ for (vseq = 1; vseq <= messages; vseq++) {
+ mail_index_lookup_ext(ctx->sync_view, vseq,
+ ctx->mbox->virtual_ext_id, &mail_data, NULL);
+ vrec = mail_data;
+ sync_mail = array_append_space(&ctx->all_mails);
+ sync_mail->vseq = vseq;
+ sync_mail->vrec = *vrec;
+ }
+ array_sort(&ctx->all_mails, virtual_sync_mail_mailbox_cmp);
+}
+
+static int virtual_sync_backend_boxes(struct virtual_sync_context *ctx)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+ int ret;
+
+ if (virtual_sync_apply_existing_appends(ctx) < 0)
+ return -1;
+
+ i_array_init(&ctx->all_adds, 128);
+ bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+
+ /* we have different optimizations depending on whether the virtual
+ mailbox consists of multiple backend boxes or just one */
+ if (count > 1)
+ virtual_sync_bboxes_get_mails(ctx);
+
+ for (i = 0; i < count; i++) {
+ if (virtual_sync_backend_box(ctx, bboxes[i]) < 0) {
+ /* backend failed, copy the error */
+ virtual_box_copy_error(&ctx->mbox->box,
+ bboxes[i]->box);
+ return -1;
+ }
+ }
+
+ if (!ctx->mbox->uids_mapped) {
+ /* initial sync: assign virtual UIDs to existing messages and
+ sync all flags */
+ ctx->mbox->uids_mapped = TRUE;
+ virtual_sync_backend_map_uids(ctx);
+ virtual_sync_new_backend_boxes(ctx);
+ }
+ ret = virtual_sync_backend_add_new(ctx);
+#ifdef DEBUG
+ for (i = 0; i < count; i++) {
+ const struct virtual_backend_uidmap *uidmap;
+
+ array_foreach(&bboxes[i]->uids, uidmap)
+ i_assert(uidmap->virtual_uid > 0);
+ }
+#endif
+ array_free(&ctx->all_adds);
+ if (array_is_created(&ctx->all_mails))
+ array_free(&ctx->all_mails);
+ return ret;
+}
+
+static void virtual_sync_backend_boxes_finish(struct virtual_sync_context *ctx)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++)
+ virtual_backend_box_sync_mail_unset(bboxes[i]);
+}
+
+static int virtual_sync_finish(struct virtual_sync_context *ctx, bool success)
+{
+ int ret = success ? 0 : -1;
+
+ virtual_sync_backend_boxes_finish(ctx);
+ if (success) {
+ if (mail_index_sync_commit(&ctx->index_sync_ctx) < 0) {
+ mailbox_set_index_error(&ctx->mbox->box);
+ ret = -1;
+ }
+ ctx->mbox->ext_header_rewrite = FALSE;
+ } else {
+ if (ctx->index_broken) {
+ /* make sure we don't complain about the same errors
+ over and over again. */
+ if (mail_index_unlink(ctx->index) < 0) {
+ i_error("virtual index %s: Failed to unlink() "
+ "broken indexes: %m",
+ mailbox_get_path(&ctx->mbox->box));
+ }
+ }
+ mail_index_sync_rollback(&ctx->index_sync_ctx);
+ }
+ i_free(ctx);
+ return ret;
+}
+
+static int virtual_sync(struct virtual_mailbox *mbox,
+ enum mailbox_sync_flags flags)
+{
+ struct virtual_sync_context *ctx;
+ enum mail_index_sync_flags index_sync_flags;
+ bool broken;
+ int ret;
+
+ ctx = i_new(struct virtual_sync_context, 1);
+ ctx->mbox = mbox;
+ ctx->flags = flags;
+ ctx->index = mbox->box.index;
+ /* Removed messages are expunged when
+ a) EXPUNGE is used
+ b) Mailbox is being opened (FIX_INCONSISTENT is set) */
+ ctx->expunge_removed =
+ (ctx->flags & (MAILBOX_SYNC_FLAG_EXPUNGE |
+ MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) != 0;
+
+ index_sync_flags = MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY |
+ MAIL_INDEX_SYNC_FLAG_AVOID_FLAG_UPDATES;
+ if ((mbox->box.flags & MAILBOX_FLAG_DROP_RECENT) != 0)
+ index_sync_flags |= MAIL_INDEX_SYNC_FLAG_DROP_RECENT;
+
+ ret = mail_index_sync_begin(ctx->index, &ctx->index_sync_ctx,
+ &ctx->sync_view, &ctx->trans,
+ index_sync_flags);
+ if (ret <= 0) {
+ if (ret < 0)
+ mailbox_set_index_error(&mbox->box);
+ i_free(ctx);
+ return ret;
+ }
+
+ ret = virtual_mailbox_ext_header_read(mbox, ctx->sync_view, &broken);
+ if (ret < 0)
+ return virtual_sync_finish(ctx, FALSE);
+ if (broken)
+ ctx->index_broken = TRUE;
+ /* apply changes from virtual index to backend mailboxes */
+ if (virtual_sync_index_changes(ctx) < 0)
+ return virtual_sync_finish(ctx, FALSE);
+ /* update list of UIDs in backend mailboxes */
+ if (virtual_sync_backend_boxes(ctx) < 0)
+ return virtual_sync_finish(ctx, FALSE);
+
+ virtual_sync_index_finish(ctx);
+ return virtual_sync_finish(ctx, TRUE);
+}
+
+struct mailbox_sync_context *
+virtual_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct mailbox_sync_context *sync_ctx;
+ int ret = 0;
+
+ if (!box->opened) {
+ if (mailbox_open(box) < 0)
+ ret = -1;
+ }
+
+ if (index_mailbox_want_full_sync(&mbox->box, flags) && ret == 0)
+ ret = virtual_sync(mbox, flags);
+
+ sync_ctx = index_mailbox_sync_init(box, flags, ret < 0);
+ virtual_sync_apply_existing_expunges(mbox, sync_ctx);
+ return sync_ctx;
+}
diff --git a/src/plugins/virtual/virtual-transaction.c b/src/plugins/virtual/virtual-transaction.c
new file mode 100644
index 0000000..53ac0a8
--- /dev/null
+++ b/src/plugins/virtual/virtual-transaction.c
@@ -0,0 +1,87 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "virtual-storage.h"
+#include "virtual-transaction.h"
+
+struct mailbox_transaction_context *
+virtual_transaction_get(struct mailbox_transaction_context *trans,
+ struct mailbox *backend_box)
+{
+ struct virtual_transaction_context *vt =
+ (struct virtual_transaction_context *)trans;
+ struct mailbox_transaction_context *const *bt, *new_bt;
+ unsigned int i, count;
+
+ bt = array_get(&vt->backend_transactions, &count);
+ for (i = 0; i < count; i++) {
+ if (bt[i]->box == backend_box)
+ return bt[i];
+ }
+
+ new_bt = mailbox_transaction_begin(backend_box, trans->flags, __func__);
+ array_push_back(&vt->backend_transactions, &new_bt);
+ return new_bt;
+}
+
+struct mailbox_transaction_context *
+virtual_transaction_begin(struct mailbox *box,
+ enum mailbox_transaction_flags flags,
+ const char *reason)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_transaction_context *vt;
+
+ vt = i_new(struct virtual_transaction_context, 1);
+ i_array_init(&vt->backend_transactions,
+ array_count(&mbox->backend_boxes));
+ index_transaction_init(&vt->t, box, flags, reason);
+ return &vt->t;
+}
+
+int virtual_transaction_commit(struct mailbox_transaction_context *t,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ struct virtual_transaction_context *vt =
+ (struct virtual_transaction_context *)t;
+ struct mailbox_transaction_context **bt;
+ unsigned int i, count;
+ int ret = 0;
+
+ if (t->save_ctx != NULL) {
+ virtual_save_free(t->save_ctx);
+ t->save_ctx = NULL;
+ }
+
+ bt = array_get_modifiable(&vt->backend_transactions, &count);
+ for (i = 0; i < count; i++) {
+ if (mailbox_transaction_commit(&bt[i]) < 0)
+ ret = -1;
+ }
+ array_free(&vt->backend_transactions);
+
+ if (index_transaction_commit(t, changes_r) < 0)
+ ret = -1;
+ return ret;
+}
+
+void virtual_transaction_rollback(struct mailbox_transaction_context *t)
+{
+ struct virtual_transaction_context *vt =
+ (struct virtual_transaction_context *)t;
+ struct mailbox_transaction_context **bt;
+ unsigned int i, count;
+
+ if (t->save_ctx != NULL) {
+ virtual_save_free(t->save_ctx);
+ t->save_ctx = NULL;
+ }
+
+ bt = array_get_modifiable(&vt->backend_transactions, &count);
+ for (i = 0; i < count; i++)
+ mailbox_transaction_rollback(&bt[i]);
+ array_free(&vt->backend_transactions);
+
+ index_transaction_rollback(t);
+}
diff --git a/src/plugins/virtual/virtual-transaction.h b/src/plugins/virtual/virtual-transaction.h
new file mode 100644
index 0000000..a950fcb
--- /dev/null
+++ b/src/plugins/virtual/virtual-transaction.h
@@ -0,0 +1,24 @@
+#ifndef VIRTUAL_TRANSACTION_H
+#define VIRTUAL_TRANSACTION_H
+
+#include "index-storage.h"
+
+struct virtual_transaction_context {
+ struct mailbox_transaction_context t;
+
+ ARRAY(struct mailbox_transaction_context *) backend_transactions;
+};
+
+struct mailbox_transaction_context *
+virtual_transaction_get(struct mailbox_transaction_context *trans,
+ struct mailbox *backend_box);
+
+struct mailbox_transaction_context *
+virtual_transaction_begin(struct mailbox *box,
+ enum mailbox_transaction_flags flags,
+ const char *reason);
+int virtual_transaction_commit(struct mailbox_transaction_context *t,
+ struct mail_transaction_commit_changes *changes_r);
+void virtual_transaction_rollback(struct mailbox_transaction_context *t);
+
+#endif
diff --git a/src/plugins/welcome/Makefile.am b/src/plugins/welcome/Makefile.am
new file mode 100644
index 0000000..98968f5
--- /dev/null
+++ b/src/plugins/welcome/Makefile.am
@@ -0,0 +1,14 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+NOPLUGIN_LDFLAGS =
+lib99_welcome_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib99_welcome_plugin.la
+
+lib99_welcome_plugin_la_SOURCES = \
+ welcome-plugin.c
diff --git a/src/plugins/welcome/Makefile.in b/src/plugins/welcome/Makefile.in
new file mode 100644
index 0000000..5e55c1c
--- /dev/null
+++ b/src/plugins/welcome/Makefile.in
@@ -0,0 +1,814 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/welcome
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib99_welcome_plugin_la_LIBADD =
+am_lib99_welcome_plugin_la_OBJECTS = welcome-plugin.lo
+lib99_welcome_plugin_la_OBJECTS = \
+ $(am_lib99_welcome_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib99_welcome_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib99_welcome_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/welcome-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib99_welcome_plugin_la_SOURCES)
+DIST_SOURCES = $(lib99_welcome_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage
+
+lib99_welcome_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib99_welcome_plugin.la
+
+lib99_welcome_plugin_la_SOURCES = \
+ welcome-plugin.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/welcome/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/welcome/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib99_welcome_plugin.la: $(lib99_welcome_plugin_la_OBJECTS) $(lib99_welcome_plugin_la_DEPENDENCIES) $(EXTRA_lib99_welcome_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib99_welcome_plugin_la_LINK) -rpath $(moduledir) $(lib99_welcome_plugin_la_OBJECTS) $(lib99_welcome_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/welcome-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/welcome-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/welcome-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/welcome/welcome-plugin.c b/src/plugins/welcome/welcome-plugin.c
new file mode 100644
index 0000000..e98d126
--- /dev/null
+++ b/src/plugins/welcome/welcome-plugin.c
@@ -0,0 +1,146 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "str.h"
+#include "strescape.h"
+#include "eacces-error.h"
+#include "write-full.h"
+#include "module-context.h"
+#include "mail-storage-private.h"
+
+#define WELCOME_SOCKET_TIMEOUT_SECS 30
+
+#define WELCOME_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, welcome_storage_module)
+
+struct welcome_mailbox {
+ union mailbox_module_context module_ctx;
+ bool created;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(welcome_storage_module,
+ &mail_storage_module_register);
+
+static void script_execute(struct mail_user *user, const char *cmd, bool wait)
+{
+ const char *socket_path, *const *args;
+ string_t *str;
+ char buf[1024];
+ int fd, ret;
+
+ e_debug(user->event, "welcome: Executing %s (wait=%d)", cmd, wait ? 1 : 0);
+
+ args = t_strsplit_spaces(cmd, " ");
+ socket_path = args[0];
+ args++;
+
+ if (*socket_path != '/') {
+ socket_path = t_strconcat(user->set->base_dir, "/",
+ socket_path, NULL);
+ }
+ if ((fd = net_connect_unix_with_retries(socket_path, 1000)) < 0) {
+ if (errno == EACCES) {
+ i_error("welcome: %s",
+ eacces_error_get("net_connect_unix",
+ socket_path));
+ } else {
+ i_error("welcome: net_connect_unix(%s) failed: %m",
+ socket_path);
+ }
+ return;
+ }
+
+ str = t_str_new(1024);
+ str_append(str, "VERSION\tscript\t4\t0\n");
+ if (!wait)
+ str_append(str, "noreply\n");
+ else
+ str_append(str, "-\n");
+ for (; *args != NULL; args++) {
+ str_append_tabescaped(str, *args);
+ str_append_c(str, '\n');
+ }
+ str_append_c(str, '\n');
+
+ alarm(WELCOME_SOCKET_TIMEOUT_SECS);
+ net_set_nonblock(fd, FALSE);
+ if (write_full(fd, str_data(str), str_len(str)) < 0)
+ i_error("write(%s) failed: %m", socket_path);
+ else if (wait) {
+ ret = read(fd, buf, sizeof(buf));
+ if (ret < 0)
+ i_error("welcome: read(%s) failed: %m", socket_path);
+ else if (ret < 2)
+ i_error("welcome: %s failed: Only %d bytes read", socket_path, ret);
+ else if (buf[0] != '+')
+ i_error("welcome: %s failed: Script returned error", socket_path);
+ }
+ if (close(fd) < 0)
+ i_error("close(%s) failed: %m", socket_path);
+}
+
+static int
+welcome_create_box(struct mailbox *box,
+ const struct mailbox_update *update, bool directory)
+{
+ struct welcome_mailbox *wbox = WELCOME_CONTEXT(box);
+
+ if (wbox->module_ctx.super.create_box(box, update, directory) < 0)
+ return -1;
+ /* the mailbox isn't fully created here yet, so just mark it as created
+ and wait until open() time to actually run it */
+ wbox->created = TRUE;
+ return 0;
+}
+
+static int welcome_open_box(struct mailbox *box)
+{
+ struct welcome_mailbox *wbox = WELCOME_CONTEXT(box);
+ const char *cmd;
+
+ cmd = !wbox->created ? NULL :
+ mail_user_plugin_getenv(box->storage->user, "welcome_script");
+ if (cmd != NULL) {
+ bool wait = mail_user_plugin_getenv_bool(box->storage->user,
+ "welcome_wait");
+ script_execute(box->storage->user, cmd, wait);
+ }
+ return wbox->module_ctx.super.open(box);
+}
+
+static void welcome_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct welcome_mailbox *wbox;
+
+ if (!box->inbox_user)
+ return;
+
+ wbox = p_new(box->pool, struct welcome_mailbox, 1);
+ wbox->module_ctx.super = *v;
+ box->vlast = &wbox->module_ctx.super;
+
+ v->create_box = welcome_create_box;
+ v->open = welcome_open_box;
+ MODULE_CONTEXT_SET(box, welcome_storage_module, wbox);
+}
+
+static struct mail_storage_hooks welcome_mail_storage_hooks = {
+ .mailbox_allocated = welcome_mailbox_allocated
+};
+
+void welcome_plugin_init(struct module *module);
+void welcome_plugin_deinit(void);
+
+void welcome_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &welcome_mail_storage_hooks);
+}
+
+void welcome_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&welcome_mail_storage_hooks);
+}
+
+const char *welcome_plugin_version = DOVECOT_ABI_VERSION;
diff --git a/src/plugins/zlib/Makefile.am b/src/plugins/zlib/Makefile.am
new file mode 100644
index 0000000..45c3d76
--- /dev/null
+++ b/src/plugins/zlib/Makefile.am
@@ -0,0 +1,24 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-compression \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/dbox-common
+
+NOPLUGIN_LDFLAGS =
+lib20_zlib_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib20_zlib_plugin.la
+
+lib20_zlib_plugin_la_LIBADD = \
+ ../../lib-compression/libcompression.la
+
+lib20_zlib_plugin_la_SOURCES = \
+ zlib-plugin.c
+
+noinst_HEADERS = \
+ zlib-plugin.h
diff --git a/src/plugins/zlib/Makefile.in b/src/plugins/zlib/Makefile.in
new file mode 100644
index 0000000..5b3a3ab
--- /dev/null
+++ b/src/plugins/zlib/Makefile.in
@@ -0,0 +1,827 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/plugins/zlib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(moduledir)"
+LTLIBRARIES = $(module_LTLIBRARIES)
+lib20_zlib_plugin_la_DEPENDENCIES = \
+ ../../lib-compression/libcompression.la
+am_lib20_zlib_plugin_la_OBJECTS = zlib-plugin.lo
+lib20_zlib_plugin_la_OBJECTS = $(am_lib20_zlib_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib20_zlib_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib20_zlib_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/zlib-plugin.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib20_zlib_plugin_la_SOURCES)
+DIST_SOURCES = $(lib20_zlib_plugin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-compression \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/dbox-common
+
+lib20_zlib_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib20_zlib_plugin.la
+
+lib20_zlib_plugin_la_LIBADD = \
+ ../../lib-compression/libcompression.la
+
+lib20_zlib_plugin_la_SOURCES = \
+ zlib-plugin.c
+
+noinst_HEADERS = \
+ zlib-plugin.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/zlib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/zlib/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib20_zlib_plugin.la: $(lib20_zlib_plugin_la_OBJECTS) $(lib20_zlib_plugin_la_DEPENDENCIES) $(EXTRA_lib20_zlib_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib20_zlib_plugin_la_LINK) -rpath $(moduledir) $(lib20_zlib_plugin_la_OBJECTS) $(lib20_zlib_plugin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zlib-plugin.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(moduledir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/zlib-plugin.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/zlib-plugin.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-moduleLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/plugins/zlib/zlib-plugin.c b/src/plugins/zlib/zlib-plugin.c
new file mode 100644
index 0000000..f716058
--- /dev/null
+++ b/src/plugins/zlib/zlib-plugin.c
@@ -0,0 +1,388 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "istream-seekable.h"
+#include "ostream.h"
+#include "str.h"
+#include "mail-user.h"
+#include "index-storage.h"
+#include "index-mail.h"
+#include "compression.h"
+#include "zlib-plugin.h"
+
+#include <fcntl.h>
+
+#define ZLIB_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, zlib_storage_module)
+#define ZLIB_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, zlib_mail_module)
+#define ZLIB_USER_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, zlib_user_module)
+
+#define MAX_INBUF_SIZE (1024*1024)
+#define ZLIB_MAIL_CACHE_EXPIRE_MSECS (60*1000)
+
+struct zlib_mail {
+ union mail_module_context module_ctx;
+ bool verifying_save;
+};
+
+struct zlib_mail_cache {
+ struct timeout *to;
+ struct mailbox *box;
+ uint32_t uid;
+
+ struct istream *input;
+};
+
+struct zlib_user {
+ union mail_user_module_context module_ctx;
+
+ struct zlib_mail_cache cache;
+
+ const struct compression_handler *save_handler;
+ int save_level;
+};
+
+const char *zlib_plugin_version = DOVECOT_ABI_VERSION;
+
+static MODULE_CONTEXT_DEFINE_INIT(zlib_user_module,
+ &mail_user_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(zlib_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(zlib_mail_module, &mail_module_register);
+
+static bool zlib_mailbox_is_permail(struct mailbox *box)
+{
+ enum mail_storage_class_flags class_flags = box->storage->class_flags;
+
+ return (class_flags & MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) == 0 &&
+ (class_flags & MAIL_STORAGE_CLASS_FLAG_BINARY_DATA) != 0;
+}
+
+static void zlib_mail_cache_close(struct zlib_user *zuser)
+{
+ struct zlib_mail_cache *cache = &zuser->cache;
+
+ timeout_remove(&cache->to);
+ i_stream_unref(&cache->input);
+ i_zero(cache);
+}
+
+static struct istream *
+zlib_mail_cache_open(struct zlib_user *zuser, struct mail *mail,
+ struct istream *input, bool do_cache)
+{
+ struct zlib_mail_cache *cache = &zuser->cache;
+ struct istream *inputs[2];
+ string_t *temp_prefix = t_str_new(128);
+
+ if (do_cache)
+ zlib_mail_cache_close(zuser);
+
+ /* zlib istream is seekable, but very slow. create a seekable istream
+ which we can use to quickly seek around in the stream that's been
+ read so far. usually the partial IMAP FETCHes continue from where
+ the previous left off, so this isn't strictly necessary, but with
+ the way lib-imap-storage's CRLF-cache works it has to seek backwards
+ somewhat, which causes a zlib stream reset. And the CRLF-cache isn't
+ easy to fix.. */
+ input->seekable = FALSE;
+ inputs[0] = input;
+ inputs[1] = NULL;
+ mail_user_set_get_temp_prefix(temp_prefix, mail->box->storage->user->set);
+ input = i_stream_create_seekable_path(inputs,
+ i_stream_get_max_buffer_size(inputs[0]),
+ str_c(temp_prefix));
+ i_stream_set_name(input, t_strdup_printf("compress(%s)",
+ i_stream_get_name(inputs[0])));
+ i_stream_unref(&inputs[0]);
+
+ if (do_cache) {
+ cache->to = timeout_add(ZLIB_MAIL_CACHE_EXPIRE_MSECS,
+ zlib_mail_cache_close, zuser);
+ cache->box = mail->box;
+ cache->uid = mail->uid;
+ cache->input = input;
+ /* index-mail wants the stream to be destroyed at close, so create
+ a new stream instead of just increasing reference. */
+ return i_stream_create_limit(cache->input, UOFF_T_MAX);
+ } else {
+ return input;
+ }
+}
+
+static int zlib_istream_opened(struct mail *_mail, struct istream **stream)
+{
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(_mail->box->storage->user);
+ struct zlib_mail_cache *cache = &zuser->cache;
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail);
+ struct istream *input;
+ const struct compression_handler *handler;
+
+ if (zmail->verifying_save) {
+ /* zlib_mail_save_finish() is verifying that the user-given
+ input doesn't look compressed. */
+ return zmail->module_ctx.super.istream_opened(_mail, stream);
+ }
+
+ if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) {
+ /* use the cached stream. when doing partial reads it should
+ already be seeked into the wanted offset. */
+ i_stream_unref(stream);
+ i_stream_seek(cache->input, 0);
+ *stream = i_stream_create_limit(cache->input, UOFF_T_MAX);
+ return zmail->module_ctx.super.istream_opened(_mail, stream);
+ }
+
+ handler = compression_detect_handler(*stream);
+ if (handler != NULL) {
+ if (handler->create_istream == NULL) {
+ mail_set_critical(_mail,
+ "zlib plugin: Detected %s compression "
+ "but support not compiled in", handler->ext);
+ return -1;
+ }
+
+ input = *stream;
+ *stream = handler->create_istream(input);
+ i_stream_unref(&input);
+ /* dont cache the stream if _mail->uid is 0 */
+ *stream = zlib_mail_cache_open(zuser, _mail, *stream, (_mail->uid > 0));
+ }
+ return zmail->module_ctx.super.istream_opened(_mail, stream);
+}
+
+static void zlib_mail_close(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail);
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(_mail->box->storage->user);
+ struct zlib_mail_cache *cache = &zuser->cache;
+ uoff_t size;
+
+ if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) {
+ /* make sure we have read the entire email into the seekable
+ stream (which causes the original input stream to be
+ unrefed). we can't safely keep the original input stream
+ open after the mail is closed. */
+ if (i_stream_get_size(cache->input, TRUE, &size) < 0)
+ zlib_mail_cache_close(zuser);
+ }
+ zmail->module_ctx.super.close(_mail);
+}
+
+static void zlib_mail_allocated(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ struct zlib_mail *zmail;
+
+ if (!zlib_mailbox_is_permail(_mail->box))
+ return;
+
+ zmail = p_new(mail->pool, struct zlib_mail, 1);
+ zmail->module_ctx.super = *v;
+ mail->vlast = &zmail->module_ctx.super;
+
+ v->istream_opened = zlib_istream_opened;
+ v->close = zlib_mail_close;
+ MODULE_CONTEXT_SET(mail, zlib_mail_module, zmail);
+}
+
+static int zlib_mail_save_finish(struct mail_save_context *ctx)
+{
+ struct mailbox *box = ctx->transaction->box;
+ union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
+ struct mail_private *mail = (struct mail_private *)ctx->dest_mail;
+ struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail);
+ struct istream *input;
+ int ret;
+
+ if (zbox->super.save_finish(ctx) < 0)
+ return -1;
+
+ zmail->verifying_save = TRUE;
+ ret = mail_get_stream(ctx->dest_mail, NULL, NULL, &input);
+ zmail->verifying_save = FALSE;
+ if (ret < 0)
+ return -1;
+
+ if (compression_detect_handler(input) != NULL) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Saving mails compressed by client isn't supported");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+zlib_mail_save_compress_begin(struct mail_save_context *ctx,
+ struct istream *input)
+{
+ struct mailbox *box = ctx->transaction->box;
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user);
+ union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
+ struct ostream *output;
+
+ if (zbox->super.save_begin(ctx, input) < 0)
+ return -1;
+
+ output = zuser->save_handler->create_ostream(ctx->data.output,
+ zuser->save_level);
+ o_stream_unref(&ctx->data.output);
+ ctx->data.output = output;
+ o_stream_cork(ctx->data.output);
+ return 0;
+}
+
+static void
+zlib_permail_alloc_init(struct mailbox *box, struct mailbox_vfuncs *v)
+{
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user);
+
+ if (zuser->save_handler == NULL) {
+ v->save_finish = zlib_mail_save_finish;
+ } else {
+ v->save_begin = zlib_mail_save_compress_begin;
+ }
+}
+
+static void zlib_mailbox_open_input(struct mailbox *box)
+{
+ const struct compression_handler *handler;
+ struct istream *input;
+ struct stat st;
+ int fd;
+
+ if (compression_lookup_handler_from_ext(box->name, &handler) <= 0)
+ return;
+
+ if (mail_storage_is_mailbox_file(box->storage)) {
+ /* looks like a compressed single file mailbox. we should be
+ able to handle this. */
+ const char *box_path = mailbox_get_path(box);
+
+ fd = open(box_path, O_RDONLY);
+ if (fd == -1) {
+ /* let the standard handler figure out what to do
+ with the failure */
+ return;
+ }
+ if (fstat(fd, &st) == 0 && S_ISDIR(st.st_mode)) {
+ i_close_fd(&fd);
+ return;
+ }
+ input = i_stream_create_fd_autoclose(&fd, MAX_INBUF_SIZE);
+ i_stream_set_name(input, box_path);
+ box->input = handler->create_istream(input);
+ i_stream_unref(&input);
+ box->flags |= MAILBOX_FLAG_READONLY;
+ }
+}
+
+static int zlib_mailbox_open(struct mailbox *box)
+{
+ union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
+
+ if (box->input == NULL &&
+ (box->storage->class_flags &
+ MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) != 0)
+ zlib_mailbox_open_input(box);
+
+ return zbox->super.open(box);
+}
+
+static void zlib_mailbox_close(struct mailbox *box)
+{
+ union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user);
+
+ if (zuser->cache.box == box)
+ zlib_mail_cache_close(zuser);
+ zbox->super.close(box);
+}
+
+static void zlib_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ union mailbox_module_context *zbox;
+
+ zbox = p_new(box->pool, union mailbox_module_context, 1);
+ zbox->super = *v;
+ box->vlast = &zbox->super;
+ v->open = zlib_mailbox_open;
+ v->close = zlib_mailbox_close;
+
+ MODULE_CONTEXT_SET_SELF(box, zlib_storage_module, zbox);
+
+ if (zlib_mailbox_is_permail(box))
+ zlib_permail_alloc_init(box, v);
+}
+
+static void zlib_mail_user_deinit(struct mail_user *user)
+{
+ struct zlib_user *zuser = ZLIB_USER_CONTEXT(user);
+
+ zlib_mail_cache_close(zuser);
+ zuser->module_ctx.super.deinit(user);
+}
+
+static void zlib_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct zlib_user *zuser;
+ const char *name;
+ int ret;
+
+ zuser = p_new(user->pool, struct zlib_user, 1);
+ zuser->module_ctx.super = *v;
+ user->vlast = &zuser->module_ctx.super;
+ v->deinit = zlib_mail_user_deinit;
+
+ name = mail_user_plugin_getenv(user, "zlib_save");
+ if (name != NULL && *name != '\0') {
+ ret = compression_lookup_handler(name, &zuser->save_handler);
+ if (ret <= 0) {
+ i_error("zlib_save: %s: %s", ret == 0 ?
+ "Support not compiled in for handler" :
+ "Unknown handler", name);
+ zuser->save_handler = NULL;
+ }
+ }
+ name = zuser->save_handler == NULL ? NULL :
+ mail_user_plugin_getenv(user, "zlib_save_level");
+ if (name != NULL && name[0] != '\0') {
+ if (str_to_int(name, &zuser->save_level) < 0 ||
+ zuser->save_level < zuser->save_handler->get_min_level() ||
+ zuser->save_level > zuser->save_handler->get_max_level()) {
+ i_error("zlib_save_level: Level must be between %d..%d",
+ zuser->save_handler->get_min_level(),
+ zuser->save_handler->get_max_level());
+ zuser->save_level =
+ zuser->save_handler->get_default_level();
+ }
+ } else if (zuser->save_handler != NULL) {
+ zuser->save_level = zuser->save_handler->get_default_level();
+ }
+ MODULE_CONTEXT_SET(user, zlib_user_module, zuser);
+}
+
+static struct mail_storage_hooks zlib_mail_storage_hooks = {
+ .mail_user_created = zlib_mail_user_created,
+ .mailbox_allocated = zlib_mailbox_allocated,
+ .mail_allocated = zlib_mail_allocated
+};
+
+void zlib_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &zlib_mail_storage_hooks);
+}
+
+void zlib_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&zlib_mail_storage_hooks);
+}
diff --git a/src/plugins/zlib/zlib-plugin.h b/src/plugins/zlib/zlib-plugin.h
new file mode 100644
index 0000000..98f520d
--- /dev/null
+++ b/src/plugins/zlib/zlib-plugin.h
@@ -0,0 +1,7 @@
+#ifndef ZLIB_PLUGIN_H
+#define ZLIB_PLUGIN_H
+
+void zlib_plugin_init(struct module *module);
+void zlib_plugin_deinit(void);
+
+#endif