diff options
Diffstat (limited to 'src/plugins/acl')
27 files changed, 7590 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..4b133e0 --- /dev/null +++ b/src/plugins/acl/Makefile.in @@ -0,0 +1,1035 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/plugins/acl +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(pkginc_lib_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-acl$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(doveadm_moduledir)" \ + "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)" +LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES) +lib01_acl_plugin_la_LIBADD = +am_lib01_acl_plugin_la_OBJECTS = acl-api.lo acl-attributes.lo \ + acl-backend.lo acl-backend-vfile.lo \ + acl-backend-vfile-acllist.lo acl-backend-vfile-update.lo \ + acl-cache.lo acl-global-file.lo acl-lookup-dict.lo \ + acl-mailbox.lo acl-mailbox-list.lo acl-plugin.lo \ + acl-shared-storage.lo acl-storage.lo +lib01_acl_plugin_la_OBJECTS = $(am_lib01_acl_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib01_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib01_acl_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +lib10_doveadm_acl_plugin_la_LIBADD = +am_lib10_doveadm_acl_plugin_la_OBJECTS = doveadm-acl.lo +lib10_doveadm_acl_plugin_la_OBJECTS = \ + $(am_lib10_doveadm_acl_plugin_la_OBJECTS) +lib10_doveadm_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib10_doveadm_acl_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_acl_OBJECTS = test-acl.$(OBJEXT) +test_acl_OBJECTS = $(am_test_acl_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = $(module_LTLIBRARIES) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/acl-api.Plo \ + ./$(DEPDIR)/acl-attributes.Plo \ + ./$(DEPDIR)/acl-backend-vfile-acllist.Plo \ + ./$(DEPDIR)/acl-backend-vfile-update.Plo \ + ./$(DEPDIR)/acl-backend-vfile.Plo ./$(DEPDIR)/acl-backend.Plo \ + ./$(DEPDIR)/acl-cache.Plo ./$(DEPDIR)/acl-global-file.Plo \ + ./$(DEPDIR)/acl-lookup-dict.Plo \ + ./$(DEPDIR)/acl-mailbox-list.Plo ./$(DEPDIR)/acl-mailbox.Plo \ + ./$(DEPDIR)/acl-plugin.Plo ./$(DEPDIR)/acl-shared-storage.Plo \ + ./$(DEPDIR)/acl-storage.Plo ./$(DEPDIR)/doveadm-acl.Plo \ + ./$(DEPDIR)/test-acl.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib01_acl_plugin_la_SOURCES) \ + $(lib10_doveadm_acl_plugin_la_SOURCES) $(test_acl_SOURCES) +DIST_SOURCES = $(lib01_acl_plugin_la_SOURCES) \ + $(lib10_doveadm_acl_plugin_la_SOURCES) $(test_acl_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +doveadm_moduledir = $(moduledir)/doveadm +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/list \ + -I$(top_srcdir)/src/doveadm + +lib10_doveadm_acl_plugin_la_LDFLAGS = -module -avoid-version +lib01_acl_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib01_acl_plugin.la + +lib01_acl_plugin_la_SOURCES = \ + acl-api.c \ + acl-attributes.c \ + acl-backend.c \ + acl-backend-vfile.c \ + acl-backend-vfile-acllist.c \ + acl-backend-vfile-update.c \ + acl-cache.c \ + acl-global-file.c \ + acl-lookup-dict.c \ + acl-mailbox.c \ + acl-mailbox-list.c \ + acl-plugin.c \ + acl-shared-storage.c \ + acl-storage.c + +noinst_HEADERS = \ + acl-backend-vfile.h \ + acl-shared-storage.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = \ + acl-api.h \ + acl-api-private.h \ + acl-cache.h \ + acl-global-file.h \ + acl-lookup-dict.h \ + acl-plugin.h \ + acl-storage.h + +doveadm_module_LTLIBRARIES = \ + lib10_doveadm_acl_plugin.la + +lib10_doveadm_acl_plugin_la_SOURCES = \ + doveadm-acl.c + +test_programs = \ + test-acl + +test_libs = \ + $(module_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) + +test_deps = \ + $(module_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +test_acl_SOURCES = test-acl.c +test_acl_LDADD = $(test_libs) +test_acl_DEPENDENCIES = $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/acl/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/acl/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \ + } + +uninstall-doveadm_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \ + done + +clean-doveadm_moduleLTLIBRARIES: + -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES) + @list='$(doveadm_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib01_acl_plugin.la: $(lib01_acl_plugin_la_OBJECTS) $(lib01_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib01_acl_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib01_acl_plugin_la_LINK) -rpath $(moduledir) $(lib01_acl_plugin_la_OBJECTS) $(lib01_acl_plugin_la_LIBADD) $(LIBS) + +lib10_doveadm_acl_plugin.la: $(lib10_doveadm_acl_plugin_la_OBJECTS) $(lib10_doveadm_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib10_doveadm_acl_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib10_doveadm_acl_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib10_doveadm_acl_plugin_la_OBJECTS) $(lib10_doveadm_acl_plugin_la_LIBADD) $(LIBS) + +test-acl$(EXEEXT): $(test_acl_OBJECTS) $(test_acl_DEPENDENCIES) $(EXTRA_test_acl_DEPENDENCIES) + @rm -f test-acl$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_acl_OBJECTS) $(test_acl_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-api.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-attributes.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile-acllist.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile-update.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-cache.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-global-file.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-lookup-dict.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-mailbox-list.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-mailbox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-shared-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-acl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-acl.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/acl-api.Plo + -rm -f ./$(DEPDIR)/acl-attributes.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile-acllist.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile-update.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile.Plo + -rm -f ./$(DEPDIR)/acl-backend.Plo + -rm -f ./$(DEPDIR)/acl-cache.Plo + -rm -f ./$(DEPDIR)/acl-global-file.Plo + -rm -f ./$(DEPDIR)/acl-lookup-dict.Plo + -rm -f ./$(DEPDIR)/acl-mailbox-list.Plo + -rm -f ./$(DEPDIR)/acl-mailbox.Plo + -rm -f ./$(DEPDIR)/acl-plugin.Plo + -rm -f ./$(DEPDIR)/acl-shared-storage.Plo + -rm -f ./$(DEPDIR)/acl-storage.Plo + -rm -f ./$(DEPDIR)/doveadm-acl.Plo + -rm -f ./$(DEPDIR)/test-acl.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-doveadm_moduleLTLIBRARIES \ + install-moduleLTLIBRARIES install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/acl-api.Plo + -rm -f ./$(DEPDIR)/acl-attributes.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile-acllist.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile-update.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile.Plo + -rm -f ./$(DEPDIR)/acl-backend.Plo + -rm -f ./$(DEPDIR)/acl-cache.Plo + -rm -f ./$(DEPDIR)/acl-global-file.Plo + -rm -f ./$(DEPDIR)/acl-lookup-dict.Plo + -rm -f ./$(DEPDIR)/acl-mailbox-list.Plo + -rm -f ./$(DEPDIR)/acl-mailbox.Plo + -rm -f ./$(DEPDIR)/acl-plugin.Plo + -rm -f ./$(DEPDIR)/acl-shared-storage.Plo + -rm -f ./$(DEPDIR)/acl-storage.Plo + -rm -f ./$(DEPDIR)/doveadm-acl.Plo + -rm -f ./$(DEPDIR)/test-acl.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-doveadm_moduleLTLIBRARIES \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am \ + install-doveadm_moduleLTLIBRARIES install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/acl/acl-api-private.h b/src/plugins/acl/acl-api-private.h new file mode 100644 index 0000000..6859779 --- /dev/null +++ b/src/plugins/acl/acl-api-private.h @@ -0,0 +1,135 @@ +#ifndef ACL_API_PRIVATE_H +#define ACL_API_PRIVATE_H + +#include "acl-api.h" + +#define ACL_ID_NAME_ANYONE "anyone" +#define ACL_ID_NAME_AUTHENTICATED "authenticated" +#define ACL_ID_NAME_OWNER "owner" +#define ACL_ID_NAME_USER_PREFIX "user=" +#define ACL_ID_NAME_GROUP_PREFIX "group=" +#define ACL_ID_NAME_GROUP_OVERRIDE_PREFIX "group-override=" + +struct acl_backend_vfuncs { + struct acl_backend *(*alloc)(void); + int (*init)(struct acl_backend *backend, const char *data); + void (*deinit)(struct acl_backend *backend); + + struct acl_mailbox_list_context * + (*nonowner_lookups_iter_init)(struct acl_backend *backend); + bool (*nonowner_lookups_iter_next)(struct acl_mailbox_list_context *ctx, + const char **name_r); + int (*nonowner_lookups_iter_deinit) + (struct acl_mailbox_list_context *ctx); + int (*nonowner_lookups_rebuild)(struct acl_backend *backend); + + struct acl_object *(*object_init)(struct acl_backend *backend, + const char *name); + struct acl_object *(*object_init_parent)(struct acl_backend *backend, + const char *child_name); + void (*object_deinit)(struct acl_object *aclobj); + + int (*object_refresh_cache)(struct acl_object *aclobj); + int (*object_update)(struct acl_object *aclobj, + const struct acl_rights_update *update); + int (*last_changed)(struct acl_object *aclobj, time_t *last_changed_r); + + struct acl_object_list_iter * + (*object_list_init)(struct acl_object *aclobj); + bool (*object_list_next)(struct acl_object_list_iter *iter, + struct acl_rights *rights_r); + int (*object_list_deinit)(struct acl_object_list_iter *iter); +}; + +struct acl_backend { + pool_t pool; + const char *username; + const char **groups; + unsigned int group_count; + + struct mailbox_list *list; + struct acl_cache *cache; + struct acl_global_file *global_file; + + struct acl_object *default_aclobj; + struct acl_mask *default_aclmask; + const char *const *default_rights; + + struct acl_backend_vfuncs v; + + bool owner:1; + bool debug:1; + bool globals_only:1; +}; + +struct acl_mailbox_list_context { + struct acl_backend *backend; + + bool empty:1; + bool failed:1; + const char *error; +}; + +struct acl_object { + struct acl_backend *backend; + char *name; + + pool_t rights_pool; + ARRAY_TYPE(acl_rights) rights; +}; + +struct acl_object_list_iter { + struct acl_object *aclobj; + pool_t pool; + + struct acl_rights *rights; + unsigned int idx, count; + + bool empty:1; + bool failed:1; + const char *error; +}; + +extern const char *const all_mailbox_rights[]; + +struct acl_object_list_iter * +acl_default_object_list_init(struct acl_object *aclobj); +bool acl_default_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r); +int acl_default_object_list_deinit(struct acl_object_list_iter *iter); + +const char *const * +acl_backend_mask_get_names(struct acl_backend *backend, + const struct acl_mask *mask, pool_t pool); +struct acl_object *acl_backend_get_default_object(struct acl_backend *backend); +int acl_backend_get_default_rights(struct acl_backend *backend, + const struct acl_mask **mask_r); +void acl_rights_write_id(string_t *dest, const struct acl_rights *right); +bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights); + +int acl_identifier_parse(const char *line, struct acl_rights *rights); +int acl_rights_update_import(struct acl_rights_update *update, + const char *id, const char *const *rights, + const char **error_r); +const char *acl_rights_export(const struct acl_rights *rights); +int acl_rights_parse_line(const char *line, pool_t pool, + struct acl_rights *rights_r, const char **error_r); +void acl_rights_dup(const struct acl_rights *src, + pool_t pool, struct acl_rights *dest_r); +int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2); +void acl_rights_sort(struct acl_object *aclobj); + +const char *const * +acl_right_names_parse(pool_t pool, const char *acl, const char **error_r); +void acl_right_names_write(string_t *dest, const char *const *rights); +void acl_right_names_merge(pool_t pool, const char *const **destp, + const char *const *src, bool dup_strings); +bool acl_right_names_modify(pool_t pool, + const char *const **rightsp, + const char *const *modify_rights, + enum acl_modify_mode modify_mode); +void acl_object_rebuild_cache(struct acl_object *aclobj); +void acl_object_remove_all_access(struct acl_object *aclobj); +void acl_object_add_global_acls(struct acl_object *aclobj); + +#endif diff --git a/src/plugins/acl/acl-api.c b/src/plugins/acl/acl-api.c new file mode 100644 index 0000000..2e422ea --- /dev/null +++ b/src/plugins/acl/acl-api.c @@ -0,0 +1,847 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strescape.h" +#include "hash.h" +#include "mail-user.h" +#include "mailbox-list.h" +#include "acl-global-file.h" +#include "acl-cache.h" +#include "acl-api-private.h" + +struct acl_letter_map { + char letter; + const char *name; +}; + +static const struct acl_letter_map acl_letter_map[] = { + { 'l', MAIL_ACL_LOOKUP }, + { 'r', MAIL_ACL_READ }, + { 'w', MAIL_ACL_WRITE }, + { 's', MAIL_ACL_WRITE_SEEN }, + { 't', MAIL_ACL_WRITE_DELETED }, + { 'i', MAIL_ACL_INSERT }, + { 'p', MAIL_ACL_POST }, + { 'e', MAIL_ACL_EXPUNGE }, + { 'k', MAIL_ACL_CREATE }, + { 'x', MAIL_ACL_DELETE }, + { 'a', MAIL_ACL_ADMIN }, + { '\0', NULL } +}; + +struct acl_object *acl_object_init_from_name(struct acl_backend *backend, + const char *name) +{ + return backend->v.object_init(backend, name); +} + +struct acl_object *acl_object_init_from_parent(struct acl_backend *backend, + const char *child_name) +{ + return backend->v.object_init_parent(backend, child_name); +} + +void acl_object_deinit(struct acl_object **_aclobj) +{ + struct acl_object *aclobj = *_aclobj; + + *_aclobj = NULL; + aclobj->backend->v.object_deinit(aclobj); +} + +int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx) +{ + struct acl_backend *backend = aclobj->backend; + const struct acl_mask *have_mask; + unsigned int read_idx; + + if (backend->v.object_refresh_cache(aclobj) < 0) + return -1; + + have_mask = acl_cache_get_my_rights(backend->cache, aclobj->name); + if (have_mask == NULL) { + if (acl_backend_get_default_rights(backend, &have_mask) < 0) + return -1; + } + + if (acl_cache_mask_isset(have_mask, right_idx)) + return 1; + + if (mailbox_list_get_user(aclobj->backend->list)->dsyncing) { + /* when dsync is running on a shared mailbox, it must be able + to do everything inside it. however, dsync shouldn't touch + mailboxes where user doesn't have any read access, because + that could make them readable on the replica. */ + read_idx = acl_backend_lookup_right(aclobj->backend, + MAIL_ACL_READ); + if (acl_cache_mask_isset(have_mask, read_idx)) + return 1; + } + return 0; +} + +const char *const * +acl_backend_mask_get_names(struct acl_backend *backend, + const struct acl_mask *mask, pool_t pool) +{ + const char *const *names; + const char **buf, **rights; + unsigned int names_count, count, i, j, name_idx; + + names = acl_cache_get_names(backend->cache, &names_count); + buf = t_new(const char *, (mask->size * CHAR_BIT) + 1); + count = 0; + for (i = 0, name_idx = 0; i < mask->size; i++) { + if (mask->mask[i] == 0) + name_idx += CHAR_BIT; + else { + for (j = 1; j < (1 << CHAR_BIT); j <<= 1, name_idx++) { + if ((mask->mask[i] & j) == 0) + continue; + + /* @UNSAFE */ + i_assert(name_idx < names_count); + buf[count++] = p_strdup(pool, names[name_idx]); + } + } + } + + /* @UNSAFE */ + rights = p_new(pool, const char *, count + 1); + memcpy(rights, buf, count * sizeof(const char *)); + return rights; +} + +static int acl_object_get_my_rights_real(struct acl_object *aclobj, pool_t pool, + const char *const **rights_r) +{ + struct acl_backend *backend = aclobj->backend; + const struct acl_mask *mask; + + if (backend->v.object_refresh_cache(aclobj) < 0) + return -1; + + mask = acl_cache_get_my_rights(backend->cache, aclobj->name); + if (mask == NULL) { + if (acl_backend_get_default_rights(backend, &mask) < 0) + return -1; + } + + *rights_r = acl_backend_mask_get_names(backend, mask, pool); + return 0; +} + +int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool, + const char *const **rights_r) +{ + int ret; + + if (pool->datastack_pool) + return acl_object_get_my_rights_real(aclobj, pool, rights_r); + T_BEGIN { + ret = acl_object_get_my_rights_real(aclobj, pool, rights_r); + } T_END; + return ret; +} + +const char *const *acl_object_get_default_rights(struct acl_object *aclobj) +{ + return acl_backend_mask_get_names(aclobj->backend, + aclobj->backend->default_aclmask, + pool_datastack_create()); +} + +int acl_object_last_changed(struct acl_object *aclobj, time_t *last_changed_r) +{ + return aclobj->backend->v.last_changed(aclobj, last_changed_r); +} + +int acl_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update) +{ + return aclobj->backend->v.object_update(aclobj, update); +} + +struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj) +{ + return aclobj->backend->v.object_list_init(aclobj); +} + +bool acl_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r) +{ + if (iter->failed) + return FALSE; + + return iter->aclobj->backend->v.object_list_next(iter, rights_r); +} + +int acl_object_list_deinit(struct acl_object_list_iter **_iter) +{ + struct acl_object_list_iter *iter = *_iter; + + *_iter = NULL; + return iter->aclobj->backend->v.object_list_deinit(iter); +} + +struct acl_object_list_iter * +acl_default_object_list_init(struct acl_object *aclobj) +{ + struct acl_object_list_iter *iter; + const struct acl_rights *aclobj_rights; + unsigned int i; + pool_t pool; + + pool = pool_alloconly_create("acl object list", 512); + iter = p_new(pool, struct acl_object_list_iter, 1); + iter->pool = pool; + iter->aclobj = aclobj; + + if (!array_is_created(&aclobj->rights)) { + /* we may have the object cached, but we don't have all the + rights read into memory */ + acl_cache_flush(aclobj->backend->cache, aclobj->name); + } + + if (aclobj->backend->v.object_refresh_cache(aclobj) < 0) + iter->failed = TRUE; + + aclobj_rights = array_get(&aclobj->rights, &iter->count); + if (iter->count > 0) { + iter->rights = p_new(pool, struct acl_rights, iter->count); + for (i = 0; i < iter->count; i++) + acl_rights_dup(&aclobj_rights[i], pool, &iter->rights[i]); + } else + iter->empty = TRUE; + return iter; +} + +bool acl_default_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r) +{ + if (iter->failed) + return FALSE; + + if (iter->idx == iter->count) + return FALSE; + *rights_r = iter->rights[iter->idx++]; + return TRUE; +} + +int acl_default_object_list_deinit(struct acl_object_list_iter *iter) +{ + int ret = 0; + if (iter->failed) + ret = -1; + else if (iter->empty) + ret = 0; + else + ret = 1; + + pool_unref(&iter->pool); + return ret; +} + +struct acl_mailbox_list_context * +acl_backend_nonowner_lookups_iter_init(struct acl_backend *backend) +{ + return backend->v.nonowner_lookups_iter_init(backend); +} + +bool acl_backend_nonowner_lookups_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r) +{ + return ctx->backend->v.nonowner_lookups_iter_next(ctx, name_r); +} + +int acl_backend_nonowner_lookups_iter_deinit(struct acl_mailbox_list_context **_ctx) +{ + struct acl_mailbox_list_context *ctx = *_ctx; + + *_ctx = NULL; + return ctx->backend->v.nonowner_lookups_iter_deinit(ctx); +} + +int acl_backend_nonowner_lookups_rebuild(struct acl_backend *backend) +{ + return backend->v.nonowner_lookups_rebuild(backend); +} + +void acl_rights_write_id(string_t *dest, const struct acl_rights *right) +{ + switch (right->id_type) { + case ACL_ID_ANYONE: + str_append(dest, ACL_ID_NAME_ANYONE); + break; + case ACL_ID_AUTHENTICATED: + str_append(dest, ACL_ID_NAME_AUTHENTICATED); + break; + case ACL_ID_OWNER: + str_append(dest, ACL_ID_NAME_OWNER); + break; + case ACL_ID_USER: + str_append(dest, ACL_ID_NAME_USER_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP: + str_append(dest, ACL_ID_NAME_GROUP_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP_OVERRIDE: + str_append(dest, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_TYPE_COUNT: + i_unreached(); + } +} + +const char *acl_rights_get_id(const struct acl_rights *right) +{ + string_t *str = t_str_new(32); + + acl_rights_write_id(str, right); + return str_c(str); +} + +static bool is_standard_right(const char *name) +{ + unsigned int i; + + for (i = 0; all_mailbox_rights[i] != NULL; i++) { + if (strcmp(all_mailbox_rights[i], name) == 0) + return TRUE; + } + return FALSE; +} + +int acl_rights_update_import(struct acl_rights_update *update, + const char *id, const char *const *rights, + const char **error_r) +{ + ARRAY_TYPE(const_string) dest_rights, dest_neg_rights, *dest; + unsigned int i, j; + + if (acl_identifier_parse(id, &update->rights) < 0) { + *error_r = t_strdup_printf("Invalid ID: %s", id); + return -1; + } + if (rights == NULL) { + update->modify_mode = ACL_MODIFY_MODE_CLEAR; + update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR; + return 0; + } + + t_array_init(&dest_rights, 8); + t_array_init(&dest_neg_rights, 8); + for (i = 0; rights[i] != NULL; i++) { + const char *right = rights[i]; + + if (right[0] != '-') + dest = &dest_rights; + else { + right++; + dest = &dest_neg_rights; + } + if (strcmp(right, "all") != 0) { + if (*right == ':') { + /* non-standard right */ + right++; + array_push_back(dest, &right); + } else if (is_standard_right(right)) { + array_push_back(dest, &right); + } else { + *error_r = t_strdup_printf("Invalid right '%s'", + right); + return -1; + } + } else { + for (j = 0; all_mailbox_rights[j] != NULL; j++) + array_push_back(dest, &all_mailbox_rights[j]); + } + } + if (array_count(&dest_rights) > 0) { + array_append_zero(&dest_rights); + update->rights.rights = array_front(&dest_rights); + } else if (update->modify_mode == ACL_MODIFY_MODE_REPLACE) { + update->modify_mode = ACL_MODIFY_MODE_CLEAR; + } + if (array_count(&dest_neg_rights) > 0) { + array_append_zero(&dest_neg_rights); + update->rights.neg_rights = array_front(&dest_neg_rights); + } else if (update->neg_modify_mode == ACL_MODIFY_MODE_REPLACE) { + update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR; + } + return 0; +} + +const char *acl_rights_export(const struct acl_rights *rights) +{ + string_t *str = t_str_new(128); + + if (rights->rights != NULL) + str_append(str, t_strarray_join(rights->rights, " ")); + if (rights->neg_rights != NULL && rights->neg_rights[0] != NULL) { + if (str_len(str) > 0) + str_append_c(str, ' '); + str_append_c(str, '-'); + str_append(str, t_strarray_join(rights->neg_rights, " -")); + } + return str_c(str); +} + +int acl_rights_parse_line(const char *line, pool_t pool, + struct acl_rights *rights_r, const char **error_r) +{ + const char *id_str, *const *right_names, *error = NULL; + + /* <id> [<imap acls>] [:<named acls>] */ + if (*line == '"') { + line++; + if (str_unescape_next(&line, &id_str) < 0 || + (line[0] != ' ' && line[0] != '\0')) { + *error_r = "Invalid quoted ID"; + return -1; + } + if (line[0] == ' ') + line++; + } else { + id_str = line; + line = strchr(id_str, ' '); + if (line == NULL) + line = ""; + else + id_str = t_strdup_until(id_str, line++); + } + + i_zero(rights_r); + + right_names = acl_right_names_parse(pool, line, &error); + if (*id_str != '-') + rights_r->rights = right_names; + else { + id_str++; + rights_r->neg_rights = right_names; + } + + if (acl_identifier_parse(id_str, rights_r) < 0) + error = t_strdup_printf("Unknown ID '%s'", id_str); + + if (error != NULL) { + *error_r = error; + return -1; + } + + rights_r->identifier = p_strdup(pool, rights_r->identifier); + return 0; +} + +void acl_rights_dup(const struct acl_rights *src, + pool_t pool, struct acl_rights *dest_r) +{ + i_zero(dest_r); + dest_r->id_type = src->id_type; + dest_r->identifier = p_strdup(pool, src->identifier); + dest_r->rights = src->rights == NULL ? NULL : + p_strarray_dup(pool, src->rights); + dest_r->neg_rights = src->neg_rights == NULL ? NULL : + p_strarray_dup(pool, src->neg_rights); + dest_r->global = src->global; +} + +int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2) +{ + int ret; + + if (r1->global != r2->global) { + /* globals have higher priority than locals */ + return r1->global ? 1 : -1; + } + + ret = (int)r1->id_type - (int)r2->id_type; + if (ret != 0) + return ret; + + return null_strcmp(r1->identifier, r2->identifier); +} + +void acl_rights_sort(struct acl_object *aclobj) +{ + struct acl_rights *rights; + unsigned int i, dest, count; + + if (!array_is_created(&aclobj->rights)) + return; + + array_sort(&aclobj->rights, acl_rights_cmp); + + /* merge identical identifiers */ + rights = array_get_modifiable(&aclobj->rights, &count); + for (dest = 0, i = 1; i < count; i++) { + if (acl_rights_cmp(&rights[i], &rights[dest]) == 0) { + /* add i's rights to dest and delete i */ + acl_right_names_merge(aclobj->rights_pool, + &rights[dest].rights, + rights[i].rights, FALSE); + acl_right_names_merge(aclobj->rights_pool, + &rights[dest].neg_rights, + rights[i].neg_rights, FALSE); + } else { + if (++dest != i) + rights[dest] = rights[i]; + } + } + if (++dest < count) + array_delete(&aclobj->rights, dest, count - dest); +} + +bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights) +{ + const char *const *p; + + if (rights->id_type == ACL_ID_OWNER) { + /* ignore owner rights */ + return FALSE; + } + + if (rights->rights == NULL) + return FALSE; + + for (p = rights->rights; *p != NULL; p++) { + if (strcmp(*p, MAIL_ACL_LOOKUP) == 0) + return TRUE; + } + return FALSE; +} + +int acl_identifier_parse(const char *line, struct acl_rights *rights) +{ + if (str_begins(line, ACL_ID_NAME_USER_PREFIX)) { + rights->id_type = ACL_ID_USER; + rights->identifier = line + 5; + } else if (strcmp(line, ACL_ID_NAME_OWNER) == 0) { + rights->id_type = ACL_ID_OWNER; + } else if (str_begins(line, ACL_ID_NAME_GROUP_PREFIX)) { + rights->id_type = ACL_ID_GROUP; + rights->identifier = line + 6; + } else if (str_begins(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX)) { + rights->id_type = ACL_ID_GROUP_OVERRIDE; + rights->identifier = line + 15; + } else if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) { + rights->id_type = ACL_ID_AUTHENTICATED; + } else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 || + strcmp(line, "anonymous") == 0) { + rights->id_type = ACL_ID_ANYONE; + } else { + return -1; + } + return 0; +} + +static const char *const * +acl_right_names_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr, + bool dup_strings) +{ + const char **ret, *const *rights; + unsigned int i, dest, count; + + /* sort the rights first so we can easily drop duplicates */ + array_sort(rights_arr, i_strcmp_p); + + /* @UNSAFE */ + rights = array_get(rights_arr, &count); + ret = p_new(pool, const char *, count + 1); + if (count > 0) { + ret[0] = rights[0]; + for (i = dest = 1; i < count; i++) { + if (strcmp(rights[i-1], rights[i]) != 0) + ret[dest++] = rights[i]; + } + ret[dest] = NULL; + if (dup_strings) { + for (i = 0; i < dest; i++) + ret[i] = p_strdup(pool, ret[i]); + } + } + return ret; +} + +const char *const * +acl_right_names_parse(pool_t pool, const char *acl, const char **error_r) +{ + ARRAY_TYPE(const_string) rights; + const char *const *names; + unsigned int i; + + /* parse IMAP ACL list */ + while (*acl == ' ' || *acl == '\t') + acl++; + + t_array_init(&rights, 64); + while (*acl != '\0' && *acl != ' ' && *acl != '\t' && *acl != ':') { + for (i = 0; acl_letter_map[i].letter != '\0'; i++) { + if (acl_letter_map[i].letter == *acl) + break; + } + + if (acl_letter_map[i].letter == '\0') { + *error_r = t_strdup_printf("Unknown ACL '%c'", *acl); + return NULL; + } + + array_push_back(&rights, &acl_letter_map[i].name); + acl++; + } + while (*acl == ' ' || *acl == '\t') acl++; + + if (*acl != '\0') { + /* parse our own extended ACLs */ + if (*acl != ':') { + *error_r = "Missing ':' prefix in ACL extensions"; + return NULL; + } + + names = t_strsplit_spaces(acl + 1, ", \t"); + for (; *names != NULL; names++) { + const char *name = p_strdup(pool, *names); + array_push_back(&rights, &name); + } + } + + return acl_right_names_alloc(pool, &rights, FALSE); +} + +void acl_right_names_write(string_t *dest, const char *const *rights) +{ + char c2[2]; + unsigned int i, j, pos; + + c2[1] = '\0'; + pos = str_len(dest); + for (i = 0; rights[i] != NULL; i++) { + /* use letters if possible */ + for (j = 0; acl_letter_map[j].name != NULL; j++) { + if (strcmp(rights[i], acl_letter_map[j].name) == 0) { + c2[0] = acl_letter_map[j].letter; + str_insert(dest, pos, c2); + pos++; + break; + } + } + if (acl_letter_map[j].name == NULL) { + /* fallback to full name */ + str_append_c(dest, ' '); + str_append(dest, rights[i]); + } + } + if (pos + 1 < str_len(dest)) { + c2[0] = ':'; + str_insert(dest, pos + 1, c2); + } +} + +void acl_right_names_merge(pool_t pool, const char *const **destp, + const char *const *src, bool dup_strings) +{ + const char *const *dest = *destp; + ARRAY_TYPE(const_string) rights; + unsigned int i; + + t_array_init(&rights, 64); + if (dest != NULL) { + for (i = 0; dest[i] != NULL; i++) + array_push_back(&rights, &dest[i]); + } + if (src != NULL) { + for (i = 0; src[i] != NULL; i++) + array_push_back(&rights, &src[i]); + } + + *destp = acl_right_names_alloc(pool, &rights, dup_strings); +} + +bool acl_right_names_modify(pool_t pool, + const char *const **rightsp, + const char *const *modify_rights, + enum acl_modify_mode modify_mode) +{ + const char *const *old_rights = *rightsp; + const char *const *new_rights = NULL; + const char *null = NULL; + ARRAY_TYPE(const_string) rights; + unsigned int i, j; + + if (modify_rights == NULL && modify_mode != ACL_MODIFY_MODE_CLEAR) { + /* nothing to do here */ + return FALSE; + } + + switch (modify_mode) { + case ACL_MODIFY_MODE_REMOVE: + if (old_rights == NULL || *old_rights == NULL) { + /* nothing to do */ + return FALSE; + } + t_array_init(&rights, 64); + for (i = 0; old_rights[i] != NULL; i++) { + for (j = 0; modify_rights[j] != NULL; j++) { + if (strcmp(old_rights[i], modify_rights[j]) == 0) + break; + } + if (modify_rights[j] == NULL) + array_push_back(&rights, &old_rights[i]); + } + new_rights = &null; + modify_rights = array_count(&rights) == 0 ? NULL : + array_front(&rights); + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_ADD: + new_rights = old_rights; + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_REPLACE: + new_rights = &null; + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_CLEAR: + if (*rightsp == NULL) { + /* ACL didn't exist before either */ + return FALSE; + } + *rightsp = NULL; + return TRUE; + } + i_assert(new_rights != NULL); + *rightsp = new_rights; + + if (old_rights == NULL) + return new_rights[0] != NULL; + + /* see if anything changed */ + for (i = 0; old_rights[i] != NULL && new_rights[i] != NULL; i++) { + if (strcmp(old_rights[i], new_rights[i]) != 0) + return TRUE; + } + return old_rights[i] != NULL || new_rights[i] != NULL; +} + +static void apply_owner_default_rights(struct acl_object *aclobj) +{ + struct acl_rights_update ru; + const char *null = NULL; + + i_zero(&ru); + ru.modify_mode = ACL_MODIFY_MODE_REPLACE; + ru.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + ru.rights.id_type = ACL_ID_OWNER; + ru.rights.rights = aclobj->backend->default_rights; + ru.rights.neg_rights = &null; + acl_cache_update(aclobj->backend->cache, aclobj->name, &ru); +} + +void acl_object_rebuild_cache(struct acl_object *aclobj) +{ + struct acl_rights_update ru; + enum acl_modify_mode add_mode; + const struct acl_rights *rights, *prev_match = NULL; + unsigned int i, count; + bool first_global = TRUE; + + acl_cache_flush(aclobj->backend->cache, aclobj->name); + + if (!array_is_created(&aclobj->rights)) + return; + + /* Rights are sorted by their 1) locals first, globals next, + 2) acl_id_type. We'll apply only the rights matching ourself. + + Every time acl_id_type or local/global changes, the new ACLs will + replace all of the existing ACLs. Basically this means that if + user belongs to multiple matching groups or group-overrides, their + ACLs are merged. In all other situations the ACLs are replaced + (because there aren't duplicate rights entries and a user can't + match multiple usernames). */ + i_zero(&ru); + rights = array_get(&aclobj->rights, &count); + if (!acl_backend_user_is_owner(aclobj->backend)) + i = 0; + else { + /* we're the owner. skip over all rights entries until we + reach ACL_ID_OWNER or higher, or alternatively when we + reach a global ACL (even ACL_ID_ANYONE overrides owner's + rights if it's global) */ + for (i = 0; i < count; i++) { + if (rights[i].id_type >= ACL_ID_OWNER || + rights[i].global) + break; + } + apply_owner_default_rights(aclobj); + /* now continue applying the rest of the rights, + if there are any */ + } + for (; i < count; i++) { + if (!acl_backend_rights_match_me(aclobj->backend, &rights[i])) + continue; + + if (prev_match == NULL || + prev_match->id_type != rights[i].id_type || + prev_match->global != rights[i].global) { + /* replace old ACLs */ + add_mode = ACL_MODIFY_MODE_REPLACE; + } else { + /* merging to existing ACLs */ + i_assert(rights[i].id_type == ACL_ID_GROUP || + rights[i].id_type == ACL_ID_GROUP_OVERRIDE); + add_mode = ACL_MODIFY_MODE_ADD; + } + prev_match = &rights[i]; + + /* If [neg_]rights is NULL it needs to be ignored. + The easiest way to do that is to just mark it with + REMOVE mode */ + ru.modify_mode = rights[i].rights == NULL ? + ACL_MODIFY_MODE_REMOVE : add_mode; + ru.neg_modify_mode = rights[i].neg_rights == NULL ? + ACL_MODIFY_MODE_REMOVE : add_mode; + ru.rights = rights[i]; + if (rights[i].global && first_global) { + /* first global: reset negative ACLs so local ACLs + can't mess things up via them */ + first_global = FALSE; + ru.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + } + acl_cache_update(aclobj->backend->cache, aclobj->name, &ru); + } +} + +void acl_object_remove_all_access(struct acl_object *aclobj) +{ + static const char *null = NULL; + struct acl_rights rights; + + i_zero(&rights); + rights.id_type = ACL_ID_ANYONE; + rights.rights = &null; + array_push_back(&aclobj->rights, &rights); + + rights.id_type = ACL_ID_OWNER; + rights.rights = &null; + array_push_back(&aclobj->rights, &rights); +} + +void acl_object_add_global_acls(struct acl_object *aclobj) +{ + struct acl_backend *backend = aclobj->backend; + const char *vname, *error; + + if (mailbox_list_is_valid_name(backend->list, aclobj->name, &error)) + vname = mailbox_list_get_vname(backend->list, aclobj->name); + else + vname = ""; + + acl_global_file_get(backend->global_file, vname, + aclobj->rights_pool, &aclobj->rights); +} diff --git a/src/plugins/acl/acl-api.h b/src/plugins/acl/acl-api.h new file mode 100644 index 0000000..7b19a98 --- /dev/null +++ b/src/plugins/acl/acl-api.h @@ -0,0 +1,167 @@ +#ifndef ACL_API_H +#define ACL_API_H + +#include <sys/stat.h> + +struct mailbox_list; +struct mail_storage; +struct mailbox; +struct acl_object; + +/* Show mailbox in mailbox list. Allow subscribing to it. */ +#define MAIL_ACL_LOOKUP "lookup" +/* Allow opening mailbox for reading */ +#define MAIL_ACL_READ "read" +/* Allow permanent flag changes (except for seen/deleted). + If not set, doesn't allow save/copy to set any flags either. */ +#define MAIL_ACL_WRITE "write" +/* Allow permanent seen-flag changes */ +#define MAIL_ACL_WRITE_SEEN "write-seen" +/* Allow permanent deleted-flag changes */ +#define MAIL_ACL_WRITE_DELETED "write-deleted" +/* Allow saving and copying mails into the mailbox */ +#define MAIL_ACL_INSERT "insert" +/* Allow posting mails to the mailbox (e.g. Sieve fileinto) */ +#define MAIL_ACL_POST "post" +/* Allow expunging mails */ +#define MAIL_ACL_EXPUNGE "expunge" +/* Allow creating child mailboxes */ +#define MAIL_ACL_CREATE "create" +/* Allow deleting this mailbox */ +#define MAIL_ACL_DELETE "delete" +/* Allow changing ACL state in this mailbox */ +#define MAIL_ACL_ADMIN "admin" + +#define MAILBOX_ATTRIBUTE_PREFIX_ACL \ + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"acl/" + +/* ACL identifiers in override order */ +enum acl_id_type { + /* Anyone's rights, including anonymous's. + identifier name is ignored. */ + ACL_ID_ANYONE, + /* Authenticate users' rights. identifier name is ignored. */ + ACL_ID_AUTHENTICATED, + /* Group's rights */ + ACL_ID_GROUP, + /* Owner's rights, used when user is the storage's owner. + identifier name is ignored. */ + ACL_ID_OWNER, + /* User's rights */ + ACL_ID_USER, + /* Same as group's rights, but also overrides user's rights */ + ACL_ID_GROUP_OVERRIDE, + + ACL_ID_TYPE_COUNT +}; + +enum acl_modify_mode { + /* Remove rights from existing ACL */ + ACL_MODIFY_MODE_REMOVE = 0, + /* Add rights to existing ACL (or create a new one) */ + ACL_MODIFY_MODE_ADD, + /* Replace existing ACL with given rights */ + ACL_MODIFY_MODE_REPLACE, + /* Clear all the rights from an existing ACL */ + ACL_MODIFY_MODE_CLEAR +}; + +struct acl_rights { + /* Type of the identifier, user/group */ + enum acl_id_type id_type; + /* Identifier, eg. username / group name */ + const char *identifier; + + /* Rights assigned. NULL entry can be ignored, but { NULL } means user + has no rights. */ + const char *const *rights; + /* Negative rights assigned */ + const char *const *neg_rights; + + /* These rights are global for all users */ + bool global:1; +}; +ARRAY_DEFINE_TYPE(acl_rights, struct acl_rights); + +struct acl_rights_update { + struct acl_rights rights; + + enum acl_modify_mode modify_mode; + enum acl_modify_mode neg_modify_mode; + /* These changes' "last changed" timestamp */ + time_t last_change; +}; + +/* data contains the information needed to initialize ACL backend. If username + is NULL, it means the user is anonymous. Username and groups are matched + case-sensitively. */ +struct acl_backend * +acl_backend_init(const char *data, struct mailbox_list *list, + const char *acl_username, const char *const *groups, + bool owner); +void acl_backend_deinit(struct acl_backend **backend); + +/* Returns the acl_username passed to acl_backend_init(). Note that with + anonymous users NULL is returned. */ +const char *acl_backend_get_acl_username(struct acl_backend *backend); + +/* Returns TRUE if user isn't anonymous. */ +bool acl_backend_user_is_authenticated(struct acl_backend *backend); +/* Returns TRUE if user owns the storage. */ +bool acl_backend_user_is_owner(struct acl_backend *backend); +/* Returns TRUE if given name matches the ACL user name. */ +bool acl_backend_user_name_equals(struct acl_backend *backend, + const char *username); +/* Returns TRUE if ACL user is in given group. */ +bool acl_backend_user_is_in_group(struct acl_backend *backend, + const char *group_name); +/* Returns index for the right name. If it doesn't exist, it's created. */ +unsigned int acl_backend_lookup_right(struct acl_backend *backend, + const char *right); +/* Returns TRUE if acl_rights matches backend user. */ +bool acl_backend_rights_match_me(struct acl_backend *backend, + const struct acl_rights *rights); + +/* List mailboxes that have lookup right to some non-owners. */ +struct acl_mailbox_list_context * +acl_backend_nonowner_lookups_iter_init(struct acl_backend *backend); +bool acl_backend_nonowner_lookups_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r); +int +acl_backend_nonowner_lookups_iter_deinit(struct acl_mailbox_list_context **ctx); + +/* Force a rebuild for nonowner lookups index */ +int acl_backend_nonowner_lookups_rebuild(struct acl_backend *backend); + +struct acl_object *acl_object_init_from_name(struct acl_backend *backend, + const char *name); +struct acl_object *acl_object_init_from_parent(struct acl_backend *backend, + const char *child_name); +void acl_object_deinit(struct acl_object **aclobj); + +/* Returns 1 if we have the requested rights, 0 if not, or -1 if internal + error occurred. */ +int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx); +/* Returns 0 = ok, -1 = internal error */ +int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool, + const char *const **rights_r); +/* Returns the default rights for the object. */ +const char *const *acl_object_get_default_rights(struct acl_object *aclobj); +/* Returns timestamp of when the ACLs were last changed for this object, + or 0 = never. */ +int acl_object_last_changed(struct acl_object *aclobj, time_t *last_changed_r); + +/* Update ACL of given object. */ +int acl_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update); + +/* List all identifiers. */ +struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj); +bool acl_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r); +int acl_object_list_deinit(struct acl_object_list_iter **iter); + +/* Returns the canonical ID for the right. */ +const char *acl_rights_get_id(const struct acl_rights *right); + +#endif diff --git a/src/plugins/acl/acl-attributes.c b/src/plugins/acl/acl-attributes.c new file mode 100644 index 0000000..515ff42 --- /dev/null +++ b/src/plugins/acl/acl-attributes.c @@ -0,0 +1,233 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "mail-storage-private.h" +#include "acl-api-private.h" +#include "acl-plugin.h" +#include "acl-storage.h" + +struct acl_mailbox_attribute_iter { + struct mailbox_attribute_iter iter; + struct mailbox_attribute_iter *super; + + struct acl_object_list_iter *acl_iter; + string_t *acl_name; + + bool failed; +}; + +static int +acl_attribute_update_acl(struct mailbox_transaction_context *t, const char *key, + const struct mail_attribute_value *value) +{ + const char *value_str, *id, *const *rights, *error; + struct acl_rights_update update; + + /* for now allow only dsync to update ACLs this way. + if this check is removed, it should be replaced by a setting, since + some admins may still have configured Dovecot using dovecot-acl + files directly that they don't want users to update. and in any case + ACL_STORAGE_RIGHT_ADMIN must be checked then. */ + if (!t->box->storage->user->dsyncing) { + mail_storage_set_error(t->box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + return -1; + } + + if (mailbox_attribute_value_to_string(t->box->storage, value, + &value_str) < 0) + return -1; + + i_zero(&update); + update.modify_mode = ACL_MODIFY_MODE_REPLACE; + update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + update.last_change = value->last_change; + id = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL); + rights = value_str == NULL ? NULL : t_strsplit(value_str, " "); + if (acl_rights_update_import(&update, id, rights, &error) < 0) { + mail_storage_set_error(t->box->storage, MAIL_ERROR_PARAMS, error); + return -1; + } + /* FIXME: this should actually be done only at commit().. */ + return acl_mailbox_update_acl(t, &update); +} + +static int acl_attribute_get_acl(struct mailbox *box, const char *key, + struct mail_attribute_value *value_r) +{ + struct acl_object *aclobj = acl_mailbox_get_aclobj(box); + struct acl_object_list_iter *iter; + struct acl_rights rights, wanted_rights; + const char *id; + int ret = 0; + + i_zero(value_r); + + if (!box->storage->user->dsyncing) { + mail_storage_set_error(box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + return -1; + } + /* set last_change for all ACL objects, even if they don't exist + (because they could have been removed by the last change, and dsync + can use this information) */ + (void)acl_object_last_changed(aclobj, &value_r->last_change); + + i_zero(&wanted_rights); + id = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL); + if (acl_identifier_parse(id, &wanted_rights) < 0) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + t_strdup_printf("Invalid ID: %s", id)); + return -1; + } + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (!rights.global && + rights.id_type == wanted_rights.id_type && + null_strcmp(rights.identifier, wanted_rights.identifier) == 0) { + value_r->value = acl_rights_export(&rights); + ret = 1; + break; + } + } + /* the return value here cannot be used, because this function + needs to return whether it actually matched something + or not */ + if (acl_object_list_deinit(&iter) < 0) { + mail_storage_set_internal_error(box->storage); + ret = -1; + } + return ret; +} + +static int acl_have_attribute_rights(struct mailbox *box) +{ + int ret; + + if (box->deleting) { + /* deleting attributes during mailbox deletion */ + return 1; + } + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP); + if (ret <= 0) { + if (ret < 0) + return -1; + mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND, + T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname)); + return -1; + } + + return acl_mailbox_have_extra_attribute_rights(box) ? 0 : -1; +} + +int acl_attribute_set(struct mailbox_transaction_context *t, + enum mail_attribute_type type, const char *key, + const struct mail_attribute_value *value) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(t->box); + + if (acl_have_attribute_rights(t->box) < 0) + return -1; + if (str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_ACL)) + return acl_attribute_update_acl(t, key, value); + return abox->module_ctx.super.attribute_set(t, type, key, value); +} + +int acl_attribute_get(struct mailbox *box, + enum mail_attribute_type type, const char *key, + struct mail_attribute_value *value_r) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + if (acl_have_attribute_rights(box) < 0) + return -1; + if (str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_ACL)) + return acl_attribute_get_acl(box, key, value_r); + return abox->module_ctx.super.attribute_get(box, type, key, value_r); +} + +struct mailbox_attribute_iter * +acl_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type, + const char *prefix) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + struct acl_mailbox_attribute_iter *aiter; + + aiter = i_new(struct acl_mailbox_attribute_iter, 1); + aiter->iter.box = box; + if (acl_have_attribute_rights(box) < 0) + aiter->failed = TRUE; + else { + aiter->super = abox->module_ctx.super. + attribute_iter_init(box, type, prefix); + if (box->storage->user->dsyncing && + type == MAIL_ATTRIBUTE_TYPE_SHARED && + str_begins(MAILBOX_ATTRIBUTE_PREFIX_ACL, prefix)) { + aiter->acl_iter = acl_object_list_init(abox->aclobj); + aiter->acl_name = str_new(default_pool, 128); + str_append(aiter->acl_name, MAILBOX_ATTRIBUTE_PREFIX_ACL); + } + } + return &aiter->iter; +} + +static const char * +acl_attribute_iter_next_acl(struct acl_mailbox_attribute_iter *aiter) +{ + struct acl_rights rights; + + if (aiter->failed) + return NULL; + + while (acl_object_list_next(aiter->acl_iter, &rights)) { + if (rights.global) + continue; + str_truncate(aiter->acl_name, strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL)); + acl_rights_write_id(aiter->acl_name, &rights); + return str_c(aiter->acl_name); + } + if (acl_object_list_deinit(&aiter->acl_iter) < 0) { + mail_storage_set_internal_error(aiter->iter.box->storage); + aiter->failed = TRUE; + } + return NULL; +} + +const char *acl_attribute_iter_next(struct mailbox_attribute_iter *iter) +{ + struct acl_mailbox_attribute_iter *aiter = + (struct acl_mailbox_attribute_iter *)iter; + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(iter->box); + const char *key; + + if (aiter->super == NULL) + return NULL; + if (aiter->acl_iter != NULL) { + if ((key = acl_attribute_iter_next_acl(aiter)) != NULL) + return key; + } + return abox->module_ctx.super.attribute_iter_next(aiter->super); +} + +int acl_attribute_iter_deinit(struct mailbox_attribute_iter *iter) +{ + struct acl_mailbox_attribute_iter *aiter = + (struct acl_mailbox_attribute_iter *)iter; + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(iter->box); + int ret = aiter->failed ? -1 : 0; + + if (aiter->super != NULL) { + if (abox->module_ctx.super.attribute_iter_deinit(aiter->super) < 0) + ret = -1; + } + if (aiter->acl_iter != NULL && acl_object_list_deinit(&aiter->acl_iter) < 0) { + mail_storage_set_internal_error(aiter->iter.box->storage); + ret = -1; + } + str_free(&aiter->acl_name); + i_free(aiter); + return ret; +} diff --git a/src/plugins/acl/acl-backend-vfile-acllist.c b/src/plugins/acl/acl-backend-vfile-acllist.c new file mode 100644 index 0000000..c6029a2 --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile-acllist.c @@ -0,0 +1,424 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "safe-mkstemp.h" +#include "istream.h" +#include "ostream.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "acl-plugin.h" +#include "acl-cache.h" +#include "acl-lookup-dict.h" +#include "acl-backend-vfile.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +struct acl_mailbox_list_context_vfile { + struct acl_mailbox_list_context ctx; + + unsigned int idx; +}; + +static void +acllist_clear(struct acl_backend_vfile *backend, uoff_t file_size) +{ + if (backend->acllist_pool == NULL) { + backend->acllist_pool = + pool_alloconly_create("vfile acllist", + I_MAX(file_size / 2, 128)); + i_array_init(&backend->acllist, I_MAX(16, file_size / 60)); + } else { + p_clear(backend->acllist_pool); + array_clear(&backend->acllist); + } +} + +static bool acl_list_get_root_dir(struct acl_backend_vfile *backend, + const char **root_dir_r, + enum mailbox_list_path_type *type_r) +{ + struct mail_storage *storage; + const char *rootdir, *maildir; + enum mailbox_list_path_type type; + + if (backend->backend.globals_only) + return FALSE; + + storage = mailbox_list_get_namespace(backend->backend.list)->storage; + type = mail_storage_get_acl_list_path_type(storage); + if (!mailbox_list_get_root_path(backend->backend.list, type, &rootdir)) + return FALSE; + *type_r = type; + + if (type == MAILBOX_LIST_PATH_TYPE_DIR && + mail_storage_is_mailbox_file(storage)) { + maildir = mailbox_list_get_root_forced(backend->backend.list, + MAILBOX_LIST_PATH_TYPE_MAILBOX); + if (strcmp(maildir, rootdir) == 0) { + /* dovecot-acl-list would show up as a mailbox if we + created it to root dir. since we don't really have + any other good alternatives, place it to control + dir */ + rootdir = mailbox_list_get_root_forced(backend->backend.list, + MAILBOX_LIST_PATH_TYPE_CONTROL); + *type_r = MAILBOX_LIST_PATH_TYPE_CONTROL; + } + } + *root_dir_r = rootdir; + return TRUE; +} + +static bool acl_list_get_path(struct acl_backend_vfile *backend, + const char **path_r) +{ + enum mailbox_list_path_type type; + const char *root_dir; + + if (!acl_list_get_root_dir(backend, &root_dir, &type)) + return FALSE; + *path_r = t_strconcat(root_dir, "/"ACLLIST_FILENAME, NULL); + return TRUE; +} + +static int acl_backend_vfile_acllist_read(struct acl_backend_vfile *backend) +{ + struct acl_backend_vfile_acllist acllist; + struct istream *input; + struct stat st; + const char *path, *line, *p; + int fd, ret = 0; + + backend->acllist_last_check = ioloop_time; + + if (!acl_list_get_path(backend, &path)) { + /* we're never going to build acllist for this namespace. */ + acllist_clear(backend, 0); + return 0; + } + + if (backend->acllist_mtime != 0) { + /* see if the file's mtime has changed */ + if (stat(path, &st) < 0) { + if (errno == ENOENT) + backend->acllist_mtime = 0; + else + i_error("stat(%s) failed: %m", path); + return -1; + } + if (st.st_mtime == backend->acllist_mtime) + return 0; + } + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + backend->acllist_mtime = 0; + return -1; + } + i_error("open(%s) failed: %m", path); + return -1; + } + if (fstat(fd, &st) < 0) { + i_error("fstat(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + backend->acllist_mtime = st.st_mtime; + acllist_clear(backend, st.st_size); + + input = i_stream_create_fd(fd, SIZE_MAX); + while ((line = i_stream_read_next_line(input)) != NULL) { + acllist.mtime = 0; + for (p = line; *p >= '0' && *p <= '9'; p++) + acllist.mtime = acllist.mtime * 10 + (*p - '0'); + + if (p == line || *p != ' ' || p[1] == '\0') { + i_error("Broken acllist file: %s", path); + i_unlink_if_exists(path); + i_close_fd(&fd); + return -1; + } + acllist.name = p_strdup(backend->acllist_pool, p + 1); + array_push_back(&backend->acllist, &acllist); + } + if (input->stream_errno != 0) + ret = -1; + i_stream_destroy(&input); + + if (close(fd) < 0) + i_error("close(%s) failed: %m", path); + return ret; +} + +void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend) +{ + i_assert(!backend->iterating_acllist); + + if (backend->acllist_last_check + + (time_t)backend->cache_secs > ioloop_time) + return; + + if (acl_backend_vfile_acllist_read(backend) < 0) { + acllist_clear(backend, 0); + if (!backend->rebuilding_acllist) + (void)acl_backend_vfile_acllist_rebuild(backend); + } +} + +static int +acllist_append(struct acl_backend_vfile *backend, struct ostream *output, + const char *vname) +{ + struct acl_object *aclobj; + struct acl_object_list_iter *iter; + struct acl_rights rights; + struct acl_backend_vfile_acllist acllist; + const char *name; + int ret; + + name = mailbox_list_get_storage_name(backend->backend.list, vname); + acl_cache_flush(backend->backend.cache, name); + aclobj = acl_object_init_from_name(&backend->backend, name); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (acl_rights_has_nonowner_lookup_changes(&rights)) + break; + } + ret = acl_object_list_deinit(&iter); + + if (acl_backend_vfile_object_get_mtime(aclobj, &acllist.mtime) < 0) + ret = -1; + + if (ret > 0) { + acllist.name = p_strdup(backend->acllist_pool, name); + array_push_back(&backend->acllist, &acllist); + + o_stream_nsend_str(output, t_strdup_printf( + "%s %s\n", dec2str(acllist.mtime), name)); + } + acl_object_deinit(&aclobj); + return ret < 0 ? -1 : 0; +} + +static int +acl_backend_vfile_acllist_try_rebuild(struct acl_backend_vfile *backend) +{ + struct mailbox_list *list = backend->backend.list; + struct mail_namespace *ns; + struct mailbox_list_iterate_context *iter; + enum mailbox_list_path_type type; + const struct mailbox_info *info; + const char *rootdir, *acllist_path; + struct ostream *output; + struct stat st; + struct mailbox_permissions perm; + string_t *path; + int fd, ret; + + i_assert(!backend->rebuilding_acllist); + + if (!acl_list_get_root_dir(backend, &rootdir, &type)) + return 0; + + ns = mailbox_list_get_namespace(list); + if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) { + /* we can't write anything here */ + return 0; + } + + path = t_str_new(256); + str_printfa(path, "%s/%s", rootdir, mailbox_list_get_temp_prefix(list)); + + /* Build it into a temporary file and rename() over. There's no need + to use locking, because even if multiple processes are rebuilding + the file at the same time the result should be the same. */ + mailbox_list_get_root_permissions(list, &perm); + fd = safe_mkstemp_group(path, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + if (fd == -1 && errno == ENOENT) { + if (mailbox_list_mkdir_root(backend->backend.list, + rootdir, type) < 0) + return -1; + fd = safe_mkstemp_group(path, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + } + if (fd == -1) { + if (errno == EACCES) { + /* Ignore silently if we can't create it */ + return 0; + } + i_error("dovecot-acl-list creation failed: " + "safe_mkstemp(%s) failed: %m", str_c(path)); + return -1; + } + output = o_stream_create_fd_file(fd, 0, FALSE); + o_stream_cork(output); + + ret = 0; + acllist_clear(backend, 0); + + backend->rebuilding_acllist = TRUE; + iter = mailbox_list_iter_init(list, "*", + MAILBOX_LIST_ITER_RAW_LIST | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while (ret == 0 && (info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN { + ret = acllist_append(backend, output, info->vname); + } T_END; + + if (o_stream_finish(output) < 0) { + i_error("write(%s) failed: %s", str_c(path), + o_stream_get_error(output)); + ret = -1; + } + if (mailbox_list_iter_deinit(&iter) < 0) + ret = -1; + o_stream_destroy(&output); + + if (ret == 0) { + if (fstat(fd, &st) < 0) { + i_error("fstat(%s) failed: %m", str_c(path)); + ret = -1; + } + } + if (close(fd) < 0) { + i_error("close(%s) failed: %m", str_c(path)); + ret = -1; + } + + if (ret == 0) { + if (!acl_list_get_path(backend, &acllist_path)) + i_unreached(); + if (rename(str_c(path), acllist_path) < 0) { + i_error("rename(%s, %s) failed: %m", + str_c(path), acllist_path); + ret = -1; + } + } + if (ret == 0) { + struct acl_user *auser = ACL_USER_CONTEXT(ns->user); + i_assert(auser != NULL); + backend->acllist_mtime = st.st_mtime; + backend->acllist_last_check = ioloop_time; + /* FIXME: dict rebuild is expensive, try to avoid it */ + (void)acl_lookup_dict_rebuild(auser->acl_lookup_dict); + } else { + acllist_clear(backend, 0); + i_unlink_if_exists(str_c(path)); + } + backend->rebuilding_acllist = FALSE; + return ret; +} + +int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend) +{ + const char *acllist_path; + + if (acl_backend_vfile_acllist_try_rebuild(backend) == 0) + return 0; + else { + /* delete it to make sure it gets rebuilt later */ + if (!acl_list_get_path(backend, &acllist_path)) + i_unreached(); + i_unlink_if_exists(acllist_path); + return -1; + } +} + +static const struct acl_backend_vfile_acllist * +acl_backend_vfile_acllist_find(struct acl_backend_vfile *backend, + const char *name) +{ + const struct acl_backend_vfile_acllist *acllist; + + array_foreach(&backend->acllist, acllist) { + if (strcmp(acllist->name, name) == 0) + return acllist; + } + return NULL; +} + +void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend, + const char *name, time_t mtime) +{ + const struct acl_backend_vfile_acllist *acllist; + + if (backend->rebuilding_acllist || backend->iterating_acllist) + return; + + acl_backend_vfile_acllist_refresh(backend); + acllist = acl_backend_vfile_acllist_find(backend, name); + if (acllist != NULL && acllist->mtime != mtime) + (void)acl_backend_vfile_acllist_rebuild(backend); +} + +struct acl_mailbox_list_context * +acl_backend_vfile_nonowner_iter_init(struct acl_backend *_backend) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct acl_mailbox_list_context_vfile *ctx; + + acl_backend_vfile_acllist_refresh(backend); + + ctx = i_new(struct acl_mailbox_list_context_vfile, 1); + ctx->ctx.backend = _backend; + backend->iterating_acllist = TRUE; + return &ctx->ctx; +} + +bool acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *_ctx, + const char **name_r) +{ + struct acl_mailbox_list_context_vfile *ctx = + (struct acl_mailbox_list_context_vfile *)_ctx; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_ctx->backend; + const struct acl_backend_vfile_acllist *acllist; + unsigned int count; + + if (_ctx->failed) + return FALSE; + + acllist = array_get(&backend->acllist, &count); + if (count == 0) + _ctx->empty = TRUE; + if (ctx->idx == count) + return FALSE; + + *name_r = acllist[ctx->idx++].name; + return TRUE; +} + +int +acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)ctx->backend; + int ret; + + backend->iterating_acllist = FALSE; + if (ctx->failed) + ret = -1; + else if (ctx->empty) + ret = 0; + else + ret = 1; + i_free(ctx); + return ret; +} + +int acl_backend_vfile_nonowner_lookups_rebuild(struct acl_backend *_backend) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + + return acl_backend_vfile_acllist_rebuild(backend); +} diff --git a/src/plugins/acl/acl-backend-vfile-update.c b/src/plugins/acl/acl-backend-vfile-update.c new file mode 100644 index 0000000..7c48c4e --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile-update.c @@ -0,0 +1,260 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "bsearch-insert-pos.h" +#include "ioloop.h" +#include "str.h" +#include "strescape.h" +#include "file-dotlock.h" +#include "ostream.h" +#include "mail-storage.h" +#include "acl-cache.h" +#include "acl-backend-vfile.h" + +#include <utime.h> +#include <sys/stat.h> + +static struct dotlock_settings dotlock_set = { + .timeout = 30, + .stale_timeout = 120 +}; + +static int acl_backend_vfile_update_begin(struct acl_object_vfile *aclobj, + struct dotlock **dotlock_r) +{ + struct acl_object *_aclobj = &aclobj->aclobj; + struct mailbox_permissions perm; + int fd; + + if (aclobj->local_path == NULL) { + i_error("Can't update acl object '%s': No local acl file path", + aclobj->aclobj.name); + return -1; + } + + /* first lock the ACL file */ + mailbox_list_get_permissions(_aclobj->backend->list, + _aclobj->name, &perm); + fd = file_dotlock_open_group(&dotlock_set, aclobj->local_path, 0, + perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin, dotlock_r); + if (fd == -1) { + i_error("file_dotlock_open(%s) failed: %m", aclobj->local_path); + return -1; + } + + /* locked successfully, re-read the existing file to make sure we + don't lose any changes. */ + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + if (_aclobj->backend->v.object_refresh_cache(_aclobj) < 0) { + file_dotlock_delete(dotlock_r); + return -1; + } + return fd; +} + +static bool +vfile_object_modify_right(struct acl_object *aclobj, unsigned int idx, + const struct acl_rights_update *update) +{ + struct acl_rights *right; + bool c1, c2; + + right = array_idx_modifiable(&aclobj->rights, idx); + c1 = acl_right_names_modify(aclobj->rights_pool, &right->rights, + update->rights.rights, update->modify_mode); + c2 = acl_right_names_modify(aclobj->rights_pool, &right->neg_rights, + update->rights.neg_rights, + update->neg_modify_mode); + + if (right->rights == NULL && right->neg_rights == NULL) { + /* this identifier no longer exists */ + array_delete(&aclobj->rights, idx, 1); + c1 = TRUE; + } + return c1 || c2; +} + +static bool +vfile_object_add_right(struct acl_object *aclobj, unsigned int idx, + const struct acl_rights_update *update) +{ + struct acl_rights right; + bool c1, c2; + + if (update->modify_mode == ACL_MODIFY_MODE_REMOVE && + update->neg_modify_mode == ACL_MODIFY_MODE_REMOVE) { + /* nothing to do */ + return FALSE; + } + + i_zero(&right); + right.id_type = update->rights.id_type; + right.identifier = p_strdup(aclobj->rights_pool, + update->rights.identifier); + + c1 = acl_right_names_modify(aclobj->rights_pool, &right.rights, + update->rights.rights, update->modify_mode); + c2 = acl_right_names_modify(aclobj->rights_pool, &right.neg_rights, + update->rights.neg_rights, + update->neg_modify_mode); + if (c1 || c2) { + array_insert(&aclobj->rights, idx, &right, 1); + return TRUE; + } + return FALSE; +} + +static void +vfile_write_right(string_t *dest, const struct acl_rights *right, + bool neg) +{ + const char *const *rights = neg ? right->neg_rights : right->rights; + + if (neg) str_append_c(dest,'-'); + acl_rights_write_id(dest, right); + + if (strchr(str_c(dest), ' ') != NULL) T_BEGIN { + /* need to escape it */ + const char *escaped = t_strdup(str_escape(str_c(dest))); + str_truncate(dest, 0); + str_printfa(dest, "\"%s\"", escaped); + } T_END; + + str_append_c(dest, ' '); + acl_right_names_write(dest, rights); + str_append_c(dest, '\n'); +} + +static int +acl_backend_vfile_update_write(struct acl_object *aclobj, + int fd, const char *path) +{ + struct ostream *output; + string_t *str; + const struct acl_rights *rights; + unsigned int i, count; + int ret = 0; + + output = o_stream_create_fd_file(fd, 0, FALSE); + o_stream_cork(output); + + str = str_new(default_pool, 256); + /* rights are sorted with globals at the end, so we can stop at the + first global */ + rights = array_get(&aclobj->rights, &count); + for (i = 0; i < count && !rights[i].global; i++) { + if (rights[i].rights != NULL) { + vfile_write_right(str, &rights[i], FALSE); + o_stream_nsend(output, str_data(str), str_len(str)); + str_truncate(str, 0); + } + if (rights[i].neg_rights != NULL) { + vfile_write_right(str, &rights[i], TRUE); + o_stream_nsend(output, str_data(str), str_len(str)); + str_truncate(str, 0); + } + } + str_free(&str); + if (o_stream_finish(output) < 0) { + i_error("write(%s) failed: %s", path, + o_stream_get_error(output)); + ret = -1; + } + o_stream_destroy(&output); + /* we really don't want to lose ACL files' contents, so fsync() always + before renaming */ + if (fsync(fd) < 0) { + i_error("fsync(%s) failed: %m", path); + ret = -1; + } + return ret; +} + +static void acl_backend_vfile_update_cache(struct acl_object *_aclobj, int fd) +{ + struct acl_backend_vfile_validity *validity; + struct stat st; + + if (fstat(fd, &st) < 0) { + /* we'll just recalculate or fail it later */ + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return; + } + + validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + validity->local_validity.last_read_time = ioloop_time; + validity->local_validity.last_mtime = st.st_mtime; + validity->local_validity.last_size = st.st_size; +} + +int acl_backend_vfile_object_update(struct acl_object *_aclobj, + const struct acl_rights_update *update) +{ + struct acl_object_vfile *aclobj = + (struct acl_object_vfile *)_aclobj; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_aclobj->backend; + struct acl_backend_vfile_validity *validity; + struct dotlock *dotlock; + struct utimbuf ut; + time_t orig_mtime; + const char *path; + unsigned int i; + int fd; + bool changed; + + /* global ACLs can't be updated here */ + i_assert(!update->rights.global); + + fd = acl_backend_vfile_update_begin(aclobj, &dotlock); + if (fd == -1) + return -1; + + if (!array_bsearch_insert_pos(&_aclobj->rights, &update->rights, + acl_rights_cmp, &i)) + changed = vfile_object_add_right(_aclobj, i, update); + else + changed = vfile_object_modify_right(_aclobj, i, update); + if (!changed) { + file_dotlock_delete(&dotlock); + return 0; + } + + validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + orig_mtime = validity->local_validity.last_mtime; + + /* ACLs were really changed, write the new ones */ + path = file_dotlock_get_lock_path(dotlock); + if (acl_backend_vfile_update_write(_aclobj, fd, path) < 0) { + file_dotlock_delete(&dotlock); + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return -1; + } + if (orig_mtime < update->last_change && update->last_change != 0) { + /* set mtime to last_change, if it's higher than the file's + original mtime. if original mtime is higher, then we're + merging some changes and it's better for the mtime to get + updated. */ + ut.actime = ioloop_time; + ut.modtime = update->last_change; + if (utime(path, &ut) < 0) + i_error("utime(%s) failed: %m", path); + } + acl_backend_vfile_update_cache(_aclobj, fd); + if (file_dotlock_replace(&dotlock, 0) < 0) { + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return -1; + } + /* make sure dovecot-acl-list gets updated if we changed any + lookup rights. */ + if (acl_rights_has_nonowner_lookup_changes(&update->rights) || + update->modify_mode == ACL_MODIFY_MODE_REPLACE || + update->modify_mode == ACL_MODIFY_MODE_CLEAR) + (void)acl_backend_vfile_acllist_rebuild(backend); + return 0; +} diff --git a/src/plugins/acl/acl-backend-vfile.c b/src/plugins/acl/acl-backend-vfile.c new file mode 100644 index 0000000..3ee0af9 --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile.c @@ -0,0 +1,659 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "istream.h" +#include "nfs-workarounds.h" +#include "mail-storage-private.h" +#include "acl-global-file.h" +#include "acl-cache.h" +#include "acl-backend-vfile.h" + +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> + +#define ACL_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT +#define ACL_VFILE_DEFAULT_CACHE_SECS 30 + +static struct acl_backend *acl_backend_vfile_alloc(void) +{ + struct acl_backend_vfile *backend; + pool_t pool; + + pool = pool_alloconly_create("ACL backend", 512); + backend = p_new(pool, struct acl_backend_vfile, 1); + backend->backend.pool = pool; + return &backend->backend; +} + +static int +acl_backend_vfile_init(struct acl_backend *_backend, const char *data) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct stat st; + const char *const *tmp; + + tmp = t_strsplit(data, ":"); + backend->global_path = p_strdup_empty(_backend->pool, *tmp); + backend->cache_secs = ACL_VFILE_DEFAULT_CACHE_SECS; + + if (*tmp != NULL) + tmp++; + for (; *tmp != NULL; tmp++) { + if (str_begins(*tmp, "cache_secs=")) { + if (str_to_uint(*tmp + 11, &backend->cache_secs) < 0) { + i_error("acl vfile: Invalid cache_secs value: %s", + *tmp + 11); + return -1; + } + } else { + i_error("acl vfile: Unknown parameter: %s", *tmp); + return -1; + } + } + if (backend->global_path != NULL) { + if (stat(backend->global_path, &st) < 0) { + if (errno != ENOENT) { + i_error("acl vfile: stat(%s) failed: %m", + backend->global_path); + return -1; + } + } else if (!S_ISDIR(st.st_mode)) { + _backend->global_file = + acl_global_file_init(backend->global_path, backend->cache_secs, + _backend->debug); + } + } + if (_backend->debug) { + if (backend->global_path == NULL) + i_debug("acl vfile: Global ACLs disabled"); + else if (_backend->global_file != NULL) { + i_debug("acl vfile: Global ACL file: %s", + backend->global_path); + } else { + i_debug("acl vfile: Global ACL legacy directory: %s", + backend->global_path); + } + } + + _backend->cache = + acl_cache_init(_backend, + sizeof(struct acl_backend_vfile_validity)); + return 0; +} + +static void acl_backend_vfile_deinit(struct acl_backend *_backend) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + + if (backend->acllist_pool != NULL) { + array_free(&backend->acllist); + pool_unref(&backend->acllist_pool); + } + if (_backend->global_file != NULL) + acl_global_file_deinit(&_backend->global_file); + pool_unref(&backend->backend.pool); +} + +static const char * +acl_backend_vfile_get_local_dir(struct acl_backend *backend, + const char *name, const char *vname) +{ + struct mail_namespace *ns = mailbox_list_get_namespace(backend->list); + struct mailbox_list *list = ns->list; + struct mail_storage *storage; + enum mailbox_list_path_type type; + const char *dir, *inbox; + + if (*name == '\0') + name = NULL; + + if (backend->globals_only) + return NULL; + + /* ACL files are very important. try to keep them among the main + mail files. that's not possible though with a) if the mailbox is + a file or b) if the mailbox path doesn't point to filesystem. */ + if (mailbox_list_get_storage(&list, vname, &storage) < 0) + return NULL; + i_assert(list == ns->list); + + type = mail_storage_get_acl_list_path_type(storage); + if (name == NULL) { + if (!mailbox_list_get_root_path(list, type, &dir)) + return NULL; + } else { + if (mailbox_list_get_path(list, name, type, &dir) <= 0) + return NULL; + } + + /* verify that the directory isn't same as INBOX's directory. + this is mainly for Maildir. */ + if (name == NULL && + mailbox_list_get_path(list, "INBOX", + MAILBOX_LIST_PATH_TYPE_MAILBOX, &inbox) > 0 && + strcmp(inbox, dir) == 0) { + /* can't have default ACLs with this setup */ + return NULL; + } + return dir; +} + +static struct acl_object * +acl_backend_vfile_object_init(struct acl_backend *_backend, + const char *name) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct acl_object_vfile *aclobj; + const char *dir, *vname, *error; + + aclobj = i_new(struct acl_object_vfile, 1); + aclobj->aclobj.backend = _backend; + aclobj->aclobj.name = i_strdup(name); + + T_BEGIN { + if (*name == '\0' || + mailbox_list_is_valid_name(_backend->list, name, &error)) { + vname = *name == '\0' ? "" : + mailbox_list_get_vname(_backend->list, name); + + dir = acl_backend_vfile_get_local_dir(_backend, name, vname); + aclobj->local_path = dir == NULL ? NULL : + i_strconcat(dir, "/"ACL_FILENAME, NULL); + if (backend->global_path != NULL && + _backend->global_file == NULL) { + aclobj->global_path = + i_strconcat(backend->global_path, "/", vname, NULL); + } + } else { + /* Invalid mailbox name, just use the default + global ACL files */ + } + } T_END; + return &aclobj->aclobj; +} + +static const char * +get_parent_mailbox(struct acl_backend *backend, const char *name) +{ + const char *p; + + p = strrchr(name, mailbox_list_get_hierarchy_sep(backend->list)); + return p == NULL ? NULL : t_strdup_until(name, p); +} + +static int +acl_backend_vfile_exists(struct acl_backend_vfile *backend, const char *path, + struct acl_vfile_validity *validity) +{ + struct stat st; + + if (validity->last_check + (time_t)backend->cache_secs > ioloop_time) { + /* use the cached value */ + return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOTFOUND ? 1 : 0; + } + + validity->last_check = ioloop_time; + if (stat(path, &st) < 0) { + if (errno == ENOENT || errno == ENOTDIR) { + validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOTFOUND; + return 0; + } + if (errno == EACCES) { + validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOACCESS; + return 1; + } + i_error("stat(%s) failed: %m", path); + return -1; + } + validity->last_mtime = st.st_mtime; + validity->last_size = st.st_size; + return 1; +} + +static bool +acl_backend_vfile_has_acl(struct acl_backend *_backend, const char *name) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct acl_backend_vfile_validity *old_validity, new_validity; + const char *global_path, *vname; + int ret; + + old_validity = acl_cache_get_validity(_backend->cache, name); + if (old_validity != NULL) + new_validity = *old_validity; + else + i_zero(&new_validity); + + /* The caller wants to stop whenever a parent mailbox exists, even if + it has no ACL file. Also, if a mailbox doesn't exist then it can't + have a local ACL file. First check if there's a matching global ACL. + If not, check if the mailbox exists. */ + vname = *name == '\0' ? "" : + mailbox_list_get_vname(_backend->list, name); + struct mailbox *box = + mailbox_alloc(_backend->list, vname, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (backend->global_path == NULL) { + /* global ACLs disabled */ + ret = 0; + } else if (_backend->global_file != NULL) { + /* check global ACL file */ + ret = acl_global_file_refresh(_backend->global_file); + if (ret == 0 && acl_global_file_have_any(_backend->global_file, box->vname)) + ret = 1; + } else { + /* check global ACL directory */ + global_path = t_strconcat(backend->global_path, "/", name, NULL); + ret = acl_backend_vfile_exists(backend, global_path, + &new_validity.global_validity); + } + + if (ret != 0) { + /* error / global ACL found */ + } else if (mailbox_open(box) == 0) { + /* mailbox exists */ + ret = 1; + } else { + enum mail_error error; + const char *errstr = + mailbox_get_last_internal_error(box, &error); + if (error == MAIL_ERROR_NOTFOUND) + ret = 0; + else { + e_error(box->event, "acl: Failed to open mailbox: %s", + errstr); + ret = -1; + } + } + + acl_cache_set_validity(_backend->cache, name, &new_validity); + mailbox_free(&box); + return ret > 0; +} + +static struct acl_object * +acl_backend_vfile_object_init_parent(struct acl_backend *backend, + const char *child_name) +{ + const char *parent; + + /* stop at the first parent that + a) has global ACL file + b) has local ACL file + c) exists */ + while ((parent = get_parent_mailbox(backend, child_name)) != NULL) { + if (acl_backend_vfile_has_acl(backend, parent)) + break; + child_name = parent; + } + if (parent == NULL) { + /* use the root */ + parent = acl_backend_get_default_object(backend)->name; + } + return acl_backend_vfile_object_init(backend, parent); +} + +static void acl_backend_vfile_object_deinit(struct acl_object *_aclobj) +{ + struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj; + + i_free(aclobj->local_path); + i_free(aclobj->global_path); + + if (array_is_created(&aclobj->aclobj.rights)) + array_free(&aclobj->aclobj.rights); + pool_unref(&aclobj->aclobj.rights_pool); + i_free(aclobj->aclobj.name); + i_free(aclobj); +} + +static int +acl_backend_vfile_read(struct acl_object *aclobj, bool global, const char *path, + struct acl_vfile_validity *validity, bool try_retry, + bool *is_dir_r) +{ + struct istream *input; + struct stat st; + struct acl_rights rights; + const char *line, *error; + unsigned int linenum; + int fd, ret = 0; + + *is_dir_r = FALSE; + + fd = nfs_safe_open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT || errno == ENOTDIR) { + if (aclobj->backend->debug) + i_debug("acl vfile: file %s not found", path); + validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOTFOUND; + } else if (errno == EACCES) { + if (aclobj->backend->debug) + i_debug("acl vfile: no access to file %s", + path); + + acl_object_remove_all_access(aclobj); + validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOACCESS; + } else { + i_error("open(%s) failed: %m", path); + return -1; + } + + validity->last_size = 0; + validity->last_read_time = ioloop_time; + return 1; + } + + if (fstat(fd, &st) < 0) { + if (errno == ESTALE && try_retry) { + i_close_fd(&fd); + return 0; + } + + i_error("fstat(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + if (S_ISDIR(st.st_mode)) { + /* we opened a directory. */ + *is_dir_r = TRUE; + i_close_fd(&fd); + return 0; + } + + if (aclobj->backend->debug) + i_debug("acl vfile: reading file %s", path); + + input = i_stream_create_fd(fd, SIZE_MAX); + i_stream_set_return_partial_line(input, TRUE); + linenum = 0; + while ((line = i_stream_read_next_line(input)) != NULL) { + linenum++; + if (line[0] == '\0' || line[0] == '#') + continue; + T_BEGIN { + ret = acl_rights_parse_line(line, aclobj->rights_pool, + &rights, &error); + rights.global = global; + if (ret < 0) { + i_error("ACL file %s line %u: %s", + path, linenum, error); + } else { + array_push_back(&aclobj->rights, &rights); + } + } T_END; + if (ret < 0) + break; + } + + if (ret < 0) { + /* parsing failure */ + } else if (input->stream_errno != 0) { + if (input->stream_errno == ESTALE && try_retry) + ret = 0; + else { + ret = -1; + i_error("read(%s) failed: %s", path, + i_stream_get_error(input)); + } + } else { + if (fstat(fd, &st) < 0) { + if (errno == ESTALE && try_retry) + ret = 0; + else { + ret = -1; + i_error("fstat(%s) failed: %m", path); + } + } else { + ret = 1; + validity->last_read_time = ioloop_time; + validity->last_mtime = st.st_mtime; + validity->last_size = st.st_size; + } + } + + i_stream_unref(&input); + if (close(fd) < 0) { + if (errno == ESTALE && try_retry) + return 0; + + i_error("close(%s) failed: %m", path); + return -1; + } + return ret; +} + +static int +acl_backend_vfile_read_with_retry(struct acl_object *aclobj, + bool global, const char *path, + struct acl_vfile_validity *validity) +{ + unsigned int i; + int ret; + bool is_dir; + + if (path == NULL) + return 0; + + for (i = 0;; i++) { + ret = acl_backend_vfile_read(aclobj, global, path, validity, + i < ACL_ESTALE_RETRY_COUNT, + &is_dir); + if (ret != 0) + break; + + if (is_dir) { + /* opened a directory. use dir/.DEFAULT instead */ + path = t_strconcat(path, "/.DEFAULT", NULL); + } else { + /* ESTALE - try again */ + } + } + + return ret <= 0 ? -1 : 0; +} + +static bool +acl_vfile_validity_has_changed(struct acl_backend_vfile *backend, + const struct acl_vfile_validity *validity, + const struct stat *st) +{ + if (st->st_mtime == validity->last_mtime && + st->st_size == validity->last_size) { + /* same timestamp, but if it was modified within the + same second we want to refresh it again later (but + do it only after a couple of seconds so we don't + keep re-reading it all the time within those + seconds) */ + time_t cache_secs = backend->cache_secs; + + if (validity->last_read_time != 0 && + (st->st_mtime < validity->last_read_time - cache_secs || + ioloop_time - validity->last_read_time <= cache_secs)) + return FALSE; + } + return TRUE; +} + +static int +acl_backend_vfile_refresh(struct acl_object *aclobj, const char *path, + struct acl_vfile_validity *validity) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)aclobj->backend; + struct stat st; + int ret; + + if (validity == NULL) + return 1; + if (path == NULL || + validity->last_check + (time_t)backend->cache_secs > ioloop_time) + return 0; + + validity->last_check = ioloop_time; + ret = stat(path, &st); + if (ret == 0 && S_ISDIR(st.st_mode)) { + /* it's a directory. use dir/.DEFAULT instead */ + path = t_strconcat(path, "/.DEFAULT", NULL); + ret = stat(path, &st); + } + + if (ret < 0) { + if (errno == ENOENT || errno == ENOTDIR) { + /* if the file used to exist, we have to re-read it */ + return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOTFOUND ? 1 : 0; + } + if (errno == EACCES) + return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOACCESS ? 1 : 0; + i_error("stat(%s) failed: %m", path); + return -1; + } + return acl_vfile_validity_has_changed(backend, validity, &st) ? 1 : 0; +} + +int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj, + time_t *mtime_r) +{ + struct acl_backend_vfile_validity *validity; + + validity = acl_cache_get_validity(aclobj->backend->cache, aclobj->name); + if (validity == NULL) + return -1; + + if (validity->local_validity.last_mtime != 0) + *mtime_r = validity->local_validity.last_mtime; + else if (validity->global_validity.last_mtime != 0) + *mtime_r = validity->global_validity.last_mtime; + else + *mtime_r = 0; + return 0; +} + +static int +acl_backend_global_file_refresh(struct acl_object *_aclobj, + struct acl_vfile_validity *validity) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_aclobj->backend; + struct stat st; + + if (acl_global_file_refresh(_aclobj->backend->global_file) < 0) + return -1; + + acl_global_file_last_stat(_aclobj->backend->global_file, &st); + if (validity == NULL) + return 1; + return acl_vfile_validity_has_changed(backend, validity, &st) ? 1 : 0; +} + +static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj) +{ + struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_aclobj->backend; + struct acl_backend_vfile_validity *old_validity; + struct acl_backend_vfile_validity validity; + time_t mtime; + int ret; + + old_validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + ret = _aclobj->backend->global_file != NULL ? + acl_backend_global_file_refresh(_aclobj, old_validity == NULL ? NULL : + &old_validity->global_validity) : + acl_backend_vfile_refresh(_aclobj, aclobj->global_path, + old_validity == NULL ? NULL : + &old_validity->global_validity); + if (ret == 0) { + ret = acl_backend_vfile_refresh(_aclobj, aclobj->local_path, + old_validity == NULL ? NULL : + &old_validity->local_validity); + } + if (ret <= 0) + return ret; + + /* either global or local ACLs changed, need to re-read both */ + if (!array_is_created(&_aclobj->rights)) { + _aclobj->rights_pool = + pool_alloconly_create("acl rights", 256); + i_array_init(&_aclobj->rights, 16); + } else { + array_clear(&_aclobj->rights); + p_clear(_aclobj->rights_pool); + } + + i_zero(&validity); + if (_aclobj->backend->global_file != NULL) { + struct stat st; + + acl_object_add_global_acls(_aclobj); + acl_global_file_last_stat(_aclobj->backend->global_file, &st); + validity.global_validity.last_read_time = ioloop_time; + validity.global_validity.last_mtime = st.st_mtime; + validity.global_validity.last_size = st.st_size; + } else { + if (acl_backend_vfile_read_with_retry(_aclobj, TRUE, aclobj->global_path, + &validity.global_validity) < 0) + return -1; + } + if (acl_backend_vfile_read_with_retry(_aclobj, FALSE, aclobj->local_path, + &validity.local_validity) < 0) + return -1; + + acl_rights_sort(_aclobj); + /* update cache only after we've successfully read everything */ + acl_object_rebuild_cache(_aclobj); + acl_cache_set_validity(_aclobj->backend->cache, + _aclobj->name, &validity); + + if (acl_backend_vfile_object_get_mtime(_aclobj, &mtime) == 0) + acl_backend_vfile_acllist_verify(backend, _aclobj->name, mtime); + return 0; +} + +static int acl_backend_vfile_object_last_changed(struct acl_object *_aclobj, + time_t *last_changed_r) +{ + struct acl_backend_vfile_validity *old_validity; + + *last_changed_r = 0; + + old_validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + if (old_validity == NULL) { + if (acl_backend_vfile_object_refresh_cache(_aclobj) < 0) + return -1; + old_validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + if (old_validity == NULL) + return 0; + } + *last_changed_r = old_validity->local_validity.last_mtime; + return 0; +} + +struct acl_backend_vfuncs acl_backend_vfile = { + acl_backend_vfile_alloc, + acl_backend_vfile_init, + acl_backend_vfile_deinit, + acl_backend_vfile_nonowner_iter_init, + acl_backend_vfile_nonowner_iter_next, + acl_backend_vfile_nonowner_iter_deinit, + acl_backend_vfile_nonowner_lookups_rebuild, + acl_backend_vfile_object_init, + acl_backend_vfile_object_init_parent, + acl_backend_vfile_object_deinit, + acl_backend_vfile_object_refresh_cache, + acl_backend_vfile_object_update, + acl_backend_vfile_object_last_changed, + acl_default_object_list_init, + acl_default_object_list_next, + acl_default_object_list_deinit +}; diff --git a/src/plugins/acl/acl-backend-vfile.h b/src/plugins/acl/acl-backend-vfile.h new file mode 100644 index 0000000..c5aaa25 --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile.h @@ -0,0 +1,88 @@ +#ifndef ACL_BACKEND_VFILE_H +#define ACL_BACKEND_VFILE_H + +#include "acl-api-private.h" +#include "mail-storage-private.h" + +#define ACL_FILENAME "dovecot-acl" +#define ACLLIST_FILENAME "dovecot-acl-list" + +#define ACL_VFILE_VALIDITY_MTIME_NOTFOUND 0 +#define ACL_VFILE_VALIDITY_MTIME_NOACCESS -1 + +struct acl_vfile_validity { + time_t last_check; + + time_t last_read_time; + time_t last_mtime; + off_t last_size; +}; + +struct acl_backend_vfile_validity { + struct acl_vfile_validity global_validity, local_validity; +}; + +struct acl_object_vfile { + struct acl_object aclobj; + + /* if backend->global_file is NULL, assume legacy separate global + ACL file per mailbox */ + char *global_path, *local_path; +}; + +struct acl_backend_vfile_acllist { + time_t mtime; + const char *name; +}; + +struct acl_backend_vfile { + struct acl_backend backend; + const char *global_path; + + pool_t acllist_pool; + ARRAY(struct acl_backend_vfile_acllist) acllist; + + time_t acllist_last_check; + time_t acllist_mtime; + unsigned int acllist_change_counter; + + unsigned int cache_secs; + bool rebuilding_acllist:1; + bool iterating_acllist:1; +}; + +void acl_vfile_write_rights_list(string_t *dest, const char *const *rights); +int acl_backend_vfile_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update); + +void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend); +int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend); +void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend, + const char *name, time_t mtime); + +struct acl_mailbox_list_context * +acl_backend_vfile_nonowner_iter_init(struct acl_backend *backend); +bool acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r); +int +acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx); +int acl_backend_vfile_nonowner_lookups_rebuild(struct acl_backend *backend); + +int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj, + time_t *mtime_r); + +static inline enum mailbox_list_path_type +mail_storage_get_acl_list_path_type(struct mail_storage *storage) +{ + if (mail_storage_is_mailbox_file(storage)) { + /* mailbox is a directory (e.g. mbox) */ + return MAILBOX_LIST_PATH_TYPE_CONTROL; + } + if ((storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_ROOT) != 0) { + /* there is no local mailbox directory */ + return MAILBOX_LIST_PATH_TYPE_CONTROL; + } + return MAILBOX_LIST_PATH_TYPE_MAILBOX; +} + +#endif diff --git a/src/plugins/acl/acl-backend.c b/src/plugins/acl/acl-backend.c new file mode 100644 index 0000000..0514dc7 --- /dev/null +++ b/src/plugins/acl/acl-backend.c @@ -0,0 +1,194 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "hash.h" +#include "sort.h" +#include "mail-storage-settings.h" +#include "mailbox-list.h" +#include "mail-namespace.h" +#include "mail-user.h" +#include "acl-cache.h" +#include "acl-api-private.h" + + +extern struct acl_backend_vfuncs acl_backend_vfile; + +const char *const all_mailbox_rights[] = { + MAIL_ACL_LOOKUP, + MAIL_ACL_READ, + MAIL_ACL_WRITE, + MAIL_ACL_WRITE_SEEN, + MAIL_ACL_WRITE_DELETED, + MAIL_ACL_INSERT, + MAIL_ACL_POST, + MAIL_ACL_EXPUNGE, + MAIL_ACL_CREATE, + MAIL_ACL_DELETE, + MAIL_ACL_ADMIN, + NULL +}; + +static const char *const *owner_mailbox_rights = all_mailbox_rights; +static const char *const non_owner_mailbox_rights[] = { NULL }; + +struct acl_backend * +acl_backend_init(const char *data, struct mailbox_list *list, + const char *acl_username, const char *const *groups, + bool owner) +{ + struct mail_user *user = mailbox_list_get_user(list); + struct acl_backend *backend; + unsigned int i, group_count; + + e_debug(user->event, "acl: initializing backend with data: %s", data); + e_debug(user->event, "acl: acl username = %s", acl_username); + e_debug(user->event, "acl: owner = %d", owner ? 1 : 0); + + group_count = str_array_length(groups); + + if (str_begins(data, "vfile:")) + data += 6; + else if (strcmp(data, "vfile") == 0) + data = ""; + else + i_fatal("Unknown ACL backend: %s", t_strcut(data, ':')); + + backend = acl_backend_vfile.alloc(); + backend->debug = user->mail_debug; + backend->v = acl_backend_vfile; + backend->list = list; + backend->username = p_strdup(backend->pool, acl_username); + backend->owner = owner; + backend->globals_only = + mail_user_plugin_getenv_bool(user, "acl_globals_only"); + + if (group_count > 0) { + backend->group_count = group_count; + backend->groups = + p_new(backend->pool, const char *, group_count); + for (i = 0; i < group_count; i++) { + backend->groups[i] = p_strdup(backend->pool, groups[i]); + e_debug(user->event, "acl: group added: %s", groups[i]); + } + i_qsort(backend->groups, group_count, sizeof(const char *), + i_strcmp_p); + } + + T_BEGIN { + if (acl_backend_vfile.init(backend, data) < 0) + i_fatal("acl: backend vfile init failed with data: %s", + data); + } T_END; + + backend->default_rights = owner ? owner_mailbox_rights : + non_owner_mailbox_rights; + backend->default_aclmask = + acl_cache_mask_init(backend->cache, backend->pool, + backend->default_rights); + return backend; +} + +void acl_backend_deinit(struct acl_backend **_backend) +{ + struct acl_backend *backend = *_backend; + + *_backend = NULL; + + if (backend->default_aclobj != NULL) + acl_object_deinit(&backend->default_aclobj); + acl_cache_deinit(&backend->cache); + backend->v.deinit(backend); +} + +const char *acl_backend_get_acl_username(struct acl_backend *backend) +{ + return backend->username; +} + +bool acl_backend_user_is_authenticated(struct acl_backend *backend) +{ + return backend->username != NULL; +} + +bool acl_backend_user_is_owner(struct acl_backend *backend) +{ + return backend->owner; +} + +bool acl_backend_user_name_equals(struct acl_backend *backend, + const char *username) +{ + if (backend->username == NULL) { + /* anonymous user never matches */ + return FALSE; + } + + return strcmp(backend->username, username) == 0; +} + +bool acl_backend_user_is_in_group(struct acl_backend *backend, + const char *group_name) +{ + return i_bsearch(group_name, backend->groups, backend->group_count, + sizeof(const char *), bsearch_strcmp) != NULL; +} + +bool acl_backend_rights_match_me(struct acl_backend *backend, + const struct acl_rights *rights) +{ + switch (rights->id_type) { + case ACL_ID_ANYONE: + return TRUE; + case ACL_ID_AUTHENTICATED: + return acl_backend_user_is_authenticated(backend); + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + return acl_backend_user_is_in_group(backend, rights->identifier); + case ACL_ID_USER: + return acl_backend_user_name_equals(backend, rights->identifier); + case ACL_ID_OWNER: + return acl_backend_user_is_owner(backend); + case ACL_ID_TYPE_COUNT: + break; + } + i_unreached(); +} + +unsigned int acl_backend_lookup_right(struct acl_backend *backend, + const char *right) +{ + return acl_cache_right_lookup(backend->cache, right); +} + +struct acl_object *acl_backend_get_default_object(struct acl_backend *backend) +{ + struct mail_user *user = mailbox_list_get_user(backend->list); + struct mail_namespace *ns = mailbox_list_get_namespace(backend->list); + const char *default_name = ""; + + if (backend->default_aclobj != NULL) + return backend->default_aclobj; + + if (mail_user_plugin_getenv_bool(user, "acl_defaults_from_inbox")) { + if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE || + ns->type == MAIL_NAMESPACE_TYPE_SHARED) + default_name = "INBOX"; + } + backend->default_aclobj = + acl_object_init_from_name(backend, default_name); + return backend->default_aclobj; +} + +int acl_backend_get_default_rights(struct acl_backend *backend, + const struct acl_mask **mask_r) +{ + struct acl_object *aclobj = acl_backend_get_default_object(backend); + + if (backend->v.object_refresh_cache(aclobj) < 0) + return -1; + + *mask_r = acl_cache_get_my_rights(backend->cache, aclobj->name); + if (*mask_r == NULL) + *mask_r = backend->default_aclmask; + return 0; +} diff --git a/src/plugins/acl/acl-cache.c b/src/plugins/acl/acl-cache.c new file mode 100644 index 0000000..8f07d56 --- /dev/null +++ b/src/plugins/acl/acl-cache.c @@ -0,0 +1,395 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "acl-cache.h" +#include "acl-api-private.h" + +/* Give more than enough so that the arrays should never have to be grown. + IMAP ACLs define only 10 standard rights and 10 user-defined rights. */ +#define DEFAULT_ACL_RIGHTS_COUNT 64 + +#define ACL_GLOBAL_COUNT 2 + +struct acl_object_cache { + char *name; + + struct acl_mask *my_rights, *my_neg_rights; + struct acl_mask *my_current_rights; +}; + +struct acl_cache { + struct acl_backend *backend; + /* name => object */ + HASH_TABLE(char *, struct acl_object_cache *) objects; + + size_t validity_rec_size; + + /* Right names mapping is used for faster rights checking. Note that + acl_mask bitmask relies on the order to never change, so only new + rights can be added to the mapping. */ + pool_t right_names_pool; + /* idx => right name. */ + ARRAY(const char *) right_idx_name_map; + /* name => idx+1 */ + HASH_TABLE(char *, void *) right_name_idx_map; +}; + +static struct acl_mask negative_cache_entry; + +struct acl_cache *acl_cache_init(struct acl_backend *backend, + size_t validity_rec_size) +{ + struct acl_cache *cache; + + cache = i_new(struct acl_cache, 1); + cache->backend = backend; + cache->validity_rec_size = validity_rec_size; + cache->right_names_pool = + pool_alloconly_create("ACL right names", 1024); + hash_table_create(&cache->objects, default_pool, 0, str_hash, strcmp); + hash_table_create(&cache->right_name_idx_map, + cache->right_names_pool, 0, str_hash, strcmp); + i_array_init(&cache->right_idx_name_map, DEFAULT_ACL_RIGHTS_COUNT); + return cache; +} + +void acl_cache_deinit(struct acl_cache **_cache) +{ + struct acl_cache *cache = *_cache; + + *_cache = NULL; + + acl_cache_flush_all(cache); + array_free(&cache->right_idx_name_map); + hash_table_destroy(&cache->right_name_idx_map); + hash_table_destroy(&cache->objects); + pool_unref(&cache->right_names_pool); + i_free(cache); +} + +static void acl_cache_free_object_cache(struct acl_object_cache *obj_cache) +{ + if (obj_cache->my_current_rights != NULL && + obj_cache->my_current_rights != &negative_cache_entry) + acl_cache_mask_deinit(&obj_cache->my_current_rights); + if (obj_cache->my_rights != NULL) + acl_cache_mask_deinit(&obj_cache->my_rights); + if (obj_cache->my_neg_rights != NULL) + acl_cache_mask_deinit(&obj_cache->my_neg_rights); + i_free(obj_cache->name); + i_free(obj_cache); +} + +static struct acl_mask * +acl_cache_mask_init_real(struct acl_cache *cache, pool_t pool, + const char *const *rights) +{ + struct acl_mask *mask; + unsigned int rights_count, i, idx; + unsigned char *p; + buffer_t *bitmask; + + rights_count = str_array_length(rights); + bitmask = t_buffer_create(DEFAULT_ACL_RIGHTS_COUNT / CHAR_BIT); + for (i = 0; i < rights_count; i++) { + idx = acl_cache_right_lookup(cache, rights[i]); + p = buffer_get_space_unsafe(bitmask, idx / CHAR_BIT, 1); + *p |= 1 << (idx % CHAR_BIT); + } + + /* @UNSAFE */ + mask = p_malloc(pool, SIZEOF_ACL_MASK(bitmask->used)); + memcpy(mask->mask, bitmask->data, bitmask->used); + mask->pool = pool; + mask->size = bitmask->used; + return mask; +} + +struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool, + const char *const *rights) +{ + struct acl_mask *mask; + + T_BEGIN { + mask = acl_cache_mask_init_real(cache, pool, rights); + } T_END; + return mask; +} + +static struct acl_mask * +acl_cache_mask_dup(pool_t pool, const struct acl_mask *src) +{ + struct acl_mask *mask; + + mask = p_malloc(pool, SIZEOF_ACL_MASK(src->size)); + memcpy(mask->mask, src->mask, src->size); + mask->pool = pool; + mask->size = src->size; + return mask; +} + +void acl_cache_mask_deinit(struct acl_mask **_mask) +{ + struct acl_mask *mask = *_mask; + + *_mask = NULL; + p_free(mask->pool, mask); +} + +unsigned int acl_cache_right_lookup(struct acl_cache *cache, const char *right) +{ + unsigned int idx; + void *idx_p; + char *name; + const char *const_name; + + /* use +1 for right_name_idx_map values because we can't add NULL + values. */ + idx_p = hash_table_lookup(cache->right_name_idx_map, right); + if (idx_p == NULL) { + /* new right name, add it */ + const_name = name = p_strdup(cache->right_names_pool, right); + + idx = array_count(&cache->right_idx_name_map); + array_push_back(&cache->right_idx_name_map, &const_name); + hash_table_insert(cache->right_name_idx_map, name, + POINTER_CAST(idx + 1)); + } else { + idx = POINTER_CAST_TO(idx_p, unsigned int)-1; + } + return idx; +} + +void acl_cache_flush(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache != NULL) { + hash_table_remove(cache->objects, objname); + acl_cache_free_object_cache(obj_cache); + } +} + +void acl_cache_flush_all(struct acl_cache *cache) +{ + struct hash_iterate_context *iter; + char *key; + struct acl_object_cache *obj_cache; + + iter = hash_table_iterate_init(cache->objects); + while (hash_table_iterate(iter, cache->objects, &key, &obj_cache)) + acl_cache_free_object_cache(obj_cache); + hash_table_iterate_deinit(&iter); + + hash_table_clear(cache->objects, FALSE); +} + +static void +acl_cache_update_rights_mask(struct acl_cache *cache, + struct acl_object_cache *obj_cache, + enum acl_modify_mode modify_mode, + const char *const *rights, + struct acl_mask **mask_p) +{ + struct acl_mask *change_mask, *old_mask, *new_mask; + unsigned int i, size; + bool changed = TRUE; + + change_mask = rights == NULL ? NULL : + acl_cache_mask_init(cache, default_pool, rights); + old_mask = *mask_p; + new_mask = old_mask; + + switch (modify_mode) { + case ACL_MODIFY_MODE_ADD: + if (old_mask == NULL) { + new_mask = change_mask; + break; + } + + if (change_mask == NULL) { + /* no changes */ + changed = FALSE; + break; + } + + /* merge the masks */ + if (old_mask->size >= change_mask->size) { + /* keep using the old mask */ + for (i = 0; i < change_mask->size; i++) + old_mask->mask[i] |= change_mask->mask[i]; + } else { + /* use the new mask, put old changes into it */ + for (i = 0; i < old_mask->size; i++) + change_mask->mask[i] |= old_mask->mask[i]; + new_mask = change_mask; + } + break; + case ACL_MODIFY_MODE_REMOVE: + if (old_mask == NULL || change_mask == NULL) { + changed = FALSE; + break; + } + + /* remove changed bits from old mask */ + size = I_MIN(old_mask->size, change_mask->size); + for (i = 0; i < size; i++) + old_mask->mask[i] &= ENUM_NEGATE(change_mask->mask[i]); + break; + case ACL_MODIFY_MODE_REPLACE: + if (old_mask == NULL && change_mask == NULL) + changed = FALSE; + new_mask = change_mask; + break; + case ACL_MODIFY_MODE_CLEAR: + i_unreached(); + } + + if (new_mask != old_mask) { + *mask_p = new_mask; + if (old_mask != NULL) + acl_cache_mask_deinit(&old_mask); + } + if (new_mask != change_mask && change_mask != NULL) + acl_cache_mask_deinit(&change_mask); + + if (changed && obj_cache->my_current_rights != NULL) { + /* current rights need to be recalculated */ + if (obj_cache->my_current_rights == &negative_cache_entry) + obj_cache->my_current_rights = NULL; + else + acl_cache_mask_deinit(&obj_cache->my_current_rights); + } +} + +static struct acl_object_cache * +acl_cache_object_get(struct acl_cache *cache, const char *objname, + bool *created_r) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache == NULL) { + obj_cache = i_malloc(MALLOC_ADD(sizeof(struct acl_object_cache), + cache->validity_rec_size)); + obj_cache->name = i_strdup(objname); + hash_table_insert(cache->objects, obj_cache->name, obj_cache); + *created_r = TRUE; + } else { + *created_r = FALSE; + } + return obj_cache; +} + +void acl_cache_update(struct acl_cache *cache, const char *objname, + const struct acl_rights_update *update) +{ + struct acl_object_cache *obj_cache; + bool created; + + obj_cache = acl_cache_object_get(cache, objname, &created); + i_assert(obj_cache->my_current_rights != &negative_cache_entry); + + if (created && update->modify_mode != ACL_MODIFY_MODE_REPLACE) { + /* since the rights aren't being replaced, start with our + default rights */ + obj_cache->my_rights = + acl_cache_mask_dup(default_pool, + cache->backend->default_aclmask); + } + + acl_cache_update_rights_mask(cache, obj_cache, update->modify_mode, + update->rights.rights, + &obj_cache->my_rights); + acl_cache_update_rights_mask(cache, obj_cache, update->neg_modify_mode, + update->rights.neg_rights, + &obj_cache->my_neg_rights); +} + +void acl_cache_set_validity(struct acl_cache *cache, const char *objname, + const void *validity) +{ + struct acl_object_cache *obj_cache; + bool created; + + obj_cache = acl_cache_object_get(cache, objname, &created); + + /* @UNSAFE: validity is stored after the cache record */ + memcpy(obj_cache + 1, validity, cache->validity_rec_size); + + if (created) { + /* negative cache entry */ + obj_cache->my_current_rights = &negative_cache_entry; + } +} + +void *acl_cache_get_validity(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + return obj_cache == NULL ? NULL : (obj_cache + 1); +} + +const char *const *acl_cache_get_names(struct acl_cache *cache, + unsigned int *count_r) +{ + *count_r = array_count(&cache->right_idx_name_map); + return array_front(&cache->right_idx_name_map); +} + +static void +acl_cache_my_current_rights_recalculate(struct acl_object_cache *obj_cache) +{ + struct acl_mask *mask; + unsigned int i, size; + + /* @UNSAFE */ + size = obj_cache->my_rights == NULL ? 0 : + obj_cache->my_rights->size; + mask = i_malloc(SIZEOF_ACL_MASK(size)); + mask->pool = default_pool; + mask->size = size; + + /* apply the positive rights */ + if (obj_cache->my_rights != NULL) + memcpy(mask->mask, obj_cache->my_rights->mask, mask->size); + if (obj_cache->my_neg_rights != NULL) { + /* apply the negative rights. they override positive rights. */ + size = I_MIN(mask->size, obj_cache->my_neg_rights->size); + for (i = 0; i < size; i++) + mask->mask[i] &= ENUM_NEGATE(obj_cache->my_neg_rights->mask[i]); + } + + obj_cache->my_current_rights = mask; +} + +const struct acl_mask * +acl_cache_get_my_rights(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache == NULL || + obj_cache->my_current_rights == &negative_cache_entry) + return NULL; + + if (obj_cache->my_current_rights == NULL) { + T_BEGIN { + acl_cache_my_current_rights_recalculate(obj_cache); + } T_END; + } + return obj_cache->my_current_rights; +} + +bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx) +{ + unsigned int mask_idx; + + mask_idx = right_idx / CHAR_BIT; + return mask_idx < mask->size && + (mask->mask[mask_idx] & (1 << (right_idx % CHAR_BIT))) != 0; +} diff --git a/src/plugins/acl/acl-cache.h b/src/plugins/acl/acl-cache.h new file mode 100644 index 0000000..b7c2065 --- /dev/null +++ b/src/plugins/acl/acl-cache.h @@ -0,0 +1,57 @@ +#ifndef ACL_CACHE_H +#define ACL_CACHE_H + +struct acl_backend; +struct acl_rights_update; + +struct acl_mask { + pool_t pool; + + /* mask[] size as bytes */ + unsigned int size; + + /* variable length bitmask */ + unsigned char mask[1]; +}; +#define SIZEOF_ACL_MASK(bitmask_size) \ + (MALLOC_ADD((bitmask_size), sizeof(pool_t) + sizeof(unsigned int))) + +struct acl_cache *acl_cache_init(struct acl_backend *backend, + size_t validity_rec_size); +void acl_cache_deinit(struct acl_cache **cache); + +struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool, + const char *const *rights); +void acl_cache_mask_deinit(struct acl_mask **mask); +unsigned int acl_cache_right_lookup(struct acl_cache *cache, + const char *right); + +/* Flush cache for given object name */ +void acl_cache_flush(struct acl_cache *cache, const char *objname); +/* Flush cache for all objects */ +void acl_cache_flush_all(struct acl_cache *cache); + +/* Update object ACLs. The new rights are always applied on top of the + existing rights. The ordering by acl_id_type must be done by the caller. */ +void acl_cache_update(struct acl_cache *cache, const char *objname, + const struct acl_rights_update *update); +/* Return ACL object validity, or NULL if object doesn't exit. */ +void *acl_cache_get_validity(struct acl_cache *cache, const char *objname); +/* Update ACL object validity, creating the object if needed. */ +void acl_cache_set_validity(struct acl_cache *cache, const char *objname, + const void *validity); + +/* Returns all the right names currently created. The returned pointer may + change after calling acl_cache_update(). */ +const char *const *acl_cache_get_names(struct acl_cache *cache, + unsigned int *count_r); + +/* Returns user's current rights, or NULL if no rights have been specified + for this object. */ +const struct acl_mask * +acl_cache_get_my_rights(struct acl_cache *cache, const char *objname); + +/* Returns TRUE if given right index is set in mask. */ +bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx); + +#endif diff --git a/src/plugins/acl/acl-global-file.c b/src/plugins/acl/acl-global-file.c new file mode 100644 index 0000000..02f6643 --- /dev/null +++ b/src/plugins/acl/acl-global-file.c @@ -0,0 +1,246 @@ +/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "istream.h" +#include "strescape.h" +#include "wildcard-match.h" +#include "acl-api-private.h" +#include "acl-global-file.h" + +#include <sys/stat.h> + +struct acl_global_rights { + const char *vpattern; + ARRAY_TYPE(acl_rights) rights; +}; + +struct acl_global_parse_rights { + const char *vpattern; + struct acl_rights rights; +}; + +struct acl_global_file { + char *path; + struct stat prev_st; + time_t last_refresh_time; + + pool_t rights_pool; + ARRAY(struct acl_global_rights) rights; + + unsigned int refresh_interval_secs; + bool debug; +}; + +struct acl_global_file * +acl_global_file_init(const char *path, unsigned int refresh_interval_secs, + bool debug) +{ + struct acl_global_file *file; + + file = i_new(struct acl_global_file, 1); + file->path = i_strdup(path); + file->refresh_interval_secs = refresh_interval_secs; + file->debug = debug; + i_array_init(&file->rights, 32); + file->rights_pool = pool_alloconly_create("acl global file rights", 1024); + return file; +} + +void acl_global_file_deinit(struct acl_global_file **_file) +{ + struct acl_global_file *file = *_file; + + *_file = NULL; + + array_free(&file->rights); + pool_unref(&file->rights_pool); + i_free(file->path); + i_free(file); +} + +static int acl_global_parse_rights_cmp(const struct acl_global_parse_rights *r1, + const struct acl_global_parse_rights *r2) +{ + return strcmp(r1->vpattern, r2->vpattern); +} + +struct acl_global_file_parse_ctx { + struct acl_global_file *file; + ARRAY(struct acl_global_parse_rights) parse_rights; +}; + +static int +acl_global_file_parse_line(struct acl_global_file_parse_ctx *ctx, + const char *line, const char **error_r) +{ + struct acl_global_parse_rights *pright; + const char *p, *vpattern; + + if (*line == '"') { + line++; + if (str_unescape_next(&line, &vpattern) < 0) { + *error_r = "Missing '\"'"; + return -1; + } + if (line[0] != ' ') { + *error_r = "Expecting space after '\"'"; + return -1; + } + line++; + } else { + p = strchr(line, ' '); + if (p == NULL) { + *error_r = "Missing ACL rights"; + return -1; + } + if (p == line) { + *error_r = "Empty ACL pattern"; + return -1; + } + vpattern = t_strdup_until(line, p); + line = p + 1; + } + + pright = array_append_space(&ctx->parse_rights); + pright->vpattern = p_strdup(ctx->file->rights_pool, vpattern); + if (acl_rights_parse_line(line, ctx->file->rights_pool, + &pright->rights, error_r) < 0) + return -1; + pright->rights.global = TRUE; + return 0; +} + +static int acl_global_file_read(struct acl_global_file *file) +{ + struct acl_global_file_parse_ctx ctx; + struct acl_global_parse_rights *pright; + struct acl_global_rights *right; + struct istream *input; + const char *line, *error, *prev_vpattern; + unsigned int linenum = 0; + int ret = 0; + + array_clear(&file->rights); + p_clear(file->rights_pool); + + i_zero(&ctx); + ctx.file = file; + i_array_init(&ctx.parse_rights, 32); + + input = i_stream_create_file(file->path, SIZE_MAX); + i_stream_set_return_partial_line(input, TRUE); + while ((line = i_stream_read_next_line(input)) != NULL) { + linenum++; + if (line[0] == '\0' || line[0] == '#') + continue; + T_BEGIN { + ret = acl_global_file_parse_line(&ctx, line, &error); + if (ret < 0) { + i_error("Global ACL file %s line %u: %s", + file->path, linenum, error); + } + } T_END; + if (ret < 0) + break; + } + if (ret == 0 && input->stream_errno != 0) { + i_error("Couldn't read global ACL file %s: %s", + file->path, i_stream_get_error(input)); + ret = -1; + } + if (ret == 0) { + const struct stat *st; + + if (i_stream_stat(input, TRUE, &st) < 0) { + i_error("Couldn't stat global ACL file %s: %s", + file->path, i_stream_get_error(input)); + ret = -1; + } else { + file->prev_st = *st; + } + } + i_stream_destroy(&input); + + /* sort all parsed rights */ + array_sort(&ctx.parse_rights, acl_global_parse_rights_cmp); + /* combine identical patterns into same structs */ + prev_vpattern = ""; right = NULL; + array_foreach_modifiable(&ctx.parse_rights, pright) { + if (right == NULL || + strcmp(prev_vpattern, pright->vpattern) != 0) { + right = array_append_space(&file->rights); + right->vpattern = pright->vpattern; + p_array_init(&right->rights, file->rights_pool, 4); + } + array_push_back(&right->rights, &pright->rights); + } + + array_free(&ctx.parse_rights); + return ret; +} + +int acl_global_file_refresh(struct acl_global_file *file) +{ + struct stat st; + + if (file->last_refresh_time + (time_t)file->refresh_interval_secs > ioloop_time) + return 0; + if (file->last_refresh_time != 0) { + if (stat(file->path, &st) < 0) { + i_error("stat(%s) failed: %m", file->path); + return -1; + } + if (st.st_ino == file->prev_st.st_ino && + st.st_size == file->prev_st.st_size && + CMP_ST_MTIME(&st, &file->prev_st)) { + /* no change to the file */ + file->last_refresh_time = ioloop_time; + return 0; + } + } + if (acl_global_file_read(file) < 0) + return -1; + file->last_refresh_time = ioloop_time; + return 0; +} + +void acl_global_file_last_stat(struct acl_global_file *file, struct stat *st_r) +{ + *st_r = file->prev_st; +} + +void acl_global_file_get(struct acl_global_file *file, const char *vname, + pool_t pool, ARRAY_TYPE(acl_rights) *rights_r) +{ + struct acl_global_rights *global_rights; + const struct acl_rights *rights; + struct acl_rights *new_rights; + + array_foreach_modifiable(&file->rights, global_rights) { + if (!wildcard_match(vname, global_rights->vpattern)) + continue; + if (file->debug) { + i_debug("Mailbox '%s' matches global ACL pattern '%s'", + vname, global_rights->vpattern); + } + array_foreach(&global_rights->rights, rights) { + new_rights = array_append_space(rights_r); + acl_rights_dup(rights, pool, new_rights); + } + } +} + +bool acl_global_file_have_any(struct acl_global_file *file, const char *vname) +{ + struct acl_global_rights *rights; + + i_assert(file->last_refresh_time != 0); + + array_foreach_modifiable(&file->rights, rights) { + if (wildcard_match(vname, rights->vpattern)) + return TRUE; + } + return FALSE; +} diff --git a/src/plugins/acl/acl-global-file.h b/src/plugins/acl/acl-global-file.h new file mode 100644 index 0000000..d393dec --- /dev/null +++ b/src/plugins/acl/acl-global-file.h @@ -0,0 +1,23 @@ +#ifndef ACL_GLOBAL_FILE_H +#define ACL_GLOBAL_FILE_H + +#include "acl-api.h" + +struct acl_global_file * +acl_global_file_init(const char *path, unsigned int refresh_interval_secs, + bool debug); +void acl_global_file_deinit(struct acl_global_file **file); + +/* Read the global ACLs into memory. */ +int acl_global_file_refresh(struct acl_global_file *file); +/* Return stat data for the last refresh. */ +void acl_global_file_last_stat(struct acl_global_file *file, struct stat *st_r); + +/* Return global ACL rights matching the mailbox name. The file must already + have been refreshed at least once. */ +void acl_global_file_get(struct acl_global_file *file, const char *vname, + pool_t pool, ARRAY_TYPE(acl_rights) *rights_r); +/* Returns TRUE if there are any global ACLs matching the mailbox name. */ +bool acl_global_file_have_any(struct acl_global_file *file, const char *vname); + +#endif diff --git a/src/plugins/acl/acl-lookup-dict.c b/src/plugins/acl/acl-lookup-dict.c new file mode 100644 index 0000000..638a767 --- /dev/null +++ b/src/plugins/acl/acl-lookup-dict.c @@ -0,0 +1,373 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "dict.h" +#include "mail-user.h" +#include "mail-namespace.h" +#include "acl-api-private.h" +#include "acl-storage.h" +#include "acl-plugin.h" +#include "acl-lookup-dict.h" + + +#define DICT_SHARED_BOXES_PATH "shared-boxes/" + +struct acl_lookup_dict { + struct mail_user *user; + struct dict *dict; +}; + +struct acl_lookup_dict_iter { + pool_t pool; + struct acl_lookup_dict *dict; + + pool_t iter_value_pool; + ARRAY_TYPE(const_string) iter_ids; + ARRAY_TYPE(const_string) iter_values; + unsigned int iter_idx, iter_value_idx; + + bool failed:1; +}; + +struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user) +{ + struct acl_lookup_dict *dict; + const char *uri, *error; + + dict = i_new(struct acl_lookup_dict, 1); + dict->user = user; + + uri = mail_user_plugin_getenv(user, "acl_shared_dict"); + if (uri != NULL) { + struct dict_settings dict_set; + + i_zero(&dict_set); + dict_set.base_dir = user->set->base_dir; + dict_set.event_parent = user->event; + if (dict_init(uri, &dict_set, &dict->dict, &error) < 0) + i_error("acl: dict_init(%s) failed: %s", uri, error); + } else { + e_debug(user->event, "acl: No acl_shared_dict setting - " + "shared mailbox listing is disabled"); + } + return dict; +} + +void acl_lookup_dict_deinit(struct acl_lookup_dict **_dict) +{ + struct acl_lookup_dict *dict = *_dict; + + *_dict = NULL; + if (dict->dict != NULL) + dict_deinit(&dict->dict); + i_free(dict); +} + +bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict) +{ + return dict->dict != NULL; +} + +static void +acl_lookup_dict_write_rights_id(string_t *dest, const struct acl_rights *right) +{ + switch (right->id_type) { + case ACL_ID_ANYONE: + case ACL_ID_AUTHENTICATED: + /* don't bother separating these */ + str_append(dest, "anyone"); + break; + case ACL_ID_USER: + str_append(dest, "user/"); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + str_append(dest, "group/"); + str_append(dest, right->identifier); + break; + case ACL_ID_OWNER: + case ACL_ID_TYPE_COUNT: + i_unreached(); + } +} + +static bool +acl_rights_is_same_user(const struct acl_rights *right, struct mail_user *user) +{ + return right->id_type == ACL_ID_USER && + strcmp(right->identifier, user->username) == 0; +} + +static int acl_lookup_dict_rebuild_add_backend(struct mail_namespace *ns, + ARRAY_TYPE(const_string) *ids) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct acl_backend *backend; + struct acl_mailbox_list_context *ctx; + struct acl_object *aclobj; + struct acl_object_list_iter *iter; + struct acl_rights rights; + const char *name, *id_dup; + string_t *id; + int ret = 0; + + if ((ns->flags & NAMESPACE_FLAG_NOACL) != 0 || ns->owner == NULL || + alist == NULL || alist->ignore_acls) + return 0; + + id = t_str_new(128); + backend = acl_mailbox_list_get_backend(ns->list); + ctx = acl_backend_nonowner_lookups_iter_init(backend); + while (acl_backend_nonowner_lookups_iter_next(ctx, &name)) { + aclobj = acl_object_init_from_name(backend, name); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + /* avoid pointless user -> user entries, + which some clients do */ + if (acl_rights_has_nonowner_lookup_changes(&rights) && + !acl_rights_is_same_user(&rights, ns->owner)) { + str_truncate(id, 0); + acl_lookup_dict_write_rights_id(id, &rights); + str_append_c(id, '/'); + str_append(id, ns->owner->username); + id_dup = t_strdup(str_c(id)); + array_push_back(ids, &id_dup); + } + } + if (acl_object_list_deinit(&iter) < 0) ret = -1; + acl_object_deinit(&aclobj); + } + if (acl_backend_nonowner_lookups_iter_deinit(&ctx) < 0) ret = -1; + return ret; +} + +static int +acl_lookup_dict_rebuild_update(struct acl_lookup_dict *dict, + const ARRAY_TYPE(const_string) *new_ids_arr, + bool no_removes) +{ + const char *username = dict->user->username; + struct dict_iterate_context *iter; + struct dict_transaction_context *dt = NULL; + const char *prefix, *key, *value, *const *old_ids, *const *new_ids, *p; + const char *error; + ARRAY_TYPE(const_string) old_ids_arr; + unsigned int newi, oldi, old_count, new_count; + string_t *path; + size_t prefix_len; + int ret; + const struct dict_op_settings *set = mail_user_get_dict_op_settings(dict->user); + + /* get all existing identifiers for the user. we might be able to + sync identifiers also for other users whose shared namespaces we + have, but it's possible that the other users have other namespaces + that aren't visible to us, so we don't want to remove anything + that could break them. */ + t_array_init(&old_ids_arr, 128); + prefix = DICT_PATH_SHARED DICT_SHARED_BOXES_PATH; + prefix_len = strlen(prefix); + iter = dict_iterate_init(dict->dict, set, prefix, DICT_ITERATE_FLAG_RECURSE); + while (dict_iterate(iter, &key, &value)) { + /* prefix/$type/$dest/$source */ + key += prefix_len; + p = strrchr(key, '/'); + if (p != NULL && strcmp(p + 1, username) == 0) { + key = t_strdup_until(key, p); + array_push_back(&old_ids_arr, &key); + } + } + if (dict_iterate_deinit(&iter, &error) < 0) { + i_error("acl: dict iteration failed: %s - can't update dict", error); + return -1; + } + + /* sort the existing identifiers */ + array_sort(&old_ids_arr, i_strcmp_p); + + /* sync the identifiers */ + path = t_str_new(256); + str_append(path, prefix); + + old_ids = array_get(&old_ids_arr, &old_count); + new_ids = array_get(new_ids_arr, &new_count); + for (newi = oldi = 0; newi < new_count || oldi < old_count; ) { + ret = newi == new_count ? 1 : + oldi == old_count ? -1 : + strcmp(new_ids[newi], old_ids[oldi]); + if (ret == 0) { + newi++; oldi++; + } else if (ret < 0) { + /* new identifier, add it */ + str_truncate(path, prefix_len); + str_append(path, new_ids[newi]); + dt = dict_transaction_begin(dict->dict, set); + dict_set(dt, str_c(path), "1"); + newi++; + } else if (!no_removes) { + /* old identifier removed */ + str_truncate(path, prefix_len); + str_append(path, old_ids[oldi]); + str_append_c(path, '/'); + str_append(path, username); + dt = dict_transaction_begin(dict->dict, set); + dict_unset(dt, str_c(path)); + oldi++; + } + if (dt != NULL && dict_transaction_commit(&dt, &error) < 0) { + i_error("acl: dict commit failed: %s", error); + return -1; + } + i_assert(dt == NULL); + } + return 0; +} + +int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict) +{ + struct mail_namespace *ns; + ARRAY_TYPE(const_string) ids_arr; + const char **ids; + unsigned int i, dest, count; + int ret = 0; + + if (dict->dict == NULL) + return 0; + + /* get all ACL identifiers with a positive lookup right */ + t_array_init(&ids_arr, 128); + for (ns = dict->user->namespaces; ns != NULL; ns = ns->next) { + if (acl_lookup_dict_rebuild_add_backend(ns, &ids_arr) < 0) + ret = -1; + } + + /* sort identifiers and remove duplicates */ + array_sort(&ids_arr, i_strcmp_p); + + ids = array_get_modifiable(&ids_arr, &count); + for (i = 1, dest = 0; i < count; i++) { + if (strcmp(ids[dest], ids[i]) != 0) { + if (++dest != i) + ids[dest] = ids[i]; + } + } + if (++dest < count) + array_delete(&ids_arr, dest, count-dest); + + /* if lookup failed at some point we can still add new ids, + but we can't remove any existing ones */ + if (acl_lookup_dict_rebuild_update(dict, &ids_arr, ret < 0) < 0) + ret = -1; + return ret; +} + +static void acl_lookup_dict_iterate_read(struct acl_lookup_dict_iter *iter) +{ + struct dict_iterate_context *dict_iter; + const char *id, *prefix, *key, *value, *error; + size_t prefix_len; + + id = array_idx_elem(&iter->iter_ids, iter->iter_idx); + iter->iter_idx++; + iter->iter_value_idx = 0; + + prefix = t_strconcat(DICT_PATH_SHARED DICT_SHARED_BOXES_PATH, + id, "/", NULL); + prefix_len = strlen(prefix); + + /* read all of it to memory. at least currently dict-proxy can support + only one iteration at a time, but the acl code can end up rebuilding + the dict, which opens another iteration. */ + p_clear(iter->iter_value_pool); + array_clear(&iter->iter_values); + const struct dict_op_settings *set = mail_user_get_dict_op_settings(iter->dict->user); + dict_iter = dict_iterate_init(iter->dict->dict, set, prefix, + DICT_ITERATE_FLAG_RECURSE); + while (dict_iterate(dict_iter, &key, &value)) { + i_assert(prefix_len < strlen(key)); + + key = p_strdup(iter->iter_value_pool, key + prefix_len); + array_push_back(&iter->iter_values, &key); + } + if (dict_iterate_deinit(&dict_iter, &error) < 0) { + i_error("%s", error); + iter->failed = TRUE; + } +} + +struct acl_lookup_dict_iter * +acl_lookup_dict_iterate_visible_init(struct acl_lookup_dict *dict) +{ + struct acl_user *auser = ACL_USER_CONTEXT(dict->user); + struct acl_lookup_dict_iter *iter; + const char *id; + unsigned int i; + pool_t pool; + + i_assert(auser != NULL); + + pool = pool_alloconly_create("acl lookup dict iter", 1024); + iter = p_new(pool, struct acl_lookup_dict_iter, 1); + iter->pool = pool; + iter->dict = dict; + + p_array_init(&iter->iter_ids, pool, 16); + id = "anyone"; + array_push_back(&iter->iter_ids, &id); + id = p_strconcat(pool, "user/", dict->user->username, NULL); + array_push_back(&iter->iter_ids, &id); + + i_array_init(&iter->iter_values, 64); + iter->iter_value_pool = + pool_alloconly_create("acl lookup dict iter values", 1024); + + /* get all groups we belong to */ + if (auser->groups != NULL) { + for (i = 0; auser->groups[i] != NULL; i++) { + id = p_strconcat(pool, "group/", auser->groups[i], + NULL); + array_push_back(&iter->iter_ids, &id); + } + } + + /* iterate through all identifiers that match us, start with the + first one */ + if (dict->dict != NULL) + acl_lookup_dict_iterate_read(iter); + else + array_clear(&iter->iter_ids); + return iter; +} + +const char * +acl_lookup_dict_iterate_visible_next(struct acl_lookup_dict_iter *iter) +{ + const char *const *keys; + unsigned int count; + + keys = array_get(&iter->iter_values, &count); + if (iter->iter_value_idx < count) + return keys[iter->iter_value_idx++]; + + if (iter->iter_idx < array_count(&iter->iter_ids)) { + /* get to the next iterator */ + acl_lookup_dict_iterate_read(iter); + return acl_lookup_dict_iterate_visible_next(iter); + } + return NULL; +} + +int acl_lookup_dict_iterate_visible_deinit(struct acl_lookup_dict_iter **_iter) +{ + struct acl_lookup_dict_iter *iter = *_iter; + int ret = iter->failed ? -1 : 0; + + *_iter = NULL; + array_free(&iter->iter_values); + pool_unref(&iter->iter_value_pool); + pool_unref(&iter->pool); + return ret; +} diff --git a/src/plugins/acl/acl-lookup-dict.h b/src/plugins/acl/acl-lookup-dict.h new file mode 100644 index 0000000..856aaf4 --- /dev/null +++ b/src/plugins/acl/acl-lookup-dict.h @@ -0,0 +1,17 @@ +#ifndef ACL_LOOKUP_DICT_H +#define ACL_LOOKUP_DICT_H + +struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user); +void acl_lookup_dict_deinit(struct acl_lookup_dict **dict); + +bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict); + +int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict); + +struct acl_lookup_dict_iter * +acl_lookup_dict_iterate_visible_init(struct acl_lookup_dict *dict); +const char * +acl_lookup_dict_iterate_visible_next(struct acl_lookup_dict_iter *iter); +int acl_lookup_dict_iterate_visible_deinit(struct acl_lookup_dict_iter **iter); + +#endif diff --git a/src/plugins/acl/acl-mailbox-list.c b/src/plugins/acl/acl-mailbox-list.c new file mode 100644 index 0000000..18e04ad --- /dev/null +++ b/src/plugins/acl/acl-mailbox-list.c @@ -0,0 +1,629 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "imap-match.h" +#include "wildcard-match.h" +#include "mailbox-tree.h" +#include "mail-namespace.h" +#include "mailbox-list-iter-private.h" +#include "acl-api-private.h" +#include "acl-cache.h" +#include "acl-shared-storage.h" +#include "acl-plugin.h" + +#define MAILBOX_FLAG_MATCHED 0x40000000 + +struct acl_mailbox_list_iterate_context { + union mailbox_list_iterate_module_context module_ctx; + + struct mailbox_tree_context *lookup_boxes; + struct mailbox_info info; + + char sep; + bool hide_nonlistable_subscriptions:1; + bool simple_star_glob:1; + bool autocreate_acls_checked:1; +}; + +static const char *acl_storage_right_names[ACL_STORAGE_RIGHT_COUNT] = { + MAIL_ACL_LOOKUP, + MAIL_ACL_READ, + MAIL_ACL_WRITE, + MAIL_ACL_WRITE_SEEN, + MAIL_ACL_WRITE_DELETED, + MAIL_ACL_INSERT, + MAIL_ACL_POST, + MAIL_ACL_EXPUNGE, + MAIL_ACL_CREATE, + MAIL_ACL_DELETE, + MAIL_ACL_ADMIN +}; + +#define ACL_LIST_ITERATE_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_mailbox_list_module) + +struct acl_mailbox_list_module acl_mailbox_list_module = + MODULE_CONTEXT_INIT(&mailbox_list_module_register); + +struct acl_backend *acl_mailbox_list_get_backend(struct mailbox_list *list) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + + return alist->rights.backend; +} + +int acl_mailbox_list_have_right(struct mailbox_list *list, const char *name, + bool parent, unsigned int acl_storage_right_idx, + bool *can_see_r) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + struct acl_backend *backend = alist->rights.backend; + const unsigned int *idx_arr = alist->rights.acl_storage_right_idx; + struct acl_object *aclobj; + int ret, ret2; + + if (alist->ignore_acls) + return 1; + + aclobj = !parent ? + acl_object_init_from_name(backend, name) : + acl_object_init_from_parent(backend, name); + ret = acl_object_have_right(aclobj, idx_arr[acl_storage_right_idx]); + + if (can_see_r != NULL) { + ret2 = acl_object_have_right(aclobj, + idx_arr[ACL_STORAGE_RIGHT_LOOKUP]); + if (ret2 < 0) + ret = -1; + *can_see_r = ret2 > 0; + } + acl_object_deinit(&aclobj); + + if (ret < 0) + mailbox_list_set_internal_error(list); + return ret; +} + +static void +acl_mailbox_try_list_fast(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list); + struct acl_backend *backend = alist->rights.backend; + const unsigned int *idxp; + const struct acl_mask *acl_mask; + struct acl_mailbox_list_context *nonowner_list_ctx; + struct mail_namespace *ns = _ctx->list->ns; + struct mailbox_list_iter_update_context update_ctx; + const char *name; + + if ((_ctx->flags & (MAILBOX_LIST_ITER_RAW_LIST | + MAILBOX_LIST_ITER_SELECT_SUBSCRIBED)) != 0) + return; + + if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC) { + /* mailboxes in public namespace should all be listable to + someone. we don't benefit from fast listing. */ + return; + } + + /* If ACLs are ignored for this namespace don't try fast listing. */ + if (alist->ignore_acls) + return; + + /* if this namespace's default rights contain LOOKUP, we'll need to + go through all mailboxes in any case. */ + idxp = alist->rights.acl_storage_right_idx + ACL_STORAGE_RIGHT_LOOKUP; + if (acl_backend_get_default_rights(backend, &acl_mask) < 0 || + acl_cache_mask_isset(acl_mask, *idxp)) + return; + + /* no LOOKUP right by default, we can optimize this */ + i_zero(&update_ctx); + update_ctx.iter_ctx = _ctx; + update_ctx.glob = + imap_match_init(pool_datastack_create(), "*", + (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0, + ctx->sep); + update_ctx.match_parents = TRUE; + update_ctx.tree_ctx = mailbox_tree_init(ctx->sep); + + nonowner_list_ctx = acl_backend_nonowner_lookups_iter_init(backend); + while (acl_backend_nonowner_lookups_iter_next(nonowner_list_ctx, + &name)) { + T_BEGIN { + const char *vname = + mailbox_list_get_vname(ns->list, name); + mailbox_list_iter_update(&update_ctx, vname); + } T_END; + } + + if (acl_backend_nonowner_lookups_iter_deinit(&nonowner_list_ctx) >= 0) + ctx->lookup_boxes = update_ctx.tree_ctx; + else + mailbox_tree_deinit(&update_ctx.tree_ctx); +} + +static struct mailbox_list_iterate_context * +acl_mailbox_list_iter_init_shared(struct mailbox_list *list, + const char *const *patterns, + enum mailbox_list_iter_flags flags) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + struct mailbox_list_iterate_context *ctx; + int ret; + + /* before listing anything add namespaces for all users + who may have visible mailboxes */ + ret = acl_shared_namespaces_add(list->ns); + + ctx = alist->module_ctx.super.iter_init(list, patterns, flags); + if (ret < 0) + ctx->failed = TRUE; + return ctx; +} + +static struct mailbox_list_iterate_context * +acl_mailbox_list_iter_init(struct mailbox_list *list, + const char *const *patterns, + enum mailbox_list_iter_flags flags) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + struct mailbox_list_iterate_context *_ctx; + struct acl_mailbox_list_iterate_context *ctx; + const char *p; + unsigned int i; + + _ctx = alist->module_ctx.super.iter_init(list, patterns, flags); + + ctx = p_new(_ctx->pool, struct acl_mailbox_list_iterate_context, 1); + + if (list->ns->type != MAIL_NAMESPACE_TYPE_PRIVATE && + (list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0) { + /* non-private namespace with subscriptions=yes. this could be + a site-global subscriptions file, so hide subscriptions for + mailboxes the user doesn't see. */ + ctx->hide_nonlistable_subscriptions = TRUE; + } + + ctx->sep = mail_namespace_get_sep(list->ns); + /* see if all patterns have only a single '*' and it's at the end. + we can use it to do some optimizations. */ + ctx->simple_star_glob = TRUE; + for (i = 0; patterns[i] != NULL; i++) { + p = strchr(patterns[i], '*'); + if (p == NULL || p[1] != '\0') { + ctx->simple_star_glob = FALSE; + break; + } + } + + MODULE_CONTEXT_SET(_ctx, acl_mailbox_list_module, ctx); + + /* Try to avoid reading ACLs from all mailboxes by getting a smaller + list of mailboxes that have even potential to be visible. If we + couldn't get such a list, we'll go through all mailboxes. */ + T_BEGIN { + acl_mailbox_try_list_fast(_ctx); + } T_END; + + return _ctx; +} + +static const struct mailbox_info * +acl_mailbox_list_iter_next_info(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list); + const struct mailbox_info *info; + + for (;;) { + /* Normally the data stack frame is in mailbox_list_iter_next(), + but we're bypassing it here by calling super.iter_next() + directly. */ + T_BEGIN { + info = alist->module_ctx.super.iter_next(_ctx); + } T_END; + if (info == NULL) + break; + + /* if we've a list of mailboxes with LOOKUP rights, skip the + mailboxes not in the list (since we know they can't be + visible to us). */ + if (ctx->lookup_boxes == NULL || + mailbox_tree_lookup(ctx->lookup_boxes, info->vname) != NULL) + break; + e_debug(_ctx->list->ns->user->event, + "acl: Mailbox not in dovecot-acl-list: %s", info->vname); + } + + return info; +} + +static const char * +acl_mailbox_list_iter_get_name(struct mailbox_list_iterate_context *ctx, + const char *vname) +{ + struct mail_namespace *ns = ctx->list->ns; + const char *name; + size_t len; + + name = mailbox_list_get_storage_name(ns->list, vname); + len = strlen(name); + if (len > 0 && name[len-1] == mailbox_list_get_hierarchy_sep(ns->list)) { + /* name ends with separator. this can happen if doing e.g. + LIST "" foo/% and it lists "foo/". */ + name = t_strndup(name, len-1); + } + return name; +} + +static bool +iter_is_listing_all_children(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + const char *child; + + /* If all patterns (with '.' separator) are in "name*", "name.*" or + "%.*" style format, simple_star_glob=TRUE and we can easily test + this by simply checking if name/child mailbox matches. */ + child = t_strdup_printf("%s%cx", ctx->info.vname, ctx->sep); + return ctx->simple_star_glob && + imap_match(_ctx->glob, child) == IMAP_MATCH_YES; +} + +static bool +iter_mailbox_has_visible_children(struct mailbox_list_iterate_context *_ctx, + bool only_nonpatterns, bool subscribed) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + string_t *pattern; + const char *prefix; + size_t i, prefix_len; + bool stars = FALSE, ret = FALSE; + + /* do we have child mailboxes with LOOKUP right that don't match + the list pattern? */ + if (ctx->lookup_boxes != NULL) { + /* we have a list of mailboxes with LOOKUP rights. before + starting the slow list iteration, check check first + if there even are any children with LOOKUP rights. */ + struct mailbox_node *node; + + node = mailbox_tree_lookup(ctx->lookup_boxes, ctx->info.vname); + i_assert(node != NULL); + if (node->children == NULL) + return FALSE; + } + + /* if mailbox name has '*' characters in it, they'll conflict with the + LIST wildcard. replace then with '%' and verify later that all + results have the correct prefix. */ + pattern = t_str_new(128); + for (i = 0; ctx->info.vname[i] != '\0'; i++) { + if (ctx->info.vname[i] != '*') + str_append_c(pattern, ctx->info.vname[i]); + else { + stars = TRUE; + str_append_c(pattern, '%'); + } + } + if (i > 0 && ctx->info.vname[i-1] != ctx->sep) + str_append_c(pattern, ctx->sep); + str_append_c(pattern, '*'); + prefix = str_c(pattern); + prefix_len = str_len(pattern) - 1; + + iter = mailbox_list_iter_init(_ctx->list, str_c(pattern), + (!subscribed ? 0 : + MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + if (only_nonpatterns && + imap_match(_ctx->glob, info->vname) == IMAP_MATCH_YES) { + /* at least one child matches also the original list + patterns. we don't need to show this mailbox. */ + ret = FALSE; + break; + } + if (!stars || strncmp(info->vname, prefix, prefix_len) == 0) + ret = TRUE; + } + (void)mailbox_list_iter_deinit(&iter); + return ret; +} + +static int +acl_mailbox_list_info_is_visible(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); +#define PRESERVE_MAILBOX_FLAGS (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED) + struct mailbox_info *info = &ctx->info; + const char *acl_name; + int ret; + + if ((_ctx->flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) { + /* skip ACL checks. */ + return 1; + } + + if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0 && + (_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 && + !ctx->hide_nonlistable_subscriptions) { + /* don't waste time doing an ACL check. we're going to list + all subscriptions anyway. */ + info->flags &= MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED; + return 1; + } + + acl_name = acl_mailbox_list_iter_get_name(_ctx, info->vname); + ret = acl_mailbox_list_have_right(_ctx->list, acl_name, FALSE, + ACL_STORAGE_RIGHT_LOOKUP, + NULL); + if (ret != 0) { + if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0) { + /* don't waste time checking if there are visible + children, but also don't return incorrect flags */ + info->flags &= ENUM_NEGATE(MAILBOX_CHILDREN); + } else if ((info->flags & MAILBOX_CHILDREN) != 0 && + !iter_mailbox_has_visible_children(_ctx, FALSE, FALSE)) { + info->flags &= ENUM_NEGATE(MAILBOX_CHILDREN); + info->flags |= MAILBOX_NOCHILDREN; + } + return ret; + } + + /* no permission to see this mailbox */ + if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) { + /* we're listing subscribed mailboxes. this one or its child + is subscribed, so we'll need to list it. but since we don't + have LOOKUP right, we'll need to show it as nonexistent. */ + i_assert((info->flags & PRESERVE_MAILBOX_FLAGS) != 0); + info->flags = MAILBOX_NONEXISTENT | + (info->flags & PRESERVE_MAILBOX_FLAGS); + if (ctx->hide_nonlistable_subscriptions) { + /* global subscriptions file. hide this entry if there + are no visible subscribed children or if we're going + to list the subscribed children anyway. */ + if ((info->flags & MAILBOX_CHILD_SUBSCRIBED) == 0) + return 0; + if (iter_is_listing_all_children(_ctx) || + !iter_mailbox_has_visible_children(_ctx, TRUE, TRUE)) + return 0; + /* e.g. LSUB "" % with visible subscribed children */ + } + return 1; + } + + if (!iter_is_listing_all_children(_ctx) && + iter_mailbox_has_visible_children(_ctx, TRUE, FALSE)) { + /* no child mailboxes match the list pattern(s), but mailbox + has visible children. we'll need to show this as + non-existent. */ + info->flags = MAILBOX_NONEXISTENT | MAILBOX_CHILDREN | + (info->flags & PRESERVE_MAILBOX_FLAGS); + return 1; + } + return 0; +} + +static int +acl_mailbox_list_iter_check_autocreate_acls(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct mailbox_settings *const *box_sets; + unsigned int i, count; + int ret; + + ctx->autocreate_acls_checked = TRUE; + if (_ctx->autocreate_ctx == NULL) + return 0; + if ((_ctx->flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) { + /* skip ACL checks. */ + return 0; + } + + box_sets = array_get(&_ctx->autocreate_ctx->box_sets, &count); + i_assert(array_count(&_ctx->autocreate_ctx->boxes) == count); + + for (i = 0; i < count; ) { + const char *acl_name = + acl_mailbox_list_iter_get_name(_ctx, box_sets[i]->name); + ret = acl_mailbox_list_have_right(_ctx->list, acl_name, FALSE, + ACL_STORAGE_RIGHT_LOOKUP, + NULL); + if (ret < 0) + return -1; + if (ret > 0) + i++; + else { + /* no list right - remove the whole autobox */ + array_delete(&_ctx->autocreate_ctx->box_sets, i, 1); + array_delete(&_ctx->autocreate_ctx->boxes, i, 1); + box_sets = array_get(&_ctx->autocreate_ctx->box_sets, &count); + } + } + return 0; +} + +static const struct mailbox_info * +acl_mailbox_list_iter_next(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + const struct mailbox_info *info; + int ret; + + if (!ctx->autocreate_acls_checked) { + if (acl_mailbox_list_iter_check_autocreate_acls(_ctx) < 0) { + _ctx->failed = TRUE; + return NULL; + } + } + + while ((info = acl_mailbox_list_iter_next_info(_ctx)) != NULL) { + ctx->info = *info; + T_BEGIN { + ret = acl_mailbox_list_info_is_visible(_ctx); + } T_END; + if (ret > 0) + break; + if (ret < 0) { + _ctx->failed = TRUE; + return NULL; + } + /* skip to next one */ + e_debug(_ctx->list->ns->user->event, + "acl: No lookup right to mailbox: %s", info->vname); + } + return info == NULL ? NULL : &ctx->info; +} + +static int +acl_mailbox_list_iter_deinit(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list); + int ret = _ctx->failed ? -1 : 0; + + if (ctx->lookup_boxes != NULL) + mailbox_tree_deinit(&ctx->lookup_boxes); + if (alist->module_ctx.super.iter_deinit(_ctx) < 0) + ret = -1; + return ret; +} + +static void acl_mailbox_list_deinit(struct mailbox_list *list) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + + if (alist->rights.backend != NULL) + acl_backend_deinit(&alist->rights.backend); + alist->module_ctx.super.deinit(list); +} + +static void acl_mailbox_list_init_shared(struct mailbox_list *list) +{ + struct acl_mailbox_list *alist; + struct mailbox_list_vfuncs *v = list->vlast; + + alist = p_new(list->pool, struct acl_mailbox_list, 1); + alist->module_ctx.super = *v; + list->vlast = &alist->module_ctx.super; + v->deinit = acl_mailbox_list_deinit; + v->iter_init = acl_mailbox_list_iter_init_shared; + + MODULE_CONTEXT_SET(list, acl_mailbox_list_module, alist); +} + +static void acl_storage_rights_ctx_init(struct acl_storage_rights_context *ctx, + struct acl_backend *backend) +{ + unsigned int i; + + ctx->backend = backend; + for (i = 0; i < ACL_STORAGE_RIGHT_COUNT; i++) { + ctx->acl_storage_right_idx[i] = + acl_backend_lookup_right(backend, + acl_storage_right_names[i]); + } +} + +static bool acl_namespace_is_ignored(struct mailbox_list *list) +{ + const char *value = + mail_user_plugin_getenv(list->ns->user, "acl_ignore_namespace"); + for (unsigned int i = 2; value != NULL; i++) { + if (wildcard_match(list->ns->prefix, value)) + return TRUE; + value = mail_user_plugin_getenv(list->ns->user, + t_strdup_printf("acl_ignore_namespace%u", i)); + } + return FALSE; +} + +static void acl_mailbox_list_init_default(struct mailbox_list *list) +{ + struct mailbox_list_vfuncs *v = list->vlast; + struct acl_mailbox_list *alist; + + if (list->mail_set->mail_full_filesystem_access) { + /* not necessarily, but safer to do this for now. */ + i_fatal("mail_full_filesystem_access=yes is " + "incompatible with ACLs"); + } + + alist = p_new(list->pool, struct acl_mailbox_list, 1); + alist->module_ctx.super = *v; + list->vlast = &alist->module_ctx.super; + v->deinit = acl_mailbox_list_deinit; + v->iter_init = acl_mailbox_list_iter_init; + v->iter_next = acl_mailbox_list_iter_next; + v->iter_deinit = acl_mailbox_list_iter_deinit; + if (acl_namespace_is_ignored(list)) + alist->ignore_acls = TRUE; + + MODULE_CONTEXT_SET(list, acl_mailbox_list_module, alist); +} + +void acl_mail_namespace_storage_added(struct mail_namespace *ns) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct acl_backend *backend; + const char *current_username, *owner_username; + bool owner = TRUE; + + if (alist == NULL) + return; + struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(ns->user); + + owner_username = ns->user->username; + current_username = auser->acl_user; + if (current_username == NULL) + current_username = owner_username; + else + owner = strcmp(current_username, owner_username) == 0; + + /* We don't care about the username for non-private mailboxes. + It's used only when checking if we're the mailbox owner. We never + are for shared/public mailboxes. */ + if (ns->type != MAIL_NAMESPACE_TYPE_PRIVATE) + owner = FALSE; + + /* we need to know the storage when initializing backend */ + backend = acl_backend_init(auser->acl_env, ns->list, current_username, + auser->groups, owner); + if (backend == NULL) + i_fatal("ACL backend initialization failed"); + acl_storage_rights_ctx_init(&alist->rights, backend); +} + +void acl_mailbox_list_created(struct mailbox_list *list) +{ + struct acl_user *auser = ACL_USER_CONTEXT(list->ns->user); + + if (auser == NULL) { + /* ACLs disabled for this user */ + } else if ((list->ns->flags & NAMESPACE_FLAG_NOACL) != 0) { + /* no ACL checks for internal namespaces (lda, shared) */ + if (list->ns->type == MAIL_NAMESPACE_TYPE_SHARED) + acl_mailbox_list_init_shared(list); + } else if ((list->ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) { + /* this namespace is empty. don't attempt to lookup ACLs, + because they're not going to work anyway and we could + crash doing it. */ + } else { + acl_mailbox_list_init_default(list); + } +} diff --git a/src/plugins/acl/acl-mailbox.c b/src/plugins/acl/acl-mailbox.c new file mode 100644 index 0000000..134be7f --- /dev/null +++ b/src/plugins/acl/acl-mailbox.c @@ -0,0 +1,714 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +/* FIXME: If we don't have permission to change flags/keywords, the changes + should still be stored temporarily for this session. However most clients + don't care and it's a huge job, so I currently this isn't done. The same + problem actually exists when opening read-only mailboxes. */ +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "istream.h" +#include "mailbox-list-private.h" +#include "acl-api-private.h" +#include "acl-plugin.h" + +#include <sys/stat.h> + +#define ACL_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_mail_module) + +struct acl_transaction_context { + union mailbox_transaction_module_context module_ctx; +}; + +static MODULE_CONTEXT_DEFINE_INIT(acl_mail_module, &mail_module_register); +static struct acl_transaction_context acl_transaction_failure; + +struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + return abox->aclobj; +} + +int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + int ret; + + if (abox->skip_acl_checks) + return 1; + + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list); + + /* If acls are ignored for this namespace do not check if + there are rights. */ + if (alist->ignore_acls) + return 1; + + ret = acl_object_have_right(abox->aclobj, + alist->rights.acl_storage_right_idx[right_idx]); + if (ret > 0) + return 1; + if (ret < 0) { + mail_storage_set_internal_error(box->storage); + return -1; + } + + mail_storage_set_error(box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + return 0; +} + +static bool acl_is_readonly(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + enum acl_storage_rights save_right; + + if (abox->module_ctx.super.is_readonly(box)) + return TRUE; + + save_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? + ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; + if (acl_mailbox_right_lookup(box, save_right) > 0) + return FALSE; + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_EXPUNGE) > 0) + return FALSE; + + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0) + return FALSE; + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0) + return FALSE; + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0) + return FALSE; + + return TRUE; +} + +static void acl_mailbox_free(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + if (abox->aclobj != NULL) + acl_object_deinit(&abox->aclobj); + abox->module_ctx.super.free(box); +} + +static void acl_mailbox_copy_acls_from_parent(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list); + struct acl_object *parent_aclobj; + struct acl_object_list_iter *iter; + struct acl_rights_update update; + + i_zero(&update); + update.modify_mode = ACL_MODIFY_MODE_REPLACE; + update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + + parent_aclobj = acl_object_init_from_parent(alist->rights.backend, + box->name); + iter = acl_object_list_init(parent_aclobj); + while (acl_object_list_next(iter, &update.rights)) { + /* don't copy global ACL rights. */ + if (!update.rights.global) + (void)acl_object_update(abox->aclobj, &update); + } + /* FIXME: Add error handling */ + (void)acl_object_list_deinit(&iter); + acl_object_deinit(&parent_aclobj); +} + +static int +acl_mailbox_create(struct mailbox *box, const struct mailbox_update *update, + bool directory) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + int ret; + + if (!mailbox_is_autocreated(box)) { + /* we're looking up CREATE permission from our parent's rights */ + ret = acl_mailbox_list_have_right(box->list, box->name, TRUE, + ACL_STORAGE_RIGHT_CREATE, NULL); + } else { + /* mailbox is autocreated, so we need to treat it as if it + already exists. ignore the "create" ACL here. */ + ret = 1; + } + if (ret <= 0) { + if (ret < 0) { + mail_storage_set_internal_error(box->storage); + return -1; + } + /* Note that if user didn't have LOOKUP permission to parent + mailbox, this may reveal the mailbox's existence to user. + Can't help it. */ + mail_storage_set_error(box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + return -1; + } + + /* ignore ACLs in this mailbox until creation is complete, because + super.create() may call e.g. mailbox_open() which will fail since + we haven't yet copied ACLs to this mailbox. */ + abox->skip_acl_checks = TRUE; + ret = abox->module_ctx.super.create_box(box, update, directory); + abox->skip_acl_checks = FALSE; + /* update local acl object, otherwise with LAYOUT=INDEX, we end up + without local path to acl file, and copying fails. */ + struct acl_backend *acl_be = abox->aclobj->backend; + acl_object_deinit(&abox->aclobj); + abox->aclobj = acl_object_init_from_name(acl_be, box->name); + + if (ret == 0) + acl_mailbox_copy_acls_from_parent(box); + return ret; +} + +static int +acl_mailbox_update(struct mailbox *box, const struct mailbox_update *update) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + int ret; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN); + if (ret <= 0) + return -1; + return abox->module_ctx.super.update_box(box, update); +} + +static void acl_mailbox_fail_not_found(struct mailbox *box) +{ + int ret; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP); + if (ret > 0) { + mail_storage_set_error(box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + } else if (ret == 0) { + box->acl_no_lookup_right = TRUE; + mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND, + T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname)); + } +} + +static int +acl_mailbox_delete(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + int ret; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_DELETE); + if (ret <= 0) { + if (ret == 0) + acl_mailbox_fail_not_found(box); + return -1; + } + + return abox->module_ctx.super.delete_box(box); +} + +static int +acl_mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(src); + int ret; + + /* renaming requires rights to delete the old mailbox */ + ret = acl_mailbox_right_lookup(src, ACL_STORAGE_RIGHT_DELETE); + if (ret <= 0) { + if (ret == 0) + acl_mailbox_fail_not_found(src); + return -1; + } + + /* and create the new one under the parent mailbox */ + T_BEGIN { + ret = acl_mailbox_list_have_right(dest->list, dest->name, TRUE, + ACL_STORAGE_RIGHT_CREATE, NULL); + } T_END; + + if (ret <= 0) { + if (ret == 0) { + /* Note that if the mailbox didn't have LOOKUP + permission, this now reveals to user the mailbox's + existence. Can't help it. */ + mail_storage_set_error(src->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + } else { + mail_storage_set_internal_error(src->storage); + } + return -1; + } + + return abox->module_ctx.super.rename_box(src, dest); +} + +static int +acl_get_write_rights(struct mailbox *box, + bool *flags_r, bool *flag_seen_r, bool *flag_del_r) +{ + int ret; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE); + if (ret < 0) + return -1; + *flags_r = ret > 0; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN); + if (ret < 0) + return -1; + *flag_seen_r = ret > 0; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED); + if (ret < 0) + return -1; + *flag_del_r = ret > 0; + return 0; +} + +static void acl_transaction_set_failure(struct mailbox_transaction_context *t) +{ + MODULE_CONTEXT_SET(t, acl_storage_module, + &acl_transaction_failure); +} + +static void +acl_mail_update_flags(struct mail *_mail, enum modify_type modify_type, + enum mail_flags flags) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *amail = ACL_MAIL_CONTEXT(mail); + bool acl_flags, acl_flag_seen, acl_flag_del; + + if (acl_get_write_rights(_mail->box, &acl_flags, &acl_flag_seen, + &acl_flag_del) < 0) { + acl_transaction_set_failure(_mail->transaction); + return; + } + + if (modify_type != MODIFY_REPLACE) { + /* adding/removing flags. just remove the disallowed + flags from the mask. */ + if (!acl_flags) + flags &= MAIL_SEEN | MAIL_DELETED; + if (!acl_flag_seen) + flags &= ENUM_NEGATE(MAIL_SEEN); + if (!acl_flag_del) + flags &= ENUM_NEGATE(MAIL_DELETED); + } else if (!acl_flags || !acl_flag_seen || !acl_flag_del) { + /* we don't have permission to replace all the flags. */ + if (!acl_flags && !acl_flag_seen && !acl_flag_del) { + /* no flag changes allowed. ignore silently. */ + return; + } + + /* handle this by first removing the allowed flags and + then adding the allowed flags */ + acl_mail_update_flags(_mail, MODIFY_REMOVE, + ENUM_NEGATE(flags)); + if (flags != 0) + acl_mail_update_flags(_mail, MODIFY_ADD, flags); + return; + } + + amail->super.update_flags(_mail, modify_type, flags); +} + +static void +acl_mail_update_keywords(struct mail *_mail, enum modify_type modify_type, + struct mail_keywords *keywords) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *amail = ACL_MAIL_CONTEXT(mail); + int ret; + + ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_WRITE); + if (ret <= 0) { + /* if we don't have permission, just silently return success. */ + if (ret < 0) + acl_transaction_set_failure(_mail->transaction); + return; + } + + amail->super.update_keywords(_mail, modify_type, keywords); +} + +static void acl_mail_expunge(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *amail = ACL_MAIL_CONTEXT(mail); + int ret; + + ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_EXPUNGE); + if (ret <= 0) { + /* if we don't have permission, silently return success so + users won't see annoying error messages in case their + clients try automatic expunging. */ + acl_transaction_set_failure(_mail->transaction); + return; + } + + amail->super.expunge(_mail); +} + +void acl_mail_allocated(struct mail *_mail) +{ + struct acl_mailbox *abox = ACL_CONTEXT(_mail->box); + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + union mail_module_context *amail; + + if (abox == NULL || !abox->acl_enabled) + return; + + amail = p_new(mail->pool, union mail_module_context, 1); + amail->super = *v; + mail->vlast = &amail->super; + + v->update_flags = acl_mail_update_flags; + v->update_keywords = acl_mail_update_keywords; + v->expunge = acl_mail_expunge; + MODULE_CONTEXT_SET_SELF(mail, acl_mail_module, amail); +} + +static int +acl_save_get_flags(struct mailbox *box, enum mail_flags *flags, + enum mail_flags *pvt_flags, struct mail_keywords **keywords) +{ + bool acl_flags, acl_flag_seen, acl_flag_del; + + if (acl_get_write_rights(box, &acl_flags, &acl_flag_seen, + &acl_flag_del) < 0) + return -1; + + if (!acl_flag_seen) { + *flags &= ENUM_NEGATE(MAIL_SEEN); + *pvt_flags &= ENUM_NEGATE(MAIL_SEEN); + } + if (!acl_flag_del) { + *flags &= ENUM_NEGATE(MAIL_DELETED); + *pvt_flags &= ENUM_NEGATE(MAIL_DELETED); + } + if (!acl_flags) { + *flags &= MAIL_SEEN | MAIL_DELETED; + *pvt_flags &= MAIL_SEEN | MAIL_DELETED; + *keywords = NULL; + } + return 0; +} + +static int +acl_save_begin(struct mail_save_context *ctx, struct istream *input) +{ + struct mailbox *box = ctx->transaction->box; + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + enum acl_storage_rights save_right; + + save_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? + ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; + if (acl_mailbox_right_lookup(box, save_right) <= 0) + return -1; + if (acl_save_get_flags(box, &ctx->data.flags, + &ctx->data.pvt_flags, &ctx->data.keywords) < 0) + return -1; + + return abox->module_ctx.super.save_begin(ctx, input); +} + +static bool +acl_copy_has_rights(struct mail_save_context *ctx, struct mail *mail) +{ + struct mailbox *destbox = ctx->transaction->box; + enum acl_storage_rights save_right; + + if (ctx->moving) { + if (acl_mailbox_right_lookup(mail->box, + ACL_STORAGE_RIGHT_EXPUNGE) <= 0) + return FALSE; + } + + save_right = (destbox->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? + ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; + if (acl_mailbox_right_lookup(destbox, save_right) <= 0) + return FALSE; + if (acl_save_get_flags(destbox, &ctx->data.flags, + &ctx->data.pvt_flags, &ctx->data.keywords) < 0) + return FALSE; + return TRUE; +} + +static int +acl_copy(struct mail_save_context *ctx, struct mail *mail) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(t->box); + + if (!acl_copy_has_rights(ctx, mail)) { + mailbox_save_cancel(&ctx); + return -1; + } + + return abox->module_ctx.super.copy(ctx, mail); +} + +static int +acl_transaction_commit(struct mailbox_transaction_context *ctx, + struct mail_transaction_commit_changes *changes_r) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(ctx->box); + void *at = ACL_CONTEXT(ctx); + int ret; + + if (at != NULL) { + abox->module_ctx.super.transaction_rollback(ctx); + return -1; + } + + ret = abox->module_ctx.super.transaction_commit(ctx, changes_r); + if (abox->no_read_right) { + /* don't allow IMAP client to see what UIDs the messages got */ + changes_r->no_read_perm = TRUE; + } + return ret; +} + +static int acl_mailbox_exists(struct mailbox *box, bool auto_boxes, + enum mailbox_existence *existence_r) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + const char *const *rights; + unsigned int i; + + if (acl_object_get_my_rights(abox->aclobj, pool_datastack_create(), &rights) < 0) { + mail_storage_set_internal_error(box->storage); + return -1; + } + + /* for now this is used only by IMAP SUBSCRIBE. we'll intentionally + violate RFC 4314 here, because it says SUBSCRIBE should succeed only + when mailbox has 'l' right. But there's no point in not allowing + a subscribe for a mailbox that can be selected anyway. Just the + opposite: subscribing to such mailboxes is a very useful feature. */ + for (i = 0; rights[i] != NULL; i++) { + if (strcmp(rights[i], MAIL_ACL_LOOKUP) == 0 || + strcmp(rights[i], MAIL_ACL_READ) == 0 || + strcmp(rights[i], MAIL_ACL_INSERT) == 0) + return abox->module_ctx.super.exists(box, auto_boxes, + existence_r); + } + *existence_r = MAILBOX_EXISTENCE_NONE; + return 0; +} + +bool acl_mailbox_have_extra_attribute_rights(struct mailbox *box) +{ + /* RFC 5464: + + When the ACL extension [RFC4314] is present, users can only set and + retrieve private or shared mailbox annotations on a mailbox on which + they have the "l" right and any one of the "r", "s", "w", "i", or "p" + rights. + */ + const enum acl_storage_rights check_rights[] = { + ACL_STORAGE_RIGHT_READ, + ACL_STORAGE_RIGHT_WRITE_SEEN, + ACL_STORAGE_RIGHT_WRITE, + ACL_STORAGE_RIGHT_INSERT, + ACL_STORAGE_RIGHT_POST, + }; + for (unsigned int i = 0; i < N_ELEMENTS(check_rights); i++) { + int ret = acl_mailbox_right_lookup(box, check_rights[i]); + if (ret > 0) + return TRUE; + if (ret < 0) { + /* unexpected error - stop checking further */ + return FALSE; + } + } + return FALSE; +} + +static int acl_mailbox_open_check_acl(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list); + const unsigned int *idx_arr = alist->rights.acl_storage_right_idx; + enum acl_storage_rights open_right; + int ret; + + /* mailbox can be opened either for reading or appending new messages */ + if ((box->flags & MAILBOX_FLAG_IGNORE_ACLS) != 0 || + (box->list->ns->flags & NAMESPACE_FLAG_NOACL) != 0 || + abox->skip_acl_checks) + return 0; + + if ((box->flags & MAILBOX_FLAG_SAVEONLY) != 0) { + open_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? + ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; + } else if (box->deleting) { + open_right = ACL_STORAGE_RIGHT_DELETE; + } else if ((box->flags & MAILBOX_FLAG_ATTRIBUTE_SESSION) != 0) { + /* GETMETADATA/SETMETADATA requires "l" right and another one + which is checked afterwards. */ + open_right = ACL_STORAGE_RIGHT_LOOKUP; + } else { + open_right = ACL_STORAGE_RIGHT_READ; + } + + ret = acl_object_have_right(abox->aclobj, idx_arr[open_right]); + if (ret <= 0) { + if (ret == 0) { + /* no access. */ + acl_mailbox_fail_not_found(box); + } + return -1; + } + if (open_right != ACL_STORAGE_RIGHT_READ) { + ret = acl_object_have_right(abox->aclobj, + idx_arr[ACL_STORAGE_RIGHT_READ]); + if (ret < 0) + return -1; + if (ret == 0) + abox->no_read_right = TRUE; + } + if ((box->flags & MAILBOX_FLAG_ATTRIBUTE_SESSION) != 0) { + if (!acl_mailbox_have_extra_attribute_rights(box)) + return -1; + } + return 0; +} + +static int acl_mailbox_open(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + if (acl_mailbox_open_check_acl(box) < 0) + return -1; + + return abox->module_ctx.super.open(box); +} + +static int acl_mailbox_get_status(struct mailbox *box, + enum mailbox_status_items items, + struct mailbox_status *status_r) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + if (abox->module_ctx.super.get_status(box, items, status_r) < 0) + return -1; + + if ((items & STATUS_PERMANENT_FLAGS) != 0) { + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) <= 0) { + status_r->permanent_flags &= MAIL_DELETED|MAIL_SEEN; + status_r->permanent_keywords = FALSE; + status_r->allow_new_keywords = FALSE; + } + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) <= 0) + status_r->permanent_flags &= ENUM_NEGATE(MAIL_DELETED); + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) <= 0) + status_r->permanent_flags &= ENUM_NEGATE(MAIL_SEEN); + } + return 0; +} + +void acl_mailbox_allocated(struct mailbox *box) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(box->list); + struct mailbox_vfuncs *v = box->vlast; + struct acl_mailbox *abox; + bool ignore_acls = (box->flags & MAILBOX_FLAG_IGNORE_ACLS) != 0; + + if (alist == NULL) { + /* ACLs disabled */ + return; + } + + if (mail_namespace_is_shared_user_root(box->list->ns) || alist->ignore_acls) { + /* this is the root shared namespace, which itself doesn't + have any existing mailboxes. */ + ignore_acls = TRUE; + } + + abox = p_new(box->pool, struct acl_mailbox, 1); + abox->module_ctx.super = *v; + box->vlast = &abox->module_ctx.super; + /* aclobj can be used for setting ACLs, even when mailbox is opened + with IGNORE_ACLS flag */ + if (alist->rights.backend != NULL) + abox->aclobj = acl_object_init_from_name(alist->rights.backend, + mailbox_get_name(box)); + else + i_assert(ignore_acls); + + v->free = acl_mailbox_free; + if (!ignore_acls) { + abox->acl_enabled = TRUE; + v->is_readonly = acl_is_readonly; + v->exists = acl_mailbox_exists; + v->open = acl_mailbox_open; + v->get_status = acl_mailbox_get_status; + v->create_box = acl_mailbox_create; + v->update_box = acl_mailbox_update; + v->delete_box = acl_mailbox_delete; + v->rename_box = acl_mailbox_rename; + v->save_begin = acl_save_begin; + v->copy = acl_copy; + v->transaction_commit = acl_transaction_commit; + v->attribute_set = acl_attribute_set; + v->attribute_get = acl_attribute_get; + v->attribute_iter_init = acl_attribute_iter_init; + v->attribute_iter_next = acl_attribute_iter_next; + v->attribute_iter_deinit = acl_attribute_iter_deinit; + } + MODULE_CONTEXT_SET(box, acl_storage_module, abox); +} + +static bool +acl_mailbox_update_removed_id(struct acl_object *aclobj, + const struct acl_rights_update *update) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + + if (update->modify_mode != ACL_MODIFY_MODE_CLEAR && + update->neg_modify_mode != ACL_MODIFY_MODE_CLEAR) + return FALSE; + if (update->modify_mode == ACL_MODIFY_MODE_CLEAR && + update->neg_modify_mode == ACL_MODIFY_MODE_CLEAR) + return TRUE; + + /* mixed clear/non-clear. see if the identifier exists anymore */ + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (rights.id_type == update->rights.id_type && + null_strcmp(rights.identifier, update->rights.identifier) == 0) + break; + } + return acl_object_list_deinit(&iter) >= 0; +} + +int acl_mailbox_update_acl(struct mailbox_transaction_context *t, + const struct acl_rights_update *update) +{ + struct acl_object *aclobj; + const char *key; + time_t ts = update->last_change != 0 ? + update->last_change : ioloop_time; + + key = t_strdup_printf(MAILBOX_ATTRIBUTE_PREFIX_ACL"%s", + acl_rights_get_id(&update->rights)); + aclobj = acl_mailbox_get_aclobj(t->box); + if (acl_object_update(aclobj, update) < 0) { + mailbox_set_critical(t->box, "Failed to set ACL"); + return -1; + } + + /* FIXME: figure out some value lengths, so maybe some day + quota could apply to ACLs as well. */ + if (acl_mailbox_update_removed_id(aclobj, update)) + mail_index_attribute_unset(t->itrans, FALSE, key, ts); + else + mail_index_attribute_set(t->itrans, FALSE, key, ts, 0); + return 0; +} diff --git a/src/plugins/acl/acl-plugin.c b/src/plugins/acl/acl-plugin.c new file mode 100644 index 0000000..b446dcd --- /dev/null +++ b/src/plugins/acl/acl-plugin.c @@ -0,0 +1,27 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mailbox-list-private.h" +#include "acl-api.h" +#include "acl-plugin.h" + + +const char *acl_plugin_version = DOVECOT_ABI_VERSION; + +static struct mail_storage_hooks acl_mail_storage_hooks = { + .mail_user_created = acl_mail_user_created, + .mailbox_list_created = acl_mailbox_list_created, + .mail_namespace_storage_added = acl_mail_namespace_storage_added, + .mailbox_allocated = acl_mailbox_allocated, + .mail_allocated = acl_mail_allocated +}; + +void acl_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &acl_mail_storage_hooks); +} + +void acl_plugin_deinit(void) +{ + mail_storage_hooks_remove(&acl_mail_storage_hooks); +} diff --git a/src/plugins/acl/acl-plugin.h b/src/plugins/acl/acl-plugin.h new file mode 100644 index 0000000..6acfe89 --- /dev/null +++ b/src/plugins/acl/acl-plugin.h @@ -0,0 +1,73 @@ +#ifndef ACL_PLUGIN_H +#define ACL_PLUGIN_H + +#include "mail-user.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "acl-storage.h" + +#define ACL_CONTEXT(obj) \ + MODULE_CONTEXT(obj, acl_storage_module) +#define ACL_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_storage_module) +#define ACL_LIST_CONTEXT(obj) \ + MODULE_CONTEXT(obj, acl_mailbox_list_module) +#define ACL_LIST_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_mailbox_list_module) +#define ACL_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, acl_user_module) +#define ACL_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_user_module) + +struct acl_user { + union mail_user_module_context module_ctx; + + const char *acl_user; + const char *acl_env; + const char *const *groups; + + struct acl_lookup_dict *acl_lookup_dict; +}; + +struct acl_storage_rights_context { + struct acl_backend *backend; + unsigned int acl_storage_right_idx[ACL_STORAGE_RIGHT_COUNT]; +}; + +struct acl_mailbox_list { + union mailbox_list_module_context module_ctx; + struct acl_storage_rights_context rights; + + time_t last_shared_add_check; + bool ignore_acls; +}; + +struct acl_mailbox { + union mailbox_module_context module_ctx; + struct acl_object *aclobj; + bool skip_acl_checks; + bool acl_enabled; + bool no_read_right; +}; + +extern MODULE_CONTEXT_DEFINE(acl_storage_module, &mail_storage_module_register); +extern MODULE_CONTEXT_DEFINE(acl_user_module, &mail_user_module_register); +extern MODULE_CONTEXT_DEFINE(acl_mailbox_list_module, + &mailbox_list_module_register); + +void acl_mailbox_list_created(struct mailbox_list *list); +void acl_mail_namespace_storage_added(struct mail_namespace *ns); +void acl_mail_user_created(struct mail_user *list); + +void acl_mailbox_allocated(struct mailbox *box); +void acl_mail_allocated(struct mail *mail); + +struct acl_backend *acl_mailbox_list_get_backend(struct mailbox_list *list); +int acl_mailbox_list_have_right(struct mailbox_list *list, const char *name, + bool parent, unsigned int acl_storage_right_idx, + bool *can_see_r) ATTR_NULL(5); + +void acl_plugin_init(struct module *module); +void acl_plugin_deinit(void); + +#endif diff --git a/src/plugins/acl/acl-shared-storage.c b/src/plugins/acl/acl-shared-storage.c new file mode 100644 index 0000000..1329cd3 --- /dev/null +++ b/src/plugins/acl/acl-shared-storage.c @@ -0,0 +1,103 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "str.h" +#include "var-expand.h" +#include "acl-plugin.h" +#include "acl-lookup-dict.h" +#include "acl-shared-storage.h" +#include "index/shared/shared-storage.h" + +#define SHARED_NS_RETRY_SECS (60*60) + +static bool acl_ns_prefix_exists(struct mail_namespace *ns) +{ + struct mailbox *box; + const char *vname; + enum mailbox_existence existence; + bool ret; + + if (ns->list->mail_set->mail_shared_explicit_inbox) + return FALSE; + + vname = t_strndup(ns->prefix, ns->prefix_len-1); + box = mailbox_alloc(ns->list, vname, 0); + ret = mailbox_exists(box, FALSE, &existence) == 0 && + existence == MAILBOX_EXISTENCE_SELECT; + mailbox_free(&box); + return ret; +} + +static void +acl_shared_namespace_add(struct mail_namespace *ns, + struct mail_storage *storage, const char *userdomain) +{ + struct shared_storage *sstorage = (struct shared_storage *)storage; + struct mail_namespace *new_ns = ns; + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + const char *mailbox, *error; + string_t *str; + + if (strcmp(ns->user->username, userdomain) == 0) { + /* skip ourself */ + return; + } + + const struct var_expand_table tab[] = { + { 'u', userdomain, "user" }, + { 'n', t_strcut(userdomain, '@'), "username" }, + { 'd', i_strchr_to_next(userdomain, '@'), "domain" }, + { '\0', NULL, NULL } + }; + + str = t_str_new(128); + if (var_expand(str, sstorage->ns_prefix_pattern, tab, &error) <= 0) { + i_error("Failed to expand namespace prefix %s: %s", + sstorage->ns_prefix_pattern, error); + return; + } + mailbox = str_c(str); + if (shared_storage_get_namespace(&new_ns, &mailbox) < 0) + return; + + /* check if there are any mailboxes really visible to us */ + iter = mailbox_list_iter_init(new_ns->list, "*", + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + info = mailbox_list_iter_next(iter); + (void)mailbox_list_iter_deinit(&iter); + + if (info == NULL && !acl_ns_prefix_exists(new_ns)) { + /* no visible mailboxes, remove the namespace */ + mail_namespace_destroy(new_ns); + } +} + +int acl_shared_namespaces_add(struct mail_namespace *ns) +{ + struct acl_user *auser = ACL_USER_CONTEXT(ns->user); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct mail_storage *storage = mail_namespace_get_default_storage(ns); + struct acl_lookup_dict_iter *iter; + const char *name; + + i_assert(auser != NULL && alist != NULL); + i_assert(ns->type == MAIL_NAMESPACE_TYPE_SHARED); + i_assert(strcmp(storage->name, MAIL_SHARED_STORAGE_NAME) == 0); + + if (ioloop_time < alist->last_shared_add_check + SHARED_NS_RETRY_SECS) { + /* already added, don't bother rechecking */ + return 0; + } + alist->last_shared_add_check = ioloop_time; + + iter = acl_lookup_dict_iterate_visible_init(auser->acl_lookup_dict); + while ((name = acl_lookup_dict_iterate_visible_next(iter)) != NULL) { + T_BEGIN { + acl_shared_namespace_add(ns, storage, name); + } T_END; + } + return acl_lookup_dict_iterate_visible_deinit(&iter); +} diff --git a/src/plugins/acl/acl-shared-storage.h b/src/plugins/acl/acl-shared-storage.h new file mode 100644 index 0000000..6e7736b --- /dev/null +++ b/src/plugins/acl/acl-shared-storage.h @@ -0,0 +1,6 @@ +#ifndef ACL_SHARED_STORAGE_H +#define ACL_SHARED_STORAGE_H + +int acl_shared_namespaces_add(struct mail_namespace *ns); + +#endif diff --git a/src/plugins/acl/acl-storage.c b/src/plugins/acl/acl-storage.c new file mode 100644 index 0000000..988c0b8 --- /dev/null +++ b/src/plugins/acl/acl-storage.c @@ -0,0 +1,62 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "mail-namespace.h" +#include "mailbox-list-private.h" +#include "acl-api-private.h" +#include "acl-lookup-dict.h" +#include "acl-plugin.h" + + +struct acl_storage_module acl_storage_module = + MODULE_CONTEXT_INIT(&mail_storage_module_register); +struct acl_user_module acl_user_module = + MODULE_CONTEXT_INIT(&mail_user_module_register); + +static void acl_user_deinit(struct mail_user *user) +{ + struct acl_user *auser = ACL_USER_CONTEXT(user); + + i_assert(auser != NULL); + acl_lookup_dict_deinit(&auser->acl_lookup_dict); + auser->module_ctx.super.deinit(user); +} + +static void acl_mail_user_create(struct mail_user *user, const char *env) +{ + struct mail_user_vfuncs *v = user->vlast; + struct acl_user *auser; + + auser = p_new(user->pool, struct acl_user, 1); + auser->module_ctx.super = *v; + user->vlast = &auser->module_ctx.super; + v->deinit = acl_user_deinit; + auser->acl_lookup_dict = acl_lookup_dict_init(user); + + auser->acl_env = env; + auser->acl_user = mail_user_plugin_getenv(user, "acl_user"); + if (auser->acl_user == NULL) + auser->acl_user = mail_user_plugin_getenv(user, "master_user"); + + env = mail_user_plugin_getenv(user, "acl_groups"); + if (env != NULL) { + auser->groups = + (const char *const *)p_strsplit(user->pool, env, ","); + } + + MODULE_CONTEXT_SET(user, acl_user_module, auser); +} + +void acl_mail_user_created(struct mail_user *user) +{ + const char *env; + + env = mail_user_plugin_getenv(user, "acl"); + if (env != NULL && *env != '\0') + acl_mail_user_create(user, env); + else { + e_debug(user->event, "acl: No acl setting - ACLs are disabled"); + } +} diff --git a/src/plugins/acl/acl-storage.h b/src/plugins/acl/acl-storage.h new file mode 100644 index 0000000..8be4c26 --- /dev/null +++ b/src/plugins/acl/acl-storage.h @@ -0,0 +1,50 @@ +#ifndef ACL_STORAGE_H +#define ACL_STORAGE_H + +#include "mail-storage.h" + +struct acl_rights_update; + +enum acl_storage_rights { + ACL_STORAGE_RIGHT_LOOKUP, + ACL_STORAGE_RIGHT_READ, + ACL_STORAGE_RIGHT_WRITE, + ACL_STORAGE_RIGHT_WRITE_SEEN, + ACL_STORAGE_RIGHT_WRITE_DELETED, + ACL_STORAGE_RIGHT_INSERT, + ACL_STORAGE_RIGHT_POST, + ACL_STORAGE_RIGHT_EXPUNGE, + ACL_STORAGE_RIGHT_CREATE, + ACL_STORAGE_RIGHT_DELETE, + ACL_STORAGE_RIGHT_ADMIN, + + ACL_STORAGE_RIGHT_COUNT +}; + +/* Returns acl_object for the given mailbox. */ +struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box); +/* Returns 1 if we have the requested right. If not, returns 0 and sets storage + error to MAIL_ERROR_PERM. Returns -1 if internal error occurred and also + sets storage error. */ +int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx); + +/* Returns TRUE if mailbox has the necessary extra ACL for accessing + attributes. The caller must have checked the LOOKUP right already. */ +bool acl_mailbox_have_extra_attribute_rights(struct mailbox *box); + +int acl_mailbox_update_acl(struct mailbox_transaction_context *t, + const struct acl_rights_update *update); + +int acl_attribute_set(struct mailbox_transaction_context *t, + enum mail_attribute_type type, const char *key, + const struct mail_attribute_value *value); +int acl_attribute_get(struct mailbox *box, + enum mail_attribute_type type, const char *key, + struct mail_attribute_value *value_r); +struct mailbox_attribute_iter * +acl_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type, + const char *prefix); +const char *acl_attribute_iter_next(struct mailbox_attribute_iter *iter); +int acl_attribute_iter_deinit(struct mailbox_attribute_iter *iter); + +#endif diff --git a/src/plugins/acl/doveadm-acl.c b/src/plugins/acl/doveadm-acl.c new file mode 100644 index 0000000..0ab416a --- /dev/null +++ b/src/plugins/acl/doveadm-acl.c @@ -0,0 +1,629 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "module-dir.h" +#include "imap-util.h" +#include "acl-plugin.h" +#include "acl-api-private.h" +#include "acl-lookup-dict.h" +#include "doveadm-print.h" +#include "doveadm-mail.h" + +struct doveadm_acl_cmd_context { + struct doveadm_mail_cmd_context ctx; + bool get_match_me; + enum acl_modify_mode modify_mode; +}; + +const char *doveadm_acl_plugin_version = DOVECOT_ABI_VERSION; + +void doveadm_acl_plugin_init(struct module *module); +void doveadm_acl_plugin_deinit(void); + +static int +cmd_acl_mailbox_open(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user, const char *mailbox, + struct mailbox **box_r) +{ + struct acl_user *auser = ACL_USER_CONTEXT(user); + struct mail_namespace *ns; + struct mailbox *box; + + if (auser == NULL) { + i_error("ACL not enabled for %s", user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + + ns = mail_namespace_find(user->namespaces, mailbox); + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (mailbox_open(box) < 0) { + i_error("Can't open mailbox %s: %s", mailbox, + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_mailbox(ctx, box); + mailbox_free(&box); + return -1; + } + *box_r = box; + return 0; +} + +static void cmd_acl_get_right(const struct acl_rights *rights) +{ + string_t *str; + + doveadm_print(acl_rights_get_id(rights)); + + if (rights->global) + doveadm_print("global"); + else + doveadm_print(""); + + str = t_str_new(256); + if (rights->rights != NULL) + str_append(str, t_strarray_join(rights->rights, " ")); + if (rights->neg_rights != NULL) { + if (str_len(str) > 0) + str_append_c(str, ' '); + str_append_c(str, '-'); + str_append(str, t_strarray_join(rights->neg_rights, " -")); + } + doveadm_print(str_c(str)); +} + +static int cmd_acl_get_mailbox(struct doveadm_acl_cmd_context *ctx, + struct mailbox *box) +{ + struct acl_object *aclobj = acl_mailbox_get_aclobj(box); + struct acl_backend *backend; + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret; + + backend = acl_mailbox_list_get_backend(box->list); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) T_BEGIN { + if (!ctx->get_match_me || + acl_backend_rights_match_me(backend, &rights)) + cmd_acl_get_right(&rights); + } T_END; + + if ((ret = acl_object_list_deinit(&iter))<0) { + i_error("ACL iteration failed"); + doveadm_mail_failed_error(&ctx->ctx, MAIL_ERROR_TEMP); + } + return ret; +} + +static int +cmd_acl_get_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + const char *mailbox = _ctx->args[0]; + struct mailbox *box; + int ret; + + if (cmd_acl_mailbox_open(_ctx, user, mailbox, &box) < 0) + return -1; + + ret = cmd_acl_get_mailbox(ctx, box); + mailbox_free(&box); + return ret; +} + +static bool cmd_acl_get_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + + switch (c) { + case 'm': + ctx->get_match_me = TRUE; + break; + default: + return FALSE; + } + return TRUE; +} + +static void cmd_acl_get_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl get"); + doveadm_print_header("id", "ID", 0); + doveadm_print_header("global", "Global", 0); + doveadm_print_header("rights", "Rights", 0); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_get_alloc(void) +{ + struct doveadm_acl_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_acl_cmd_context); + ctx->ctx.getopt_args = "m"; + ctx->ctx.v.parse_arg = cmd_acl_get_parse_arg; + ctx->ctx.v.run = cmd_acl_get_run; + ctx->ctx.v.init = cmd_acl_get_init; + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + return &ctx->ctx; +} + +static int +cmd_acl_rights_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0]; + struct mailbox *box; + struct acl_object *aclobj; + const char *const *rights; + int ret = 0; + + if (cmd_acl_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + aclobj = acl_mailbox_get_aclobj(box); + if (acl_object_get_my_rights(aclobj, pool_datastack_create(), + &rights) < 0) { + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + i_error("Failed to get rights"); + ret = -1; + } else { + doveadm_print(t_strarray_join(rights, " ")); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_rights_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl rights"); + doveadm_print_header("rights", "Rights", 0); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_rights_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_rights_run; + ctx->v.init = cmd_acl_rights_init; + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + return ctx; +} + +static int +cmd_acl_mailbox_update(struct doveadm_mail_cmd_context *ctx, struct mailbox *box, + const struct acl_rights_update *update) +{ + struct mailbox_transaction_context *t; + int ret; + + t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL | + ctx->transaction_flags, __func__); + ret = acl_mailbox_update_acl(t, update); + if (mailbox_transaction_commit(&t) < 0) + ret = -1; + return ret; +} + +static int +cmd_acl_set_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + const char *mailbox = _ctx->args[0], *id = _ctx->args[1]; + const char *const *rights = _ctx->args + 2; + struct mailbox *box; + struct acl_rights_update update; + const char *error; + int ret; + + if (cmd_acl_mailbox_open(_ctx, user, mailbox, &box) < 0) + return -1; + + i_zero(&update); + update.modify_mode = ctx->modify_mode; + update.neg_modify_mode = ctx->modify_mode; + if (acl_rights_update_import(&update, id, rights, &error) < 0) + i_fatal_status(EX_USAGE, "%s", error); + if ((ret = cmd_acl_mailbox_update(&ctx->ctx, box, &update)) < 0) { + i_error("Failed to set ACL: %s", + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_error(_ctx, MAIL_ERROR_TEMP); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_set_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (str_array_length(args) < 3) + doveadm_mail_help_name("acl set"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_change_alloc(enum acl_modify_mode modify_mode) +{ + struct doveadm_acl_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_acl_cmd_context); + ctx->ctx.v.run = cmd_acl_set_run; + ctx->ctx.v.init = cmd_acl_set_init; + ctx->modify_mode = modify_mode; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_acl_set_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_REPLACE); +} + +static struct doveadm_mail_cmd_context *cmd_acl_add_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_ADD); +} + +static struct doveadm_mail_cmd_context *cmd_acl_remove_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_REMOVE); +} + +static int +cmd_acl_delete_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0], *id = ctx->args[1]; + struct mailbox *box; + struct acl_rights_update update; + const char *error; + int ret = 0; + + if (cmd_acl_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + i_zero(&update); + if (acl_rights_update_import(&update, id, NULL, &error) < 0) + i_fatal_status(EX_USAGE, "%s", error); + if ((ret = cmd_acl_mailbox_update(ctx, box, &update)) < 0) { + i_error("Failed to delete ACL: %s", + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_delete_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (str_array_length(args) < 2) + doveadm_mail_help_name("acl delete"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_delete_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_delete_run; + ctx->v.init = cmd_acl_delete_init; + return ctx; +} + +static int +cmd_acl_recalc_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + struct acl_user *auser = ACL_USER_CONTEXT(user); + + if (auser == NULL) { + i_error("ACL not enabled for %s", user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0) { + i_error("Failed to recalculate ACL dicts"); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + return -1; + } + return 0; +} + +static struct doveadm_mail_cmd_context * +cmd_acl_recalc_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_recalc_run; + return ctx; +} + +static int +cmd_acl_debug_mailbox_open(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user, const char *mailbox, + struct mailbox **box_r) +{ + struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(user); + struct mail_namespace *ns; + struct mailbox *box; + const char *path, *errstr; + enum mail_error error; + + ns = mail_namespace_find(user->namespaces, mailbox); + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (mailbox_open(box) < 0) { + errstr = mail_storage_get_last_internal_error(box->storage, &error); + errstr = t_strdup(errstr); + doveadm_mail_failed_error(ctx, error); + + if (error != MAIL_ERROR_NOTFOUND || + mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &path) <= 0) + i_error("Can't open mailbox %s: %s", mailbox, errstr); + else { + i_error("Mailbox '%s' in namespace '%s' doesn't exist in %s", + box->name, ns->prefix, path); + } + mailbox_free(&box); + return -1; + } + + if (auser == NULL) { + i_info("ACL not enabled for user %s, mailbox can be accessed", + user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + mailbox_free(&box); + return -1; + } + + *box_r = box; + return 0; +} + +static bool cmd_acl_debug_mailbox(struct mailbox *box, bool *retry_r) +{ + struct mail_namespace *ns = mailbox_get_namespace(box); + struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(ns->user); + struct acl_object *aclobj = acl_mailbox_get_aclobj(box); + struct acl_backend *backend = acl_mailbox_list_get_backend(box->list); + struct acl_mailbox_list_context *iter; + struct acl_lookup_dict_iter *diter; + const char *const *rights, *name, *path; + enum mail_flags private_flags_mask; + string_t *str; + int ret; + bool all_ok = TRUE; + + *retry_r = FALSE; + + i_info("Mailbox '%s' is in namespace '%s'", + box->name, box->list->ns->prefix); + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) > 0) + i_info("Mailbox path: %s", path); + + private_flags_mask = mailbox_get_private_flags_mask(box); + if (private_flags_mask == 0) + i_info("All message flags are shared across users in mailbox"); + else { + str = t_str_new(64); + imap_write_flags(str, private_flags_mask, NULL); + i_info("Per-user private flags in mailbox: %s", str_c(str)); + } + + /* check if user has lookup right */ + if (acl_object_get_my_rights(aclobj, pool_datastack_create(), + &rights) < 0) + i_fatal("Failed to get rights"); + + if (rights[0] == NULL) + i_info("User %s has no rights for mailbox", ns->user->username); + else { + i_info("User %s has rights: %s", + ns->user->username, t_strarray_join(rights, " ")); + } + if (!str_array_find(rights, MAIL_ACL_LOOKUP)) { + i_error("User %s is missing 'lookup' right", + ns->user->username); + return FALSE; + } + + /* check if mailbox is listable */ + if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) { + i_info("Mailbox in user's private namespace"); + return TRUE; + } + + iter = acl_backend_nonowner_lookups_iter_init(backend); + while (acl_backend_nonowner_lookups_iter_next(iter, &name)) { + if (strcmp(name, box->name) == 0) + break; + } + if ((ret = acl_backend_nonowner_lookups_iter_deinit(&iter))<0) + i_fatal("ACL non-owner iteration failed"); + if (ret == 0) { + i_error("Mailbox not found from dovecot-acl-list, rebuilding"); + if (acl_backend_nonowner_lookups_rebuild(backend) < 0) + i_fatal("dovecot-acl-list rebuilding failed"); + all_ok = FALSE; + *retry_r = TRUE; + } else { + i_info("Mailbox found from dovecot-acl-list"); + } + + if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC) { + i_info("Mailbox is in public namespace"); + return TRUE; + } + + if (!acl_lookup_dict_is_enabled(auser->acl_lookup_dict)) { + i_error("acl_lookup_dict not enabled"); + return FALSE; + } + + /* shared namespace. see if it's in acl lookup dict */ + diter = acl_lookup_dict_iterate_visible_init(auser->acl_lookup_dict); + while ((name = acl_lookup_dict_iterate_visible_next(diter)) != NULL) { + if (strcmp(name, ns->owner->username) == 0) + break; + } + if (acl_lookup_dict_iterate_visible_deinit(&diter) < 0) + i_fatal("ACL shared dict iteration failed"); + if (name == NULL) { + i_error("User %s not found from ACL shared dict, rebuilding", + ns->owner->username); + if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0) + i_fatal("ACL lookup dict rebuild failed"); + all_ok = FALSE; + *retry_r = TRUE; + } else { + i_info("User %s found from ACL shared dict", + ns->owner->username); + } + return all_ok; +} + +static int +cmd_acl_debug_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0]; + struct mailbox *box; + bool ret, retry; + + if (cmd_acl_debug_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + ret = cmd_acl_debug_mailbox(box, &retry); + if (!ret && retry) { + i_info("Retrying after rebuilds:"); + ret = cmd_acl_debug_mailbox(box, &retry); + } + if (ret) + i_info("Mailbox %s is visible in LIST", box->vname); + else + i_info("Mailbox %s is NOT visible in LIST", box->vname); + mailbox_free(&box); + return 0; +} + +static void cmd_acl_debug_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl debug"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_debug_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_debug_run; + ctx->v.init = cmd_acl_debug_init; + return ctx; +} + +static struct doveadm_cmd_ver2 acl_commands[] = { +{ + .name = "acl get", + .mail_cmd = cmd_acl_get_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-m] <mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('m', "match-me", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl rights", + .mail_cmd = cmd_acl_rights_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl set", + .mail_cmd = cmd_acl_set_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl add", + .mail_cmd = cmd_acl_add_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl remove", + .mail_cmd = cmd_acl_remove_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl delete", + .mail_cmd = cmd_acl_delete_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl recalc", + .mail_cmd = cmd_acl_recalc_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX, +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl debug", + .mail_cmd = cmd_acl_debug_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +} +}; + +void doveadm_acl_plugin_init(struct module *module ATTR_UNUSED) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(acl_commands); i++) + doveadm_cmd_register_ver2(&acl_commands[i]); +} + +void doveadm_acl_plugin_deinit(void) +{ +} diff --git a/src/plugins/acl/test-acl.c b/src/plugins/acl/test-acl.c new file mode 100644 index 0000000..1fafd7a --- /dev/null +++ b/src/plugins/acl/test-acl.c @@ -0,0 +1,66 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "test-common.h" +#include "acl-api-private.h" + +static void test_acl_rights_sort(void) +{ + struct acl_rights rights1 = { + .rights = t_strsplit("a b a c d b", " "), + .neg_rights = t_strsplit("e d c a a d b e", " "), + }; + struct acl_rights rights2 = { + .rights = t_strsplit("a c x", " "), + .neg_rights = t_strsplit("b c y", " "), + }; + struct acl_object obj = { + .rights_pool = pool_alloconly_create("acl rights", 256) + }; + const struct acl_rights *rights; + + test_begin("acl_rights_sort"); + t_array_init(&obj.rights, 8); + + /* try with zero rights */ + acl_rights_sort(&obj); + test_assert(array_count(&obj.rights) == 0); + + /* try with just one right */ + array_push_back(&obj.rights, &rights1); + acl_rights_sort(&obj); + test_assert(array_count(&obj.rights) == 1); + rights = array_idx(&obj.rights, 0); + test_assert(acl_rights_cmp(rights, &rights1) == 0); + + /* try with two rights that don't have equal ID */ + struct acl_rights rights1_id2 = rights1; + rights1_id2.identifier = "id2"; + array_push_back(&obj.rights, &rights1_id2); + acl_rights_sort(&obj); + test_assert(array_count(&obj.rights) == 2); + rights = array_idx(&obj.rights, 0); + test_assert(acl_rights_cmp(&rights[0], &rights1) == 0); + test_assert(acl_rights_cmp(&rights[1], &rights1_id2) == 0); + + /* try with 3 rights where first has equal ID */ + array_push_back(&obj.rights, &rights2); + acl_rights_sort(&obj); + test_assert(array_count(&obj.rights) == 2); + rights = array_idx(&obj.rights, 0); + test_assert_strcmp(t_strarray_join(rights[0].rights, " "), "a b c d x"); + test_assert_strcmp(t_strarray_join(rights[0].neg_rights, " "), "a b c d e y"); + + pool_unref(&obj.rights_pool); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_acl_rights_sort, + NULL + }; + return test_run(test_functions); +} |