summaryrefslogtreecommitdiffstats
path: root/src/plugins/acl
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/plugins/acl
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/plugins/acl')
-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.c633
-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
27 files changed, 7594 insertions, 0 deletions
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..2c56fb3
--- /dev/null
+++ b/src/plugins/acl/Makefile.in
@@ -0,0 +1,1035 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 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..de0b00c
--- /dev/null
+++ b/src/plugins/acl/acl-mailbox-list.c
@@ -0,0 +1,633 @@
+/* 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;
+ size_t i, prefix_len;
+ bool wildcards = 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] != '*' && ctx->info.vname[i] != '%')
+ str_append_c(pattern, ctx->info.vname[i]);
+ else {
+ wildcards = 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_len = str_len(pattern) - 1;
+ if (prefix_len == 0) {
+ /* empty mailbox names shouldn't be possible */
+ return FALSE;
+ }
+
+ 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 (!wildcards ||
+ (strncmp(info->vname, ctx->info.vname, prefix_len-1) == 0 &&
+ info->vname[prefix_len-1] == ctx->sep))
+ 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);
+}