diff options
Diffstat (limited to 'src/plugins')
291 files changed, 84979 insertions, 0 deletions
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am new file mode 100644 index 0000000..ab0b9d1 --- /dev/null +++ b/src/plugins/Makefile.am @@ -0,0 +1,53 @@ +if BUILD_ZLIB_PLUGIN +ZLIB = zlib imap-zlib +endif + +if BUILD_LUCENE +FTS_LUCENE = fts-lucene +endif + +if BUILD_SOLR +FTS_SOLR = fts-solr +endif + +if HAVE_APPARMOR +APPARMOR = apparmor +endif + +if HAVE_LUA +MAIL_LUA = mail-lua +endif + +SUBDIRS = \ + acl \ + imap-acl \ + fts \ + fts-squat \ + last-login \ + lazy-expunge \ + listescape \ + notify \ + notify-status \ + push-notification \ + mail-log \ + $(MAIL_LUA) \ + mailbox-alias \ + quota \ + quota-clone \ + imap-quota \ + pop3-migration \ + replication \ + old-stats \ + imap-old-stats \ + mail-crypt \ + trash \ + virtual \ + welcome \ + $(ZLIB) \ + $(FTS_LUCENE) \ + $(FTS_SOLR) \ + $(DICT_LDAP) \ + $(APPARMOR) \ + fs-compress \ + var-expand-crypt \ + charset-alias diff --git a/src/plugins/Makefile.in b/src/plugins/Makefile.in new file mode 100644 index 0000000..727ab4d --- /dev/null +++ b/src/plugins/Makefile.in @@ -0,0 +1,810 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = acl imap-acl fts fts-squat last-login lazy-expunge \ + listescape notify notify-status push-notification mail-log \ + mail-lua mailbox-alias quota quota-clone imap-quota \ + pop3-migration replication old-stats imap-old-stats mail-crypt \ + trash virtual welcome zlib imap-zlib fts-lucene fts-solr \ + apparmor fs-compress var-expand-crypt charset-alias +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +@BUILD_ZLIB_PLUGIN_TRUE@ZLIB = zlib imap-zlib +@BUILD_LUCENE_TRUE@FTS_LUCENE = fts-lucene +@BUILD_SOLR_TRUE@FTS_SOLR = fts-solr +@HAVE_APPARMOR_TRUE@APPARMOR = apparmor +@HAVE_LUA_TRUE@MAIL_LUA = mail-lua +SUBDIRS = \ + acl \ + imap-acl \ + fts \ + fts-squat \ + last-login \ + lazy-expunge \ + listescape \ + notify \ + notify-status \ + push-notification \ + mail-log \ + $(MAIL_LUA) \ + mailbox-alias \ + quota \ + quota-clone \ + imap-quota \ + pop3-migration \ + replication \ + old-stats \ + imap-old-stats \ + mail-crypt \ + trash \ + virtual \ + welcome \ + $(ZLIB) \ + $(FTS_LUCENE) \ + $(FTS_SOLR) \ + $(DICT_LDAP) \ + $(APPARMOR) \ + fs-compress \ + var-expand-crypt \ + charset-alias + +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-generic clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/acl/Makefile.am b/src/plugins/acl/Makefile.am new file mode 100644 index 0000000..8a5323e --- /dev/null +++ b/src/plugins/acl/Makefile.am @@ -0,0 +1,78 @@ +doveadm_moduledir = $(moduledir)/doveadm + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/list \ + -I$(top_srcdir)/src/doveadm + +NOPLUGIN_LDFLAGS = +lib10_doveadm_acl_plugin_la_LDFLAGS = -module -avoid-version +lib01_acl_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib01_acl_plugin.la + +lib01_acl_plugin_la_SOURCES = \ + acl-api.c \ + acl-attributes.c \ + acl-backend.c \ + acl-backend-vfile.c \ + acl-backend-vfile-acllist.c \ + acl-backend-vfile-update.c \ + acl-cache.c \ + acl-global-file.c \ + acl-lookup-dict.c \ + acl-mailbox.c \ + acl-mailbox-list.c \ + acl-plugin.c \ + acl-shared-storage.c \ + acl-storage.c + +noinst_HEADERS = \ + acl-backend-vfile.h \ + acl-shared-storage.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = \ + acl-api.h \ + acl-api-private.h \ + acl-cache.h \ + acl-global-file.h \ + acl-lookup-dict.h \ + acl-plugin.h \ + acl-storage.h + +doveadm_module_LTLIBRARIES = \ + lib10_doveadm_acl_plugin.la + +lib10_doveadm_acl_plugin_la_SOURCES = \ + doveadm-acl.c + +test_programs = \ + test-acl + +test_libs = \ + $(module_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) +test_deps = \ + $(module_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +test_acl_SOURCES = test-acl.c +test_acl_LDADD = $(test_libs) +test_acl_DEPENDENCIES = $(test_deps) + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +noinst_PROGRAMS = $(test_programs) diff --git a/src/plugins/acl/Makefile.in b/src/plugins/acl/Makefile.in new file mode 100644 index 0000000..4b133e0 --- /dev/null +++ b/src/plugins/acl/Makefile.in @@ -0,0 +1,1035 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/plugins/acl +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(pkginc_lib_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-acl$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(doveadm_moduledir)" \ + "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)" +LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES) +lib01_acl_plugin_la_LIBADD = +am_lib01_acl_plugin_la_OBJECTS = acl-api.lo acl-attributes.lo \ + acl-backend.lo acl-backend-vfile.lo \ + acl-backend-vfile-acllist.lo acl-backend-vfile-update.lo \ + acl-cache.lo acl-global-file.lo acl-lookup-dict.lo \ + acl-mailbox.lo acl-mailbox-list.lo acl-plugin.lo \ + acl-shared-storage.lo acl-storage.lo +lib01_acl_plugin_la_OBJECTS = $(am_lib01_acl_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib01_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib01_acl_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +lib10_doveadm_acl_plugin_la_LIBADD = +am_lib10_doveadm_acl_plugin_la_OBJECTS = doveadm-acl.lo +lib10_doveadm_acl_plugin_la_OBJECTS = \ + $(am_lib10_doveadm_acl_plugin_la_OBJECTS) +lib10_doveadm_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib10_doveadm_acl_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_acl_OBJECTS = test-acl.$(OBJEXT) +test_acl_OBJECTS = $(am_test_acl_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = $(module_LTLIBRARIES) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/acl-api.Plo \ + ./$(DEPDIR)/acl-attributes.Plo \ + ./$(DEPDIR)/acl-backend-vfile-acllist.Plo \ + ./$(DEPDIR)/acl-backend-vfile-update.Plo \ + ./$(DEPDIR)/acl-backend-vfile.Plo ./$(DEPDIR)/acl-backend.Plo \ + ./$(DEPDIR)/acl-cache.Plo ./$(DEPDIR)/acl-global-file.Plo \ + ./$(DEPDIR)/acl-lookup-dict.Plo \ + ./$(DEPDIR)/acl-mailbox-list.Plo ./$(DEPDIR)/acl-mailbox.Plo \ + ./$(DEPDIR)/acl-plugin.Plo ./$(DEPDIR)/acl-shared-storage.Plo \ + ./$(DEPDIR)/acl-storage.Plo ./$(DEPDIR)/doveadm-acl.Plo \ + ./$(DEPDIR)/test-acl.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib01_acl_plugin_la_SOURCES) \ + $(lib10_doveadm_acl_plugin_la_SOURCES) $(test_acl_SOURCES) +DIST_SOURCES = $(lib01_acl_plugin_la_SOURCES) \ + $(lib10_doveadm_acl_plugin_la_SOURCES) $(test_acl_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +doveadm_moduledir = $(moduledir)/doveadm +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/list \ + -I$(top_srcdir)/src/doveadm + +lib10_doveadm_acl_plugin_la_LDFLAGS = -module -avoid-version +lib01_acl_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib01_acl_plugin.la + +lib01_acl_plugin_la_SOURCES = \ + acl-api.c \ + acl-attributes.c \ + acl-backend.c \ + acl-backend-vfile.c \ + acl-backend-vfile-acllist.c \ + acl-backend-vfile-update.c \ + acl-cache.c \ + acl-global-file.c \ + acl-lookup-dict.c \ + acl-mailbox.c \ + acl-mailbox-list.c \ + acl-plugin.c \ + acl-shared-storage.c \ + acl-storage.c + +noinst_HEADERS = \ + acl-backend-vfile.h \ + acl-shared-storage.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = \ + acl-api.h \ + acl-api-private.h \ + acl-cache.h \ + acl-global-file.h \ + acl-lookup-dict.h \ + acl-plugin.h \ + acl-storage.h + +doveadm_module_LTLIBRARIES = \ + lib10_doveadm_acl_plugin.la + +lib10_doveadm_acl_plugin_la_SOURCES = \ + doveadm-acl.c + +test_programs = \ + test-acl + +test_libs = \ + $(module_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) + +test_deps = \ + $(module_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +test_acl_SOURCES = test-acl.c +test_acl_LDADD = $(test_libs) +test_acl_DEPENDENCIES = $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/acl/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/acl/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \ + } + +uninstall-doveadm_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \ + done + +clean-doveadm_moduleLTLIBRARIES: + -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES) + @list='$(doveadm_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib01_acl_plugin.la: $(lib01_acl_plugin_la_OBJECTS) $(lib01_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib01_acl_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib01_acl_plugin_la_LINK) -rpath $(moduledir) $(lib01_acl_plugin_la_OBJECTS) $(lib01_acl_plugin_la_LIBADD) $(LIBS) + +lib10_doveadm_acl_plugin.la: $(lib10_doveadm_acl_plugin_la_OBJECTS) $(lib10_doveadm_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib10_doveadm_acl_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib10_doveadm_acl_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib10_doveadm_acl_plugin_la_OBJECTS) $(lib10_doveadm_acl_plugin_la_LIBADD) $(LIBS) + +test-acl$(EXEEXT): $(test_acl_OBJECTS) $(test_acl_DEPENDENCIES) $(EXTRA_test_acl_DEPENDENCIES) + @rm -f test-acl$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_acl_OBJECTS) $(test_acl_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-api.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-attributes.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile-acllist.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile-update.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend-vfile.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-backend.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-cache.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-global-file.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-lookup-dict.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-mailbox-list.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-mailbox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-shared-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-acl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-acl.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/acl-api.Plo + -rm -f ./$(DEPDIR)/acl-attributes.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile-acllist.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile-update.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile.Plo + -rm -f ./$(DEPDIR)/acl-backend.Plo + -rm -f ./$(DEPDIR)/acl-cache.Plo + -rm -f ./$(DEPDIR)/acl-global-file.Plo + -rm -f ./$(DEPDIR)/acl-lookup-dict.Plo + -rm -f ./$(DEPDIR)/acl-mailbox-list.Plo + -rm -f ./$(DEPDIR)/acl-mailbox.Plo + -rm -f ./$(DEPDIR)/acl-plugin.Plo + -rm -f ./$(DEPDIR)/acl-shared-storage.Plo + -rm -f ./$(DEPDIR)/acl-storage.Plo + -rm -f ./$(DEPDIR)/doveadm-acl.Plo + -rm -f ./$(DEPDIR)/test-acl.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-doveadm_moduleLTLIBRARIES \ + install-moduleLTLIBRARIES install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/acl-api.Plo + -rm -f ./$(DEPDIR)/acl-attributes.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile-acllist.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile-update.Plo + -rm -f ./$(DEPDIR)/acl-backend-vfile.Plo + -rm -f ./$(DEPDIR)/acl-backend.Plo + -rm -f ./$(DEPDIR)/acl-cache.Plo + -rm -f ./$(DEPDIR)/acl-global-file.Plo + -rm -f ./$(DEPDIR)/acl-lookup-dict.Plo + -rm -f ./$(DEPDIR)/acl-mailbox-list.Plo + -rm -f ./$(DEPDIR)/acl-mailbox.Plo + -rm -f ./$(DEPDIR)/acl-plugin.Plo + -rm -f ./$(DEPDIR)/acl-shared-storage.Plo + -rm -f ./$(DEPDIR)/acl-storage.Plo + -rm -f ./$(DEPDIR)/doveadm-acl.Plo + -rm -f ./$(DEPDIR)/test-acl.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-doveadm_moduleLTLIBRARIES \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am \ + install-doveadm_moduleLTLIBRARIES install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/acl/acl-api-private.h b/src/plugins/acl/acl-api-private.h new file mode 100644 index 0000000..6859779 --- /dev/null +++ b/src/plugins/acl/acl-api-private.h @@ -0,0 +1,135 @@ +#ifndef ACL_API_PRIVATE_H +#define ACL_API_PRIVATE_H + +#include "acl-api.h" + +#define ACL_ID_NAME_ANYONE "anyone" +#define ACL_ID_NAME_AUTHENTICATED "authenticated" +#define ACL_ID_NAME_OWNER "owner" +#define ACL_ID_NAME_USER_PREFIX "user=" +#define ACL_ID_NAME_GROUP_PREFIX "group=" +#define ACL_ID_NAME_GROUP_OVERRIDE_PREFIX "group-override=" + +struct acl_backend_vfuncs { + struct acl_backend *(*alloc)(void); + int (*init)(struct acl_backend *backend, const char *data); + void (*deinit)(struct acl_backend *backend); + + struct acl_mailbox_list_context * + (*nonowner_lookups_iter_init)(struct acl_backend *backend); + bool (*nonowner_lookups_iter_next)(struct acl_mailbox_list_context *ctx, + const char **name_r); + int (*nonowner_lookups_iter_deinit) + (struct acl_mailbox_list_context *ctx); + int (*nonowner_lookups_rebuild)(struct acl_backend *backend); + + struct acl_object *(*object_init)(struct acl_backend *backend, + const char *name); + struct acl_object *(*object_init_parent)(struct acl_backend *backend, + const char *child_name); + void (*object_deinit)(struct acl_object *aclobj); + + int (*object_refresh_cache)(struct acl_object *aclobj); + int (*object_update)(struct acl_object *aclobj, + const struct acl_rights_update *update); + int (*last_changed)(struct acl_object *aclobj, time_t *last_changed_r); + + struct acl_object_list_iter * + (*object_list_init)(struct acl_object *aclobj); + bool (*object_list_next)(struct acl_object_list_iter *iter, + struct acl_rights *rights_r); + int (*object_list_deinit)(struct acl_object_list_iter *iter); +}; + +struct acl_backend { + pool_t pool; + const char *username; + const char **groups; + unsigned int group_count; + + struct mailbox_list *list; + struct acl_cache *cache; + struct acl_global_file *global_file; + + struct acl_object *default_aclobj; + struct acl_mask *default_aclmask; + const char *const *default_rights; + + struct acl_backend_vfuncs v; + + bool owner:1; + bool debug:1; + bool globals_only:1; +}; + +struct acl_mailbox_list_context { + struct acl_backend *backend; + + bool empty:1; + bool failed:1; + const char *error; +}; + +struct acl_object { + struct acl_backend *backend; + char *name; + + pool_t rights_pool; + ARRAY_TYPE(acl_rights) rights; +}; + +struct acl_object_list_iter { + struct acl_object *aclobj; + pool_t pool; + + struct acl_rights *rights; + unsigned int idx, count; + + bool empty:1; + bool failed:1; + const char *error; +}; + +extern const char *const all_mailbox_rights[]; + +struct acl_object_list_iter * +acl_default_object_list_init(struct acl_object *aclobj); +bool acl_default_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r); +int acl_default_object_list_deinit(struct acl_object_list_iter *iter); + +const char *const * +acl_backend_mask_get_names(struct acl_backend *backend, + const struct acl_mask *mask, pool_t pool); +struct acl_object *acl_backend_get_default_object(struct acl_backend *backend); +int acl_backend_get_default_rights(struct acl_backend *backend, + const struct acl_mask **mask_r); +void acl_rights_write_id(string_t *dest, const struct acl_rights *right); +bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights); + +int acl_identifier_parse(const char *line, struct acl_rights *rights); +int acl_rights_update_import(struct acl_rights_update *update, + const char *id, const char *const *rights, + const char **error_r); +const char *acl_rights_export(const struct acl_rights *rights); +int acl_rights_parse_line(const char *line, pool_t pool, + struct acl_rights *rights_r, const char **error_r); +void acl_rights_dup(const struct acl_rights *src, + pool_t pool, struct acl_rights *dest_r); +int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2); +void acl_rights_sort(struct acl_object *aclobj); + +const char *const * +acl_right_names_parse(pool_t pool, const char *acl, const char **error_r); +void acl_right_names_write(string_t *dest, const char *const *rights); +void acl_right_names_merge(pool_t pool, const char *const **destp, + const char *const *src, bool dup_strings); +bool acl_right_names_modify(pool_t pool, + const char *const **rightsp, + const char *const *modify_rights, + enum acl_modify_mode modify_mode); +void acl_object_rebuild_cache(struct acl_object *aclobj); +void acl_object_remove_all_access(struct acl_object *aclobj); +void acl_object_add_global_acls(struct acl_object *aclobj); + +#endif diff --git a/src/plugins/acl/acl-api.c b/src/plugins/acl/acl-api.c new file mode 100644 index 0000000..2e422ea --- /dev/null +++ b/src/plugins/acl/acl-api.c @@ -0,0 +1,847 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strescape.h" +#include "hash.h" +#include "mail-user.h" +#include "mailbox-list.h" +#include "acl-global-file.h" +#include "acl-cache.h" +#include "acl-api-private.h" + +struct acl_letter_map { + char letter; + const char *name; +}; + +static const struct acl_letter_map acl_letter_map[] = { + { 'l', MAIL_ACL_LOOKUP }, + { 'r', MAIL_ACL_READ }, + { 'w', MAIL_ACL_WRITE }, + { 's', MAIL_ACL_WRITE_SEEN }, + { 't', MAIL_ACL_WRITE_DELETED }, + { 'i', MAIL_ACL_INSERT }, + { 'p', MAIL_ACL_POST }, + { 'e', MAIL_ACL_EXPUNGE }, + { 'k', MAIL_ACL_CREATE }, + { 'x', MAIL_ACL_DELETE }, + { 'a', MAIL_ACL_ADMIN }, + { '\0', NULL } +}; + +struct acl_object *acl_object_init_from_name(struct acl_backend *backend, + const char *name) +{ + return backend->v.object_init(backend, name); +} + +struct acl_object *acl_object_init_from_parent(struct acl_backend *backend, + const char *child_name) +{ + return backend->v.object_init_parent(backend, child_name); +} + +void acl_object_deinit(struct acl_object **_aclobj) +{ + struct acl_object *aclobj = *_aclobj; + + *_aclobj = NULL; + aclobj->backend->v.object_deinit(aclobj); +} + +int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx) +{ + struct acl_backend *backend = aclobj->backend; + const struct acl_mask *have_mask; + unsigned int read_idx; + + if (backend->v.object_refresh_cache(aclobj) < 0) + return -1; + + have_mask = acl_cache_get_my_rights(backend->cache, aclobj->name); + if (have_mask == NULL) { + if (acl_backend_get_default_rights(backend, &have_mask) < 0) + return -1; + } + + if (acl_cache_mask_isset(have_mask, right_idx)) + return 1; + + if (mailbox_list_get_user(aclobj->backend->list)->dsyncing) { + /* when dsync is running on a shared mailbox, it must be able + to do everything inside it. however, dsync shouldn't touch + mailboxes where user doesn't have any read access, because + that could make them readable on the replica. */ + read_idx = acl_backend_lookup_right(aclobj->backend, + MAIL_ACL_READ); + if (acl_cache_mask_isset(have_mask, read_idx)) + return 1; + } + return 0; +} + +const char *const * +acl_backend_mask_get_names(struct acl_backend *backend, + const struct acl_mask *mask, pool_t pool) +{ + const char *const *names; + const char **buf, **rights; + unsigned int names_count, count, i, j, name_idx; + + names = acl_cache_get_names(backend->cache, &names_count); + buf = t_new(const char *, (mask->size * CHAR_BIT) + 1); + count = 0; + for (i = 0, name_idx = 0; i < mask->size; i++) { + if (mask->mask[i] == 0) + name_idx += CHAR_BIT; + else { + for (j = 1; j < (1 << CHAR_BIT); j <<= 1, name_idx++) { + if ((mask->mask[i] & j) == 0) + continue; + + /* @UNSAFE */ + i_assert(name_idx < names_count); + buf[count++] = p_strdup(pool, names[name_idx]); + } + } + } + + /* @UNSAFE */ + rights = p_new(pool, const char *, count + 1); + memcpy(rights, buf, count * sizeof(const char *)); + return rights; +} + +static int acl_object_get_my_rights_real(struct acl_object *aclobj, pool_t pool, + const char *const **rights_r) +{ + struct acl_backend *backend = aclobj->backend; + const struct acl_mask *mask; + + if (backend->v.object_refresh_cache(aclobj) < 0) + return -1; + + mask = acl_cache_get_my_rights(backend->cache, aclobj->name); + if (mask == NULL) { + if (acl_backend_get_default_rights(backend, &mask) < 0) + return -1; + } + + *rights_r = acl_backend_mask_get_names(backend, mask, pool); + return 0; +} + +int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool, + const char *const **rights_r) +{ + int ret; + + if (pool->datastack_pool) + return acl_object_get_my_rights_real(aclobj, pool, rights_r); + T_BEGIN { + ret = acl_object_get_my_rights_real(aclobj, pool, rights_r); + } T_END; + return ret; +} + +const char *const *acl_object_get_default_rights(struct acl_object *aclobj) +{ + return acl_backend_mask_get_names(aclobj->backend, + aclobj->backend->default_aclmask, + pool_datastack_create()); +} + +int acl_object_last_changed(struct acl_object *aclobj, time_t *last_changed_r) +{ + return aclobj->backend->v.last_changed(aclobj, last_changed_r); +} + +int acl_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update) +{ + return aclobj->backend->v.object_update(aclobj, update); +} + +struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj) +{ + return aclobj->backend->v.object_list_init(aclobj); +} + +bool acl_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r) +{ + if (iter->failed) + return FALSE; + + return iter->aclobj->backend->v.object_list_next(iter, rights_r); +} + +int acl_object_list_deinit(struct acl_object_list_iter **_iter) +{ + struct acl_object_list_iter *iter = *_iter; + + *_iter = NULL; + return iter->aclobj->backend->v.object_list_deinit(iter); +} + +struct acl_object_list_iter * +acl_default_object_list_init(struct acl_object *aclobj) +{ + struct acl_object_list_iter *iter; + const struct acl_rights *aclobj_rights; + unsigned int i; + pool_t pool; + + pool = pool_alloconly_create("acl object list", 512); + iter = p_new(pool, struct acl_object_list_iter, 1); + iter->pool = pool; + iter->aclobj = aclobj; + + if (!array_is_created(&aclobj->rights)) { + /* we may have the object cached, but we don't have all the + rights read into memory */ + acl_cache_flush(aclobj->backend->cache, aclobj->name); + } + + if (aclobj->backend->v.object_refresh_cache(aclobj) < 0) + iter->failed = TRUE; + + aclobj_rights = array_get(&aclobj->rights, &iter->count); + if (iter->count > 0) { + iter->rights = p_new(pool, struct acl_rights, iter->count); + for (i = 0; i < iter->count; i++) + acl_rights_dup(&aclobj_rights[i], pool, &iter->rights[i]); + } else + iter->empty = TRUE; + return iter; +} + +bool acl_default_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r) +{ + if (iter->failed) + return FALSE; + + if (iter->idx == iter->count) + return FALSE; + *rights_r = iter->rights[iter->idx++]; + return TRUE; +} + +int acl_default_object_list_deinit(struct acl_object_list_iter *iter) +{ + int ret = 0; + if (iter->failed) + ret = -1; + else if (iter->empty) + ret = 0; + else + ret = 1; + + pool_unref(&iter->pool); + return ret; +} + +struct acl_mailbox_list_context * +acl_backend_nonowner_lookups_iter_init(struct acl_backend *backend) +{ + return backend->v.nonowner_lookups_iter_init(backend); +} + +bool acl_backend_nonowner_lookups_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r) +{ + return ctx->backend->v.nonowner_lookups_iter_next(ctx, name_r); +} + +int acl_backend_nonowner_lookups_iter_deinit(struct acl_mailbox_list_context **_ctx) +{ + struct acl_mailbox_list_context *ctx = *_ctx; + + *_ctx = NULL; + return ctx->backend->v.nonowner_lookups_iter_deinit(ctx); +} + +int acl_backend_nonowner_lookups_rebuild(struct acl_backend *backend) +{ + return backend->v.nonowner_lookups_rebuild(backend); +} + +void acl_rights_write_id(string_t *dest, const struct acl_rights *right) +{ + switch (right->id_type) { + case ACL_ID_ANYONE: + str_append(dest, ACL_ID_NAME_ANYONE); + break; + case ACL_ID_AUTHENTICATED: + str_append(dest, ACL_ID_NAME_AUTHENTICATED); + break; + case ACL_ID_OWNER: + str_append(dest, ACL_ID_NAME_OWNER); + break; + case ACL_ID_USER: + str_append(dest, ACL_ID_NAME_USER_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP: + str_append(dest, ACL_ID_NAME_GROUP_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP_OVERRIDE: + str_append(dest, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX); + str_append(dest, right->identifier); + break; + case ACL_ID_TYPE_COUNT: + i_unreached(); + } +} + +const char *acl_rights_get_id(const struct acl_rights *right) +{ + string_t *str = t_str_new(32); + + acl_rights_write_id(str, right); + return str_c(str); +} + +static bool is_standard_right(const char *name) +{ + unsigned int i; + + for (i = 0; all_mailbox_rights[i] != NULL; i++) { + if (strcmp(all_mailbox_rights[i], name) == 0) + return TRUE; + } + return FALSE; +} + +int acl_rights_update_import(struct acl_rights_update *update, + const char *id, const char *const *rights, + const char **error_r) +{ + ARRAY_TYPE(const_string) dest_rights, dest_neg_rights, *dest; + unsigned int i, j; + + if (acl_identifier_parse(id, &update->rights) < 0) { + *error_r = t_strdup_printf("Invalid ID: %s", id); + return -1; + } + if (rights == NULL) { + update->modify_mode = ACL_MODIFY_MODE_CLEAR; + update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR; + return 0; + } + + t_array_init(&dest_rights, 8); + t_array_init(&dest_neg_rights, 8); + for (i = 0; rights[i] != NULL; i++) { + const char *right = rights[i]; + + if (right[0] != '-') + dest = &dest_rights; + else { + right++; + dest = &dest_neg_rights; + } + if (strcmp(right, "all") != 0) { + if (*right == ':') { + /* non-standard right */ + right++; + array_push_back(dest, &right); + } else if (is_standard_right(right)) { + array_push_back(dest, &right); + } else { + *error_r = t_strdup_printf("Invalid right '%s'", + right); + return -1; + } + } else { + for (j = 0; all_mailbox_rights[j] != NULL; j++) + array_push_back(dest, &all_mailbox_rights[j]); + } + } + if (array_count(&dest_rights) > 0) { + array_append_zero(&dest_rights); + update->rights.rights = array_front(&dest_rights); + } else if (update->modify_mode == ACL_MODIFY_MODE_REPLACE) { + update->modify_mode = ACL_MODIFY_MODE_CLEAR; + } + if (array_count(&dest_neg_rights) > 0) { + array_append_zero(&dest_neg_rights); + update->rights.neg_rights = array_front(&dest_neg_rights); + } else if (update->neg_modify_mode == ACL_MODIFY_MODE_REPLACE) { + update->neg_modify_mode = ACL_MODIFY_MODE_CLEAR; + } + return 0; +} + +const char *acl_rights_export(const struct acl_rights *rights) +{ + string_t *str = t_str_new(128); + + if (rights->rights != NULL) + str_append(str, t_strarray_join(rights->rights, " ")); + if (rights->neg_rights != NULL && rights->neg_rights[0] != NULL) { + if (str_len(str) > 0) + str_append_c(str, ' '); + str_append_c(str, '-'); + str_append(str, t_strarray_join(rights->neg_rights, " -")); + } + return str_c(str); +} + +int acl_rights_parse_line(const char *line, pool_t pool, + struct acl_rights *rights_r, const char **error_r) +{ + const char *id_str, *const *right_names, *error = NULL; + + /* <id> [<imap acls>] [:<named acls>] */ + if (*line == '"') { + line++; + if (str_unescape_next(&line, &id_str) < 0 || + (line[0] != ' ' && line[0] != '\0')) { + *error_r = "Invalid quoted ID"; + return -1; + } + if (line[0] == ' ') + line++; + } else { + id_str = line; + line = strchr(id_str, ' '); + if (line == NULL) + line = ""; + else + id_str = t_strdup_until(id_str, line++); + } + + i_zero(rights_r); + + right_names = acl_right_names_parse(pool, line, &error); + if (*id_str != '-') + rights_r->rights = right_names; + else { + id_str++; + rights_r->neg_rights = right_names; + } + + if (acl_identifier_parse(id_str, rights_r) < 0) + error = t_strdup_printf("Unknown ID '%s'", id_str); + + if (error != NULL) { + *error_r = error; + return -1; + } + + rights_r->identifier = p_strdup(pool, rights_r->identifier); + return 0; +} + +void acl_rights_dup(const struct acl_rights *src, + pool_t pool, struct acl_rights *dest_r) +{ + i_zero(dest_r); + dest_r->id_type = src->id_type; + dest_r->identifier = p_strdup(pool, src->identifier); + dest_r->rights = src->rights == NULL ? NULL : + p_strarray_dup(pool, src->rights); + dest_r->neg_rights = src->neg_rights == NULL ? NULL : + p_strarray_dup(pool, src->neg_rights); + dest_r->global = src->global; +} + +int acl_rights_cmp(const struct acl_rights *r1, const struct acl_rights *r2) +{ + int ret; + + if (r1->global != r2->global) { + /* globals have higher priority than locals */ + return r1->global ? 1 : -1; + } + + ret = (int)r1->id_type - (int)r2->id_type; + if (ret != 0) + return ret; + + return null_strcmp(r1->identifier, r2->identifier); +} + +void acl_rights_sort(struct acl_object *aclobj) +{ + struct acl_rights *rights; + unsigned int i, dest, count; + + if (!array_is_created(&aclobj->rights)) + return; + + array_sort(&aclobj->rights, acl_rights_cmp); + + /* merge identical identifiers */ + rights = array_get_modifiable(&aclobj->rights, &count); + for (dest = 0, i = 1; i < count; i++) { + if (acl_rights_cmp(&rights[i], &rights[dest]) == 0) { + /* add i's rights to dest and delete i */ + acl_right_names_merge(aclobj->rights_pool, + &rights[dest].rights, + rights[i].rights, FALSE); + acl_right_names_merge(aclobj->rights_pool, + &rights[dest].neg_rights, + rights[i].neg_rights, FALSE); + } else { + if (++dest != i) + rights[dest] = rights[i]; + } + } + if (++dest < count) + array_delete(&aclobj->rights, dest, count - dest); +} + +bool acl_rights_has_nonowner_lookup_changes(const struct acl_rights *rights) +{ + const char *const *p; + + if (rights->id_type == ACL_ID_OWNER) { + /* ignore owner rights */ + return FALSE; + } + + if (rights->rights == NULL) + return FALSE; + + for (p = rights->rights; *p != NULL; p++) { + if (strcmp(*p, MAIL_ACL_LOOKUP) == 0) + return TRUE; + } + return FALSE; +} + +int acl_identifier_parse(const char *line, struct acl_rights *rights) +{ + if (str_begins(line, ACL_ID_NAME_USER_PREFIX)) { + rights->id_type = ACL_ID_USER; + rights->identifier = line + 5; + } else if (strcmp(line, ACL_ID_NAME_OWNER) == 0) { + rights->id_type = ACL_ID_OWNER; + } else if (str_begins(line, ACL_ID_NAME_GROUP_PREFIX)) { + rights->id_type = ACL_ID_GROUP; + rights->identifier = line + 6; + } else if (str_begins(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX)) { + rights->id_type = ACL_ID_GROUP_OVERRIDE; + rights->identifier = line + 15; + } else if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) { + rights->id_type = ACL_ID_AUTHENTICATED; + } else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 || + strcmp(line, "anonymous") == 0) { + rights->id_type = ACL_ID_ANYONE; + } else { + return -1; + } + return 0; +} + +static const char *const * +acl_right_names_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr, + bool dup_strings) +{ + const char **ret, *const *rights; + unsigned int i, dest, count; + + /* sort the rights first so we can easily drop duplicates */ + array_sort(rights_arr, i_strcmp_p); + + /* @UNSAFE */ + rights = array_get(rights_arr, &count); + ret = p_new(pool, const char *, count + 1); + if (count > 0) { + ret[0] = rights[0]; + for (i = dest = 1; i < count; i++) { + if (strcmp(rights[i-1], rights[i]) != 0) + ret[dest++] = rights[i]; + } + ret[dest] = NULL; + if (dup_strings) { + for (i = 0; i < dest; i++) + ret[i] = p_strdup(pool, ret[i]); + } + } + return ret; +} + +const char *const * +acl_right_names_parse(pool_t pool, const char *acl, const char **error_r) +{ + ARRAY_TYPE(const_string) rights; + const char *const *names; + unsigned int i; + + /* parse IMAP ACL list */ + while (*acl == ' ' || *acl == '\t') + acl++; + + t_array_init(&rights, 64); + while (*acl != '\0' && *acl != ' ' && *acl != '\t' && *acl != ':') { + for (i = 0; acl_letter_map[i].letter != '\0'; i++) { + if (acl_letter_map[i].letter == *acl) + break; + } + + if (acl_letter_map[i].letter == '\0') { + *error_r = t_strdup_printf("Unknown ACL '%c'", *acl); + return NULL; + } + + array_push_back(&rights, &acl_letter_map[i].name); + acl++; + } + while (*acl == ' ' || *acl == '\t') acl++; + + if (*acl != '\0') { + /* parse our own extended ACLs */ + if (*acl != ':') { + *error_r = "Missing ':' prefix in ACL extensions"; + return NULL; + } + + names = t_strsplit_spaces(acl + 1, ", \t"); + for (; *names != NULL; names++) { + const char *name = p_strdup(pool, *names); + array_push_back(&rights, &name); + } + } + + return acl_right_names_alloc(pool, &rights, FALSE); +} + +void acl_right_names_write(string_t *dest, const char *const *rights) +{ + char c2[2]; + unsigned int i, j, pos; + + c2[1] = '\0'; + pos = str_len(dest); + for (i = 0; rights[i] != NULL; i++) { + /* use letters if possible */ + for (j = 0; acl_letter_map[j].name != NULL; j++) { + if (strcmp(rights[i], acl_letter_map[j].name) == 0) { + c2[0] = acl_letter_map[j].letter; + str_insert(dest, pos, c2); + pos++; + break; + } + } + if (acl_letter_map[j].name == NULL) { + /* fallback to full name */ + str_append_c(dest, ' '); + str_append(dest, rights[i]); + } + } + if (pos + 1 < str_len(dest)) { + c2[0] = ':'; + str_insert(dest, pos + 1, c2); + } +} + +void acl_right_names_merge(pool_t pool, const char *const **destp, + const char *const *src, bool dup_strings) +{ + const char *const *dest = *destp; + ARRAY_TYPE(const_string) rights; + unsigned int i; + + t_array_init(&rights, 64); + if (dest != NULL) { + for (i = 0; dest[i] != NULL; i++) + array_push_back(&rights, &dest[i]); + } + if (src != NULL) { + for (i = 0; src[i] != NULL; i++) + array_push_back(&rights, &src[i]); + } + + *destp = acl_right_names_alloc(pool, &rights, dup_strings); +} + +bool acl_right_names_modify(pool_t pool, + const char *const **rightsp, + const char *const *modify_rights, + enum acl_modify_mode modify_mode) +{ + const char *const *old_rights = *rightsp; + const char *const *new_rights = NULL; + const char *null = NULL; + ARRAY_TYPE(const_string) rights; + unsigned int i, j; + + if (modify_rights == NULL && modify_mode != ACL_MODIFY_MODE_CLEAR) { + /* nothing to do here */ + return FALSE; + } + + switch (modify_mode) { + case ACL_MODIFY_MODE_REMOVE: + if (old_rights == NULL || *old_rights == NULL) { + /* nothing to do */ + return FALSE; + } + t_array_init(&rights, 64); + for (i = 0; old_rights[i] != NULL; i++) { + for (j = 0; modify_rights[j] != NULL; j++) { + if (strcmp(old_rights[i], modify_rights[j]) == 0) + break; + } + if (modify_rights[j] == NULL) + array_push_back(&rights, &old_rights[i]); + } + new_rights = &null; + modify_rights = array_count(&rights) == 0 ? NULL : + array_front(&rights); + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_ADD: + new_rights = old_rights; + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_REPLACE: + new_rights = &null; + acl_right_names_merge(pool, &new_rights, modify_rights, TRUE); + break; + case ACL_MODIFY_MODE_CLEAR: + if (*rightsp == NULL) { + /* ACL didn't exist before either */ + return FALSE; + } + *rightsp = NULL; + return TRUE; + } + i_assert(new_rights != NULL); + *rightsp = new_rights; + + if (old_rights == NULL) + return new_rights[0] != NULL; + + /* see if anything changed */ + for (i = 0; old_rights[i] != NULL && new_rights[i] != NULL; i++) { + if (strcmp(old_rights[i], new_rights[i]) != 0) + return TRUE; + } + return old_rights[i] != NULL || new_rights[i] != NULL; +} + +static void apply_owner_default_rights(struct acl_object *aclobj) +{ + struct acl_rights_update ru; + const char *null = NULL; + + i_zero(&ru); + ru.modify_mode = ACL_MODIFY_MODE_REPLACE; + ru.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + ru.rights.id_type = ACL_ID_OWNER; + ru.rights.rights = aclobj->backend->default_rights; + ru.rights.neg_rights = &null; + acl_cache_update(aclobj->backend->cache, aclobj->name, &ru); +} + +void acl_object_rebuild_cache(struct acl_object *aclobj) +{ + struct acl_rights_update ru; + enum acl_modify_mode add_mode; + const struct acl_rights *rights, *prev_match = NULL; + unsigned int i, count; + bool first_global = TRUE; + + acl_cache_flush(aclobj->backend->cache, aclobj->name); + + if (!array_is_created(&aclobj->rights)) + return; + + /* Rights are sorted by their 1) locals first, globals next, + 2) acl_id_type. We'll apply only the rights matching ourself. + + Every time acl_id_type or local/global changes, the new ACLs will + replace all of the existing ACLs. Basically this means that if + user belongs to multiple matching groups or group-overrides, their + ACLs are merged. In all other situations the ACLs are replaced + (because there aren't duplicate rights entries and a user can't + match multiple usernames). */ + i_zero(&ru); + rights = array_get(&aclobj->rights, &count); + if (!acl_backend_user_is_owner(aclobj->backend)) + i = 0; + else { + /* we're the owner. skip over all rights entries until we + reach ACL_ID_OWNER or higher, or alternatively when we + reach a global ACL (even ACL_ID_ANYONE overrides owner's + rights if it's global) */ + for (i = 0; i < count; i++) { + if (rights[i].id_type >= ACL_ID_OWNER || + rights[i].global) + break; + } + apply_owner_default_rights(aclobj); + /* now continue applying the rest of the rights, + if there are any */ + } + for (; i < count; i++) { + if (!acl_backend_rights_match_me(aclobj->backend, &rights[i])) + continue; + + if (prev_match == NULL || + prev_match->id_type != rights[i].id_type || + prev_match->global != rights[i].global) { + /* replace old ACLs */ + add_mode = ACL_MODIFY_MODE_REPLACE; + } else { + /* merging to existing ACLs */ + i_assert(rights[i].id_type == ACL_ID_GROUP || + rights[i].id_type == ACL_ID_GROUP_OVERRIDE); + add_mode = ACL_MODIFY_MODE_ADD; + } + prev_match = &rights[i]; + + /* If [neg_]rights is NULL it needs to be ignored. + The easiest way to do that is to just mark it with + REMOVE mode */ + ru.modify_mode = rights[i].rights == NULL ? + ACL_MODIFY_MODE_REMOVE : add_mode; + ru.neg_modify_mode = rights[i].neg_rights == NULL ? + ACL_MODIFY_MODE_REMOVE : add_mode; + ru.rights = rights[i]; + if (rights[i].global && first_global) { + /* first global: reset negative ACLs so local ACLs + can't mess things up via them */ + first_global = FALSE; + ru.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + } + acl_cache_update(aclobj->backend->cache, aclobj->name, &ru); + } +} + +void acl_object_remove_all_access(struct acl_object *aclobj) +{ + static const char *null = NULL; + struct acl_rights rights; + + i_zero(&rights); + rights.id_type = ACL_ID_ANYONE; + rights.rights = &null; + array_push_back(&aclobj->rights, &rights); + + rights.id_type = ACL_ID_OWNER; + rights.rights = &null; + array_push_back(&aclobj->rights, &rights); +} + +void acl_object_add_global_acls(struct acl_object *aclobj) +{ + struct acl_backend *backend = aclobj->backend; + const char *vname, *error; + + if (mailbox_list_is_valid_name(backend->list, aclobj->name, &error)) + vname = mailbox_list_get_vname(backend->list, aclobj->name); + else + vname = ""; + + acl_global_file_get(backend->global_file, vname, + aclobj->rights_pool, &aclobj->rights); +} diff --git a/src/plugins/acl/acl-api.h b/src/plugins/acl/acl-api.h new file mode 100644 index 0000000..7b19a98 --- /dev/null +++ b/src/plugins/acl/acl-api.h @@ -0,0 +1,167 @@ +#ifndef ACL_API_H +#define ACL_API_H + +#include <sys/stat.h> + +struct mailbox_list; +struct mail_storage; +struct mailbox; +struct acl_object; + +/* Show mailbox in mailbox list. Allow subscribing to it. */ +#define MAIL_ACL_LOOKUP "lookup" +/* Allow opening mailbox for reading */ +#define MAIL_ACL_READ "read" +/* Allow permanent flag changes (except for seen/deleted). + If not set, doesn't allow save/copy to set any flags either. */ +#define MAIL_ACL_WRITE "write" +/* Allow permanent seen-flag changes */ +#define MAIL_ACL_WRITE_SEEN "write-seen" +/* Allow permanent deleted-flag changes */ +#define MAIL_ACL_WRITE_DELETED "write-deleted" +/* Allow saving and copying mails into the mailbox */ +#define MAIL_ACL_INSERT "insert" +/* Allow posting mails to the mailbox (e.g. Sieve fileinto) */ +#define MAIL_ACL_POST "post" +/* Allow expunging mails */ +#define MAIL_ACL_EXPUNGE "expunge" +/* Allow creating child mailboxes */ +#define MAIL_ACL_CREATE "create" +/* Allow deleting this mailbox */ +#define MAIL_ACL_DELETE "delete" +/* Allow changing ACL state in this mailbox */ +#define MAIL_ACL_ADMIN "admin" + +#define MAILBOX_ATTRIBUTE_PREFIX_ACL \ + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"acl/" + +/* ACL identifiers in override order */ +enum acl_id_type { + /* Anyone's rights, including anonymous's. + identifier name is ignored. */ + ACL_ID_ANYONE, + /* Authenticate users' rights. identifier name is ignored. */ + ACL_ID_AUTHENTICATED, + /* Group's rights */ + ACL_ID_GROUP, + /* Owner's rights, used when user is the storage's owner. + identifier name is ignored. */ + ACL_ID_OWNER, + /* User's rights */ + ACL_ID_USER, + /* Same as group's rights, but also overrides user's rights */ + ACL_ID_GROUP_OVERRIDE, + + ACL_ID_TYPE_COUNT +}; + +enum acl_modify_mode { + /* Remove rights from existing ACL */ + ACL_MODIFY_MODE_REMOVE = 0, + /* Add rights to existing ACL (or create a new one) */ + ACL_MODIFY_MODE_ADD, + /* Replace existing ACL with given rights */ + ACL_MODIFY_MODE_REPLACE, + /* Clear all the rights from an existing ACL */ + ACL_MODIFY_MODE_CLEAR +}; + +struct acl_rights { + /* Type of the identifier, user/group */ + enum acl_id_type id_type; + /* Identifier, eg. username / group name */ + const char *identifier; + + /* Rights assigned. NULL entry can be ignored, but { NULL } means user + has no rights. */ + const char *const *rights; + /* Negative rights assigned */ + const char *const *neg_rights; + + /* These rights are global for all users */ + bool global:1; +}; +ARRAY_DEFINE_TYPE(acl_rights, struct acl_rights); + +struct acl_rights_update { + struct acl_rights rights; + + enum acl_modify_mode modify_mode; + enum acl_modify_mode neg_modify_mode; + /* These changes' "last changed" timestamp */ + time_t last_change; +}; + +/* data contains the information needed to initialize ACL backend. If username + is NULL, it means the user is anonymous. Username and groups are matched + case-sensitively. */ +struct acl_backend * +acl_backend_init(const char *data, struct mailbox_list *list, + const char *acl_username, const char *const *groups, + bool owner); +void acl_backend_deinit(struct acl_backend **backend); + +/* Returns the acl_username passed to acl_backend_init(). Note that with + anonymous users NULL is returned. */ +const char *acl_backend_get_acl_username(struct acl_backend *backend); + +/* Returns TRUE if user isn't anonymous. */ +bool acl_backend_user_is_authenticated(struct acl_backend *backend); +/* Returns TRUE if user owns the storage. */ +bool acl_backend_user_is_owner(struct acl_backend *backend); +/* Returns TRUE if given name matches the ACL user name. */ +bool acl_backend_user_name_equals(struct acl_backend *backend, + const char *username); +/* Returns TRUE if ACL user is in given group. */ +bool acl_backend_user_is_in_group(struct acl_backend *backend, + const char *group_name); +/* Returns index for the right name. If it doesn't exist, it's created. */ +unsigned int acl_backend_lookup_right(struct acl_backend *backend, + const char *right); +/* Returns TRUE if acl_rights matches backend user. */ +bool acl_backend_rights_match_me(struct acl_backend *backend, + const struct acl_rights *rights); + +/* List mailboxes that have lookup right to some non-owners. */ +struct acl_mailbox_list_context * +acl_backend_nonowner_lookups_iter_init(struct acl_backend *backend); +bool acl_backend_nonowner_lookups_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r); +int +acl_backend_nonowner_lookups_iter_deinit(struct acl_mailbox_list_context **ctx); + +/* Force a rebuild for nonowner lookups index */ +int acl_backend_nonowner_lookups_rebuild(struct acl_backend *backend); + +struct acl_object *acl_object_init_from_name(struct acl_backend *backend, + const char *name); +struct acl_object *acl_object_init_from_parent(struct acl_backend *backend, + const char *child_name); +void acl_object_deinit(struct acl_object **aclobj); + +/* Returns 1 if we have the requested rights, 0 if not, or -1 if internal + error occurred. */ +int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx); +/* Returns 0 = ok, -1 = internal error */ +int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool, + const char *const **rights_r); +/* Returns the default rights for the object. */ +const char *const *acl_object_get_default_rights(struct acl_object *aclobj); +/* Returns timestamp of when the ACLs were last changed for this object, + or 0 = never. */ +int acl_object_last_changed(struct acl_object *aclobj, time_t *last_changed_r); + +/* Update ACL of given object. */ +int acl_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update); + +/* List all identifiers. */ +struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj); +bool acl_object_list_next(struct acl_object_list_iter *iter, + struct acl_rights *rights_r); +int acl_object_list_deinit(struct acl_object_list_iter **iter); + +/* Returns the canonical ID for the right. */ +const char *acl_rights_get_id(const struct acl_rights *right); + +#endif diff --git a/src/plugins/acl/acl-attributes.c b/src/plugins/acl/acl-attributes.c new file mode 100644 index 0000000..515ff42 --- /dev/null +++ b/src/plugins/acl/acl-attributes.c @@ -0,0 +1,233 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "mail-storage-private.h" +#include "acl-api-private.h" +#include "acl-plugin.h" +#include "acl-storage.h" + +struct acl_mailbox_attribute_iter { + struct mailbox_attribute_iter iter; + struct mailbox_attribute_iter *super; + + struct acl_object_list_iter *acl_iter; + string_t *acl_name; + + bool failed; +}; + +static int +acl_attribute_update_acl(struct mailbox_transaction_context *t, const char *key, + const struct mail_attribute_value *value) +{ + const char *value_str, *id, *const *rights, *error; + struct acl_rights_update update; + + /* for now allow only dsync to update ACLs this way. + if this check is removed, it should be replaced by a setting, since + some admins may still have configured Dovecot using dovecot-acl + files directly that they don't want users to update. and in any case + ACL_STORAGE_RIGHT_ADMIN must be checked then. */ + if (!t->box->storage->user->dsyncing) { + mail_storage_set_error(t->box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + return -1; + } + + if (mailbox_attribute_value_to_string(t->box->storage, value, + &value_str) < 0) + return -1; + + i_zero(&update); + update.modify_mode = ACL_MODIFY_MODE_REPLACE; + update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + update.last_change = value->last_change; + id = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL); + rights = value_str == NULL ? NULL : t_strsplit(value_str, " "); + if (acl_rights_update_import(&update, id, rights, &error) < 0) { + mail_storage_set_error(t->box->storage, MAIL_ERROR_PARAMS, error); + return -1; + } + /* FIXME: this should actually be done only at commit().. */ + return acl_mailbox_update_acl(t, &update); +} + +static int acl_attribute_get_acl(struct mailbox *box, const char *key, + struct mail_attribute_value *value_r) +{ + struct acl_object *aclobj = acl_mailbox_get_aclobj(box); + struct acl_object_list_iter *iter; + struct acl_rights rights, wanted_rights; + const char *id; + int ret = 0; + + i_zero(value_r); + + if (!box->storage->user->dsyncing) { + mail_storage_set_error(box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + return -1; + } + /* set last_change for all ACL objects, even if they don't exist + (because they could have been removed by the last change, and dsync + can use this information) */ + (void)acl_object_last_changed(aclobj, &value_r->last_change); + + i_zero(&wanted_rights); + id = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL); + if (acl_identifier_parse(id, &wanted_rights) < 0) { + mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS, + t_strdup_printf("Invalid ID: %s", id)); + return -1; + } + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (!rights.global && + rights.id_type == wanted_rights.id_type && + null_strcmp(rights.identifier, wanted_rights.identifier) == 0) { + value_r->value = acl_rights_export(&rights); + ret = 1; + break; + } + } + /* the return value here cannot be used, because this function + needs to return whether it actually matched something + or not */ + if (acl_object_list_deinit(&iter) < 0) { + mail_storage_set_internal_error(box->storage); + ret = -1; + } + return ret; +} + +static int acl_have_attribute_rights(struct mailbox *box) +{ + int ret; + + if (box->deleting) { + /* deleting attributes during mailbox deletion */ + return 1; + } + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP); + if (ret <= 0) { + if (ret < 0) + return -1; + mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND, + T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname)); + return -1; + } + + return acl_mailbox_have_extra_attribute_rights(box) ? 0 : -1; +} + +int acl_attribute_set(struct mailbox_transaction_context *t, + enum mail_attribute_type type, const char *key, + const struct mail_attribute_value *value) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(t->box); + + if (acl_have_attribute_rights(t->box) < 0) + return -1; + if (str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_ACL)) + return acl_attribute_update_acl(t, key, value); + return abox->module_ctx.super.attribute_set(t, type, key, value); +} + +int acl_attribute_get(struct mailbox *box, + enum mail_attribute_type type, const char *key, + struct mail_attribute_value *value_r) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + if (acl_have_attribute_rights(box) < 0) + return -1; + if (str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_ACL)) + return acl_attribute_get_acl(box, key, value_r); + return abox->module_ctx.super.attribute_get(box, type, key, value_r); +} + +struct mailbox_attribute_iter * +acl_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type, + const char *prefix) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + struct acl_mailbox_attribute_iter *aiter; + + aiter = i_new(struct acl_mailbox_attribute_iter, 1); + aiter->iter.box = box; + if (acl_have_attribute_rights(box) < 0) + aiter->failed = TRUE; + else { + aiter->super = abox->module_ctx.super. + attribute_iter_init(box, type, prefix); + if (box->storage->user->dsyncing && + type == MAIL_ATTRIBUTE_TYPE_SHARED && + str_begins(MAILBOX_ATTRIBUTE_PREFIX_ACL, prefix)) { + aiter->acl_iter = acl_object_list_init(abox->aclobj); + aiter->acl_name = str_new(default_pool, 128); + str_append(aiter->acl_name, MAILBOX_ATTRIBUTE_PREFIX_ACL); + } + } + return &aiter->iter; +} + +static const char * +acl_attribute_iter_next_acl(struct acl_mailbox_attribute_iter *aiter) +{ + struct acl_rights rights; + + if (aiter->failed) + return NULL; + + while (acl_object_list_next(aiter->acl_iter, &rights)) { + if (rights.global) + continue; + str_truncate(aiter->acl_name, strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL)); + acl_rights_write_id(aiter->acl_name, &rights); + return str_c(aiter->acl_name); + } + if (acl_object_list_deinit(&aiter->acl_iter) < 0) { + mail_storage_set_internal_error(aiter->iter.box->storage); + aiter->failed = TRUE; + } + return NULL; +} + +const char *acl_attribute_iter_next(struct mailbox_attribute_iter *iter) +{ + struct acl_mailbox_attribute_iter *aiter = + (struct acl_mailbox_attribute_iter *)iter; + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(iter->box); + const char *key; + + if (aiter->super == NULL) + return NULL; + if (aiter->acl_iter != NULL) { + if ((key = acl_attribute_iter_next_acl(aiter)) != NULL) + return key; + } + return abox->module_ctx.super.attribute_iter_next(aiter->super); +} + +int acl_attribute_iter_deinit(struct mailbox_attribute_iter *iter) +{ + struct acl_mailbox_attribute_iter *aiter = + (struct acl_mailbox_attribute_iter *)iter; + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(iter->box); + int ret = aiter->failed ? -1 : 0; + + if (aiter->super != NULL) { + if (abox->module_ctx.super.attribute_iter_deinit(aiter->super) < 0) + ret = -1; + } + if (aiter->acl_iter != NULL && acl_object_list_deinit(&aiter->acl_iter) < 0) { + mail_storage_set_internal_error(aiter->iter.box->storage); + ret = -1; + } + str_free(&aiter->acl_name); + i_free(aiter); + return ret; +} diff --git a/src/plugins/acl/acl-backend-vfile-acllist.c b/src/plugins/acl/acl-backend-vfile-acllist.c new file mode 100644 index 0000000..c6029a2 --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile-acllist.c @@ -0,0 +1,424 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "safe-mkstemp.h" +#include "istream.h" +#include "ostream.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "acl-plugin.h" +#include "acl-cache.h" +#include "acl-lookup-dict.h" +#include "acl-backend-vfile.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +struct acl_mailbox_list_context_vfile { + struct acl_mailbox_list_context ctx; + + unsigned int idx; +}; + +static void +acllist_clear(struct acl_backend_vfile *backend, uoff_t file_size) +{ + if (backend->acllist_pool == NULL) { + backend->acllist_pool = + pool_alloconly_create("vfile acllist", + I_MAX(file_size / 2, 128)); + i_array_init(&backend->acllist, I_MAX(16, file_size / 60)); + } else { + p_clear(backend->acllist_pool); + array_clear(&backend->acllist); + } +} + +static bool acl_list_get_root_dir(struct acl_backend_vfile *backend, + const char **root_dir_r, + enum mailbox_list_path_type *type_r) +{ + struct mail_storage *storage; + const char *rootdir, *maildir; + enum mailbox_list_path_type type; + + if (backend->backend.globals_only) + return FALSE; + + storage = mailbox_list_get_namespace(backend->backend.list)->storage; + type = mail_storage_get_acl_list_path_type(storage); + if (!mailbox_list_get_root_path(backend->backend.list, type, &rootdir)) + return FALSE; + *type_r = type; + + if (type == MAILBOX_LIST_PATH_TYPE_DIR && + mail_storage_is_mailbox_file(storage)) { + maildir = mailbox_list_get_root_forced(backend->backend.list, + MAILBOX_LIST_PATH_TYPE_MAILBOX); + if (strcmp(maildir, rootdir) == 0) { + /* dovecot-acl-list would show up as a mailbox if we + created it to root dir. since we don't really have + any other good alternatives, place it to control + dir */ + rootdir = mailbox_list_get_root_forced(backend->backend.list, + MAILBOX_LIST_PATH_TYPE_CONTROL); + *type_r = MAILBOX_LIST_PATH_TYPE_CONTROL; + } + } + *root_dir_r = rootdir; + return TRUE; +} + +static bool acl_list_get_path(struct acl_backend_vfile *backend, + const char **path_r) +{ + enum mailbox_list_path_type type; + const char *root_dir; + + if (!acl_list_get_root_dir(backend, &root_dir, &type)) + return FALSE; + *path_r = t_strconcat(root_dir, "/"ACLLIST_FILENAME, NULL); + return TRUE; +} + +static int acl_backend_vfile_acllist_read(struct acl_backend_vfile *backend) +{ + struct acl_backend_vfile_acllist acllist; + struct istream *input; + struct stat st; + const char *path, *line, *p; + int fd, ret = 0; + + backend->acllist_last_check = ioloop_time; + + if (!acl_list_get_path(backend, &path)) { + /* we're never going to build acllist for this namespace. */ + acllist_clear(backend, 0); + return 0; + } + + if (backend->acllist_mtime != 0) { + /* see if the file's mtime has changed */ + if (stat(path, &st) < 0) { + if (errno == ENOENT) + backend->acllist_mtime = 0; + else + i_error("stat(%s) failed: %m", path); + return -1; + } + if (st.st_mtime == backend->acllist_mtime) + return 0; + } + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + backend->acllist_mtime = 0; + return -1; + } + i_error("open(%s) failed: %m", path); + return -1; + } + if (fstat(fd, &st) < 0) { + i_error("fstat(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + backend->acllist_mtime = st.st_mtime; + acllist_clear(backend, st.st_size); + + input = i_stream_create_fd(fd, SIZE_MAX); + while ((line = i_stream_read_next_line(input)) != NULL) { + acllist.mtime = 0; + for (p = line; *p >= '0' && *p <= '9'; p++) + acllist.mtime = acllist.mtime * 10 + (*p - '0'); + + if (p == line || *p != ' ' || p[1] == '\0') { + i_error("Broken acllist file: %s", path); + i_unlink_if_exists(path); + i_close_fd(&fd); + return -1; + } + acllist.name = p_strdup(backend->acllist_pool, p + 1); + array_push_back(&backend->acllist, &acllist); + } + if (input->stream_errno != 0) + ret = -1; + i_stream_destroy(&input); + + if (close(fd) < 0) + i_error("close(%s) failed: %m", path); + return ret; +} + +void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend) +{ + i_assert(!backend->iterating_acllist); + + if (backend->acllist_last_check + + (time_t)backend->cache_secs > ioloop_time) + return; + + if (acl_backend_vfile_acllist_read(backend) < 0) { + acllist_clear(backend, 0); + if (!backend->rebuilding_acllist) + (void)acl_backend_vfile_acllist_rebuild(backend); + } +} + +static int +acllist_append(struct acl_backend_vfile *backend, struct ostream *output, + const char *vname) +{ + struct acl_object *aclobj; + struct acl_object_list_iter *iter; + struct acl_rights rights; + struct acl_backend_vfile_acllist acllist; + const char *name; + int ret; + + name = mailbox_list_get_storage_name(backend->backend.list, vname); + acl_cache_flush(backend->backend.cache, name); + aclobj = acl_object_init_from_name(&backend->backend, name); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (acl_rights_has_nonowner_lookup_changes(&rights)) + break; + } + ret = acl_object_list_deinit(&iter); + + if (acl_backend_vfile_object_get_mtime(aclobj, &acllist.mtime) < 0) + ret = -1; + + if (ret > 0) { + acllist.name = p_strdup(backend->acllist_pool, name); + array_push_back(&backend->acllist, &acllist); + + o_stream_nsend_str(output, t_strdup_printf( + "%s %s\n", dec2str(acllist.mtime), name)); + } + acl_object_deinit(&aclobj); + return ret < 0 ? -1 : 0; +} + +static int +acl_backend_vfile_acllist_try_rebuild(struct acl_backend_vfile *backend) +{ + struct mailbox_list *list = backend->backend.list; + struct mail_namespace *ns; + struct mailbox_list_iterate_context *iter; + enum mailbox_list_path_type type; + const struct mailbox_info *info; + const char *rootdir, *acllist_path; + struct ostream *output; + struct stat st; + struct mailbox_permissions perm; + string_t *path; + int fd, ret; + + i_assert(!backend->rebuilding_acllist); + + if (!acl_list_get_root_dir(backend, &rootdir, &type)) + return 0; + + ns = mailbox_list_get_namespace(list); + if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) { + /* we can't write anything here */ + return 0; + } + + path = t_str_new(256); + str_printfa(path, "%s/%s", rootdir, mailbox_list_get_temp_prefix(list)); + + /* Build it into a temporary file and rename() over. There's no need + to use locking, because even if multiple processes are rebuilding + the file at the same time the result should be the same. */ + mailbox_list_get_root_permissions(list, &perm); + fd = safe_mkstemp_group(path, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + if (fd == -1 && errno == ENOENT) { + if (mailbox_list_mkdir_root(backend->backend.list, + rootdir, type) < 0) + return -1; + fd = safe_mkstemp_group(path, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + } + if (fd == -1) { + if (errno == EACCES) { + /* Ignore silently if we can't create it */ + return 0; + } + i_error("dovecot-acl-list creation failed: " + "safe_mkstemp(%s) failed: %m", str_c(path)); + return -1; + } + output = o_stream_create_fd_file(fd, 0, FALSE); + o_stream_cork(output); + + ret = 0; + acllist_clear(backend, 0); + + backend->rebuilding_acllist = TRUE; + iter = mailbox_list_iter_init(list, "*", + MAILBOX_LIST_ITER_RAW_LIST | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while (ret == 0 && (info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN { + ret = acllist_append(backend, output, info->vname); + } T_END; + + if (o_stream_finish(output) < 0) { + i_error("write(%s) failed: %s", str_c(path), + o_stream_get_error(output)); + ret = -1; + } + if (mailbox_list_iter_deinit(&iter) < 0) + ret = -1; + o_stream_destroy(&output); + + if (ret == 0) { + if (fstat(fd, &st) < 0) { + i_error("fstat(%s) failed: %m", str_c(path)); + ret = -1; + } + } + if (close(fd) < 0) { + i_error("close(%s) failed: %m", str_c(path)); + ret = -1; + } + + if (ret == 0) { + if (!acl_list_get_path(backend, &acllist_path)) + i_unreached(); + if (rename(str_c(path), acllist_path) < 0) { + i_error("rename(%s, %s) failed: %m", + str_c(path), acllist_path); + ret = -1; + } + } + if (ret == 0) { + struct acl_user *auser = ACL_USER_CONTEXT(ns->user); + i_assert(auser != NULL); + backend->acllist_mtime = st.st_mtime; + backend->acllist_last_check = ioloop_time; + /* FIXME: dict rebuild is expensive, try to avoid it */ + (void)acl_lookup_dict_rebuild(auser->acl_lookup_dict); + } else { + acllist_clear(backend, 0); + i_unlink_if_exists(str_c(path)); + } + backend->rebuilding_acllist = FALSE; + return ret; +} + +int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend) +{ + const char *acllist_path; + + if (acl_backend_vfile_acllist_try_rebuild(backend) == 0) + return 0; + else { + /* delete it to make sure it gets rebuilt later */ + if (!acl_list_get_path(backend, &acllist_path)) + i_unreached(); + i_unlink_if_exists(acllist_path); + return -1; + } +} + +static const struct acl_backend_vfile_acllist * +acl_backend_vfile_acllist_find(struct acl_backend_vfile *backend, + const char *name) +{ + const struct acl_backend_vfile_acllist *acllist; + + array_foreach(&backend->acllist, acllist) { + if (strcmp(acllist->name, name) == 0) + return acllist; + } + return NULL; +} + +void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend, + const char *name, time_t mtime) +{ + const struct acl_backend_vfile_acllist *acllist; + + if (backend->rebuilding_acllist || backend->iterating_acllist) + return; + + acl_backend_vfile_acllist_refresh(backend); + acllist = acl_backend_vfile_acllist_find(backend, name); + if (acllist != NULL && acllist->mtime != mtime) + (void)acl_backend_vfile_acllist_rebuild(backend); +} + +struct acl_mailbox_list_context * +acl_backend_vfile_nonowner_iter_init(struct acl_backend *_backend) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct acl_mailbox_list_context_vfile *ctx; + + acl_backend_vfile_acllist_refresh(backend); + + ctx = i_new(struct acl_mailbox_list_context_vfile, 1); + ctx->ctx.backend = _backend; + backend->iterating_acllist = TRUE; + return &ctx->ctx; +} + +bool acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *_ctx, + const char **name_r) +{ + struct acl_mailbox_list_context_vfile *ctx = + (struct acl_mailbox_list_context_vfile *)_ctx; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_ctx->backend; + const struct acl_backend_vfile_acllist *acllist; + unsigned int count; + + if (_ctx->failed) + return FALSE; + + acllist = array_get(&backend->acllist, &count); + if (count == 0) + _ctx->empty = TRUE; + if (ctx->idx == count) + return FALSE; + + *name_r = acllist[ctx->idx++].name; + return TRUE; +} + +int +acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)ctx->backend; + int ret; + + backend->iterating_acllist = FALSE; + if (ctx->failed) + ret = -1; + else if (ctx->empty) + ret = 0; + else + ret = 1; + i_free(ctx); + return ret; +} + +int acl_backend_vfile_nonowner_lookups_rebuild(struct acl_backend *_backend) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + + return acl_backend_vfile_acllist_rebuild(backend); +} diff --git a/src/plugins/acl/acl-backend-vfile-update.c b/src/plugins/acl/acl-backend-vfile-update.c new file mode 100644 index 0000000..7c48c4e --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile-update.c @@ -0,0 +1,260 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "bsearch-insert-pos.h" +#include "ioloop.h" +#include "str.h" +#include "strescape.h" +#include "file-dotlock.h" +#include "ostream.h" +#include "mail-storage.h" +#include "acl-cache.h" +#include "acl-backend-vfile.h" + +#include <utime.h> +#include <sys/stat.h> + +static struct dotlock_settings dotlock_set = { + .timeout = 30, + .stale_timeout = 120 +}; + +static int acl_backend_vfile_update_begin(struct acl_object_vfile *aclobj, + struct dotlock **dotlock_r) +{ + struct acl_object *_aclobj = &aclobj->aclobj; + struct mailbox_permissions perm; + int fd; + + if (aclobj->local_path == NULL) { + i_error("Can't update acl object '%s': No local acl file path", + aclobj->aclobj.name); + return -1; + } + + /* first lock the ACL file */ + mailbox_list_get_permissions(_aclobj->backend->list, + _aclobj->name, &perm); + fd = file_dotlock_open_group(&dotlock_set, aclobj->local_path, 0, + perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin, dotlock_r); + if (fd == -1) { + i_error("file_dotlock_open(%s) failed: %m", aclobj->local_path); + return -1; + } + + /* locked successfully, re-read the existing file to make sure we + don't lose any changes. */ + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + if (_aclobj->backend->v.object_refresh_cache(_aclobj) < 0) { + file_dotlock_delete(dotlock_r); + return -1; + } + return fd; +} + +static bool +vfile_object_modify_right(struct acl_object *aclobj, unsigned int idx, + const struct acl_rights_update *update) +{ + struct acl_rights *right; + bool c1, c2; + + right = array_idx_modifiable(&aclobj->rights, idx); + c1 = acl_right_names_modify(aclobj->rights_pool, &right->rights, + update->rights.rights, update->modify_mode); + c2 = acl_right_names_modify(aclobj->rights_pool, &right->neg_rights, + update->rights.neg_rights, + update->neg_modify_mode); + + if (right->rights == NULL && right->neg_rights == NULL) { + /* this identifier no longer exists */ + array_delete(&aclobj->rights, idx, 1); + c1 = TRUE; + } + return c1 || c2; +} + +static bool +vfile_object_add_right(struct acl_object *aclobj, unsigned int idx, + const struct acl_rights_update *update) +{ + struct acl_rights right; + bool c1, c2; + + if (update->modify_mode == ACL_MODIFY_MODE_REMOVE && + update->neg_modify_mode == ACL_MODIFY_MODE_REMOVE) { + /* nothing to do */ + return FALSE; + } + + i_zero(&right); + right.id_type = update->rights.id_type; + right.identifier = p_strdup(aclobj->rights_pool, + update->rights.identifier); + + c1 = acl_right_names_modify(aclobj->rights_pool, &right.rights, + update->rights.rights, update->modify_mode); + c2 = acl_right_names_modify(aclobj->rights_pool, &right.neg_rights, + update->rights.neg_rights, + update->neg_modify_mode); + if (c1 || c2) { + array_insert(&aclobj->rights, idx, &right, 1); + return TRUE; + } + return FALSE; +} + +static void +vfile_write_right(string_t *dest, const struct acl_rights *right, + bool neg) +{ + const char *const *rights = neg ? right->neg_rights : right->rights; + + if (neg) str_append_c(dest,'-'); + acl_rights_write_id(dest, right); + + if (strchr(str_c(dest), ' ') != NULL) T_BEGIN { + /* need to escape it */ + const char *escaped = t_strdup(str_escape(str_c(dest))); + str_truncate(dest, 0); + str_printfa(dest, "\"%s\"", escaped); + } T_END; + + str_append_c(dest, ' '); + acl_right_names_write(dest, rights); + str_append_c(dest, '\n'); +} + +static int +acl_backend_vfile_update_write(struct acl_object *aclobj, + int fd, const char *path) +{ + struct ostream *output; + string_t *str; + const struct acl_rights *rights; + unsigned int i, count; + int ret = 0; + + output = o_stream_create_fd_file(fd, 0, FALSE); + o_stream_cork(output); + + str = str_new(default_pool, 256); + /* rights are sorted with globals at the end, so we can stop at the + first global */ + rights = array_get(&aclobj->rights, &count); + for (i = 0; i < count && !rights[i].global; i++) { + if (rights[i].rights != NULL) { + vfile_write_right(str, &rights[i], FALSE); + o_stream_nsend(output, str_data(str), str_len(str)); + str_truncate(str, 0); + } + if (rights[i].neg_rights != NULL) { + vfile_write_right(str, &rights[i], TRUE); + o_stream_nsend(output, str_data(str), str_len(str)); + str_truncate(str, 0); + } + } + str_free(&str); + if (o_stream_finish(output) < 0) { + i_error("write(%s) failed: %s", path, + o_stream_get_error(output)); + ret = -1; + } + o_stream_destroy(&output); + /* we really don't want to lose ACL files' contents, so fsync() always + before renaming */ + if (fsync(fd) < 0) { + i_error("fsync(%s) failed: %m", path); + ret = -1; + } + return ret; +} + +static void acl_backend_vfile_update_cache(struct acl_object *_aclobj, int fd) +{ + struct acl_backend_vfile_validity *validity; + struct stat st; + + if (fstat(fd, &st) < 0) { + /* we'll just recalculate or fail it later */ + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return; + } + + validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + validity->local_validity.last_read_time = ioloop_time; + validity->local_validity.last_mtime = st.st_mtime; + validity->local_validity.last_size = st.st_size; +} + +int acl_backend_vfile_object_update(struct acl_object *_aclobj, + const struct acl_rights_update *update) +{ + struct acl_object_vfile *aclobj = + (struct acl_object_vfile *)_aclobj; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_aclobj->backend; + struct acl_backend_vfile_validity *validity; + struct dotlock *dotlock; + struct utimbuf ut; + time_t orig_mtime; + const char *path; + unsigned int i; + int fd; + bool changed; + + /* global ACLs can't be updated here */ + i_assert(!update->rights.global); + + fd = acl_backend_vfile_update_begin(aclobj, &dotlock); + if (fd == -1) + return -1; + + if (!array_bsearch_insert_pos(&_aclobj->rights, &update->rights, + acl_rights_cmp, &i)) + changed = vfile_object_add_right(_aclobj, i, update); + else + changed = vfile_object_modify_right(_aclobj, i, update); + if (!changed) { + file_dotlock_delete(&dotlock); + return 0; + } + + validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + orig_mtime = validity->local_validity.last_mtime; + + /* ACLs were really changed, write the new ones */ + path = file_dotlock_get_lock_path(dotlock); + if (acl_backend_vfile_update_write(_aclobj, fd, path) < 0) { + file_dotlock_delete(&dotlock); + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return -1; + } + if (orig_mtime < update->last_change && update->last_change != 0) { + /* set mtime to last_change, if it's higher than the file's + original mtime. if original mtime is higher, then we're + merging some changes and it's better for the mtime to get + updated. */ + ut.actime = ioloop_time; + ut.modtime = update->last_change; + if (utime(path, &ut) < 0) + i_error("utime(%s) failed: %m", path); + } + acl_backend_vfile_update_cache(_aclobj, fd); + if (file_dotlock_replace(&dotlock, 0) < 0) { + acl_cache_flush(_aclobj->backend->cache, _aclobj->name); + return -1; + } + /* make sure dovecot-acl-list gets updated if we changed any + lookup rights. */ + if (acl_rights_has_nonowner_lookup_changes(&update->rights) || + update->modify_mode == ACL_MODIFY_MODE_REPLACE || + update->modify_mode == ACL_MODIFY_MODE_CLEAR) + (void)acl_backend_vfile_acllist_rebuild(backend); + return 0; +} diff --git a/src/plugins/acl/acl-backend-vfile.c b/src/plugins/acl/acl-backend-vfile.c new file mode 100644 index 0000000..3ee0af9 --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile.c @@ -0,0 +1,659 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "istream.h" +#include "nfs-workarounds.h" +#include "mail-storage-private.h" +#include "acl-global-file.h" +#include "acl-cache.h" +#include "acl-backend-vfile.h" + +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> + +#define ACL_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT +#define ACL_VFILE_DEFAULT_CACHE_SECS 30 + +static struct acl_backend *acl_backend_vfile_alloc(void) +{ + struct acl_backend_vfile *backend; + pool_t pool; + + pool = pool_alloconly_create("ACL backend", 512); + backend = p_new(pool, struct acl_backend_vfile, 1); + backend->backend.pool = pool; + return &backend->backend; +} + +static int +acl_backend_vfile_init(struct acl_backend *_backend, const char *data) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct stat st; + const char *const *tmp; + + tmp = t_strsplit(data, ":"); + backend->global_path = p_strdup_empty(_backend->pool, *tmp); + backend->cache_secs = ACL_VFILE_DEFAULT_CACHE_SECS; + + if (*tmp != NULL) + tmp++; + for (; *tmp != NULL; tmp++) { + if (str_begins(*tmp, "cache_secs=")) { + if (str_to_uint(*tmp + 11, &backend->cache_secs) < 0) { + i_error("acl vfile: Invalid cache_secs value: %s", + *tmp + 11); + return -1; + } + } else { + i_error("acl vfile: Unknown parameter: %s", *tmp); + return -1; + } + } + if (backend->global_path != NULL) { + if (stat(backend->global_path, &st) < 0) { + if (errno != ENOENT) { + i_error("acl vfile: stat(%s) failed: %m", + backend->global_path); + return -1; + } + } else if (!S_ISDIR(st.st_mode)) { + _backend->global_file = + acl_global_file_init(backend->global_path, backend->cache_secs, + _backend->debug); + } + } + if (_backend->debug) { + if (backend->global_path == NULL) + i_debug("acl vfile: Global ACLs disabled"); + else if (_backend->global_file != NULL) { + i_debug("acl vfile: Global ACL file: %s", + backend->global_path); + } else { + i_debug("acl vfile: Global ACL legacy directory: %s", + backend->global_path); + } + } + + _backend->cache = + acl_cache_init(_backend, + sizeof(struct acl_backend_vfile_validity)); + return 0; +} + +static void acl_backend_vfile_deinit(struct acl_backend *_backend) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + + if (backend->acllist_pool != NULL) { + array_free(&backend->acllist); + pool_unref(&backend->acllist_pool); + } + if (_backend->global_file != NULL) + acl_global_file_deinit(&_backend->global_file); + pool_unref(&backend->backend.pool); +} + +static const char * +acl_backend_vfile_get_local_dir(struct acl_backend *backend, + const char *name, const char *vname) +{ + struct mail_namespace *ns = mailbox_list_get_namespace(backend->list); + struct mailbox_list *list = ns->list; + struct mail_storage *storage; + enum mailbox_list_path_type type; + const char *dir, *inbox; + + if (*name == '\0') + name = NULL; + + if (backend->globals_only) + return NULL; + + /* ACL files are very important. try to keep them among the main + mail files. that's not possible though with a) if the mailbox is + a file or b) if the mailbox path doesn't point to filesystem. */ + if (mailbox_list_get_storage(&list, vname, &storage) < 0) + return NULL; + i_assert(list == ns->list); + + type = mail_storage_get_acl_list_path_type(storage); + if (name == NULL) { + if (!mailbox_list_get_root_path(list, type, &dir)) + return NULL; + } else { + if (mailbox_list_get_path(list, name, type, &dir) <= 0) + return NULL; + } + + /* verify that the directory isn't same as INBOX's directory. + this is mainly for Maildir. */ + if (name == NULL && + mailbox_list_get_path(list, "INBOX", + MAILBOX_LIST_PATH_TYPE_MAILBOX, &inbox) > 0 && + strcmp(inbox, dir) == 0) { + /* can't have default ACLs with this setup */ + return NULL; + } + return dir; +} + +static struct acl_object * +acl_backend_vfile_object_init(struct acl_backend *_backend, + const char *name) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct acl_object_vfile *aclobj; + const char *dir, *vname, *error; + + aclobj = i_new(struct acl_object_vfile, 1); + aclobj->aclobj.backend = _backend; + aclobj->aclobj.name = i_strdup(name); + + T_BEGIN { + if (*name == '\0' || + mailbox_list_is_valid_name(_backend->list, name, &error)) { + vname = *name == '\0' ? "" : + mailbox_list_get_vname(_backend->list, name); + + dir = acl_backend_vfile_get_local_dir(_backend, name, vname); + aclobj->local_path = dir == NULL ? NULL : + i_strconcat(dir, "/"ACL_FILENAME, NULL); + if (backend->global_path != NULL && + _backend->global_file == NULL) { + aclobj->global_path = + i_strconcat(backend->global_path, "/", vname, NULL); + } + } else { + /* Invalid mailbox name, just use the default + global ACL files */ + } + } T_END; + return &aclobj->aclobj; +} + +static const char * +get_parent_mailbox(struct acl_backend *backend, const char *name) +{ + const char *p; + + p = strrchr(name, mailbox_list_get_hierarchy_sep(backend->list)); + return p == NULL ? NULL : t_strdup_until(name, p); +} + +static int +acl_backend_vfile_exists(struct acl_backend_vfile *backend, const char *path, + struct acl_vfile_validity *validity) +{ + struct stat st; + + if (validity->last_check + (time_t)backend->cache_secs > ioloop_time) { + /* use the cached value */ + return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOTFOUND ? 1 : 0; + } + + validity->last_check = ioloop_time; + if (stat(path, &st) < 0) { + if (errno == ENOENT || errno == ENOTDIR) { + validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOTFOUND; + return 0; + } + if (errno == EACCES) { + validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOACCESS; + return 1; + } + i_error("stat(%s) failed: %m", path); + return -1; + } + validity->last_mtime = st.st_mtime; + validity->last_size = st.st_size; + return 1; +} + +static bool +acl_backend_vfile_has_acl(struct acl_backend *_backend, const char *name) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_backend; + struct acl_backend_vfile_validity *old_validity, new_validity; + const char *global_path, *vname; + int ret; + + old_validity = acl_cache_get_validity(_backend->cache, name); + if (old_validity != NULL) + new_validity = *old_validity; + else + i_zero(&new_validity); + + /* The caller wants to stop whenever a parent mailbox exists, even if + it has no ACL file. Also, if a mailbox doesn't exist then it can't + have a local ACL file. First check if there's a matching global ACL. + If not, check if the mailbox exists. */ + vname = *name == '\0' ? "" : + mailbox_list_get_vname(_backend->list, name); + struct mailbox *box = + mailbox_alloc(_backend->list, vname, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (backend->global_path == NULL) { + /* global ACLs disabled */ + ret = 0; + } else if (_backend->global_file != NULL) { + /* check global ACL file */ + ret = acl_global_file_refresh(_backend->global_file); + if (ret == 0 && acl_global_file_have_any(_backend->global_file, box->vname)) + ret = 1; + } else { + /* check global ACL directory */ + global_path = t_strconcat(backend->global_path, "/", name, NULL); + ret = acl_backend_vfile_exists(backend, global_path, + &new_validity.global_validity); + } + + if (ret != 0) { + /* error / global ACL found */ + } else if (mailbox_open(box) == 0) { + /* mailbox exists */ + ret = 1; + } else { + enum mail_error error; + const char *errstr = + mailbox_get_last_internal_error(box, &error); + if (error == MAIL_ERROR_NOTFOUND) + ret = 0; + else { + e_error(box->event, "acl: Failed to open mailbox: %s", + errstr); + ret = -1; + } + } + + acl_cache_set_validity(_backend->cache, name, &new_validity); + mailbox_free(&box); + return ret > 0; +} + +static struct acl_object * +acl_backend_vfile_object_init_parent(struct acl_backend *backend, + const char *child_name) +{ + const char *parent; + + /* stop at the first parent that + a) has global ACL file + b) has local ACL file + c) exists */ + while ((parent = get_parent_mailbox(backend, child_name)) != NULL) { + if (acl_backend_vfile_has_acl(backend, parent)) + break; + child_name = parent; + } + if (parent == NULL) { + /* use the root */ + parent = acl_backend_get_default_object(backend)->name; + } + return acl_backend_vfile_object_init(backend, parent); +} + +static void acl_backend_vfile_object_deinit(struct acl_object *_aclobj) +{ + struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj; + + i_free(aclobj->local_path); + i_free(aclobj->global_path); + + if (array_is_created(&aclobj->aclobj.rights)) + array_free(&aclobj->aclobj.rights); + pool_unref(&aclobj->aclobj.rights_pool); + i_free(aclobj->aclobj.name); + i_free(aclobj); +} + +static int +acl_backend_vfile_read(struct acl_object *aclobj, bool global, const char *path, + struct acl_vfile_validity *validity, bool try_retry, + bool *is_dir_r) +{ + struct istream *input; + struct stat st; + struct acl_rights rights; + const char *line, *error; + unsigned int linenum; + int fd, ret = 0; + + *is_dir_r = FALSE; + + fd = nfs_safe_open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT || errno == ENOTDIR) { + if (aclobj->backend->debug) + i_debug("acl vfile: file %s not found", path); + validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOTFOUND; + } else if (errno == EACCES) { + if (aclobj->backend->debug) + i_debug("acl vfile: no access to file %s", + path); + + acl_object_remove_all_access(aclobj); + validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOACCESS; + } else { + i_error("open(%s) failed: %m", path); + return -1; + } + + validity->last_size = 0; + validity->last_read_time = ioloop_time; + return 1; + } + + if (fstat(fd, &st) < 0) { + if (errno == ESTALE && try_retry) { + i_close_fd(&fd); + return 0; + } + + i_error("fstat(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + if (S_ISDIR(st.st_mode)) { + /* we opened a directory. */ + *is_dir_r = TRUE; + i_close_fd(&fd); + return 0; + } + + if (aclobj->backend->debug) + i_debug("acl vfile: reading file %s", path); + + input = i_stream_create_fd(fd, SIZE_MAX); + i_stream_set_return_partial_line(input, TRUE); + linenum = 0; + while ((line = i_stream_read_next_line(input)) != NULL) { + linenum++; + if (line[0] == '\0' || line[0] == '#') + continue; + T_BEGIN { + ret = acl_rights_parse_line(line, aclobj->rights_pool, + &rights, &error); + rights.global = global; + if (ret < 0) { + i_error("ACL file %s line %u: %s", + path, linenum, error); + } else { + array_push_back(&aclobj->rights, &rights); + } + } T_END; + if (ret < 0) + break; + } + + if (ret < 0) { + /* parsing failure */ + } else if (input->stream_errno != 0) { + if (input->stream_errno == ESTALE && try_retry) + ret = 0; + else { + ret = -1; + i_error("read(%s) failed: %s", path, + i_stream_get_error(input)); + } + } else { + if (fstat(fd, &st) < 0) { + if (errno == ESTALE && try_retry) + ret = 0; + else { + ret = -1; + i_error("fstat(%s) failed: %m", path); + } + } else { + ret = 1; + validity->last_read_time = ioloop_time; + validity->last_mtime = st.st_mtime; + validity->last_size = st.st_size; + } + } + + i_stream_unref(&input); + if (close(fd) < 0) { + if (errno == ESTALE && try_retry) + return 0; + + i_error("close(%s) failed: %m", path); + return -1; + } + return ret; +} + +static int +acl_backend_vfile_read_with_retry(struct acl_object *aclobj, + bool global, const char *path, + struct acl_vfile_validity *validity) +{ + unsigned int i; + int ret; + bool is_dir; + + if (path == NULL) + return 0; + + for (i = 0;; i++) { + ret = acl_backend_vfile_read(aclobj, global, path, validity, + i < ACL_ESTALE_RETRY_COUNT, + &is_dir); + if (ret != 0) + break; + + if (is_dir) { + /* opened a directory. use dir/.DEFAULT instead */ + path = t_strconcat(path, "/.DEFAULT", NULL); + } else { + /* ESTALE - try again */ + } + } + + return ret <= 0 ? -1 : 0; +} + +static bool +acl_vfile_validity_has_changed(struct acl_backend_vfile *backend, + const struct acl_vfile_validity *validity, + const struct stat *st) +{ + if (st->st_mtime == validity->last_mtime && + st->st_size == validity->last_size) { + /* same timestamp, but if it was modified within the + same second we want to refresh it again later (but + do it only after a couple of seconds so we don't + keep re-reading it all the time within those + seconds) */ + time_t cache_secs = backend->cache_secs; + + if (validity->last_read_time != 0 && + (st->st_mtime < validity->last_read_time - cache_secs || + ioloop_time - validity->last_read_time <= cache_secs)) + return FALSE; + } + return TRUE; +} + +static int +acl_backend_vfile_refresh(struct acl_object *aclobj, const char *path, + struct acl_vfile_validity *validity) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)aclobj->backend; + struct stat st; + int ret; + + if (validity == NULL) + return 1; + if (path == NULL || + validity->last_check + (time_t)backend->cache_secs > ioloop_time) + return 0; + + validity->last_check = ioloop_time; + ret = stat(path, &st); + if (ret == 0 && S_ISDIR(st.st_mode)) { + /* it's a directory. use dir/.DEFAULT instead */ + path = t_strconcat(path, "/.DEFAULT", NULL); + ret = stat(path, &st); + } + + if (ret < 0) { + if (errno == ENOENT || errno == ENOTDIR) { + /* if the file used to exist, we have to re-read it */ + return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOTFOUND ? 1 : 0; + } + if (errno == EACCES) + return validity->last_mtime != ACL_VFILE_VALIDITY_MTIME_NOACCESS ? 1 : 0; + i_error("stat(%s) failed: %m", path); + return -1; + } + return acl_vfile_validity_has_changed(backend, validity, &st) ? 1 : 0; +} + +int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj, + time_t *mtime_r) +{ + struct acl_backend_vfile_validity *validity; + + validity = acl_cache_get_validity(aclobj->backend->cache, aclobj->name); + if (validity == NULL) + return -1; + + if (validity->local_validity.last_mtime != 0) + *mtime_r = validity->local_validity.last_mtime; + else if (validity->global_validity.last_mtime != 0) + *mtime_r = validity->global_validity.last_mtime; + else + *mtime_r = 0; + return 0; +} + +static int +acl_backend_global_file_refresh(struct acl_object *_aclobj, + struct acl_vfile_validity *validity) +{ + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_aclobj->backend; + struct stat st; + + if (acl_global_file_refresh(_aclobj->backend->global_file) < 0) + return -1; + + acl_global_file_last_stat(_aclobj->backend->global_file, &st); + if (validity == NULL) + return 1; + return acl_vfile_validity_has_changed(backend, validity, &st) ? 1 : 0; +} + +static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj) +{ + struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj; + struct acl_backend_vfile *backend = + (struct acl_backend_vfile *)_aclobj->backend; + struct acl_backend_vfile_validity *old_validity; + struct acl_backend_vfile_validity validity; + time_t mtime; + int ret; + + old_validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + ret = _aclobj->backend->global_file != NULL ? + acl_backend_global_file_refresh(_aclobj, old_validity == NULL ? NULL : + &old_validity->global_validity) : + acl_backend_vfile_refresh(_aclobj, aclobj->global_path, + old_validity == NULL ? NULL : + &old_validity->global_validity); + if (ret == 0) { + ret = acl_backend_vfile_refresh(_aclobj, aclobj->local_path, + old_validity == NULL ? NULL : + &old_validity->local_validity); + } + if (ret <= 0) + return ret; + + /* either global or local ACLs changed, need to re-read both */ + if (!array_is_created(&_aclobj->rights)) { + _aclobj->rights_pool = + pool_alloconly_create("acl rights", 256); + i_array_init(&_aclobj->rights, 16); + } else { + array_clear(&_aclobj->rights); + p_clear(_aclobj->rights_pool); + } + + i_zero(&validity); + if (_aclobj->backend->global_file != NULL) { + struct stat st; + + acl_object_add_global_acls(_aclobj); + acl_global_file_last_stat(_aclobj->backend->global_file, &st); + validity.global_validity.last_read_time = ioloop_time; + validity.global_validity.last_mtime = st.st_mtime; + validity.global_validity.last_size = st.st_size; + } else { + if (acl_backend_vfile_read_with_retry(_aclobj, TRUE, aclobj->global_path, + &validity.global_validity) < 0) + return -1; + } + if (acl_backend_vfile_read_with_retry(_aclobj, FALSE, aclobj->local_path, + &validity.local_validity) < 0) + return -1; + + acl_rights_sort(_aclobj); + /* update cache only after we've successfully read everything */ + acl_object_rebuild_cache(_aclobj); + acl_cache_set_validity(_aclobj->backend->cache, + _aclobj->name, &validity); + + if (acl_backend_vfile_object_get_mtime(_aclobj, &mtime) == 0) + acl_backend_vfile_acllist_verify(backend, _aclobj->name, mtime); + return 0; +} + +static int acl_backend_vfile_object_last_changed(struct acl_object *_aclobj, + time_t *last_changed_r) +{ + struct acl_backend_vfile_validity *old_validity; + + *last_changed_r = 0; + + old_validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + if (old_validity == NULL) { + if (acl_backend_vfile_object_refresh_cache(_aclobj) < 0) + return -1; + old_validity = acl_cache_get_validity(_aclobj->backend->cache, + _aclobj->name); + if (old_validity == NULL) + return 0; + } + *last_changed_r = old_validity->local_validity.last_mtime; + return 0; +} + +struct acl_backend_vfuncs acl_backend_vfile = { + acl_backend_vfile_alloc, + acl_backend_vfile_init, + acl_backend_vfile_deinit, + acl_backend_vfile_nonowner_iter_init, + acl_backend_vfile_nonowner_iter_next, + acl_backend_vfile_nonowner_iter_deinit, + acl_backend_vfile_nonowner_lookups_rebuild, + acl_backend_vfile_object_init, + acl_backend_vfile_object_init_parent, + acl_backend_vfile_object_deinit, + acl_backend_vfile_object_refresh_cache, + acl_backend_vfile_object_update, + acl_backend_vfile_object_last_changed, + acl_default_object_list_init, + acl_default_object_list_next, + acl_default_object_list_deinit +}; diff --git a/src/plugins/acl/acl-backend-vfile.h b/src/plugins/acl/acl-backend-vfile.h new file mode 100644 index 0000000..c5aaa25 --- /dev/null +++ b/src/plugins/acl/acl-backend-vfile.h @@ -0,0 +1,88 @@ +#ifndef ACL_BACKEND_VFILE_H +#define ACL_BACKEND_VFILE_H + +#include "acl-api-private.h" +#include "mail-storage-private.h" + +#define ACL_FILENAME "dovecot-acl" +#define ACLLIST_FILENAME "dovecot-acl-list" + +#define ACL_VFILE_VALIDITY_MTIME_NOTFOUND 0 +#define ACL_VFILE_VALIDITY_MTIME_NOACCESS -1 + +struct acl_vfile_validity { + time_t last_check; + + time_t last_read_time; + time_t last_mtime; + off_t last_size; +}; + +struct acl_backend_vfile_validity { + struct acl_vfile_validity global_validity, local_validity; +}; + +struct acl_object_vfile { + struct acl_object aclobj; + + /* if backend->global_file is NULL, assume legacy separate global + ACL file per mailbox */ + char *global_path, *local_path; +}; + +struct acl_backend_vfile_acllist { + time_t mtime; + const char *name; +}; + +struct acl_backend_vfile { + struct acl_backend backend; + const char *global_path; + + pool_t acllist_pool; + ARRAY(struct acl_backend_vfile_acllist) acllist; + + time_t acllist_last_check; + time_t acllist_mtime; + unsigned int acllist_change_counter; + + unsigned int cache_secs; + bool rebuilding_acllist:1; + bool iterating_acllist:1; +}; + +void acl_vfile_write_rights_list(string_t *dest, const char *const *rights); +int acl_backend_vfile_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update); + +void acl_backend_vfile_acllist_refresh(struct acl_backend_vfile *backend); +int acl_backend_vfile_acllist_rebuild(struct acl_backend_vfile *backend); +void acl_backend_vfile_acllist_verify(struct acl_backend_vfile *backend, + const char *name, time_t mtime); + +struct acl_mailbox_list_context * +acl_backend_vfile_nonowner_iter_init(struct acl_backend *backend); +bool acl_backend_vfile_nonowner_iter_next(struct acl_mailbox_list_context *ctx, + const char **name_r); +int +acl_backend_vfile_nonowner_iter_deinit(struct acl_mailbox_list_context *ctx); +int acl_backend_vfile_nonowner_lookups_rebuild(struct acl_backend *backend); + +int acl_backend_vfile_object_get_mtime(struct acl_object *aclobj, + time_t *mtime_r); + +static inline enum mailbox_list_path_type +mail_storage_get_acl_list_path_type(struct mail_storage *storage) +{ + if (mail_storage_is_mailbox_file(storage)) { + /* mailbox is a directory (e.g. mbox) */ + return MAILBOX_LIST_PATH_TYPE_CONTROL; + } + if ((storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_ROOT) != 0) { + /* there is no local mailbox directory */ + return MAILBOX_LIST_PATH_TYPE_CONTROL; + } + return MAILBOX_LIST_PATH_TYPE_MAILBOX; +} + +#endif diff --git a/src/plugins/acl/acl-backend.c b/src/plugins/acl/acl-backend.c new file mode 100644 index 0000000..0514dc7 --- /dev/null +++ b/src/plugins/acl/acl-backend.c @@ -0,0 +1,194 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "hash.h" +#include "sort.h" +#include "mail-storage-settings.h" +#include "mailbox-list.h" +#include "mail-namespace.h" +#include "mail-user.h" +#include "acl-cache.h" +#include "acl-api-private.h" + + +extern struct acl_backend_vfuncs acl_backend_vfile; + +const char *const all_mailbox_rights[] = { + MAIL_ACL_LOOKUP, + MAIL_ACL_READ, + MAIL_ACL_WRITE, + MAIL_ACL_WRITE_SEEN, + MAIL_ACL_WRITE_DELETED, + MAIL_ACL_INSERT, + MAIL_ACL_POST, + MAIL_ACL_EXPUNGE, + MAIL_ACL_CREATE, + MAIL_ACL_DELETE, + MAIL_ACL_ADMIN, + NULL +}; + +static const char *const *owner_mailbox_rights = all_mailbox_rights; +static const char *const non_owner_mailbox_rights[] = { NULL }; + +struct acl_backend * +acl_backend_init(const char *data, struct mailbox_list *list, + const char *acl_username, const char *const *groups, + bool owner) +{ + struct mail_user *user = mailbox_list_get_user(list); + struct acl_backend *backend; + unsigned int i, group_count; + + e_debug(user->event, "acl: initializing backend with data: %s", data); + e_debug(user->event, "acl: acl username = %s", acl_username); + e_debug(user->event, "acl: owner = %d", owner ? 1 : 0); + + group_count = str_array_length(groups); + + if (str_begins(data, "vfile:")) + data += 6; + else if (strcmp(data, "vfile") == 0) + data = ""; + else + i_fatal("Unknown ACL backend: %s", t_strcut(data, ':')); + + backend = acl_backend_vfile.alloc(); + backend->debug = user->mail_debug; + backend->v = acl_backend_vfile; + backend->list = list; + backend->username = p_strdup(backend->pool, acl_username); + backend->owner = owner; + backend->globals_only = + mail_user_plugin_getenv_bool(user, "acl_globals_only"); + + if (group_count > 0) { + backend->group_count = group_count; + backend->groups = + p_new(backend->pool, const char *, group_count); + for (i = 0; i < group_count; i++) { + backend->groups[i] = p_strdup(backend->pool, groups[i]); + e_debug(user->event, "acl: group added: %s", groups[i]); + } + i_qsort(backend->groups, group_count, sizeof(const char *), + i_strcmp_p); + } + + T_BEGIN { + if (acl_backend_vfile.init(backend, data) < 0) + i_fatal("acl: backend vfile init failed with data: %s", + data); + } T_END; + + backend->default_rights = owner ? owner_mailbox_rights : + non_owner_mailbox_rights; + backend->default_aclmask = + acl_cache_mask_init(backend->cache, backend->pool, + backend->default_rights); + return backend; +} + +void acl_backend_deinit(struct acl_backend **_backend) +{ + struct acl_backend *backend = *_backend; + + *_backend = NULL; + + if (backend->default_aclobj != NULL) + acl_object_deinit(&backend->default_aclobj); + acl_cache_deinit(&backend->cache); + backend->v.deinit(backend); +} + +const char *acl_backend_get_acl_username(struct acl_backend *backend) +{ + return backend->username; +} + +bool acl_backend_user_is_authenticated(struct acl_backend *backend) +{ + return backend->username != NULL; +} + +bool acl_backend_user_is_owner(struct acl_backend *backend) +{ + return backend->owner; +} + +bool acl_backend_user_name_equals(struct acl_backend *backend, + const char *username) +{ + if (backend->username == NULL) { + /* anonymous user never matches */ + return FALSE; + } + + return strcmp(backend->username, username) == 0; +} + +bool acl_backend_user_is_in_group(struct acl_backend *backend, + const char *group_name) +{ + return i_bsearch(group_name, backend->groups, backend->group_count, + sizeof(const char *), bsearch_strcmp) != NULL; +} + +bool acl_backend_rights_match_me(struct acl_backend *backend, + const struct acl_rights *rights) +{ + switch (rights->id_type) { + case ACL_ID_ANYONE: + return TRUE; + case ACL_ID_AUTHENTICATED: + return acl_backend_user_is_authenticated(backend); + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + return acl_backend_user_is_in_group(backend, rights->identifier); + case ACL_ID_USER: + return acl_backend_user_name_equals(backend, rights->identifier); + case ACL_ID_OWNER: + return acl_backend_user_is_owner(backend); + case ACL_ID_TYPE_COUNT: + break; + } + i_unreached(); +} + +unsigned int acl_backend_lookup_right(struct acl_backend *backend, + const char *right) +{ + return acl_cache_right_lookup(backend->cache, right); +} + +struct acl_object *acl_backend_get_default_object(struct acl_backend *backend) +{ + struct mail_user *user = mailbox_list_get_user(backend->list); + struct mail_namespace *ns = mailbox_list_get_namespace(backend->list); + const char *default_name = ""; + + if (backend->default_aclobj != NULL) + return backend->default_aclobj; + + if (mail_user_plugin_getenv_bool(user, "acl_defaults_from_inbox")) { + if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE || + ns->type == MAIL_NAMESPACE_TYPE_SHARED) + default_name = "INBOX"; + } + backend->default_aclobj = + acl_object_init_from_name(backend, default_name); + return backend->default_aclobj; +} + +int acl_backend_get_default_rights(struct acl_backend *backend, + const struct acl_mask **mask_r) +{ + struct acl_object *aclobj = acl_backend_get_default_object(backend); + + if (backend->v.object_refresh_cache(aclobj) < 0) + return -1; + + *mask_r = acl_cache_get_my_rights(backend->cache, aclobj->name); + if (*mask_r == NULL) + *mask_r = backend->default_aclmask; + return 0; +} diff --git a/src/plugins/acl/acl-cache.c b/src/plugins/acl/acl-cache.c new file mode 100644 index 0000000..8f07d56 --- /dev/null +++ b/src/plugins/acl/acl-cache.c @@ -0,0 +1,395 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "acl-cache.h" +#include "acl-api-private.h" + +/* Give more than enough so that the arrays should never have to be grown. + IMAP ACLs define only 10 standard rights and 10 user-defined rights. */ +#define DEFAULT_ACL_RIGHTS_COUNT 64 + +#define ACL_GLOBAL_COUNT 2 + +struct acl_object_cache { + char *name; + + struct acl_mask *my_rights, *my_neg_rights; + struct acl_mask *my_current_rights; +}; + +struct acl_cache { + struct acl_backend *backend; + /* name => object */ + HASH_TABLE(char *, struct acl_object_cache *) objects; + + size_t validity_rec_size; + + /* Right names mapping is used for faster rights checking. Note that + acl_mask bitmask relies on the order to never change, so only new + rights can be added to the mapping. */ + pool_t right_names_pool; + /* idx => right name. */ + ARRAY(const char *) right_idx_name_map; + /* name => idx+1 */ + HASH_TABLE(char *, void *) right_name_idx_map; +}; + +static struct acl_mask negative_cache_entry; + +struct acl_cache *acl_cache_init(struct acl_backend *backend, + size_t validity_rec_size) +{ + struct acl_cache *cache; + + cache = i_new(struct acl_cache, 1); + cache->backend = backend; + cache->validity_rec_size = validity_rec_size; + cache->right_names_pool = + pool_alloconly_create("ACL right names", 1024); + hash_table_create(&cache->objects, default_pool, 0, str_hash, strcmp); + hash_table_create(&cache->right_name_idx_map, + cache->right_names_pool, 0, str_hash, strcmp); + i_array_init(&cache->right_idx_name_map, DEFAULT_ACL_RIGHTS_COUNT); + return cache; +} + +void acl_cache_deinit(struct acl_cache **_cache) +{ + struct acl_cache *cache = *_cache; + + *_cache = NULL; + + acl_cache_flush_all(cache); + array_free(&cache->right_idx_name_map); + hash_table_destroy(&cache->right_name_idx_map); + hash_table_destroy(&cache->objects); + pool_unref(&cache->right_names_pool); + i_free(cache); +} + +static void acl_cache_free_object_cache(struct acl_object_cache *obj_cache) +{ + if (obj_cache->my_current_rights != NULL && + obj_cache->my_current_rights != &negative_cache_entry) + acl_cache_mask_deinit(&obj_cache->my_current_rights); + if (obj_cache->my_rights != NULL) + acl_cache_mask_deinit(&obj_cache->my_rights); + if (obj_cache->my_neg_rights != NULL) + acl_cache_mask_deinit(&obj_cache->my_neg_rights); + i_free(obj_cache->name); + i_free(obj_cache); +} + +static struct acl_mask * +acl_cache_mask_init_real(struct acl_cache *cache, pool_t pool, + const char *const *rights) +{ + struct acl_mask *mask; + unsigned int rights_count, i, idx; + unsigned char *p; + buffer_t *bitmask; + + rights_count = str_array_length(rights); + bitmask = t_buffer_create(DEFAULT_ACL_RIGHTS_COUNT / CHAR_BIT); + for (i = 0; i < rights_count; i++) { + idx = acl_cache_right_lookup(cache, rights[i]); + p = buffer_get_space_unsafe(bitmask, idx / CHAR_BIT, 1); + *p |= 1 << (idx % CHAR_BIT); + } + + /* @UNSAFE */ + mask = p_malloc(pool, SIZEOF_ACL_MASK(bitmask->used)); + memcpy(mask->mask, bitmask->data, bitmask->used); + mask->pool = pool; + mask->size = bitmask->used; + return mask; +} + +struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool, + const char *const *rights) +{ + struct acl_mask *mask; + + T_BEGIN { + mask = acl_cache_mask_init_real(cache, pool, rights); + } T_END; + return mask; +} + +static struct acl_mask * +acl_cache_mask_dup(pool_t pool, const struct acl_mask *src) +{ + struct acl_mask *mask; + + mask = p_malloc(pool, SIZEOF_ACL_MASK(src->size)); + memcpy(mask->mask, src->mask, src->size); + mask->pool = pool; + mask->size = src->size; + return mask; +} + +void acl_cache_mask_deinit(struct acl_mask **_mask) +{ + struct acl_mask *mask = *_mask; + + *_mask = NULL; + p_free(mask->pool, mask); +} + +unsigned int acl_cache_right_lookup(struct acl_cache *cache, const char *right) +{ + unsigned int idx; + void *idx_p; + char *name; + const char *const_name; + + /* use +1 for right_name_idx_map values because we can't add NULL + values. */ + idx_p = hash_table_lookup(cache->right_name_idx_map, right); + if (idx_p == NULL) { + /* new right name, add it */ + const_name = name = p_strdup(cache->right_names_pool, right); + + idx = array_count(&cache->right_idx_name_map); + array_push_back(&cache->right_idx_name_map, &const_name); + hash_table_insert(cache->right_name_idx_map, name, + POINTER_CAST(idx + 1)); + } else { + idx = POINTER_CAST_TO(idx_p, unsigned int)-1; + } + return idx; +} + +void acl_cache_flush(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache != NULL) { + hash_table_remove(cache->objects, objname); + acl_cache_free_object_cache(obj_cache); + } +} + +void acl_cache_flush_all(struct acl_cache *cache) +{ + struct hash_iterate_context *iter; + char *key; + struct acl_object_cache *obj_cache; + + iter = hash_table_iterate_init(cache->objects); + while (hash_table_iterate(iter, cache->objects, &key, &obj_cache)) + acl_cache_free_object_cache(obj_cache); + hash_table_iterate_deinit(&iter); + + hash_table_clear(cache->objects, FALSE); +} + +static void +acl_cache_update_rights_mask(struct acl_cache *cache, + struct acl_object_cache *obj_cache, + enum acl_modify_mode modify_mode, + const char *const *rights, + struct acl_mask **mask_p) +{ + struct acl_mask *change_mask, *old_mask, *new_mask; + unsigned int i, size; + bool changed = TRUE; + + change_mask = rights == NULL ? NULL : + acl_cache_mask_init(cache, default_pool, rights); + old_mask = *mask_p; + new_mask = old_mask; + + switch (modify_mode) { + case ACL_MODIFY_MODE_ADD: + if (old_mask == NULL) { + new_mask = change_mask; + break; + } + + if (change_mask == NULL) { + /* no changes */ + changed = FALSE; + break; + } + + /* merge the masks */ + if (old_mask->size >= change_mask->size) { + /* keep using the old mask */ + for (i = 0; i < change_mask->size; i++) + old_mask->mask[i] |= change_mask->mask[i]; + } else { + /* use the new mask, put old changes into it */ + for (i = 0; i < old_mask->size; i++) + change_mask->mask[i] |= old_mask->mask[i]; + new_mask = change_mask; + } + break; + case ACL_MODIFY_MODE_REMOVE: + if (old_mask == NULL || change_mask == NULL) { + changed = FALSE; + break; + } + + /* remove changed bits from old mask */ + size = I_MIN(old_mask->size, change_mask->size); + for (i = 0; i < size; i++) + old_mask->mask[i] &= ENUM_NEGATE(change_mask->mask[i]); + break; + case ACL_MODIFY_MODE_REPLACE: + if (old_mask == NULL && change_mask == NULL) + changed = FALSE; + new_mask = change_mask; + break; + case ACL_MODIFY_MODE_CLEAR: + i_unreached(); + } + + if (new_mask != old_mask) { + *mask_p = new_mask; + if (old_mask != NULL) + acl_cache_mask_deinit(&old_mask); + } + if (new_mask != change_mask && change_mask != NULL) + acl_cache_mask_deinit(&change_mask); + + if (changed && obj_cache->my_current_rights != NULL) { + /* current rights need to be recalculated */ + if (obj_cache->my_current_rights == &negative_cache_entry) + obj_cache->my_current_rights = NULL; + else + acl_cache_mask_deinit(&obj_cache->my_current_rights); + } +} + +static struct acl_object_cache * +acl_cache_object_get(struct acl_cache *cache, const char *objname, + bool *created_r) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache == NULL) { + obj_cache = i_malloc(MALLOC_ADD(sizeof(struct acl_object_cache), + cache->validity_rec_size)); + obj_cache->name = i_strdup(objname); + hash_table_insert(cache->objects, obj_cache->name, obj_cache); + *created_r = TRUE; + } else { + *created_r = FALSE; + } + return obj_cache; +} + +void acl_cache_update(struct acl_cache *cache, const char *objname, + const struct acl_rights_update *update) +{ + struct acl_object_cache *obj_cache; + bool created; + + obj_cache = acl_cache_object_get(cache, objname, &created); + i_assert(obj_cache->my_current_rights != &negative_cache_entry); + + if (created && update->modify_mode != ACL_MODIFY_MODE_REPLACE) { + /* since the rights aren't being replaced, start with our + default rights */ + obj_cache->my_rights = + acl_cache_mask_dup(default_pool, + cache->backend->default_aclmask); + } + + acl_cache_update_rights_mask(cache, obj_cache, update->modify_mode, + update->rights.rights, + &obj_cache->my_rights); + acl_cache_update_rights_mask(cache, obj_cache, update->neg_modify_mode, + update->rights.neg_rights, + &obj_cache->my_neg_rights); +} + +void acl_cache_set_validity(struct acl_cache *cache, const char *objname, + const void *validity) +{ + struct acl_object_cache *obj_cache; + bool created; + + obj_cache = acl_cache_object_get(cache, objname, &created); + + /* @UNSAFE: validity is stored after the cache record */ + memcpy(obj_cache + 1, validity, cache->validity_rec_size); + + if (created) { + /* negative cache entry */ + obj_cache->my_current_rights = &negative_cache_entry; + } +} + +void *acl_cache_get_validity(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + return obj_cache == NULL ? NULL : (obj_cache + 1); +} + +const char *const *acl_cache_get_names(struct acl_cache *cache, + unsigned int *count_r) +{ + *count_r = array_count(&cache->right_idx_name_map); + return array_front(&cache->right_idx_name_map); +} + +static void +acl_cache_my_current_rights_recalculate(struct acl_object_cache *obj_cache) +{ + struct acl_mask *mask; + unsigned int i, size; + + /* @UNSAFE */ + size = obj_cache->my_rights == NULL ? 0 : + obj_cache->my_rights->size; + mask = i_malloc(SIZEOF_ACL_MASK(size)); + mask->pool = default_pool; + mask->size = size; + + /* apply the positive rights */ + if (obj_cache->my_rights != NULL) + memcpy(mask->mask, obj_cache->my_rights->mask, mask->size); + if (obj_cache->my_neg_rights != NULL) { + /* apply the negative rights. they override positive rights. */ + size = I_MIN(mask->size, obj_cache->my_neg_rights->size); + for (i = 0; i < size; i++) + mask->mask[i] &= ENUM_NEGATE(obj_cache->my_neg_rights->mask[i]); + } + + obj_cache->my_current_rights = mask; +} + +const struct acl_mask * +acl_cache_get_my_rights(struct acl_cache *cache, const char *objname) +{ + struct acl_object_cache *obj_cache; + + obj_cache = hash_table_lookup(cache->objects, objname); + if (obj_cache == NULL || + obj_cache->my_current_rights == &negative_cache_entry) + return NULL; + + if (obj_cache->my_current_rights == NULL) { + T_BEGIN { + acl_cache_my_current_rights_recalculate(obj_cache); + } T_END; + } + return obj_cache->my_current_rights; +} + +bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx) +{ + unsigned int mask_idx; + + mask_idx = right_idx / CHAR_BIT; + return mask_idx < mask->size && + (mask->mask[mask_idx] & (1 << (right_idx % CHAR_BIT))) != 0; +} diff --git a/src/plugins/acl/acl-cache.h b/src/plugins/acl/acl-cache.h new file mode 100644 index 0000000..b7c2065 --- /dev/null +++ b/src/plugins/acl/acl-cache.h @@ -0,0 +1,57 @@ +#ifndef ACL_CACHE_H +#define ACL_CACHE_H + +struct acl_backend; +struct acl_rights_update; + +struct acl_mask { + pool_t pool; + + /* mask[] size as bytes */ + unsigned int size; + + /* variable length bitmask */ + unsigned char mask[1]; +}; +#define SIZEOF_ACL_MASK(bitmask_size) \ + (MALLOC_ADD((bitmask_size), sizeof(pool_t) + sizeof(unsigned int))) + +struct acl_cache *acl_cache_init(struct acl_backend *backend, + size_t validity_rec_size); +void acl_cache_deinit(struct acl_cache **cache); + +struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool, + const char *const *rights); +void acl_cache_mask_deinit(struct acl_mask **mask); +unsigned int acl_cache_right_lookup(struct acl_cache *cache, + const char *right); + +/* Flush cache for given object name */ +void acl_cache_flush(struct acl_cache *cache, const char *objname); +/* Flush cache for all objects */ +void acl_cache_flush_all(struct acl_cache *cache); + +/* Update object ACLs. The new rights are always applied on top of the + existing rights. The ordering by acl_id_type must be done by the caller. */ +void acl_cache_update(struct acl_cache *cache, const char *objname, + const struct acl_rights_update *update); +/* Return ACL object validity, or NULL if object doesn't exit. */ +void *acl_cache_get_validity(struct acl_cache *cache, const char *objname); +/* Update ACL object validity, creating the object if needed. */ +void acl_cache_set_validity(struct acl_cache *cache, const char *objname, + const void *validity); + +/* Returns all the right names currently created. The returned pointer may + change after calling acl_cache_update(). */ +const char *const *acl_cache_get_names(struct acl_cache *cache, + unsigned int *count_r); + +/* Returns user's current rights, or NULL if no rights have been specified + for this object. */ +const struct acl_mask * +acl_cache_get_my_rights(struct acl_cache *cache, const char *objname); + +/* Returns TRUE if given right index is set in mask. */ +bool acl_cache_mask_isset(const struct acl_mask *mask, unsigned int right_idx); + +#endif diff --git a/src/plugins/acl/acl-global-file.c b/src/plugins/acl/acl-global-file.c new file mode 100644 index 0000000..02f6643 --- /dev/null +++ b/src/plugins/acl/acl-global-file.c @@ -0,0 +1,246 @@ +/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "istream.h" +#include "strescape.h" +#include "wildcard-match.h" +#include "acl-api-private.h" +#include "acl-global-file.h" + +#include <sys/stat.h> + +struct acl_global_rights { + const char *vpattern; + ARRAY_TYPE(acl_rights) rights; +}; + +struct acl_global_parse_rights { + const char *vpattern; + struct acl_rights rights; +}; + +struct acl_global_file { + char *path; + struct stat prev_st; + time_t last_refresh_time; + + pool_t rights_pool; + ARRAY(struct acl_global_rights) rights; + + unsigned int refresh_interval_secs; + bool debug; +}; + +struct acl_global_file * +acl_global_file_init(const char *path, unsigned int refresh_interval_secs, + bool debug) +{ + struct acl_global_file *file; + + file = i_new(struct acl_global_file, 1); + file->path = i_strdup(path); + file->refresh_interval_secs = refresh_interval_secs; + file->debug = debug; + i_array_init(&file->rights, 32); + file->rights_pool = pool_alloconly_create("acl global file rights", 1024); + return file; +} + +void acl_global_file_deinit(struct acl_global_file **_file) +{ + struct acl_global_file *file = *_file; + + *_file = NULL; + + array_free(&file->rights); + pool_unref(&file->rights_pool); + i_free(file->path); + i_free(file); +} + +static int acl_global_parse_rights_cmp(const struct acl_global_parse_rights *r1, + const struct acl_global_parse_rights *r2) +{ + return strcmp(r1->vpattern, r2->vpattern); +} + +struct acl_global_file_parse_ctx { + struct acl_global_file *file; + ARRAY(struct acl_global_parse_rights) parse_rights; +}; + +static int +acl_global_file_parse_line(struct acl_global_file_parse_ctx *ctx, + const char *line, const char **error_r) +{ + struct acl_global_parse_rights *pright; + const char *p, *vpattern; + + if (*line == '"') { + line++; + if (str_unescape_next(&line, &vpattern) < 0) { + *error_r = "Missing '\"'"; + return -1; + } + if (line[0] != ' ') { + *error_r = "Expecting space after '\"'"; + return -1; + } + line++; + } else { + p = strchr(line, ' '); + if (p == NULL) { + *error_r = "Missing ACL rights"; + return -1; + } + if (p == line) { + *error_r = "Empty ACL pattern"; + return -1; + } + vpattern = t_strdup_until(line, p); + line = p + 1; + } + + pright = array_append_space(&ctx->parse_rights); + pright->vpattern = p_strdup(ctx->file->rights_pool, vpattern); + if (acl_rights_parse_line(line, ctx->file->rights_pool, + &pright->rights, error_r) < 0) + return -1; + pright->rights.global = TRUE; + return 0; +} + +static int acl_global_file_read(struct acl_global_file *file) +{ + struct acl_global_file_parse_ctx ctx; + struct acl_global_parse_rights *pright; + struct acl_global_rights *right; + struct istream *input; + const char *line, *error, *prev_vpattern; + unsigned int linenum = 0; + int ret = 0; + + array_clear(&file->rights); + p_clear(file->rights_pool); + + i_zero(&ctx); + ctx.file = file; + i_array_init(&ctx.parse_rights, 32); + + input = i_stream_create_file(file->path, SIZE_MAX); + i_stream_set_return_partial_line(input, TRUE); + while ((line = i_stream_read_next_line(input)) != NULL) { + linenum++; + if (line[0] == '\0' || line[0] == '#') + continue; + T_BEGIN { + ret = acl_global_file_parse_line(&ctx, line, &error); + if (ret < 0) { + i_error("Global ACL file %s line %u: %s", + file->path, linenum, error); + } + } T_END; + if (ret < 0) + break; + } + if (ret == 0 && input->stream_errno != 0) { + i_error("Couldn't read global ACL file %s: %s", + file->path, i_stream_get_error(input)); + ret = -1; + } + if (ret == 0) { + const struct stat *st; + + if (i_stream_stat(input, TRUE, &st) < 0) { + i_error("Couldn't stat global ACL file %s: %s", + file->path, i_stream_get_error(input)); + ret = -1; + } else { + file->prev_st = *st; + } + } + i_stream_destroy(&input); + + /* sort all parsed rights */ + array_sort(&ctx.parse_rights, acl_global_parse_rights_cmp); + /* combine identical patterns into same structs */ + prev_vpattern = ""; right = NULL; + array_foreach_modifiable(&ctx.parse_rights, pright) { + if (right == NULL || + strcmp(prev_vpattern, pright->vpattern) != 0) { + right = array_append_space(&file->rights); + right->vpattern = pright->vpattern; + p_array_init(&right->rights, file->rights_pool, 4); + } + array_push_back(&right->rights, &pright->rights); + } + + array_free(&ctx.parse_rights); + return ret; +} + +int acl_global_file_refresh(struct acl_global_file *file) +{ + struct stat st; + + if (file->last_refresh_time + (time_t)file->refresh_interval_secs > ioloop_time) + return 0; + if (file->last_refresh_time != 0) { + if (stat(file->path, &st) < 0) { + i_error("stat(%s) failed: %m", file->path); + return -1; + } + if (st.st_ino == file->prev_st.st_ino && + st.st_size == file->prev_st.st_size && + CMP_ST_MTIME(&st, &file->prev_st)) { + /* no change to the file */ + file->last_refresh_time = ioloop_time; + return 0; + } + } + if (acl_global_file_read(file) < 0) + return -1; + file->last_refresh_time = ioloop_time; + return 0; +} + +void acl_global_file_last_stat(struct acl_global_file *file, struct stat *st_r) +{ + *st_r = file->prev_st; +} + +void acl_global_file_get(struct acl_global_file *file, const char *vname, + pool_t pool, ARRAY_TYPE(acl_rights) *rights_r) +{ + struct acl_global_rights *global_rights; + const struct acl_rights *rights; + struct acl_rights *new_rights; + + array_foreach_modifiable(&file->rights, global_rights) { + if (!wildcard_match(vname, global_rights->vpattern)) + continue; + if (file->debug) { + i_debug("Mailbox '%s' matches global ACL pattern '%s'", + vname, global_rights->vpattern); + } + array_foreach(&global_rights->rights, rights) { + new_rights = array_append_space(rights_r); + acl_rights_dup(rights, pool, new_rights); + } + } +} + +bool acl_global_file_have_any(struct acl_global_file *file, const char *vname) +{ + struct acl_global_rights *rights; + + i_assert(file->last_refresh_time != 0); + + array_foreach_modifiable(&file->rights, rights) { + if (wildcard_match(vname, rights->vpattern)) + return TRUE; + } + return FALSE; +} diff --git a/src/plugins/acl/acl-global-file.h b/src/plugins/acl/acl-global-file.h new file mode 100644 index 0000000..d393dec --- /dev/null +++ b/src/plugins/acl/acl-global-file.h @@ -0,0 +1,23 @@ +#ifndef ACL_GLOBAL_FILE_H +#define ACL_GLOBAL_FILE_H + +#include "acl-api.h" + +struct acl_global_file * +acl_global_file_init(const char *path, unsigned int refresh_interval_secs, + bool debug); +void acl_global_file_deinit(struct acl_global_file **file); + +/* Read the global ACLs into memory. */ +int acl_global_file_refresh(struct acl_global_file *file); +/* Return stat data for the last refresh. */ +void acl_global_file_last_stat(struct acl_global_file *file, struct stat *st_r); + +/* Return global ACL rights matching the mailbox name. The file must already + have been refreshed at least once. */ +void acl_global_file_get(struct acl_global_file *file, const char *vname, + pool_t pool, ARRAY_TYPE(acl_rights) *rights_r); +/* Returns TRUE if there are any global ACLs matching the mailbox name. */ +bool acl_global_file_have_any(struct acl_global_file *file, const char *vname); + +#endif diff --git a/src/plugins/acl/acl-lookup-dict.c b/src/plugins/acl/acl-lookup-dict.c new file mode 100644 index 0000000..638a767 --- /dev/null +++ b/src/plugins/acl/acl-lookup-dict.c @@ -0,0 +1,373 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "dict.h" +#include "mail-user.h" +#include "mail-namespace.h" +#include "acl-api-private.h" +#include "acl-storage.h" +#include "acl-plugin.h" +#include "acl-lookup-dict.h" + + +#define DICT_SHARED_BOXES_PATH "shared-boxes/" + +struct acl_lookup_dict { + struct mail_user *user; + struct dict *dict; +}; + +struct acl_lookup_dict_iter { + pool_t pool; + struct acl_lookup_dict *dict; + + pool_t iter_value_pool; + ARRAY_TYPE(const_string) iter_ids; + ARRAY_TYPE(const_string) iter_values; + unsigned int iter_idx, iter_value_idx; + + bool failed:1; +}; + +struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user) +{ + struct acl_lookup_dict *dict; + const char *uri, *error; + + dict = i_new(struct acl_lookup_dict, 1); + dict->user = user; + + uri = mail_user_plugin_getenv(user, "acl_shared_dict"); + if (uri != NULL) { + struct dict_settings dict_set; + + i_zero(&dict_set); + dict_set.base_dir = user->set->base_dir; + dict_set.event_parent = user->event; + if (dict_init(uri, &dict_set, &dict->dict, &error) < 0) + i_error("acl: dict_init(%s) failed: %s", uri, error); + } else { + e_debug(user->event, "acl: No acl_shared_dict setting - " + "shared mailbox listing is disabled"); + } + return dict; +} + +void acl_lookup_dict_deinit(struct acl_lookup_dict **_dict) +{ + struct acl_lookup_dict *dict = *_dict; + + *_dict = NULL; + if (dict->dict != NULL) + dict_deinit(&dict->dict); + i_free(dict); +} + +bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict) +{ + return dict->dict != NULL; +} + +static void +acl_lookup_dict_write_rights_id(string_t *dest, const struct acl_rights *right) +{ + switch (right->id_type) { + case ACL_ID_ANYONE: + case ACL_ID_AUTHENTICATED: + /* don't bother separating these */ + str_append(dest, "anyone"); + break; + case ACL_ID_USER: + str_append(dest, "user/"); + str_append(dest, right->identifier); + break; + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + str_append(dest, "group/"); + str_append(dest, right->identifier); + break; + case ACL_ID_OWNER: + case ACL_ID_TYPE_COUNT: + i_unreached(); + } +} + +static bool +acl_rights_is_same_user(const struct acl_rights *right, struct mail_user *user) +{ + return right->id_type == ACL_ID_USER && + strcmp(right->identifier, user->username) == 0; +} + +static int acl_lookup_dict_rebuild_add_backend(struct mail_namespace *ns, + ARRAY_TYPE(const_string) *ids) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct acl_backend *backend; + struct acl_mailbox_list_context *ctx; + struct acl_object *aclobj; + struct acl_object_list_iter *iter; + struct acl_rights rights; + const char *name, *id_dup; + string_t *id; + int ret = 0; + + if ((ns->flags & NAMESPACE_FLAG_NOACL) != 0 || ns->owner == NULL || + alist == NULL || alist->ignore_acls) + return 0; + + id = t_str_new(128); + backend = acl_mailbox_list_get_backend(ns->list); + ctx = acl_backend_nonowner_lookups_iter_init(backend); + while (acl_backend_nonowner_lookups_iter_next(ctx, &name)) { + aclobj = acl_object_init_from_name(backend, name); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + /* avoid pointless user -> user entries, + which some clients do */ + if (acl_rights_has_nonowner_lookup_changes(&rights) && + !acl_rights_is_same_user(&rights, ns->owner)) { + str_truncate(id, 0); + acl_lookup_dict_write_rights_id(id, &rights); + str_append_c(id, '/'); + str_append(id, ns->owner->username); + id_dup = t_strdup(str_c(id)); + array_push_back(ids, &id_dup); + } + } + if (acl_object_list_deinit(&iter) < 0) ret = -1; + acl_object_deinit(&aclobj); + } + if (acl_backend_nonowner_lookups_iter_deinit(&ctx) < 0) ret = -1; + return ret; +} + +static int +acl_lookup_dict_rebuild_update(struct acl_lookup_dict *dict, + const ARRAY_TYPE(const_string) *new_ids_arr, + bool no_removes) +{ + const char *username = dict->user->username; + struct dict_iterate_context *iter; + struct dict_transaction_context *dt = NULL; + const char *prefix, *key, *value, *const *old_ids, *const *new_ids, *p; + const char *error; + ARRAY_TYPE(const_string) old_ids_arr; + unsigned int newi, oldi, old_count, new_count; + string_t *path; + size_t prefix_len; + int ret; + const struct dict_op_settings *set = mail_user_get_dict_op_settings(dict->user); + + /* get all existing identifiers for the user. we might be able to + sync identifiers also for other users whose shared namespaces we + have, but it's possible that the other users have other namespaces + that aren't visible to us, so we don't want to remove anything + that could break them. */ + t_array_init(&old_ids_arr, 128); + prefix = DICT_PATH_SHARED DICT_SHARED_BOXES_PATH; + prefix_len = strlen(prefix); + iter = dict_iterate_init(dict->dict, set, prefix, DICT_ITERATE_FLAG_RECURSE); + while (dict_iterate(iter, &key, &value)) { + /* prefix/$type/$dest/$source */ + key += prefix_len; + p = strrchr(key, '/'); + if (p != NULL && strcmp(p + 1, username) == 0) { + key = t_strdup_until(key, p); + array_push_back(&old_ids_arr, &key); + } + } + if (dict_iterate_deinit(&iter, &error) < 0) { + i_error("acl: dict iteration failed: %s - can't update dict", error); + return -1; + } + + /* sort the existing identifiers */ + array_sort(&old_ids_arr, i_strcmp_p); + + /* sync the identifiers */ + path = t_str_new(256); + str_append(path, prefix); + + old_ids = array_get(&old_ids_arr, &old_count); + new_ids = array_get(new_ids_arr, &new_count); + for (newi = oldi = 0; newi < new_count || oldi < old_count; ) { + ret = newi == new_count ? 1 : + oldi == old_count ? -1 : + strcmp(new_ids[newi], old_ids[oldi]); + if (ret == 0) { + newi++; oldi++; + } else if (ret < 0) { + /* new identifier, add it */ + str_truncate(path, prefix_len); + str_append(path, new_ids[newi]); + dt = dict_transaction_begin(dict->dict, set); + dict_set(dt, str_c(path), "1"); + newi++; + } else if (!no_removes) { + /* old identifier removed */ + str_truncate(path, prefix_len); + str_append(path, old_ids[oldi]); + str_append_c(path, '/'); + str_append(path, username); + dt = dict_transaction_begin(dict->dict, set); + dict_unset(dt, str_c(path)); + oldi++; + } + if (dt != NULL && dict_transaction_commit(&dt, &error) < 0) { + i_error("acl: dict commit failed: %s", error); + return -1; + } + i_assert(dt == NULL); + } + return 0; +} + +int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict) +{ + struct mail_namespace *ns; + ARRAY_TYPE(const_string) ids_arr; + const char **ids; + unsigned int i, dest, count; + int ret = 0; + + if (dict->dict == NULL) + return 0; + + /* get all ACL identifiers with a positive lookup right */ + t_array_init(&ids_arr, 128); + for (ns = dict->user->namespaces; ns != NULL; ns = ns->next) { + if (acl_lookup_dict_rebuild_add_backend(ns, &ids_arr) < 0) + ret = -1; + } + + /* sort identifiers and remove duplicates */ + array_sort(&ids_arr, i_strcmp_p); + + ids = array_get_modifiable(&ids_arr, &count); + for (i = 1, dest = 0; i < count; i++) { + if (strcmp(ids[dest], ids[i]) != 0) { + if (++dest != i) + ids[dest] = ids[i]; + } + } + if (++dest < count) + array_delete(&ids_arr, dest, count-dest); + + /* if lookup failed at some point we can still add new ids, + but we can't remove any existing ones */ + if (acl_lookup_dict_rebuild_update(dict, &ids_arr, ret < 0) < 0) + ret = -1; + return ret; +} + +static void acl_lookup_dict_iterate_read(struct acl_lookup_dict_iter *iter) +{ + struct dict_iterate_context *dict_iter; + const char *id, *prefix, *key, *value, *error; + size_t prefix_len; + + id = array_idx_elem(&iter->iter_ids, iter->iter_idx); + iter->iter_idx++; + iter->iter_value_idx = 0; + + prefix = t_strconcat(DICT_PATH_SHARED DICT_SHARED_BOXES_PATH, + id, "/", NULL); + prefix_len = strlen(prefix); + + /* read all of it to memory. at least currently dict-proxy can support + only one iteration at a time, but the acl code can end up rebuilding + the dict, which opens another iteration. */ + p_clear(iter->iter_value_pool); + array_clear(&iter->iter_values); + const struct dict_op_settings *set = mail_user_get_dict_op_settings(iter->dict->user); + dict_iter = dict_iterate_init(iter->dict->dict, set, prefix, + DICT_ITERATE_FLAG_RECURSE); + while (dict_iterate(dict_iter, &key, &value)) { + i_assert(prefix_len < strlen(key)); + + key = p_strdup(iter->iter_value_pool, key + prefix_len); + array_push_back(&iter->iter_values, &key); + } + if (dict_iterate_deinit(&dict_iter, &error) < 0) { + i_error("%s", error); + iter->failed = TRUE; + } +} + +struct acl_lookup_dict_iter * +acl_lookup_dict_iterate_visible_init(struct acl_lookup_dict *dict) +{ + struct acl_user *auser = ACL_USER_CONTEXT(dict->user); + struct acl_lookup_dict_iter *iter; + const char *id; + unsigned int i; + pool_t pool; + + i_assert(auser != NULL); + + pool = pool_alloconly_create("acl lookup dict iter", 1024); + iter = p_new(pool, struct acl_lookup_dict_iter, 1); + iter->pool = pool; + iter->dict = dict; + + p_array_init(&iter->iter_ids, pool, 16); + id = "anyone"; + array_push_back(&iter->iter_ids, &id); + id = p_strconcat(pool, "user/", dict->user->username, NULL); + array_push_back(&iter->iter_ids, &id); + + i_array_init(&iter->iter_values, 64); + iter->iter_value_pool = + pool_alloconly_create("acl lookup dict iter values", 1024); + + /* get all groups we belong to */ + if (auser->groups != NULL) { + for (i = 0; auser->groups[i] != NULL; i++) { + id = p_strconcat(pool, "group/", auser->groups[i], + NULL); + array_push_back(&iter->iter_ids, &id); + } + } + + /* iterate through all identifiers that match us, start with the + first one */ + if (dict->dict != NULL) + acl_lookup_dict_iterate_read(iter); + else + array_clear(&iter->iter_ids); + return iter; +} + +const char * +acl_lookup_dict_iterate_visible_next(struct acl_lookup_dict_iter *iter) +{ + const char *const *keys; + unsigned int count; + + keys = array_get(&iter->iter_values, &count); + if (iter->iter_value_idx < count) + return keys[iter->iter_value_idx++]; + + if (iter->iter_idx < array_count(&iter->iter_ids)) { + /* get to the next iterator */ + acl_lookup_dict_iterate_read(iter); + return acl_lookup_dict_iterate_visible_next(iter); + } + return NULL; +} + +int acl_lookup_dict_iterate_visible_deinit(struct acl_lookup_dict_iter **_iter) +{ + struct acl_lookup_dict_iter *iter = *_iter; + int ret = iter->failed ? -1 : 0; + + *_iter = NULL; + array_free(&iter->iter_values); + pool_unref(&iter->iter_value_pool); + pool_unref(&iter->pool); + return ret; +} diff --git a/src/plugins/acl/acl-lookup-dict.h b/src/plugins/acl/acl-lookup-dict.h new file mode 100644 index 0000000..856aaf4 --- /dev/null +++ b/src/plugins/acl/acl-lookup-dict.h @@ -0,0 +1,17 @@ +#ifndef ACL_LOOKUP_DICT_H +#define ACL_LOOKUP_DICT_H + +struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user); +void acl_lookup_dict_deinit(struct acl_lookup_dict **dict); + +bool acl_lookup_dict_is_enabled(struct acl_lookup_dict *dict); + +int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict); + +struct acl_lookup_dict_iter * +acl_lookup_dict_iterate_visible_init(struct acl_lookup_dict *dict); +const char * +acl_lookup_dict_iterate_visible_next(struct acl_lookup_dict_iter *iter); +int acl_lookup_dict_iterate_visible_deinit(struct acl_lookup_dict_iter **iter); + +#endif diff --git a/src/plugins/acl/acl-mailbox-list.c b/src/plugins/acl/acl-mailbox-list.c new file mode 100644 index 0000000..18e04ad --- /dev/null +++ b/src/plugins/acl/acl-mailbox-list.c @@ -0,0 +1,629 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "imap-match.h" +#include "wildcard-match.h" +#include "mailbox-tree.h" +#include "mail-namespace.h" +#include "mailbox-list-iter-private.h" +#include "acl-api-private.h" +#include "acl-cache.h" +#include "acl-shared-storage.h" +#include "acl-plugin.h" + +#define MAILBOX_FLAG_MATCHED 0x40000000 + +struct acl_mailbox_list_iterate_context { + union mailbox_list_iterate_module_context module_ctx; + + struct mailbox_tree_context *lookup_boxes; + struct mailbox_info info; + + char sep; + bool hide_nonlistable_subscriptions:1; + bool simple_star_glob:1; + bool autocreate_acls_checked:1; +}; + +static const char *acl_storage_right_names[ACL_STORAGE_RIGHT_COUNT] = { + MAIL_ACL_LOOKUP, + MAIL_ACL_READ, + MAIL_ACL_WRITE, + MAIL_ACL_WRITE_SEEN, + MAIL_ACL_WRITE_DELETED, + MAIL_ACL_INSERT, + MAIL_ACL_POST, + MAIL_ACL_EXPUNGE, + MAIL_ACL_CREATE, + MAIL_ACL_DELETE, + MAIL_ACL_ADMIN +}; + +#define ACL_LIST_ITERATE_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_mailbox_list_module) + +struct acl_mailbox_list_module acl_mailbox_list_module = + MODULE_CONTEXT_INIT(&mailbox_list_module_register); + +struct acl_backend *acl_mailbox_list_get_backend(struct mailbox_list *list) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + + return alist->rights.backend; +} + +int acl_mailbox_list_have_right(struct mailbox_list *list, const char *name, + bool parent, unsigned int acl_storage_right_idx, + bool *can_see_r) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + struct acl_backend *backend = alist->rights.backend; + const unsigned int *idx_arr = alist->rights.acl_storage_right_idx; + struct acl_object *aclobj; + int ret, ret2; + + if (alist->ignore_acls) + return 1; + + aclobj = !parent ? + acl_object_init_from_name(backend, name) : + acl_object_init_from_parent(backend, name); + ret = acl_object_have_right(aclobj, idx_arr[acl_storage_right_idx]); + + if (can_see_r != NULL) { + ret2 = acl_object_have_right(aclobj, + idx_arr[ACL_STORAGE_RIGHT_LOOKUP]); + if (ret2 < 0) + ret = -1; + *can_see_r = ret2 > 0; + } + acl_object_deinit(&aclobj); + + if (ret < 0) + mailbox_list_set_internal_error(list); + return ret; +} + +static void +acl_mailbox_try_list_fast(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list); + struct acl_backend *backend = alist->rights.backend; + const unsigned int *idxp; + const struct acl_mask *acl_mask; + struct acl_mailbox_list_context *nonowner_list_ctx; + struct mail_namespace *ns = _ctx->list->ns; + struct mailbox_list_iter_update_context update_ctx; + const char *name; + + if ((_ctx->flags & (MAILBOX_LIST_ITER_RAW_LIST | + MAILBOX_LIST_ITER_SELECT_SUBSCRIBED)) != 0) + return; + + if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC) { + /* mailboxes in public namespace should all be listable to + someone. we don't benefit from fast listing. */ + return; + } + + /* If ACLs are ignored for this namespace don't try fast listing. */ + if (alist->ignore_acls) + return; + + /* if this namespace's default rights contain LOOKUP, we'll need to + go through all mailboxes in any case. */ + idxp = alist->rights.acl_storage_right_idx + ACL_STORAGE_RIGHT_LOOKUP; + if (acl_backend_get_default_rights(backend, &acl_mask) < 0 || + acl_cache_mask_isset(acl_mask, *idxp)) + return; + + /* no LOOKUP right by default, we can optimize this */ + i_zero(&update_ctx); + update_ctx.iter_ctx = _ctx; + update_ctx.glob = + imap_match_init(pool_datastack_create(), "*", + (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0, + ctx->sep); + update_ctx.match_parents = TRUE; + update_ctx.tree_ctx = mailbox_tree_init(ctx->sep); + + nonowner_list_ctx = acl_backend_nonowner_lookups_iter_init(backend); + while (acl_backend_nonowner_lookups_iter_next(nonowner_list_ctx, + &name)) { + T_BEGIN { + const char *vname = + mailbox_list_get_vname(ns->list, name); + mailbox_list_iter_update(&update_ctx, vname); + } T_END; + } + + if (acl_backend_nonowner_lookups_iter_deinit(&nonowner_list_ctx) >= 0) + ctx->lookup_boxes = update_ctx.tree_ctx; + else + mailbox_tree_deinit(&update_ctx.tree_ctx); +} + +static struct mailbox_list_iterate_context * +acl_mailbox_list_iter_init_shared(struct mailbox_list *list, + const char *const *patterns, + enum mailbox_list_iter_flags flags) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + struct mailbox_list_iterate_context *ctx; + int ret; + + /* before listing anything add namespaces for all users + who may have visible mailboxes */ + ret = acl_shared_namespaces_add(list->ns); + + ctx = alist->module_ctx.super.iter_init(list, patterns, flags); + if (ret < 0) + ctx->failed = TRUE; + return ctx; +} + +static struct mailbox_list_iterate_context * +acl_mailbox_list_iter_init(struct mailbox_list *list, + const char *const *patterns, + enum mailbox_list_iter_flags flags) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + struct mailbox_list_iterate_context *_ctx; + struct acl_mailbox_list_iterate_context *ctx; + const char *p; + unsigned int i; + + _ctx = alist->module_ctx.super.iter_init(list, patterns, flags); + + ctx = p_new(_ctx->pool, struct acl_mailbox_list_iterate_context, 1); + + if (list->ns->type != MAIL_NAMESPACE_TYPE_PRIVATE && + (list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0) { + /* non-private namespace with subscriptions=yes. this could be + a site-global subscriptions file, so hide subscriptions for + mailboxes the user doesn't see. */ + ctx->hide_nonlistable_subscriptions = TRUE; + } + + ctx->sep = mail_namespace_get_sep(list->ns); + /* see if all patterns have only a single '*' and it's at the end. + we can use it to do some optimizations. */ + ctx->simple_star_glob = TRUE; + for (i = 0; patterns[i] != NULL; i++) { + p = strchr(patterns[i], '*'); + if (p == NULL || p[1] != '\0') { + ctx->simple_star_glob = FALSE; + break; + } + } + + MODULE_CONTEXT_SET(_ctx, acl_mailbox_list_module, ctx); + + /* Try to avoid reading ACLs from all mailboxes by getting a smaller + list of mailboxes that have even potential to be visible. If we + couldn't get such a list, we'll go through all mailboxes. */ + T_BEGIN { + acl_mailbox_try_list_fast(_ctx); + } T_END; + + return _ctx; +} + +static const struct mailbox_info * +acl_mailbox_list_iter_next_info(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list); + const struct mailbox_info *info; + + for (;;) { + /* Normally the data stack frame is in mailbox_list_iter_next(), + but we're bypassing it here by calling super.iter_next() + directly. */ + T_BEGIN { + info = alist->module_ctx.super.iter_next(_ctx); + } T_END; + if (info == NULL) + break; + + /* if we've a list of mailboxes with LOOKUP rights, skip the + mailboxes not in the list (since we know they can't be + visible to us). */ + if (ctx->lookup_boxes == NULL || + mailbox_tree_lookup(ctx->lookup_boxes, info->vname) != NULL) + break; + e_debug(_ctx->list->ns->user->event, + "acl: Mailbox not in dovecot-acl-list: %s", info->vname); + } + + return info; +} + +static const char * +acl_mailbox_list_iter_get_name(struct mailbox_list_iterate_context *ctx, + const char *vname) +{ + struct mail_namespace *ns = ctx->list->ns; + const char *name; + size_t len; + + name = mailbox_list_get_storage_name(ns->list, vname); + len = strlen(name); + if (len > 0 && name[len-1] == mailbox_list_get_hierarchy_sep(ns->list)) { + /* name ends with separator. this can happen if doing e.g. + LIST "" foo/% and it lists "foo/". */ + name = t_strndup(name, len-1); + } + return name; +} + +static bool +iter_is_listing_all_children(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + const char *child; + + /* If all patterns (with '.' separator) are in "name*", "name.*" or + "%.*" style format, simple_star_glob=TRUE and we can easily test + this by simply checking if name/child mailbox matches. */ + child = t_strdup_printf("%s%cx", ctx->info.vname, ctx->sep); + return ctx->simple_star_glob && + imap_match(_ctx->glob, child) == IMAP_MATCH_YES; +} + +static bool +iter_mailbox_has_visible_children(struct mailbox_list_iterate_context *_ctx, + bool only_nonpatterns, bool subscribed) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + string_t *pattern; + const char *prefix; + size_t i, prefix_len; + bool stars = FALSE, ret = FALSE; + + /* do we have child mailboxes with LOOKUP right that don't match + the list pattern? */ + if (ctx->lookup_boxes != NULL) { + /* we have a list of mailboxes with LOOKUP rights. before + starting the slow list iteration, check check first + if there even are any children with LOOKUP rights. */ + struct mailbox_node *node; + + node = mailbox_tree_lookup(ctx->lookup_boxes, ctx->info.vname); + i_assert(node != NULL); + if (node->children == NULL) + return FALSE; + } + + /* if mailbox name has '*' characters in it, they'll conflict with the + LIST wildcard. replace then with '%' and verify later that all + results have the correct prefix. */ + pattern = t_str_new(128); + for (i = 0; ctx->info.vname[i] != '\0'; i++) { + if (ctx->info.vname[i] != '*') + str_append_c(pattern, ctx->info.vname[i]); + else { + stars = TRUE; + str_append_c(pattern, '%'); + } + } + if (i > 0 && ctx->info.vname[i-1] != ctx->sep) + str_append_c(pattern, ctx->sep); + str_append_c(pattern, '*'); + prefix = str_c(pattern); + prefix_len = str_len(pattern) - 1; + + iter = mailbox_list_iter_init(_ctx->list, str_c(pattern), + (!subscribed ? 0 : + MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + if (only_nonpatterns && + imap_match(_ctx->glob, info->vname) == IMAP_MATCH_YES) { + /* at least one child matches also the original list + patterns. we don't need to show this mailbox. */ + ret = FALSE; + break; + } + if (!stars || strncmp(info->vname, prefix, prefix_len) == 0) + ret = TRUE; + } + (void)mailbox_list_iter_deinit(&iter); + return ret; +} + +static int +acl_mailbox_list_info_is_visible(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); +#define PRESERVE_MAILBOX_FLAGS (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED) + struct mailbox_info *info = &ctx->info; + const char *acl_name; + int ret; + + if ((_ctx->flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) { + /* skip ACL checks. */ + return 1; + } + + if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0 && + (_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 && + !ctx->hide_nonlistable_subscriptions) { + /* don't waste time doing an ACL check. we're going to list + all subscriptions anyway. */ + info->flags &= MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED; + return 1; + } + + acl_name = acl_mailbox_list_iter_get_name(_ctx, info->vname); + ret = acl_mailbox_list_have_right(_ctx->list, acl_name, FALSE, + ACL_STORAGE_RIGHT_LOOKUP, + NULL); + if (ret != 0) { + if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0) { + /* don't waste time checking if there are visible + children, but also don't return incorrect flags */ + info->flags &= ENUM_NEGATE(MAILBOX_CHILDREN); + } else if ((info->flags & MAILBOX_CHILDREN) != 0 && + !iter_mailbox_has_visible_children(_ctx, FALSE, FALSE)) { + info->flags &= ENUM_NEGATE(MAILBOX_CHILDREN); + info->flags |= MAILBOX_NOCHILDREN; + } + return ret; + } + + /* no permission to see this mailbox */ + if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) { + /* we're listing subscribed mailboxes. this one or its child + is subscribed, so we'll need to list it. but since we don't + have LOOKUP right, we'll need to show it as nonexistent. */ + i_assert((info->flags & PRESERVE_MAILBOX_FLAGS) != 0); + info->flags = MAILBOX_NONEXISTENT | + (info->flags & PRESERVE_MAILBOX_FLAGS); + if (ctx->hide_nonlistable_subscriptions) { + /* global subscriptions file. hide this entry if there + are no visible subscribed children or if we're going + to list the subscribed children anyway. */ + if ((info->flags & MAILBOX_CHILD_SUBSCRIBED) == 0) + return 0; + if (iter_is_listing_all_children(_ctx) || + !iter_mailbox_has_visible_children(_ctx, TRUE, TRUE)) + return 0; + /* e.g. LSUB "" % with visible subscribed children */ + } + return 1; + } + + if (!iter_is_listing_all_children(_ctx) && + iter_mailbox_has_visible_children(_ctx, TRUE, FALSE)) { + /* no child mailboxes match the list pattern(s), but mailbox + has visible children. we'll need to show this as + non-existent. */ + info->flags = MAILBOX_NONEXISTENT | MAILBOX_CHILDREN | + (info->flags & PRESERVE_MAILBOX_FLAGS); + return 1; + } + return 0; +} + +static int +acl_mailbox_list_iter_check_autocreate_acls(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct mailbox_settings *const *box_sets; + unsigned int i, count; + int ret; + + ctx->autocreate_acls_checked = TRUE; + if (_ctx->autocreate_ctx == NULL) + return 0; + if ((_ctx->flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) { + /* skip ACL checks. */ + return 0; + } + + box_sets = array_get(&_ctx->autocreate_ctx->box_sets, &count); + i_assert(array_count(&_ctx->autocreate_ctx->boxes) == count); + + for (i = 0; i < count; ) { + const char *acl_name = + acl_mailbox_list_iter_get_name(_ctx, box_sets[i]->name); + ret = acl_mailbox_list_have_right(_ctx->list, acl_name, FALSE, + ACL_STORAGE_RIGHT_LOOKUP, + NULL); + if (ret < 0) + return -1; + if (ret > 0) + i++; + else { + /* no list right - remove the whole autobox */ + array_delete(&_ctx->autocreate_ctx->box_sets, i, 1); + array_delete(&_ctx->autocreate_ctx->boxes, i, 1); + box_sets = array_get(&_ctx->autocreate_ctx->box_sets, &count); + } + } + return 0; +} + +static const struct mailbox_info * +acl_mailbox_list_iter_next(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + const struct mailbox_info *info; + int ret; + + if (!ctx->autocreate_acls_checked) { + if (acl_mailbox_list_iter_check_autocreate_acls(_ctx) < 0) { + _ctx->failed = TRUE; + return NULL; + } + } + + while ((info = acl_mailbox_list_iter_next_info(_ctx)) != NULL) { + ctx->info = *info; + T_BEGIN { + ret = acl_mailbox_list_info_is_visible(_ctx); + } T_END; + if (ret > 0) + break; + if (ret < 0) { + _ctx->failed = TRUE; + return NULL; + } + /* skip to next one */ + e_debug(_ctx->list->ns->user->event, + "acl: No lookup right to mailbox: %s", info->vname); + } + return info == NULL ? NULL : &ctx->info; +} + +static int +acl_mailbox_list_iter_deinit(struct mailbox_list_iterate_context *_ctx) +{ + struct acl_mailbox_list_iterate_context *ctx = + ACL_LIST_ITERATE_CONTEXT(_ctx); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(_ctx->list); + int ret = _ctx->failed ? -1 : 0; + + if (ctx->lookup_boxes != NULL) + mailbox_tree_deinit(&ctx->lookup_boxes); + if (alist->module_ctx.super.iter_deinit(_ctx) < 0) + ret = -1; + return ret; +} + +static void acl_mailbox_list_deinit(struct mailbox_list *list) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(list); + + if (alist->rights.backend != NULL) + acl_backend_deinit(&alist->rights.backend); + alist->module_ctx.super.deinit(list); +} + +static void acl_mailbox_list_init_shared(struct mailbox_list *list) +{ + struct acl_mailbox_list *alist; + struct mailbox_list_vfuncs *v = list->vlast; + + alist = p_new(list->pool, struct acl_mailbox_list, 1); + alist->module_ctx.super = *v; + list->vlast = &alist->module_ctx.super; + v->deinit = acl_mailbox_list_deinit; + v->iter_init = acl_mailbox_list_iter_init_shared; + + MODULE_CONTEXT_SET(list, acl_mailbox_list_module, alist); +} + +static void acl_storage_rights_ctx_init(struct acl_storage_rights_context *ctx, + struct acl_backend *backend) +{ + unsigned int i; + + ctx->backend = backend; + for (i = 0; i < ACL_STORAGE_RIGHT_COUNT; i++) { + ctx->acl_storage_right_idx[i] = + acl_backend_lookup_right(backend, + acl_storage_right_names[i]); + } +} + +static bool acl_namespace_is_ignored(struct mailbox_list *list) +{ + const char *value = + mail_user_plugin_getenv(list->ns->user, "acl_ignore_namespace"); + for (unsigned int i = 2; value != NULL; i++) { + if (wildcard_match(list->ns->prefix, value)) + return TRUE; + value = mail_user_plugin_getenv(list->ns->user, + t_strdup_printf("acl_ignore_namespace%u", i)); + } + return FALSE; +} + +static void acl_mailbox_list_init_default(struct mailbox_list *list) +{ + struct mailbox_list_vfuncs *v = list->vlast; + struct acl_mailbox_list *alist; + + if (list->mail_set->mail_full_filesystem_access) { + /* not necessarily, but safer to do this for now. */ + i_fatal("mail_full_filesystem_access=yes is " + "incompatible with ACLs"); + } + + alist = p_new(list->pool, struct acl_mailbox_list, 1); + alist->module_ctx.super = *v; + list->vlast = &alist->module_ctx.super; + v->deinit = acl_mailbox_list_deinit; + v->iter_init = acl_mailbox_list_iter_init; + v->iter_next = acl_mailbox_list_iter_next; + v->iter_deinit = acl_mailbox_list_iter_deinit; + if (acl_namespace_is_ignored(list)) + alist->ignore_acls = TRUE; + + MODULE_CONTEXT_SET(list, acl_mailbox_list_module, alist); +} + +void acl_mail_namespace_storage_added(struct mail_namespace *ns) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct acl_backend *backend; + const char *current_username, *owner_username; + bool owner = TRUE; + + if (alist == NULL) + return; + struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(ns->user); + + owner_username = ns->user->username; + current_username = auser->acl_user; + if (current_username == NULL) + current_username = owner_username; + else + owner = strcmp(current_username, owner_username) == 0; + + /* We don't care about the username for non-private mailboxes. + It's used only when checking if we're the mailbox owner. We never + are for shared/public mailboxes. */ + if (ns->type != MAIL_NAMESPACE_TYPE_PRIVATE) + owner = FALSE; + + /* we need to know the storage when initializing backend */ + backend = acl_backend_init(auser->acl_env, ns->list, current_username, + auser->groups, owner); + if (backend == NULL) + i_fatal("ACL backend initialization failed"); + acl_storage_rights_ctx_init(&alist->rights, backend); +} + +void acl_mailbox_list_created(struct mailbox_list *list) +{ + struct acl_user *auser = ACL_USER_CONTEXT(list->ns->user); + + if (auser == NULL) { + /* ACLs disabled for this user */ + } else if ((list->ns->flags & NAMESPACE_FLAG_NOACL) != 0) { + /* no ACL checks for internal namespaces (lda, shared) */ + if (list->ns->type == MAIL_NAMESPACE_TYPE_SHARED) + acl_mailbox_list_init_shared(list); + } else if ((list->ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) { + /* this namespace is empty. don't attempt to lookup ACLs, + because they're not going to work anyway and we could + crash doing it. */ + } else { + acl_mailbox_list_init_default(list); + } +} diff --git a/src/plugins/acl/acl-mailbox.c b/src/plugins/acl/acl-mailbox.c new file mode 100644 index 0000000..134be7f --- /dev/null +++ b/src/plugins/acl/acl-mailbox.c @@ -0,0 +1,714 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +/* FIXME: If we don't have permission to change flags/keywords, the changes + should still be stored temporarily for this session. However most clients + don't care and it's a huge job, so I currently this isn't done. The same + problem actually exists when opening read-only mailboxes. */ +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "istream.h" +#include "mailbox-list-private.h" +#include "acl-api-private.h" +#include "acl-plugin.h" + +#include <sys/stat.h> + +#define ACL_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_mail_module) + +struct acl_transaction_context { + union mailbox_transaction_module_context module_ctx; +}; + +static MODULE_CONTEXT_DEFINE_INIT(acl_mail_module, &mail_module_register); +static struct acl_transaction_context acl_transaction_failure; + +struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + return abox->aclobj; +} + +int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + int ret; + + if (abox->skip_acl_checks) + return 1; + + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list); + + /* If acls are ignored for this namespace do not check if + there are rights. */ + if (alist->ignore_acls) + return 1; + + ret = acl_object_have_right(abox->aclobj, + alist->rights.acl_storage_right_idx[right_idx]); + if (ret > 0) + return 1; + if (ret < 0) { + mail_storage_set_internal_error(box->storage); + return -1; + } + + mail_storage_set_error(box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + return 0; +} + +static bool acl_is_readonly(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + enum acl_storage_rights save_right; + + if (abox->module_ctx.super.is_readonly(box)) + return TRUE; + + save_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? + ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; + if (acl_mailbox_right_lookup(box, save_right) > 0) + return FALSE; + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_EXPUNGE) > 0) + return FALSE; + + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0) + return FALSE; + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0) + return FALSE; + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0) + return FALSE; + + return TRUE; +} + +static void acl_mailbox_free(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + if (abox->aclobj != NULL) + acl_object_deinit(&abox->aclobj); + abox->module_ctx.super.free(box); +} + +static void acl_mailbox_copy_acls_from_parent(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list); + struct acl_object *parent_aclobj; + struct acl_object_list_iter *iter; + struct acl_rights_update update; + + i_zero(&update); + update.modify_mode = ACL_MODIFY_MODE_REPLACE; + update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE; + + parent_aclobj = acl_object_init_from_parent(alist->rights.backend, + box->name); + iter = acl_object_list_init(parent_aclobj); + while (acl_object_list_next(iter, &update.rights)) { + /* don't copy global ACL rights. */ + if (!update.rights.global) + (void)acl_object_update(abox->aclobj, &update); + } + /* FIXME: Add error handling */ + (void)acl_object_list_deinit(&iter); + acl_object_deinit(&parent_aclobj); +} + +static int +acl_mailbox_create(struct mailbox *box, const struct mailbox_update *update, + bool directory) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + int ret; + + if (!mailbox_is_autocreated(box)) { + /* we're looking up CREATE permission from our parent's rights */ + ret = acl_mailbox_list_have_right(box->list, box->name, TRUE, + ACL_STORAGE_RIGHT_CREATE, NULL); + } else { + /* mailbox is autocreated, so we need to treat it as if it + already exists. ignore the "create" ACL here. */ + ret = 1; + } + if (ret <= 0) { + if (ret < 0) { + mail_storage_set_internal_error(box->storage); + return -1; + } + /* Note that if user didn't have LOOKUP permission to parent + mailbox, this may reveal the mailbox's existence to user. + Can't help it. */ + mail_storage_set_error(box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + return -1; + } + + /* ignore ACLs in this mailbox until creation is complete, because + super.create() may call e.g. mailbox_open() which will fail since + we haven't yet copied ACLs to this mailbox. */ + abox->skip_acl_checks = TRUE; + ret = abox->module_ctx.super.create_box(box, update, directory); + abox->skip_acl_checks = FALSE; + /* update local acl object, otherwise with LAYOUT=INDEX, we end up + without local path to acl file, and copying fails. */ + struct acl_backend *acl_be = abox->aclobj->backend; + acl_object_deinit(&abox->aclobj); + abox->aclobj = acl_object_init_from_name(acl_be, box->name); + + if (ret == 0) + acl_mailbox_copy_acls_from_parent(box); + return ret; +} + +static int +acl_mailbox_update(struct mailbox *box, const struct mailbox_update *update) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + int ret; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN); + if (ret <= 0) + return -1; + return abox->module_ctx.super.update_box(box, update); +} + +static void acl_mailbox_fail_not_found(struct mailbox *box) +{ + int ret; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP); + if (ret > 0) { + mail_storage_set_error(box->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + } else if (ret == 0) { + box->acl_no_lookup_right = TRUE; + mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND, + T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname)); + } +} + +static int +acl_mailbox_delete(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + int ret; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_DELETE); + if (ret <= 0) { + if (ret == 0) + acl_mailbox_fail_not_found(box); + return -1; + } + + return abox->module_ctx.super.delete_box(box); +} + +static int +acl_mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(src); + int ret; + + /* renaming requires rights to delete the old mailbox */ + ret = acl_mailbox_right_lookup(src, ACL_STORAGE_RIGHT_DELETE); + if (ret <= 0) { + if (ret == 0) + acl_mailbox_fail_not_found(src); + return -1; + } + + /* and create the new one under the parent mailbox */ + T_BEGIN { + ret = acl_mailbox_list_have_right(dest->list, dest->name, TRUE, + ACL_STORAGE_RIGHT_CREATE, NULL); + } T_END; + + if (ret <= 0) { + if (ret == 0) { + /* Note that if the mailbox didn't have LOOKUP + permission, this now reveals to user the mailbox's + existence. Can't help it. */ + mail_storage_set_error(src->storage, MAIL_ERROR_PERM, + MAIL_ERRSTR_NO_PERMISSION); + } else { + mail_storage_set_internal_error(src->storage); + } + return -1; + } + + return abox->module_ctx.super.rename_box(src, dest); +} + +static int +acl_get_write_rights(struct mailbox *box, + bool *flags_r, bool *flag_seen_r, bool *flag_del_r) +{ + int ret; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE); + if (ret < 0) + return -1; + *flags_r = ret > 0; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN); + if (ret < 0) + return -1; + *flag_seen_r = ret > 0; + + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED); + if (ret < 0) + return -1; + *flag_del_r = ret > 0; + return 0; +} + +static void acl_transaction_set_failure(struct mailbox_transaction_context *t) +{ + MODULE_CONTEXT_SET(t, acl_storage_module, + &acl_transaction_failure); +} + +static void +acl_mail_update_flags(struct mail *_mail, enum modify_type modify_type, + enum mail_flags flags) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *amail = ACL_MAIL_CONTEXT(mail); + bool acl_flags, acl_flag_seen, acl_flag_del; + + if (acl_get_write_rights(_mail->box, &acl_flags, &acl_flag_seen, + &acl_flag_del) < 0) { + acl_transaction_set_failure(_mail->transaction); + return; + } + + if (modify_type != MODIFY_REPLACE) { + /* adding/removing flags. just remove the disallowed + flags from the mask. */ + if (!acl_flags) + flags &= MAIL_SEEN | MAIL_DELETED; + if (!acl_flag_seen) + flags &= ENUM_NEGATE(MAIL_SEEN); + if (!acl_flag_del) + flags &= ENUM_NEGATE(MAIL_DELETED); + } else if (!acl_flags || !acl_flag_seen || !acl_flag_del) { + /* we don't have permission to replace all the flags. */ + if (!acl_flags && !acl_flag_seen && !acl_flag_del) { + /* no flag changes allowed. ignore silently. */ + return; + } + + /* handle this by first removing the allowed flags and + then adding the allowed flags */ + acl_mail_update_flags(_mail, MODIFY_REMOVE, + ENUM_NEGATE(flags)); + if (flags != 0) + acl_mail_update_flags(_mail, MODIFY_ADD, flags); + return; + } + + amail->super.update_flags(_mail, modify_type, flags); +} + +static void +acl_mail_update_keywords(struct mail *_mail, enum modify_type modify_type, + struct mail_keywords *keywords) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *amail = ACL_MAIL_CONTEXT(mail); + int ret; + + ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_WRITE); + if (ret <= 0) { + /* if we don't have permission, just silently return success. */ + if (ret < 0) + acl_transaction_set_failure(_mail->transaction); + return; + } + + amail->super.update_keywords(_mail, modify_type, keywords); +} + +static void acl_mail_expunge(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *amail = ACL_MAIL_CONTEXT(mail); + int ret; + + ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_EXPUNGE); + if (ret <= 0) { + /* if we don't have permission, silently return success so + users won't see annoying error messages in case their + clients try automatic expunging. */ + acl_transaction_set_failure(_mail->transaction); + return; + } + + amail->super.expunge(_mail); +} + +void acl_mail_allocated(struct mail *_mail) +{ + struct acl_mailbox *abox = ACL_CONTEXT(_mail->box); + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + union mail_module_context *amail; + + if (abox == NULL || !abox->acl_enabled) + return; + + amail = p_new(mail->pool, union mail_module_context, 1); + amail->super = *v; + mail->vlast = &amail->super; + + v->update_flags = acl_mail_update_flags; + v->update_keywords = acl_mail_update_keywords; + v->expunge = acl_mail_expunge; + MODULE_CONTEXT_SET_SELF(mail, acl_mail_module, amail); +} + +static int +acl_save_get_flags(struct mailbox *box, enum mail_flags *flags, + enum mail_flags *pvt_flags, struct mail_keywords **keywords) +{ + bool acl_flags, acl_flag_seen, acl_flag_del; + + if (acl_get_write_rights(box, &acl_flags, &acl_flag_seen, + &acl_flag_del) < 0) + return -1; + + if (!acl_flag_seen) { + *flags &= ENUM_NEGATE(MAIL_SEEN); + *pvt_flags &= ENUM_NEGATE(MAIL_SEEN); + } + if (!acl_flag_del) { + *flags &= ENUM_NEGATE(MAIL_DELETED); + *pvt_flags &= ENUM_NEGATE(MAIL_DELETED); + } + if (!acl_flags) { + *flags &= MAIL_SEEN | MAIL_DELETED; + *pvt_flags &= MAIL_SEEN | MAIL_DELETED; + *keywords = NULL; + } + return 0; +} + +static int +acl_save_begin(struct mail_save_context *ctx, struct istream *input) +{ + struct mailbox *box = ctx->transaction->box; + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + enum acl_storage_rights save_right; + + save_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? + ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; + if (acl_mailbox_right_lookup(box, save_right) <= 0) + return -1; + if (acl_save_get_flags(box, &ctx->data.flags, + &ctx->data.pvt_flags, &ctx->data.keywords) < 0) + return -1; + + return abox->module_ctx.super.save_begin(ctx, input); +} + +static bool +acl_copy_has_rights(struct mail_save_context *ctx, struct mail *mail) +{ + struct mailbox *destbox = ctx->transaction->box; + enum acl_storage_rights save_right; + + if (ctx->moving) { + if (acl_mailbox_right_lookup(mail->box, + ACL_STORAGE_RIGHT_EXPUNGE) <= 0) + return FALSE; + } + + save_right = (destbox->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? + ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; + if (acl_mailbox_right_lookup(destbox, save_right) <= 0) + return FALSE; + if (acl_save_get_flags(destbox, &ctx->data.flags, + &ctx->data.pvt_flags, &ctx->data.keywords) < 0) + return FALSE; + return TRUE; +} + +static int +acl_copy(struct mail_save_context *ctx, struct mail *mail) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(t->box); + + if (!acl_copy_has_rights(ctx, mail)) { + mailbox_save_cancel(&ctx); + return -1; + } + + return abox->module_ctx.super.copy(ctx, mail); +} + +static int +acl_transaction_commit(struct mailbox_transaction_context *ctx, + struct mail_transaction_commit_changes *changes_r) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(ctx->box); + void *at = ACL_CONTEXT(ctx); + int ret; + + if (at != NULL) { + abox->module_ctx.super.transaction_rollback(ctx); + return -1; + } + + ret = abox->module_ctx.super.transaction_commit(ctx, changes_r); + if (abox->no_read_right) { + /* don't allow IMAP client to see what UIDs the messages got */ + changes_r->no_read_perm = TRUE; + } + return ret; +} + +static int acl_mailbox_exists(struct mailbox *box, bool auto_boxes, + enum mailbox_existence *existence_r) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + const char *const *rights; + unsigned int i; + + if (acl_object_get_my_rights(abox->aclobj, pool_datastack_create(), &rights) < 0) { + mail_storage_set_internal_error(box->storage); + return -1; + } + + /* for now this is used only by IMAP SUBSCRIBE. we'll intentionally + violate RFC 4314 here, because it says SUBSCRIBE should succeed only + when mailbox has 'l' right. But there's no point in not allowing + a subscribe for a mailbox that can be selected anyway. Just the + opposite: subscribing to such mailboxes is a very useful feature. */ + for (i = 0; rights[i] != NULL; i++) { + if (strcmp(rights[i], MAIL_ACL_LOOKUP) == 0 || + strcmp(rights[i], MAIL_ACL_READ) == 0 || + strcmp(rights[i], MAIL_ACL_INSERT) == 0) + return abox->module_ctx.super.exists(box, auto_boxes, + existence_r); + } + *existence_r = MAILBOX_EXISTENCE_NONE; + return 0; +} + +bool acl_mailbox_have_extra_attribute_rights(struct mailbox *box) +{ + /* RFC 5464: + + When the ACL extension [RFC4314] is present, users can only set and + retrieve private or shared mailbox annotations on a mailbox on which + they have the "l" right and any one of the "r", "s", "w", "i", or "p" + rights. + */ + const enum acl_storage_rights check_rights[] = { + ACL_STORAGE_RIGHT_READ, + ACL_STORAGE_RIGHT_WRITE_SEEN, + ACL_STORAGE_RIGHT_WRITE, + ACL_STORAGE_RIGHT_INSERT, + ACL_STORAGE_RIGHT_POST, + }; + for (unsigned int i = 0; i < N_ELEMENTS(check_rights); i++) { + int ret = acl_mailbox_right_lookup(box, check_rights[i]); + if (ret > 0) + return TRUE; + if (ret < 0) { + /* unexpected error - stop checking further */ + return FALSE; + } + } + return FALSE; +} + +static int acl_mailbox_open_check_acl(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT_REQUIRE(box->list); + const unsigned int *idx_arr = alist->rights.acl_storage_right_idx; + enum acl_storage_rights open_right; + int ret; + + /* mailbox can be opened either for reading or appending new messages */ + if ((box->flags & MAILBOX_FLAG_IGNORE_ACLS) != 0 || + (box->list->ns->flags & NAMESPACE_FLAG_NOACL) != 0 || + abox->skip_acl_checks) + return 0; + + if ((box->flags & MAILBOX_FLAG_SAVEONLY) != 0) { + open_right = (box->flags & MAILBOX_FLAG_POST_SESSION) != 0 ? + ACL_STORAGE_RIGHT_POST : ACL_STORAGE_RIGHT_INSERT; + } else if (box->deleting) { + open_right = ACL_STORAGE_RIGHT_DELETE; + } else if ((box->flags & MAILBOX_FLAG_ATTRIBUTE_SESSION) != 0) { + /* GETMETADATA/SETMETADATA requires "l" right and another one + which is checked afterwards. */ + open_right = ACL_STORAGE_RIGHT_LOOKUP; + } else { + open_right = ACL_STORAGE_RIGHT_READ; + } + + ret = acl_object_have_right(abox->aclobj, idx_arr[open_right]); + if (ret <= 0) { + if (ret == 0) { + /* no access. */ + acl_mailbox_fail_not_found(box); + } + return -1; + } + if (open_right != ACL_STORAGE_RIGHT_READ) { + ret = acl_object_have_right(abox->aclobj, + idx_arr[ACL_STORAGE_RIGHT_READ]); + if (ret < 0) + return -1; + if (ret == 0) + abox->no_read_right = TRUE; + } + if ((box->flags & MAILBOX_FLAG_ATTRIBUTE_SESSION) != 0) { + if (!acl_mailbox_have_extra_attribute_rights(box)) + return -1; + } + return 0; +} + +static int acl_mailbox_open(struct mailbox *box) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + if (acl_mailbox_open_check_acl(box) < 0) + return -1; + + return abox->module_ctx.super.open(box); +} + +static int acl_mailbox_get_status(struct mailbox *box, + enum mailbox_status_items items, + struct mailbox_status *status_r) +{ + struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box); + + if (abox->module_ctx.super.get_status(box, items, status_r) < 0) + return -1; + + if ((items & STATUS_PERMANENT_FLAGS) != 0) { + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) <= 0) { + status_r->permanent_flags &= MAIL_DELETED|MAIL_SEEN; + status_r->permanent_keywords = FALSE; + status_r->allow_new_keywords = FALSE; + } + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) <= 0) + status_r->permanent_flags &= ENUM_NEGATE(MAIL_DELETED); + if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) <= 0) + status_r->permanent_flags &= ENUM_NEGATE(MAIL_SEEN); + } + return 0; +} + +void acl_mailbox_allocated(struct mailbox *box) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(box->list); + struct mailbox_vfuncs *v = box->vlast; + struct acl_mailbox *abox; + bool ignore_acls = (box->flags & MAILBOX_FLAG_IGNORE_ACLS) != 0; + + if (alist == NULL) { + /* ACLs disabled */ + return; + } + + if (mail_namespace_is_shared_user_root(box->list->ns) || alist->ignore_acls) { + /* this is the root shared namespace, which itself doesn't + have any existing mailboxes. */ + ignore_acls = TRUE; + } + + abox = p_new(box->pool, struct acl_mailbox, 1); + abox->module_ctx.super = *v; + box->vlast = &abox->module_ctx.super; + /* aclobj can be used for setting ACLs, even when mailbox is opened + with IGNORE_ACLS flag */ + if (alist->rights.backend != NULL) + abox->aclobj = acl_object_init_from_name(alist->rights.backend, + mailbox_get_name(box)); + else + i_assert(ignore_acls); + + v->free = acl_mailbox_free; + if (!ignore_acls) { + abox->acl_enabled = TRUE; + v->is_readonly = acl_is_readonly; + v->exists = acl_mailbox_exists; + v->open = acl_mailbox_open; + v->get_status = acl_mailbox_get_status; + v->create_box = acl_mailbox_create; + v->update_box = acl_mailbox_update; + v->delete_box = acl_mailbox_delete; + v->rename_box = acl_mailbox_rename; + v->save_begin = acl_save_begin; + v->copy = acl_copy; + v->transaction_commit = acl_transaction_commit; + v->attribute_set = acl_attribute_set; + v->attribute_get = acl_attribute_get; + v->attribute_iter_init = acl_attribute_iter_init; + v->attribute_iter_next = acl_attribute_iter_next; + v->attribute_iter_deinit = acl_attribute_iter_deinit; + } + MODULE_CONTEXT_SET(box, acl_storage_module, abox); +} + +static bool +acl_mailbox_update_removed_id(struct acl_object *aclobj, + const struct acl_rights_update *update) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + + if (update->modify_mode != ACL_MODIFY_MODE_CLEAR && + update->neg_modify_mode != ACL_MODIFY_MODE_CLEAR) + return FALSE; + if (update->modify_mode == ACL_MODIFY_MODE_CLEAR && + update->neg_modify_mode == ACL_MODIFY_MODE_CLEAR) + return TRUE; + + /* mixed clear/non-clear. see if the identifier exists anymore */ + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (rights.id_type == update->rights.id_type && + null_strcmp(rights.identifier, update->rights.identifier) == 0) + break; + } + return acl_object_list_deinit(&iter) >= 0; +} + +int acl_mailbox_update_acl(struct mailbox_transaction_context *t, + const struct acl_rights_update *update) +{ + struct acl_object *aclobj; + const char *key; + time_t ts = update->last_change != 0 ? + update->last_change : ioloop_time; + + key = t_strdup_printf(MAILBOX_ATTRIBUTE_PREFIX_ACL"%s", + acl_rights_get_id(&update->rights)); + aclobj = acl_mailbox_get_aclobj(t->box); + if (acl_object_update(aclobj, update) < 0) { + mailbox_set_critical(t->box, "Failed to set ACL"); + return -1; + } + + /* FIXME: figure out some value lengths, so maybe some day + quota could apply to ACLs as well. */ + if (acl_mailbox_update_removed_id(aclobj, update)) + mail_index_attribute_unset(t->itrans, FALSE, key, ts); + else + mail_index_attribute_set(t->itrans, FALSE, key, ts, 0); + return 0; +} diff --git a/src/plugins/acl/acl-plugin.c b/src/plugins/acl/acl-plugin.c new file mode 100644 index 0000000..b446dcd --- /dev/null +++ b/src/plugins/acl/acl-plugin.c @@ -0,0 +1,27 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mailbox-list-private.h" +#include "acl-api.h" +#include "acl-plugin.h" + + +const char *acl_plugin_version = DOVECOT_ABI_VERSION; + +static struct mail_storage_hooks acl_mail_storage_hooks = { + .mail_user_created = acl_mail_user_created, + .mailbox_list_created = acl_mailbox_list_created, + .mail_namespace_storage_added = acl_mail_namespace_storage_added, + .mailbox_allocated = acl_mailbox_allocated, + .mail_allocated = acl_mail_allocated +}; + +void acl_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &acl_mail_storage_hooks); +} + +void acl_plugin_deinit(void) +{ + mail_storage_hooks_remove(&acl_mail_storage_hooks); +} diff --git a/src/plugins/acl/acl-plugin.h b/src/plugins/acl/acl-plugin.h new file mode 100644 index 0000000..6acfe89 --- /dev/null +++ b/src/plugins/acl/acl-plugin.h @@ -0,0 +1,73 @@ +#ifndef ACL_PLUGIN_H +#define ACL_PLUGIN_H + +#include "mail-user.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "acl-storage.h" + +#define ACL_CONTEXT(obj) \ + MODULE_CONTEXT(obj, acl_storage_module) +#define ACL_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_storage_module) +#define ACL_LIST_CONTEXT(obj) \ + MODULE_CONTEXT(obj, acl_mailbox_list_module) +#define ACL_LIST_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_mailbox_list_module) +#define ACL_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, acl_user_module) +#define ACL_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, acl_user_module) + +struct acl_user { + union mail_user_module_context module_ctx; + + const char *acl_user; + const char *acl_env; + const char *const *groups; + + struct acl_lookup_dict *acl_lookup_dict; +}; + +struct acl_storage_rights_context { + struct acl_backend *backend; + unsigned int acl_storage_right_idx[ACL_STORAGE_RIGHT_COUNT]; +}; + +struct acl_mailbox_list { + union mailbox_list_module_context module_ctx; + struct acl_storage_rights_context rights; + + time_t last_shared_add_check; + bool ignore_acls; +}; + +struct acl_mailbox { + union mailbox_module_context module_ctx; + struct acl_object *aclobj; + bool skip_acl_checks; + bool acl_enabled; + bool no_read_right; +}; + +extern MODULE_CONTEXT_DEFINE(acl_storage_module, &mail_storage_module_register); +extern MODULE_CONTEXT_DEFINE(acl_user_module, &mail_user_module_register); +extern MODULE_CONTEXT_DEFINE(acl_mailbox_list_module, + &mailbox_list_module_register); + +void acl_mailbox_list_created(struct mailbox_list *list); +void acl_mail_namespace_storage_added(struct mail_namespace *ns); +void acl_mail_user_created(struct mail_user *list); + +void acl_mailbox_allocated(struct mailbox *box); +void acl_mail_allocated(struct mail *mail); + +struct acl_backend *acl_mailbox_list_get_backend(struct mailbox_list *list); +int acl_mailbox_list_have_right(struct mailbox_list *list, const char *name, + bool parent, unsigned int acl_storage_right_idx, + bool *can_see_r) ATTR_NULL(5); + +void acl_plugin_init(struct module *module); +void acl_plugin_deinit(void); + +#endif diff --git a/src/plugins/acl/acl-shared-storage.c b/src/plugins/acl/acl-shared-storage.c new file mode 100644 index 0000000..1329cd3 --- /dev/null +++ b/src/plugins/acl/acl-shared-storage.c @@ -0,0 +1,103 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "str.h" +#include "var-expand.h" +#include "acl-plugin.h" +#include "acl-lookup-dict.h" +#include "acl-shared-storage.h" +#include "index/shared/shared-storage.h" + +#define SHARED_NS_RETRY_SECS (60*60) + +static bool acl_ns_prefix_exists(struct mail_namespace *ns) +{ + struct mailbox *box; + const char *vname; + enum mailbox_existence existence; + bool ret; + + if (ns->list->mail_set->mail_shared_explicit_inbox) + return FALSE; + + vname = t_strndup(ns->prefix, ns->prefix_len-1); + box = mailbox_alloc(ns->list, vname, 0); + ret = mailbox_exists(box, FALSE, &existence) == 0 && + existence == MAILBOX_EXISTENCE_SELECT; + mailbox_free(&box); + return ret; +} + +static void +acl_shared_namespace_add(struct mail_namespace *ns, + struct mail_storage *storage, const char *userdomain) +{ + struct shared_storage *sstorage = (struct shared_storage *)storage; + struct mail_namespace *new_ns = ns; + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + const char *mailbox, *error; + string_t *str; + + if (strcmp(ns->user->username, userdomain) == 0) { + /* skip ourself */ + return; + } + + const struct var_expand_table tab[] = { + { 'u', userdomain, "user" }, + { 'n', t_strcut(userdomain, '@'), "username" }, + { 'd', i_strchr_to_next(userdomain, '@'), "domain" }, + { '\0', NULL, NULL } + }; + + str = t_str_new(128); + if (var_expand(str, sstorage->ns_prefix_pattern, tab, &error) <= 0) { + i_error("Failed to expand namespace prefix %s: %s", + sstorage->ns_prefix_pattern, error); + return; + } + mailbox = str_c(str); + if (shared_storage_get_namespace(&new_ns, &mailbox) < 0) + return; + + /* check if there are any mailboxes really visible to us */ + iter = mailbox_list_iter_init(new_ns->list, "*", + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + info = mailbox_list_iter_next(iter); + (void)mailbox_list_iter_deinit(&iter); + + if (info == NULL && !acl_ns_prefix_exists(new_ns)) { + /* no visible mailboxes, remove the namespace */ + mail_namespace_destroy(new_ns); + } +} + +int acl_shared_namespaces_add(struct mail_namespace *ns) +{ + struct acl_user *auser = ACL_USER_CONTEXT(ns->user); + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct mail_storage *storage = mail_namespace_get_default_storage(ns); + struct acl_lookup_dict_iter *iter; + const char *name; + + i_assert(auser != NULL && alist != NULL); + i_assert(ns->type == MAIL_NAMESPACE_TYPE_SHARED); + i_assert(strcmp(storage->name, MAIL_SHARED_STORAGE_NAME) == 0); + + if (ioloop_time < alist->last_shared_add_check + SHARED_NS_RETRY_SECS) { + /* already added, don't bother rechecking */ + return 0; + } + alist->last_shared_add_check = ioloop_time; + + iter = acl_lookup_dict_iterate_visible_init(auser->acl_lookup_dict); + while ((name = acl_lookup_dict_iterate_visible_next(iter)) != NULL) { + T_BEGIN { + acl_shared_namespace_add(ns, storage, name); + } T_END; + } + return acl_lookup_dict_iterate_visible_deinit(&iter); +} diff --git a/src/plugins/acl/acl-shared-storage.h b/src/plugins/acl/acl-shared-storage.h new file mode 100644 index 0000000..6e7736b --- /dev/null +++ b/src/plugins/acl/acl-shared-storage.h @@ -0,0 +1,6 @@ +#ifndef ACL_SHARED_STORAGE_H +#define ACL_SHARED_STORAGE_H + +int acl_shared_namespaces_add(struct mail_namespace *ns); + +#endif diff --git a/src/plugins/acl/acl-storage.c b/src/plugins/acl/acl-storage.c new file mode 100644 index 0000000..988c0b8 --- /dev/null +++ b/src/plugins/acl/acl-storage.c @@ -0,0 +1,62 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "mail-namespace.h" +#include "mailbox-list-private.h" +#include "acl-api-private.h" +#include "acl-lookup-dict.h" +#include "acl-plugin.h" + + +struct acl_storage_module acl_storage_module = + MODULE_CONTEXT_INIT(&mail_storage_module_register); +struct acl_user_module acl_user_module = + MODULE_CONTEXT_INIT(&mail_user_module_register); + +static void acl_user_deinit(struct mail_user *user) +{ + struct acl_user *auser = ACL_USER_CONTEXT(user); + + i_assert(auser != NULL); + acl_lookup_dict_deinit(&auser->acl_lookup_dict); + auser->module_ctx.super.deinit(user); +} + +static void acl_mail_user_create(struct mail_user *user, const char *env) +{ + struct mail_user_vfuncs *v = user->vlast; + struct acl_user *auser; + + auser = p_new(user->pool, struct acl_user, 1); + auser->module_ctx.super = *v; + user->vlast = &auser->module_ctx.super; + v->deinit = acl_user_deinit; + auser->acl_lookup_dict = acl_lookup_dict_init(user); + + auser->acl_env = env; + auser->acl_user = mail_user_plugin_getenv(user, "acl_user"); + if (auser->acl_user == NULL) + auser->acl_user = mail_user_plugin_getenv(user, "master_user"); + + env = mail_user_plugin_getenv(user, "acl_groups"); + if (env != NULL) { + auser->groups = + (const char *const *)p_strsplit(user->pool, env, ","); + } + + MODULE_CONTEXT_SET(user, acl_user_module, auser); +} + +void acl_mail_user_created(struct mail_user *user) +{ + const char *env; + + env = mail_user_plugin_getenv(user, "acl"); + if (env != NULL && *env != '\0') + acl_mail_user_create(user, env); + else { + e_debug(user->event, "acl: No acl setting - ACLs are disabled"); + } +} diff --git a/src/plugins/acl/acl-storage.h b/src/plugins/acl/acl-storage.h new file mode 100644 index 0000000..8be4c26 --- /dev/null +++ b/src/plugins/acl/acl-storage.h @@ -0,0 +1,50 @@ +#ifndef ACL_STORAGE_H +#define ACL_STORAGE_H + +#include "mail-storage.h" + +struct acl_rights_update; + +enum acl_storage_rights { + ACL_STORAGE_RIGHT_LOOKUP, + ACL_STORAGE_RIGHT_READ, + ACL_STORAGE_RIGHT_WRITE, + ACL_STORAGE_RIGHT_WRITE_SEEN, + ACL_STORAGE_RIGHT_WRITE_DELETED, + ACL_STORAGE_RIGHT_INSERT, + ACL_STORAGE_RIGHT_POST, + ACL_STORAGE_RIGHT_EXPUNGE, + ACL_STORAGE_RIGHT_CREATE, + ACL_STORAGE_RIGHT_DELETE, + ACL_STORAGE_RIGHT_ADMIN, + + ACL_STORAGE_RIGHT_COUNT +}; + +/* Returns acl_object for the given mailbox. */ +struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box); +/* Returns 1 if we have the requested right. If not, returns 0 and sets storage + error to MAIL_ERROR_PERM. Returns -1 if internal error occurred and also + sets storage error. */ +int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx); + +/* Returns TRUE if mailbox has the necessary extra ACL for accessing + attributes. The caller must have checked the LOOKUP right already. */ +bool acl_mailbox_have_extra_attribute_rights(struct mailbox *box); + +int acl_mailbox_update_acl(struct mailbox_transaction_context *t, + const struct acl_rights_update *update); + +int acl_attribute_set(struct mailbox_transaction_context *t, + enum mail_attribute_type type, const char *key, + const struct mail_attribute_value *value); +int acl_attribute_get(struct mailbox *box, + enum mail_attribute_type type, const char *key, + struct mail_attribute_value *value_r); +struct mailbox_attribute_iter * +acl_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type, + const char *prefix); +const char *acl_attribute_iter_next(struct mailbox_attribute_iter *iter); +int acl_attribute_iter_deinit(struct mailbox_attribute_iter *iter); + +#endif diff --git a/src/plugins/acl/doveadm-acl.c b/src/plugins/acl/doveadm-acl.c new file mode 100644 index 0000000..0ab416a --- /dev/null +++ b/src/plugins/acl/doveadm-acl.c @@ -0,0 +1,629 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "module-dir.h" +#include "imap-util.h" +#include "acl-plugin.h" +#include "acl-api-private.h" +#include "acl-lookup-dict.h" +#include "doveadm-print.h" +#include "doveadm-mail.h" + +struct doveadm_acl_cmd_context { + struct doveadm_mail_cmd_context ctx; + bool get_match_me; + enum acl_modify_mode modify_mode; +}; + +const char *doveadm_acl_plugin_version = DOVECOT_ABI_VERSION; + +void doveadm_acl_plugin_init(struct module *module); +void doveadm_acl_plugin_deinit(void); + +static int +cmd_acl_mailbox_open(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user, const char *mailbox, + struct mailbox **box_r) +{ + struct acl_user *auser = ACL_USER_CONTEXT(user); + struct mail_namespace *ns; + struct mailbox *box; + + if (auser == NULL) { + i_error("ACL not enabled for %s", user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + + ns = mail_namespace_find(user->namespaces, mailbox); + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (mailbox_open(box) < 0) { + i_error("Can't open mailbox %s: %s", mailbox, + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_mailbox(ctx, box); + mailbox_free(&box); + return -1; + } + *box_r = box; + return 0; +} + +static void cmd_acl_get_right(const struct acl_rights *rights) +{ + string_t *str; + + doveadm_print(acl_rights_get_id(rights)); + + if (rights->global) + doveadm_print("global"); + else + doveadm_print(""); + + str = t_str_new(256); + if (rights->rights != NULL) + str_append(str, t_strarray_join(rights->rights, " ")); + if (rights->neg_rights != NULL) { + if (str_len(str) > 0) + str_append_c(str, ' '); + str_append_c(str, '-'); + str_append(str, t_strarray_join(rights->neg_rights, " -")); + } + doveadm_print(str_c(str)); +} + +static int cmd_acl_get_mailbox(struct doveadm_acl_cmd_context *ctx, + struct mailbox *box) +{ + struct acl_object *aclobj = acl_mailbox_get_aclobj(box); + struct acl_backend *backend; + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret; + + backend = acl_mailbox_list_get_backend(box->list); + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) T_BEGIN { + if (!ctx->get_match_me || + acl_backend_rights_match_me(backend, &rights)) + cmd_acl_get_right(&rights); + } T_END; + + if ((ret = acl_object_list_deinit(&iter))<0) { + i_error("ACL iteration failed"); + doveadm_mail_failed_error(&ctx->ctx, MAIL_ERROR_TEMP); + } + return ret; +} + +static int +cmd_acl_get_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + const char *mailbox = _ctx->args[0]; + struct mailbox *box; + int ret; + + if (cmd_acl_mailbox_open(_ctx, user, mailbox, &box) < 0) + return -1; + + ret = cmd_acl_get_mailbox(ctx, box); + mailbox_free(&box); + return ret; +} + +static bool cmd_acl_get_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + + switch (c) { + case 'm': + ctx->get_match_me = TRUE; + break; + default: + return FALSE; + } + return TRUE; +} + +static void cmd_acl_get_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl get"); + doveadm_print_header("id", "ID", 0); + doveadm_print_header("global", "Global", 0); + doveadm_print_header("rights", "Rights", 0); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_get_alloc(void) +{ + struct doveadm_acl_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_acl_cmd_context); + ctx->ctx.getopt_args = "m"; + ctx->ctx.v.parse_arg = cmd_acl_get_parse_arg; + ctx->ctx.v.run = cmd_acl_get_run; + ctx->ctx.v.init = cmd_acl_get_init; + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + return &ctx->ctx; +} + +static int +cmd_acl_rights_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0]; + struct mailbox *box; + struct acl_object *aclobj; + const char *const *rights; + int ret = 0; + + if (cmd_acl_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + aclobj = acl_mailbox_get_aclobj(box); + if (acl_object_get_my_rights(aclobj, pool_datastack_create(), + &rights) < 0) { + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + i_error("Failed to get rights"); + ret = -1; + } else { + doveadm_print(t_strarray_join(rights, " ")); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_rights_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl rights"); + doveadm_print_header("rights", "Rights", 0); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_rights_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_rights_run; + ctx->v.init = cmd_acl_rights_init; + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + return ctx; +} + +static int +cmd_acl_mailbox_update(struct doveadm_mail_cmd_context *ctx, struct mailbox *box, + const struct acl_rights_update *update) +{ + struct mailbox_transaction_context *t; + int ret; + + t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL | + ctx->transaction_flags, __func__); + ret = acl_mailbox_update_acl(t, update); + if (mailbox_transaction_commit(&t) < 0) + ret = -1; + return ret; +} + +static int +cmd_acl_set_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) +{ + struct doveadm_acl_cmd_context *ctx = + (struct doveadm_acl_cmd_context *)_ctx; + const char *mailbox = _ctx->args[0], *id = _ctx->args[1]; + const char *const *rights = _ctx->args + 2; + struct mailbox *box; + struct acl_rights_update update; + const char *error; + int ret; + + if (cmd_acl_mailbox_open(_ctx, user, mailbox, &box) < 0) + return -1; + + i_zero(&update); + update.modify_mode = ctx->modify_mode; + update.neg_modify_mode = ctx->modify_mode; + if (acl_rights_update_import(&update, id, rights, &error) < 0) + i_fatal_status(EX_USAGE, "%s", error); + if ((ret = cmd_acl_mailbox_update(&ctx->ctx, box, &update)) < 0) { + i_error("Failed to set ACL: %s", + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_error(_ctx, MAIL_ERROR_TEMP); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_set_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (str_array_length(args) < 3) + doveadm_mail_help_name("acl set"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_change_alloc(enum acl_modify_mode modify_mode) +{ + struct doveadm_acl_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_acl_cmd_context); + ctx->ctx.v.run = cmd_acl_set_run; + ctx->ctx.v.init = cmd_acl_set_init; + ctx->modify_mode = modify_mode; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_acl_set_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_REPLACE); +} + +static struct doveadm_mail_cmd_context *cmd_acl_add_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_ADD); +} + +static struct doveadm_mail_cmd_context *cmd_acl_remove_alloc(void) +{ + return cmd_acl_change_alloc(ACL_MODIFY_MODE_REMOVE); +} + +static int +cmd_acl_delete_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0], *id = ctx->args[1]; + struct mailbox *box; + struct acl_rights_update update; + const char *error; + int ret = 0; + + if (cmd_acl_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + i_zero(&update); + if (acl_rights_update_import(&update, id, NULL, &error) < 0) + i_fatal_status(EX_USAGE, "%s", error); + if ((ret = cmd_acl_mailbox_update(ctx, box, &update)) < 0) { + i_error("Failed to delete ACL: %s", + mailbox_get_last_internal_error(box, NULL)); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + } + mailbox_free(&box); + return ret; +} + +static void cmd_acl_delete_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (str_array_length(args) < 2) + doveadm_mail_help_name("acl delete"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_delete_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_delete_run; + ctx->v.init = cmd_acl_delete_init; + return ctx; +} + +static int +cmd_acl_recalc_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + struct acl_user *auser = ACL_USER_CONTEXT(user); + + if (auser == NULL) { + i_error("ACL not enabled for %s", user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0) { + i_error("Failed to recalculate ACL dicts"); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + return -1; + } + return 0; +} + +static struct doveadm_mail_cmd_context * +cmd_acl_recalc_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_recalc_run; + return ctx; +} + +static int +cmd_acl_debug_mailbox_open(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user, const char *mailbox, + struct mailbox **box_r) +{ + struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(user); + struct mail_namespace *ns; + struct mailbox *box; + const char *path, *errstr; + enum mail_error error; + + ns = mail_namespace_find(user->namespaces, mailbox); + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (mailbox_open(box) < 0) { + errstr = mail_storage_get_last_internal_error(box->storage, &error); + errstr = t_strdup(errstr); + doveadm_mail_failed_error(ctx, error); + + if (error != MAIL_ERROR_NOTFOUND || + mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &path) <= 0) + i_error("Can't open mailbox %s: %s", mailbox, errstr); + else { + i_error("Mailbox '%s' in namespace '%s' doesn't exist in %s", + box->name, ns->prefix, path); + } + mailbox_free(&box); + return -1; + } + + if (auser == NULL) { + i_info("ACL not enabled for user %s, mailbox can be accessed", + user->username); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + mailbox_free(&box); + return -1; + } + + *box_r = box; + return 0; +} + +static bool cmd_acl_debug_mailbox(struct mailbox *box, bool *retry_r) +{ + struct mail_namespace *ns = mailbox_get_namespace(box); + struct acl_user *auser = ACL_USER_CONTEXT_REQUIRE(ns->user); + struct acl_object *aclobj = acl_mailbox_get_aclobj(box); + struct acl_backend *backend = acl_mailbox_list_get_backend(box->list); + struct acl_mailbox_list_context *iter; + struct acl_lookup_dict_iter *diter; + const char *const *rights, *name, *path; + enum mail_flags private_flags_mask; + string_t *str; + int ret; + bool all_ok = TRUE; + + *retry_r = FALSE; + + i_info("Mailbox '%s' is in namespace '%s'", + box->name, box->list->ns->prefix); + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) > 0) + i_info("Mailbox path: %s", path); + + private_flags_mask = mailbox_get_private_flags_mask(box); + if (private_flags_mask == 0) + i_info("All message flags are shared across users in mailbox"); + else { + str = t_str_new(64); + imap_write_flags(str, private_flags_mask, NULL); + i_info("Per-user private flags in mailbox: %s", str_c(str)); + } + + /* check if user has lookup right */ + if (acl_object_get_my_rights(aclobj, pool_datastack_create(), + &rights) < 0) + i_fatal("Failed to get rights"); + + if (rights[0] == NULL) + i_info("User %s has no rights for mailbox", ns->user->username); + else { + i_info("User %s has rights: %s", + ns->user->username, t_strarray_join(rights, " ")); + } + if (!str_array_find(rights, MAIL_ACL_LOOKUP)) { + i_error("User %s is missing 'lookup' right", + ns->user->username); + return FALSE; + } + + /* check if mailbox is listable */ + if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) { + i_info("Mailbox in user's private namespace"); + return TRUE; + } + + iter = acl_backend_nonowner_lookups_iter_init(backend); + while (acl_backend_nonowner_lookups_iter_next(iter, &name)) { + if (strcmp(name, box->name) == 0) + break; + } + if ((ret = acl_backend_nonowner_lookups_iter_deinit(&iter))<0) + i_fatal("ACL non-owner iteration failed"); + if (ret == 0) { + i_error("Mailbox not found from dovecot-acl-list, rebuilding"); + if (acl_backend_nonowner_lookups_rebuild(backend) < 0) + i_fatal("dovecot-acl-list rebuilding failed"); + all_ok = FALSE; + *retry_r = TRUE; + } else { + i_info("Mailbox found from dovecot-acl-list"); + } + + if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC) { + i_info("Mailbox is in public namespace"); + return TRUE; + } + + if (!acl_lookup_dict_is_enabled(auser->acl_lookup_dict)) { + i_error("acl_lookup_dict not enabled"); + return FALSE; + } + + /* shared namespace. see if it's in acl lookup dict */ + diter = acl_lookup_dict_iterate_visible_init(auser->acl_lookup_dict); + while ((name = acl_lookup_dict_iterate_visible_next(diter)) != NULL) { + if (strcmp(name, ns->owner->username) == 0) + break; + } + if (acl_lookup_dict_iterate_visible_deinit(&diter) < 0) + i_fatal("ACL shared dict iteration failed"); + if (name == NULL) { + i_error("User %s not found from ACL shared dict, rebuilding", + ns->owner->username); + if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0) + i_fatal("ACL lookup dict rebuild failed"); + all_ok = FALSE; + *retry_r = TRUE; + } else { + i_info("User %s found from ACL shared dict", + ns->owner->username); + } + return all_ok; +} + +static int +cmd_acl_debug_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *mailbox = ctx->args[0]; + struct mailbox *box; + bool ret, retry; + + if (cmd_acl_debug_mailbox_open(ctx, user, mailbox, &box) < 0) + return -1; + + ret = cmd_acl_debug_mailbox(box, &retry); + if (!ret && retry) { + i_info("Retrying after rebuilds:"); + ret = cmd_acl_debug_mailbox(box, &retry); + } + if (ret) + i_info("Mailbox %s is visible in LIST", box->vname); + else + i_info("Mailbox %s is NOT visible in LIST", box->vname); + mailbox_free(&box); + return 0; +} + +static void cmd_acl_debug_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("acl debug"); +} + +static struct doveadm_mail_cmd_context * +cmd_acl_debug_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_acl_debug_run; + ctx->v.init = cmd_acl_debug_init; + return ctx; +} + +static struct doveadm_cmd_ver2 acl_commands[] = { +{ + .name = "acl get", + .mail_cmd = cmd_acl_get_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-m] <mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('m', "match-me", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl rights", + .mail_cmd = cmd_acl_rights_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl set", + .mail_cmd = cmd_acl_set_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl add", + .mail_cmd = cmd_acl_add_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl remove", + .mail_cmd = cmd_acl_remove_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id> <right> [<right> ...]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "right", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl delete", + .mail_cmd = cmd_acl_delete_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox> <id>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', "id", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl recalc", + .mail_cmd = cmd_acl_recalc_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX, +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "acl debug", + .mail_cmd = cmd_acl_debug_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<mailbox>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +} +}; + +void doveadm_acl_plugin_init(struct module *module ATTR_UNUSED) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(acl_commands); i++) + doveadm_cmd_register_ver2(&acl_commands[i]); +} + +void doveadm_acl_plugin_deinit(void) +{ +} diff --git a/src/plugins/acl/test-acl.c b/src/plugins/acl/test-acl.c new file mode 100644 index 0000000..1fafd7a --- /dev/null +++ b/src/plugins/acl/test-acl.c @@ -0,0 +1,66 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "test-common.h" +#include "acl-api-private.h" + +static void test_acl_rights_sort(void) +{ + struct acl_rights rights1 = { + .rights = t_strsplit("a b a c d b", " "), + .neg_rights = t_strsplit("e d c a a d b e", " "), + }; + struct acl_rights rights2 = { + .rights = t_strsplit("a c x", " "), + .neg_rights = t_strsplit("b c y", " "), + }; + struct acl_object obj = { + .rights_pool = pool_alloconly_create("acl rights", 256) + }; + const struct acl_rights *rights; + + test_begin("acl_rights_sort"); + t_array_init(&obj.rights, 8); + + /* try with zero rights */ + acl_rights_sort(&obj); + test_assert(array_count(&obj.rights) == 0); + + /* try with just one right */ + array_push_back(&obj.rights, &rights1); + acl_rights_sort(&obj); + test_assert(array_count(&obj.rights) == 1); + rights = array_idx(&obj.rights, 0); + test_assert(acl_rights_cmp(rights, &rights1) == 0); + + /* try with two rights that don't have equal ID */ + struct acl_rights rights1_id2 = rights1; + rights1_id2.identifier = "id2"; + array_push_back(&obj.rights, &rights1_id2); + acl_rights_sort(&obj); + test_assert(array_count(&obj.rights) == 2); + rights = array_idx(&obj.rights, 0); + test_assert(acl_rights_cmp(&rights[0], &rights1) == 0); + test_assert(acl_rights_cmp(&rights[1], &rights1_id2) == 0); + + /* try with 3 rights where first has equal ID */ + array_push_back(&obj.rights, &rights2); + acl_rights_sort(&obj); + test_assert(array_count(&obj.rights) == 2); + rights = array_idx(&obj.rights, 0); + test_assert_strcmp(t_strarray_join(rights[0].rights, " "), "a b c d x"); + test_assert_strcmp(t_strarray_join(rights[0].neg_rights, " "), "a b c d e y"); + + pool_unref(&obj.rights_pool); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_acl_rights_sort, + NULL + }; + return test_run(test_functions); +} diff --git a/src/plugins/apparmor/Makefile.am b/src/plugins/apparmor/Makefile.am new file mode 100644 index 0000000..510053b --- /dev/null +++ b/src/plugins/apparmor/Makefile.am @@ -0,0 +1,14 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib01_apparmor_plugin_la_LDFLAGS = -module -avoid-version +lib01_apparmor_plugin_la_LIBADD = $(APPARMOR_LIBS) +lib01_apparmor_plugin_la_SOURCES = \ + apparmor-plugin.c + +module_LTLIBRARIES = \ + lib01_apparmor_plugin.la diff --git a/src/plugins/apparmor/Makefile.in b/src/plugins/apparmor/Makefile.in new file mode 100644 index 0000000..72c10cf --- /dev/null +++ b/src/plugins/apparmor/Makefile.in @@ -0,0 +1,816 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/apparmor +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +am__DEPENDENCIES_1 = +lib01_apparmor_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_lib01_apparmor_plugin_la_OBJECTS = apparmor-plugin.lo +lib01_apparmor_plugin_la_OBJECTS = \ + $(am_lib01_apparmor_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib01_apparmor_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib01_apparmor_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/apparmor-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib01_apparmor_plugin_la_SOURCES) +DIST_SOURCES = $(lib01_apparmor_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +lib01_apparmor_plugin_la_LDFLAGS = -module -avoid-version +lib01_apparmor_plugin_la_LIBADD = $(APPARMOR_LIBS) +lib01_apparmor_plugin_la_SOURCES = \ + apparmor-plugin.c + +module_LTLIBRARIES = \ + lib01_apparmor_plugin.la + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/apparmor/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/apparmor/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib01_apparmor_plugin.la: $(lib01_apparmor_plugin_la_OBJECTS) $(lib01_apparmor_plugin_la_DEPENDENCIES) $(EXTRA_lib01_apparmor_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib01_apparmor_plugin_la_LINK) -rpath $(moduledir) $(lib01_apparmor_plugin_la_OBJECTS) $(lib01_apparmor_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/apparmor-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/apparmor-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/apparmor-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/apparmor/apparmor-plugin.c b/src/plugins/apparmor/apparmor-plugin.c new file mode 100644 index 0000000..410c652 --- /dev/null +++ b/src/plugins/apparmor/apparmor-plugin.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "module-dir.h" +#include "randgen.h" +#include "mail-user.h" +#include "mail-storage-private.h" +#include "mail-storage-hooks.h" +#include <sys/apparmor.h> + +#define APPARMOR_PLUGIN_SETTING_HAT_PREFIX "apparmor_hat" + +const char *apparmor_plugin_version = DOVECOT_ABI_VERSION; + +/* hooks into user creation and deinit, will try to use + hats provided by apparmor_hat, apparmor_hat1... etc */ + +#define APPARMOR_USER_CONTEXT(obj) \ + (struct apparmor_mail_user*)MODULE_CONTEXT(obj, apparmor_mail_user_module) + +static MODULE_CONTEXT_DEFINE_INIT(apparmor_mail_user_module, + &mail_user_module_register); + +struct apparmor_mail_user { + union mail_user_module_context module_ctx; + unsigned long token; +}; + +void apparmor_plugin_init(struct module*); +void apparmor_plugin_deinit(void); + +static void apparmor_log_current_context(struct mail_user *user) +{ + char *con, *mode; + + if (aa_getcon(&con, &mode) < 0) { + e_debug(user->event, "aa_getcon() failed: %m"); + } else { + e_debug(user->event, "apparmor: Current context=%s, mode=%s", + con, mode); + free(con); + } +} + +static void apparmor_mail_user_deinit(struct mail_user *user) +{ + struct apparmor_mail_user *auser = APPARMOR_USER_CONTEXT(user); + + i_assert(auser != NULL); + auser->module_ctx.super.deinit(user); + + if (aa_change_hat(NULL, auser->token)<0) + i_fatal("aa_change_hat(NULL) failed: %m"); + + apparmor_log_current_context(user); +} + +static void apparmor_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct apparmor_mail_user *auser; + ARRAY_TYPE(const_string) hats; + /* see if we can find any hats */ + const char *hat = + mail_user_plugin_getenv(user, APPARMOR_PLUGIN_SETTING_HAT_PREFIX); + if (hat == NULL) + return; + + t_array_init(&hats, 8); + array_push_back(&hats, &hat); + for(unsigned int i = 2;; i++) { + hat = mail_user_plugin_getenv(user, t_strdup_printf("%s%u", + APPARMOR_PLUGIN_SETTING_HAT_PREFIX, i)); + if (hat == NULL) break; + array_push_back(&hats, &hat); + } + array_append_zero(&hats); + + /* we got hat(s) to try */ + auser = p_new(user->pool, struct apparmor_mail_user, 1); + auser->module_ctx.super = *v; + user->vlast = &auser->module_ctx.super; + v->deinit = apparmor_mail_user_deinit; + MODULE_CONTEXT_SET(user, apparmor_mail_user_module, auser); + + /* generate a magic token */ + random_fill(&auser->token, sizeof(auser->token)); + + /* try change hat */ + if (aa_change_hatv(array_front_modifiable(&hats), auser->token) < 0) { + i_fatal("aa_change_hatv(%s) failed: %m", + t_array_const_string_join(&hats, ",")); + } + + apparmor_log_current_context(user); +} + +static const struct mail_storage_hooks apparmor_hooks = { + .mail_user_created = apparmor_mail_user_created +}; + +void apparmor_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &apparmor_hooks); +} + +void apparmor_plugin_deinit(void) +{ + mail_storage_hooks_remove(&apparmor_hooks); +} diff --git a/src/plugins/charset-alias/Makefile.am b/src/plugins/charset-alias/Makefile.am new file mode 100644 index 0000000..da99095 --- /dev/null +++ b/src/plugins/charset-alias/Makefile.am @@ -0,0 +1,19 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib20_charset_alias_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_charset_alias_plugin.la + +lib20_charset_alias_plugin_la_SOURCES = \ + charset-alias-plugin.c + +noinst_HEADERS = \ + charset-alias-plugin.h diff --git a/src/plugins/charset-alias/Makefile.in b/src/plugins/charset-alias/Makefile.in new file mode 100644 index 0000000..ffb29dc --- /dev/null +++ b/src/plugins/charset-alias/Makefile.in @@ -0,0 +1,822 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/charset-alias +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_charset_alias_plugin_la_LIBADD = +am_lib20_charset_alias_plugin_la_OBJECTS = charset-alias-plugin.lo +lib20_charset_alias_plugin_la_OBJECTS = \ + $(am_lib20_charset_alias_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_charset_alias_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(lib20_charset_alias_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/charset-alias-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_charset_alias_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_charset_alias_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +lib20_charset_alias_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_charset_alias_plugin.la + +lib20_charset_alias_plugin_la_SOURCES = \ + charset-alias-plugin.c + +noinst_HEADERS = \ + charset-alias-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/charset-alias/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/charset-alias/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_charset_alias_plugin.la: $(lib20_charset_alias_plugin_la_OBJECTS) $(lib20_charset_alias_plugin_la_DEPENDENCIES) $(EXTRA_lib20_charset_alias_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_charset_alias_plugin_la_LINK) -rpath $(moduledir) $(lib20_charset_alias_plugin_la_OBJECTS) $(lib20_charset_alias_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/charset-alias-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/charset-alias-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/charset-alias-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/charset-alias/charset-alias-plugin.c b/src/plugins/charset-alias/charset-alias-plugin.c new file mode 100644 index 0000000..612223e --- /dev/null +++ b/src/plugins/charset-alias/charset-alias-plugin.c @@ -0,0 +1,198 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "mail-user.h" +#include "mail-storage-private.h" +#include "mail-storage-hooks.h" +#include "charset-utf8-private.h" +#include "charset-alias-plugin.h" + + +#define CHARSET_ALIAS_USER_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, charset_alias_user_module) + +static MODULE_CONTEXT_DEFINE_INIT(charset_alias_user_module, + &mail_user_module_register); + +const char *charset_alias_plugin_version = DOVECOT_ABI_VERSION; + +static int charset_alias_to_utf8_begin(const char *charset, + normalizer_func_t *normalizer, + struct charset_translation **t_r); + +static void charset_alias_to_utf8_end(struct charset_translation *t); + +static void charset_alias_to_utf8_reset(struct charset_translation *t); + +static enum charset_result charset_alias_to_utf8(struct charset_translation *t, + const unsigned char *src, + size_t *src_size, buffer_t *dest); + +/* charset_utf8_vfuncs is defined in lib-charset/charset-utf8.c */ +extern const struct charset_utf8_vfuncs *charset_utf8_vfuncs; + +static const struct charset_utf8_vfuncs *original_charset_utf8_vfuncs; + +static const struct charset_utf8_vfuncs charset_alias_utf8_vfuncs = { + charset_alias_to_utf8_begin, + charset_alias_to_utf8_end, + charset_alias_to_utf8_reset, + charset_alias_to_utf8 +}; + +struct charset_alias { + const char *charset; + const char *alias; +}; + +static ARRAY(struct charset_alias) charset_aliases; +static pool_t charset_alias_pool; +static int charset_alias_user_refcount = 0; + +struct charset_alias_user { + union mail_user_module_context module_ctx; +}; + + +static const char *charset_alias_get_alias(const char *charset) +{ + const struct charset_alias* elem; + const char *key; + + if (array_is_created(&charset_aliases)) { + key = t_str_lcase(charset); + array_foreach(&charset_aliases, elem) { + if (strcmp(key, elem->charset) == 0) { + return elem->alias; + } + } + } + return charset; +} + +static int charset_alias_to_utf8_begin(const char *charset, + normalizer_func_t *normalizer, + struct charset_translation **t_r) +{ + i_assert(original_charset_utf8_vfuncs != NULL); + charset = charset_alias_get_alias(charset); + return original_charset_utf8_vfuncs->to_utf8_begin(charset, normalizer, t_r); +} +static void charset_alias_to_utf8_end(struct charset_translation *t) +{ + i_assert(original_charset_utf8_vfuncs != NULL); + original_charset_utf8_vfuncs->to_utf8_end(t); +} + +static void charset_alias_to_utf8_reset(struct charset_translation *t) +{ + i_assert(original_charset_utf8_vfuncs != NULL); + original_charset_utf8_vfuncs->to_utf8_reset(t); +} + +static enum charset_result charset_alias_to_utf8(struct charset_translation *t, + const unsigned char *src, + size_t *src_size, buffer_t *dest) +{ + i_assert(original_charset_utf8_vfuncs != NULL); + return original_charset_utf8_vfuncs->to_utf8(t, src, src_size, dest); +} + +static unsigned int charset_aliases_init(struct mail_user *user, pool_t pool, const char *str) +{ + const char *key, *value, *const *keyvalues; + struct charset_alias alias; + int i; + + i_assert(!array_is_created(&charset_aliases)); + + p_array_init(&charset_aliases, pool, 1); + keyvalues = t_strsplit_spaces(str, " "); + for (i = 0; keyvalues[i] != NULL; i++) { + value = strchr(keyvalues[i], '='); + if (value == NULL) { + i_error("charset_alias: Missing '=' in charset_aliases setting"); + continue; + } + key = t_strdup_until(keyvalues[i], value++); + if (key[0] == '\0' || value[0] == '\0') { + i_error("charset_alias: charset or alias missing in charset_aliases setting"); + continue; + } + if (strcasecmp(key, value) != 0) { + e_debug(user->event, "charset_alias: add charset-alias %s for %s", value, key); + alias.charset = p_strdup(pool, t_str_lcase(key)); + alias.alias = p_strdup(pool, value); + array_push_back(&charset_aliases, &alias); + } + } + return array_count(&charset_aliases); +} + +static void charset_alias_utf8_vfuncs_set(void) +{ + original_charset_utf8_vfuncs = charset_utf8_vfuncs; + charset_utf8_vfuncs = &charset_alias_utf8_vfuncs; +} + +static void charset_alias_utf8_vfuncs_reset(void) +{ + if (original_charset_utf8_vfuncs != NULL) { + charset_utf8_vfuncs = original_charset_utf8_vfuncs; + original_charset_utf8_vfuncs = NULL; + } +} + +static void charset_alias_mail_user_deinit(struct mail_user *user) +{ + struct charset_alias_user *cuser = CHARSET_ALIAS_USER_CONTEXT(user); + + i_assert(charset_alias_user_refcount > 0); + if (--charset_alias_user_refcount == 0) { + charset_alias_utf8_vfuncs_reset(); + array_free(&charset_aliases); + pool_unref(&charset_alias_pool); + } + + cuser->module_ctx.super.deinit(user); +} + +static void charset_alias_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct charset_alias_user *cuser; + const char *str; + + cuser = p_new(user->pool, struct charset_alias_user, 1); + cuser->module_ctx.super = *v; + user->vlast = &cuser->module_ctx.super; + v->deinit = charset_alias_mail_user_deinit; + + if (charset_alias_user_refcount++ == 0) { + charset_alias_pool = pool_alloconly_create("charset_alias alias list", 128); + str = mail_user_plugin_getenv(user, "charset_aliases"); + if (str != NULL && str[0] != '\0') { + if (charset_aliases_init(user, charset_alias_pool, str) > 0) { + charset_alias_utf8_vfuncs_set(); + } + } + } + + MODULE_CONTEXT_SET(user, charset_alias_user_module, cuser); +} + +static struct mail_storage_hooks charset_alias_mail_storage_hooks = { + .mail_user_created = charset_alias_mail_user_created +}; + +void charset_alias_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &charset_alias_mail_storage_hooks); +} + +void charset_alias_plugin_deinit(void) +{ + mail_storage_hooks_remove(&charset_alias_mail_storage_hooks); +} diff --git a/src/plugins/charset-alias/charset-alias-plugin.h b/src/plugins/charset-alias/charset-alias-plugin.h new file mode 100644 index 0000000..0a1d518 --- /dev/null +++ b/src/plugins/charset-alias/charset-alias-plugin.h @@ -0,0 +1,7 @@ +#ifndef CHARSET_ALIAS_PLUGIN_H +#define CHARSET_ALIAS_PLUGIN_H + +void charset_alias_plugin_init(struct module *module); +void charset_alias_plugin_deinit(void); + +#endif diff --git a/src/plugins/fs-compress/Makefile.am b/src/plugins/fs-compress/Makefile.am new file mode 100644 index 0000000..77ba733 --- /dev/null +++ b/src/plugins/fs-compress/Makefile.am @@ -0,0 +1,14 @@ +fs_moduledir = $(moduledir) +fs_module_LTLIBRARIES = \ + libfs_compress.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-compression \ + -I$(top_srcdir)/src/lib-fs + +NOPLUGIN_LDFLAGS = +libfs_compress_la_SOURCES = fs-compress.c +libfs_compress_la_LIBADD = ../../lib-compression/libdovecot-compression.la +libfs_compress_la_DEPENDENCIES = ../../lib-compression/libdovecot-compression.la +libfs_compress_la_LDFLAGS = -module -avoid-version diff --git a/src/plugins/fs-compress/Makefile.in b/src/plugins/fs-compress/Makefile.in new file mode 100644 index 0000000..255f03d --- /dev/null +++ b/src/plugins/fs-compress/Makefile.in @@ -0,0 +1,811 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/fs-compress +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(fs_moduledir)" +LTLIBRARIES = $(fs_module_LTLIBRARIES) +am_libfs_compress_la_OBJECTS = fs-compress.lo +libfs_compress_la_OBJECTS = $(am_libfs_compress_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libfs_compress_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libfs_compress_la_LDFLAGS) $(LDFLAGS) \ + -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/fs-compress.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libfs_compress_la_SOURCES) +DIST_SOURCES = $(libfs_compress_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +fs_moduledir = $(moduledir) +fs_module_LTLIBRARIES = \ + libfs_compress.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-compression \ + -I$(top_srcdir)/src/lib-fs + +libfs_compress_la_SOURCES = fs-compress.c +libfs_compress_la_LIBADD = ../../lib-compression/libdovecot-compression.la +libfs_compress_la_DEPENDENCIES = ../../lib-compression/libdovecot-compression.la +libfs_compress_la_LDFLAGS = -module -avoid-version +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fs-compress/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/fs-compress/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-fs_moduleLTLIBRARIES: $(fs_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(fs_module_LTLIBRARIES)'; test -n "$(fs_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(fs_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(fs_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(fs_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(fs_moduledir)"; \ + } + +uninstall-fs_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(fs_module_LTLIBRARIES)'; test -n "$(fs_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(fs_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(fs_moduledir)/$$f"; \ + done + +clean-fs_moduleLTLIBRARIES: + -test -z "$(fs_module_LTLIBRARIES)" || rm -f $(fs_module_LTLIBRARIES) + @list='$(fs_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libfs_compress.la: $(libfs_compress_la_OBJECTS) $(libfs_compress_la_DEPENDENCIES) $(EXTRA_libfs_compress_la_DEPENDENCIES) + $(AM_V_CCLD)$(libfs_compress_la_LINK) -rpath $(fs_moduledir) $(libfs_compress_la_OBJECTS) $(libfs_compress_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fs-compress.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(fs_moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-fs_moduleLTLIBRARIES clean-generic clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/fs-compress.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-fs_moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/fs-compress.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-fs_moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-fs_moduleLTLIBRARIES clean-generic clean-libtool \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-fs_moduleLTLIBRARIES \ + install-html install-html-am install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-fs_moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/fs-compress/fs-compress.c b/src/plugins/fs-compress/fs-compress.c new file mode 100644 index 0000000..ab03076 --- /dev/null +++ b/src/plugins/fs-compress/fs-compress.c @@ -0,0 +1,285 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "istream-tee.h" +#include "istream-try.h" +#include "ostream.h" +#include "iostream-temp.h" +#include "compression.h" +#include "fs-api-private.h" + +struct compress_fs { + struct fs fs; + const struct compression_handler *compress_handler; + int compress_level; + bool try_plain; +}; + +struct compress_fs_file { + struct fs_file file; + struct compress_fs *fs; + struct fs_file *super_read; + enum fs_open_mode open_mode; + struct istream *input; + + struct ostream *super_output; + struct ostream *temp_output; +}; + +#define COMPRESS_FS(ptr) container_of((ptr), struct compress_fs, fs) +#define COMPRESS_FILE(ptr) container_of((ptr), struct compress_fs_file, file) + +extern const struct fs fs_class_compress; + +static struct fs *fs_compress_alloc(void) +{ + struct compress_fs *fs; + + fs = i_new(struct compress_fs, 1); + fs->fs = fs_class_compress; + return &fs->fs; +} + +static int +fs_compress_init(struct fs *_fs, const char *args, + const struct fs_settings *set, const char **error_r) +{ + struct compress_fs *fs = COMPRESS_FS(_fs); + const char *p, *compression_name, *level_str; + const char *parent_name, *parent_args; + int ret; + + /* get compression handler name */ + if (str_begins(args, "maybe-")) { + fs->try_plain = TRUE; + args += 6; + } + + p = strchr(args, ':'); + if (p == NULL) { + *error_r = "Compression method not given as parameter"; + return -1; + } + compression_name = t_strdup_until(args, p++); + args = p; + + /* get compression level */ + p = strchr(args, ':'); + if (p == NULL || p[1] == '\0') { + *error_r = "Parent filesystem not given as parameter"; + return -1; + } + + level_str = t_strdup_until(args, p++); + args = p; + ret = compression_lookup_handler(compression_name, &fs->compress_handler); + if (ret <= 0) { + *error_r = t_strdup_printf("Compression method '%s' %s.", + compression_name, ret == 0 ? + "not supported" : "unknown"); + return -1; + } + if (str_to_int(level_str, &fs->compress_level) < 0 || + fs->compress_level < fs->compress_handler->get_min_level() || + fs->compress_level > fs->compress_handler->get_max_level()) { + *error_r = t_strdup_printf( + "Invalid compression level parameter '%s': " + "Level must be between %d..%d", level_str, + fs->compress_handler->get_min_level(), + fs->compress_handler->get_max_level()); + return -1; + } + parent_args = strchr(args, ':'); + if (parent_args == NULL) { + parent_name = args; + parent_args = ""; + } else { + parent_name = t_strdup_until(args, parent_args); + parent_args++; + } + return fs_init(parent_name, parent_args, set, &_fs->parent, error_r); +} + +static void fs_compress_free(struct fs *_fs) +{ + struct compress_fs *fs = COMPRESS_FS(_fs); + + i_free(fs); +} + +static struct fs_file *fs_compress_file_alloc(void) +{ + struct compress_fs_file *file = i_new(struct compress_fs_file, 1); + return &file->file; +} + +static void +fs_compress_file_init(struct fs_file *_file, const char *path, + enum fs_open_mode mode, enum fs_open_flags flags) +{ + struct compress_fs *fs = COMPRESS_FS(_file->fs); + struct compress_fs_file *file = COMPRESS_FILE(_file); + + file->file.path = i_strdup(path); + file->fs = fs; + file->open_mode = mode; + + /* avoid unnecessarily creating two seekable streams */ + flags &= ENUM_NEGATE(FS_OPEN_FLAG_SEEKABLE); + + file->file.parent = fs_file_init_parent(_file, path, mode, flags); + if (mode == FS_OPEN_MODE_READONLY && + (flags & FS_OPEN_FLAG_ASYNC) == 0) { + /* use async stream for parent, so fs_read_stream() won't create + another seekable stream needlessly */ + file->super_read = fs_file_init_parent(_file, path, + mode, flags | FS_OPEN_FLAG_ASYNC | + FS_OPEN_FLAG_ASYNC_NOQUEUE); + } else { + file->super_read = file->file.parent; + } +} + +static void fs_compress_file_deinit(struct fs_file *_file) +{ + struct compress_fs_file *file = COMPRESS_FILE(_file); + + if (file->super_read != _file->parent) + fs_file_deinit(&file->super_read); + fs_file_free(_file); + i_free(file->file.path); + i_free(file); +} + +static void fs_compress_file_close(struct fs_file *_file) +{ + struct compress_fs_file *file = COMPRESS_FILE(_file); + + i_stream_unref(&file->input); + fs_file_close(file->super_read); + fs_file_close(_file->parent); +} + +static struct istream * +fs_compress_read_stream(struct fs_file *_file, size_t max_buffer_size) +{ + struct compress_fs_file *file = COMPRESS_FILE(_file); + struct istream *input; + enum istream_decompress_flags flags = 0; + + if (file->input != NULL) { + i_stream_ref(file->input); + i_stream_seek(file->input, 0); + return file->input; + } + + input = fs_read_stream(file->super_read, max_buffer_size); + if (file->fs->try_plain) + flags |= ISTREAM_DECOMPRESS_FLAG_TRY; + file->input = i_stream_create_decompress(input, flags); + i_stream_unref(&input); + i_stream_ref(file->input); + return file->input; +} + +static void fs_compress_write_stream(struct fs_file *_file) +{ + struct compress_fs_file *file = COMPRESS_FILE(_file); + + if (file->fs->compress_level == 0) { + fs_wrapper_write_stream(_file); + return; + } + + i_assert(_file->output == NULL); + + file->temp_output = + iostream_temp_create_named(_file->fs->temp_path_prefix, + IOSTREAM_TEMP_FLAG_TRY_FD_DUP, + fs_file_path(_file)); + _file->output = file->fs->compress_handler-> + create_ostream(file->temp_output, file->fs->compress_level); +} + +static int fs_compress_write_stream_finish(struct fs_file *_file, bool success) +{ + struct compress_fs_file *file = COMPRESS_FILE(_file); + struct istream *input; + int ret; + + if (file->fs->compress_level == 0) + return fs_wrapper_write_stream_finish(_file, success); + + if (_file->output != NULL) { + if (_file->output->closed) + success = FALSE; + if (_file->output == file->super_output) + _file->output = NULL; + else + o_stream_unref(&_file->output); + } + if (!success) { + o_stream_destroy(&file->temp_output); + if (file->super_output != NULL) + fs_write_stream_abort_parent(_file, &file->super_output); + return -1; + } + + if (file->super_output != NULL) { + i_assert(file->temp_output == NULL); + return fs_write_stream_finish(_file->parent, &file->super_output); + } + if (file->temp_output == NULL) { + /* finishing up */ + i_assert(file->super_output == NULL); + return fs_write_stream_finish(_file->parent, &file->temp_output); + } + /* finish writing the temporary file */ + input = iostream_temp_finish(&file->temp_output, IO_BLOCK_SIZE); + file->super_output = fs_write_stream(_file->parent); + o_stream_nsend_istream(file->super_output, input); + ret = fs_write_stream_finish(_file->parent, &file->super_output); + i_stream_unref(&input); + return ret; +} + +const struct fs fs_class_compress = { + .name = "compress", + .v = { + fs_compress_alloc, + fs_compress_init, + NULL, + fs_compress_free, + fs_wrapper_get_properties, + fs_compress_file_alloc, + fs_compress_file_init, + fs_compress_file_deinit, + fs_compress_file_close, + fs_wrapper_file_get_path, + fs_wrapper_set_async_callback, + fs_wrapper_wait_async, + fs_wrapper_set_metadata, + fs_wrapper_get_metadata, + fs_wrapper_prefetch, + fs_read_via_stream, + fs_compress_read_stream, + fs_write_via_stream, + fs_compress_write_stream, + fs_compress_write_stream_finish, + fs_wrapper_lock, + fs_wrapper_unlock, + fs_wrapper_exists, + fs_wrapper_stat, + fs_wrapper_copy, + fs_wrapper_rename, + fs_wrapper_delete, + fs_wrapper_iter_alloc, + fs_wrapper_iter_init, + fs_wrapper_iter_next, + fs_wrapper_iter_deinit, + NULL, + fs_wrapper_get_nlinks + } +}; diff --git a/src/plugins/fts-lucene/Makefile.am b/src/plugins/fts-lucene/Makefile.am new file mode 100644 index 0000000..d68e6ae --- /dev/null +++ b/src/plugins/fts-lucene/Makefile.am @@ -0,0 +1,61 @@ +doveadm_moduledir = $(moduledir)/doveadm + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/fts \ + -I$(top_srcdir)/src/doveadm + +AM_CXXFLAGS = \ + $(CLUCENE_CFLAGS) \ + $(LIBEXTTEXTCAT_CFLAGS) + +NOPLUGIN_LDFLAGS = +lib21_fts_lucene_plugin_la_LDFLAGS = -module -avoid-version +lib20_doveadm_fts_lucene_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib21_fts_lucene_plugin.la + +if BUILD_FTS_STEMMER +STEMMER_LIBS = -lstemmer +SHOWBALL_SOURCES = Snowball.cc +endif + +if BUILD_FTS_EXTTEXTCAT +TEXTCAT_LIBS = $(LIBEXTTEXTCAT_LIBS) +else +if BUILD_FTS_TEXTCAT +TEXTCAT_LIBS = -ltextcat +endif +endif + +lib21_fts_lucene_plugin_la_LIBADD = \ + $(CLUCENE_LIBS) $(TEXTCAT_LIBS) $(STEMMER_LIBS) + +lib21_fts_lucene_plugin_la_SOURCES = \ + fts-lucene-plugin.c \ + fts-backend-lucene.c \ + lucene-wrapper.cc \ + $(SHOWBALL_SOURCES) + +noinst_HEADERS = \ + fts-lucene-plugin.h \ + lucene-wrapper.h \ + SnowballAnalyzer.h \ + SnowballFilter.h + +if BUILD_FTS_TEXTCAT +exampledir = $(docdir)/example-config +example_DATA = \ + textcat.conf +endif +EXTRA_DIST = textcat.conf + +doveadm_module_LTLIBRARIES = \ + lib20_doveadm_fts_lucene_plugin.la + +lib20_doveadm_fts_lucene_plugin_la_SOURCES = \ + doveadm-fts-lucene.c diff --git a/src/plugins/fts-lucene/Makefile.in b/src/plugins/fts-lucene/Makefile.in new file mode 100644 index 0000000..323982d --- /dev/null +++ b/src/plugins/fts-lucene/Makefile.in @@ -0,0 +1,990 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/fts-lucene +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(doveadm_moduledir)" \ + "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(exampledir)" +LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES) +lib20_doveadm_fts_lucene_plugin_la_LIBADD = +am_lib20_doveadm_fts_lucene_plugin_la_OBJECTS = doveadm-fts-lucene.lo +lib20_doveadm_fts_lucene_plugin_la_OBJECTS = \ + $(am_lib20_doveadm_fts_lucene_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_doveadm_fts_lucene_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(lib20_doveadm_fts_lucene_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +am__DEPENDENCIES_1 = +@BUILD_FTS_EXTTEXTCAT_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +lib21_fts_lucene_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +am__lib21_fts_lucene_plugin_la_SOURCES_DIST = fts-lucene-plugin.c \ + fts-backend-lucene.c lucene-wrapper.cc Snowball.cc +@BUILD_FTS_STEMMER_TRUE@am__objects_1 = Snowball.lo +am_lib21_fts_lucene_plugin_la_OBJECTS = fts-lucene-plugin.lo \ + fts-backend-lucene.lo lucene-wrapper.lo $(am__objects_1) +lib21_fts_lucene_plugin_la_OBJECTS = \ + $(am_lib21_fts_lucene_plugin_la_OBJECTS) +lib21_fts_lucene_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) \ + $(lib21_fts_lucene_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/Snowball.Plo \ + ./$(DEPDIR)/doveadm-fts-lucene.Plo \ + ./$(DEPDIR)/fts-backend-lucene.Plo \ + ./$(DEPDIR)/fts-lucene-plugin.Plo \ + ./$(DEPDIR)/lucene-wrapper.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(lib20_doveadm_fts_lucene_plugin_la_SOURCES) \ + $(lib21_fts_lucene_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_doveadm_fts_lucene_plugin_la_SOURCES) \ + $(am__lib21_fts_lucene_plugin_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +DATA = $(example_DATA) +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +doveadm_moduledir = $(moduledir)/doveadm +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/fts \ + -I$(top_srcdir)/src/doveadm + +AM_CXXFLAGS = \ + $(CLUCENE_CFLAGS) \ + $(LIBEXTTEXTCAT_CFLAGS) + +lib21_fts_lucene_plugin_la_LDFLAGS = -module -avoid-version +lib20_doveadm_fts_lucene_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib21_fts_lucene_plugin.la + +@BUILD_FTS_STEMMER_TRUE@STEMMER_LIBS = -lstemmer +@BUILD_FTS_STEMMER_TRUE@SHOWBALL_SOURCES = Snowball.cc +@BUILD_FTS_EXTTEXTCAT_FALSE@@BUILD_FTS_TEXTCAT_TRUE@TEXTCAT_LIBS = -ltextcat +@BUILD_FTS_EXTTEXTCAT_TRUE@TEXTCAT_LIBS = $(LIBEXTTEXTCAT_LIBS) +lib21_fts_lucene_plugin_la_LIBADD = \ + $(CLUCENE_LIBS) $(TEXTCAT_LIBS) $(STEMMER_LIBS) + +lib21_fts_lucene_plugin_la_SOURCES = \ + fts-lucene-plugin.c \ + fts-backend-lucene.c \ + lucene-wrapper.cc \ + $(SHOWBALL_SOURCES) + +noinst_HEADERS = \ + fts-lucene-plugin.h \ + lucene-wrapper.h \ + SnowballAnalyzer.h \ + SnowballFilter.h + +@BUILD_FTS_TEXTCAT_TRUE@exampledir = $(docdir)/example-config +@BUILD_FTS_TEXTCAT_TRUE@example_DATA = \ +@BUILD_FTS_TEXTCAT_TRUE@ textcat.conf + +EXTRA_DIST = textcat.conf +doveadm_module_LTLIBRARIES = \ + lib20_doveadm_fts_lucene_plugin.la + +lib20_doveadm_fts_lucene_plugin_la_SOURCES = \ + doveadm-fts-lucene.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .cc .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fts-lucene/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/fts-lucene/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \ + } + +uninstall-doveadm_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \ + done + +clean-doveadm_moduleLTLIBRARIES: + -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES) + @list='$(doveadm_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_doveadm_fts_lucene_plugin.la: $(lib20_doveadm_fts_lucene_plugin_la_OBJECTS) $(lib20_doveadm_fts_lucene_plugin_la_DEPENDENCIES) $(EXTRA_lib20_doveadm_fts_lucene_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_doveadm_fts_lucene_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib20_doveadm_fts_lucene_plugin_la_OBJECTS) $(lib20_doveadm_fts_lucene_plugin_la_LIBADD) $(LIBS) + +lib21_fts_lucene_plugin.la: $(lib21_fts_lucene_plugin_la_OBJECTS) $(lib21_fts_lucene_plugin_la_DEPENDENCIES) $(EXTRA_lib21_fts_lucene_plugin_la_DEPENDENCIES) + $(AM_V_CXXLD)$(lib21_fts_lucene_plugin_la_LINK) -rpath $(moduledir) $(lib21_fts_lucene_plugin_la_OBJECTS) $(lib21_fts_lucene_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Snowball.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-fts-lucene.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-backend-lucene.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-lucene-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lucene-wrapper.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-exampleDATA: $(example_DATA) + @$(NORMAL_INSTALL) + @list='$(example_DATA)'; test -n "$(exampledir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(exampledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(exampledir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(exampledir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(exampledir)" || exit $$?; \ + done + +uninstall-exampleDATA: + @$(NORMAL_UNINSTALL) + @list='$(example_DATA)'; test -n "$(exampledir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(exampledir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(DATA) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(exampledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/Snowball.Plo + -rm -f ./$(DEPDIR)/doveadm-fts-lucene.Plo + -rm -f ./$(DEPDIR)/fts-backend-lucene.Plo + -rm -f ./$(DEPDIR)/fts-lucene-plugin.Plo + -rm -f ./$(DEPDIR)/lucene-wrapper.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-doveadm_moduleLTLIBRARIES install-exampleDATA \ + install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/Snowball.Plo + -rm -f ./$(DEPDIR)/doveadm-fts-lucene.Plo + -rm -f ./$(DEPDIR)/fts-backend-lucene.Plo + -rm -f ./$(DEPDIR)/fts-lucene-plugin.Plo + -rm -f ./$(DEPDIR)/lucene-wrapper.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-exampleDATA uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am \ + install-doveadm_moduleLTLIBRARIES install-dvi install-dvi-am \ + install-exampleDATA install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-doveadm_moduleLTLIBRARIES uninstall-exampleDATA \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/fts-lucene/Snowball.cc b/src/plugins/fts-lucene/Snowball.cc new file mode 100644 index 0000000..43b54e3 --- /dev/null +++ b/src/plugins/fts-lucene/Snowball.cc @@ -0,0 +1,151 @@ +/*------------------------------------------------------------------------------ +* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team +* +* Distributable under the terms of either the Apache License (Version 2.0) or +* the GNU Lesser General Public License, as specified in the COPYING file. +------------------------------------------------------------------------------*/ +#include <CLucene.h> +#include "SnowballAnalyzer.h" +#include "SnowballFilter.h" +#include <CLucene/util/CLStreams.h> +#include <CLucene/analysis/Analyzers.h> +#include <CLucene/analysis/standard/StandardTokenizer.h> +#include <CLucene/analysis/standard/StandardFilter.h> + +extern "C" { +#include "lib.h" +#include "buffer.h" +#include "unichar.h" +#include "lucene-wrapper.h" +}; + +CL_NS_USE(analysis) +CL_NS_USE(util) +CL_NS_USE2(analysis,standard) + +CL_NS_DEF2(analysis,snowball) + + /** Builds the named analyzer with no stop words. */ + SnowballAnalyzer::SnowballAnalyzer(normalizer_func_t *_normalizer, const char* _language) + : language(i_strdup(_language)), + normalizer(_normalizer), + stopSet(NULL), + prevstream(NULL) + { + } + + SnowballAnalyzer::~SnowballAnalyzer() + { + if (prevstream) + _CLDELETE(prevstream); + i_free(language); + if ( stopSet != NULL ) + _CLDELETE(stopSet); + } + + /** Builds the named analyzer with the given stop words. + */ + SnowballAnalyzer::SnowballAnalyzer(const char* language, const TCHAR** stopWords) + : language(i_strdup(language)), + normalizer(NULL), + stopSet(_CLNEW CLTCSetList(true)), + prevstream(NULL) + { + StopFilter::fillStopTable(stopSet,stopWords); + } + + TokenStream* SnowballAnalyzer::tokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader) { + return this->tokenStream(fieldName,reader,false); + } + + /** Constructs a {@link StandardTokenizer} filtered by a {@link + StandardFilter}, a {@link LowerCaseFilter} and a {@link StopFilter}. */ + TokenStream* SnowballAnalyzer::tokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader, bool deleteReader) { + BufferedReader* bufferedReader = reader->__asBufferedReader(); + TokenStream* result; + + if ( bufferedReader == NULL ) + result = _CLNEW StandardTokenizer( _CLNEW FilteredBufferedReader(reader, deleteReader), true ); + else + result = _CLNEW StandardTokenizer(bufferedReader, deleteReader); + + result = _CLNEW StandardFilter(result, true); + result = _CLNEW CL_NS(analysis)::LowerCaseFilter(result, true); + if (stopSet != NULL) + result = _CLNEW CL_NS(analysis)::StopFilter(result, true, stopSet); + result = _CLNEW SnowballFilter(result, normalizer, language, true); + return result; + } + + TokenStream* SnowballAnalyzer::reusableTokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader) { + if (prevstream) _CLDELETE(prevstream); + prevstream = this->tokenStream(fieldName, reader); + return prevstream; + } + + + + + + + /** Construct the named stemming filter. + * + * @param in the input tokens to stem + * @param name the name of a stemmer + */ + SnowballFilter::SnowballFilter(TokenStream* in, normalizer_func_t *normalizer, const char* language, bool deleteTS): + TokenFilter(in,deleteTS) + { + stemmer = sb_stemmer_new(language, NULL); //use utf8 encoding + this->normalizer = normalizer; + + if ( stemmer == NULL ){ + _CLTHROWA(CL_ERR_IllegalArgument, "language not available for stemming\n"); //todo: richer error + } + } + + SnowballFilter::~SnowballFilter(){ + sb_stemmer_delete(stemmer); + } + + /** Returns the next input Token, after being stemmed */ + Token* SnowballFilter::next(Token* token){ + if (input->next(token) == NULL) + return NULL; + + unsigned char utf8text[LUCENE_MAX_WORD_LEN*5+1]; + unsigned int len = I_MIN(LUCENE_MAX_WORD_LEN, token->termLength()); + + buffer_t buf = { { 0, 0 } }; + i_assert(sizeof(wchar_t) == sizeof(unichar_t)); + buffer_create_from_data(&buf, utf8text, sizeof(utf8text)); + uni_ucs4_to_utf8((const unichar_t *)token->termBuffer(), len, &buf); + + const sb_symbol* stemmed = sb_stemmer_stem(stemmer, utf8text, buf.used); + if ( stemmed == NULL ) + _CLTHROWA(CL_ERR_Runtime,"Out of memory"); + + int stemmedLen=sb_stemmer_length(stemmer); + + if (normalizer == NULL) { + unsigned int tchartext_size = + uni_utf8_strlen_n(stemmed, stemmedLen) + 1; + TCHAR tchartext[tchartext_size]; + lucene_utf8_n_to_tchar(stemmed, stemmedLen, tchartext, tchartext_size); + token->set(tchartext,token->startOffset(), token->endOffset(), token->type()); + } else T_BEGIN { + buffer_t *norm_buf = t_buffer_create(stemmedLen); + normalizer(stemmed, stemmedLen, norm_buf); + + unsigned int tchartext_size = + uni_utf8_strlen_n(norm_buf->data, norm_buf->used) + 1; + TCHAR tchartext[tchartext_size]; + lucene_utf8_n_to_tchar((const unsigned char *)norm_buf->data, + norm_buf->used, tchartext, tchartext_size); + token->set(tchartext,token->startOffset(), token->endOffset(), token->type()); + } T_END; + return token; + } + + +CL_NS_END2 diff --git a/src/plugins/fts-lucene/SnowballAnalyzer.h b/src/plugins/fts-lucene/SnowballAnalyzer.h new file mode 100644 index 0000000..45455c5 --- /dev/null +++ b/src/plugins/fts-lucene/SnowballAnalyzer.h @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------------ +* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team +* +* Distributable under the terms of either the Apache License (Version 2.0) or +* the GNU Lesser General Public License, as specified in the COPYING file. +------------------------------------------------------------------------------*/ +#ifndef _lucene_analysis_snowball_analyser_ +#define _lucene_analysis_snowball_analyser_ + +extern "C" { +#include "lib.h" +#include "unichar.h" +}; +#include "CLucene/analysis/AnalysisHeader.h" + +CL_CLASS_DEF(util,BufferedReader) +CL_NS_DEF2(analysis,snowball) + +/** Filters {@link StandardTokenizer} with {@link StandardFilter}, {@link + * LowerCaseFilter}, {@link StopFilter} and {@link SnowballFilter}. + * + * Available stemmers are listed in {@link net.sf.snowball.ext}. The name of a + * stemmer is the part of the class name before "Stemmer", e.g., the stemmer in + * {@link EnglishStemmer} is named "English". + */ +class CLUCENE_CONTRIBS_EXPORT SnowballAnalyzer: public Analyzer { + char* language; + normalizer_func_t *normalizer; + CLTCSetList* stopSet; + TokenStream *prevstream; + +public: + /** Builds the named analyzer with no stop words. */ + SnowballAnalyzer(normalizer_func_t *normalizer, const char* language="english"); + + /** Builds the named analyzer with the given stop words. + */ + SnowballAnalyzer(const char* language, const TCHAR** stopWords); + + ~SnowballAnalyzer(); + + /** Constructs a {@link StandardTokenizer} filtered by a {@link + StandardFilter}, a {@link LowerCaseFilter} and a {@link StopFilter}. */ + TokenStream* tokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader); + TokenStream* tokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader, bool deleteReader); + TokenStream* reusableTokenStream(const TCHAR* fieldName, CL_NS(util)::Reader* reader); +}; + +CL_NS_END2 +#endif + diff --git a/src/plugins/fts-lucene/SnowballFilter.h b/src/plugins/fts-lucene/SnowballFilter.h new file mode 100644 index 0000000..6a0ed12 --- /dev/null +++ b/src/plugins/fts-lucene/SnowballFilter.h @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------------ +* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team +* +* Distributable under the terms of either the Apache License (Version 2.0) or +* the GNU Lesser General Public License, as specified in the COPYING file. +------------------------------------------------------------------------------*/ +#ifndef _lucene_analysis_snowball_filter_ +#define _lucene_analysis_snowball_filter_ + +#include "CLucene/analysis/AnalysisHeader.h" +#include "libstemmer.h" + +CL_NS_DEF2(analysis,snowball) + +/** A filter that stems words using a Snowball-generated stemmer. + * + * Available stemmers are listed in {@link net.sf.snowball.ext}. The name of a + * stemmer is the part of the class name before "Stemmer", e.g., the stemmer in + * {@link EnglishStemmer} is named "English". + * + * Note: todo: This is not thread safe... + */ +class CLUCENE_CONTRIBS_EXPORT SnowballFilter: public TokenFilter { + struct sb_stemmer * stemmer; + normalizer_func_t *normalizer; +public: + + /** Construct the named stemming filter. + * + * @param in the input tokens to stem + * @param name the name of a stemmer + */ + SnowballFilter(TokenStream* in, normalizer_func_t *normalizer, const char* language, bool deleteTS); + + ~SnowballFilter(); + + /** Returns the next input Token, after being stemmed */ + Token* next(Token* token); +}; + +CL_NS_END2 +#endif diff --git a/src/plugins/fts-lucene/doveadm-fts-lucene.c b/src/plugins/fts-lucene/doveadm-fts-lucene.c new file mode 100644 index 0000000..a761907 --- /dev/null +++ b/src/plugins/fts-lucene/doveadm-fts-lucene.c @@ -0,0 +1,70 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "doveadm-dump.h" +#include "doveadm-fts.h" +#include "lucene-wrapper.h" + +#include <stdio.h> +#include <sys/stat.h> + +const char *doveadm_fts_lucene_plugin_version = DOVECOT_ABI_VERSION; + +void doveadm_fts_lucene_plugin_init(struct module *module); +void doveadm_fts_lucene_plugin_deinit(void); + +static void +cmd_dump_fts_lucene(const char *path, const char *const *args ATTR_UNUSED) +{ + struct lucene_index *index; + struct lucene_index_iter *iter; + guid_128_t prev_guid; + const struct lucene_index_record *rec; + bool first = TRUE; + + i_zero(&prev_guid); + index = lucene_index_init(path, NULL, NULL); + iter = lucene_index_iter_init(index); + while ((rec = lucene_index_iter_next(iter)) != NULL) { + if (memcmp(prev_guid, rec->mailbox_guid, + sizeof(prev_guid)) != 0) { + if (first) + first = FALSE; + else + printf("\n"); + memcpy(prev_guid, rec->mailbox_guid, sizeof(prev_guid)); + printf("%s: ", guid_128_to_string(prev_guid)); + } + printf("%u", rec->uid); + if (rec->part_num != 0) + printf("[%u]", rec->part_num); + printf("\n"); + } + printf("\n"); + if (lucene_index_iter_deinit(&iter) < 0) + i_error("Lucene index iteration failed"); + lucene_index_deinit(index); +} + +static bool test_dump_fts_lucene(const char *path) +{ + struct stat st; + + path = t_strconcat(path, "/segments.gen", NULL); + return stat(path, &st) == 0; +} + +static const struct doveadm_cmd_dump doveadm_cmd_dump_fts_lucene = { + "fts-lucene", + test_dump_fts_lucene, + cmd_dump_fts_lucene +}; + +void doveadm_fts_lucene_plugin_init(struct module *module ATTR_UNUSED) +{ + doveadm_dump_register(&doveadm_cmd_dump_fts_lucene); +} + +void doveadm_fts_lucene_plugin_deinit(void) +{ +} diff --git a/src/plugins/fts-lucene/fts-backend-lucene.c b/src/plugins/fts-lucene/fts-backend-lucene.c new file mode 100644 index 0000000..963dbdf --- /dev/null +++ b/src/plugins/fts-lucene/fts-backend-lucene.c @@ -0,0 +1,605 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "hex-binary.h" +#include "strescape.h" +#include "message-part.h" +#include "mail-namespace.h" +#include "mail-storage-private.h" +#include "fts-expunge-log.h" +#include "lucene-wrapper.h" +#include "fts-indexer.h" +#include "fts-lucene-plugin.h" + +#include <wchar.h> + +#define LUCENE_INDEX_DIR_NAME "lucene-indexes" +#define LUCENE_EXPUNGE_LOG_NAME "dovecot-expunges.log" +#define LUCENE_OPTIMIZE_BATCH_MSGS_COUNT 100 + +struct lucene_fts_backend { + struct fts_backend backend; + char *dir_path; + + struct lucene_index *index; + struct mailbox *selected_box; + unsigned int selected_box_generation; + guid_128_t selected_box_guid; + + struct fts_expunge_log *expunge_log; + + bool dir_created:1; + bool updating:1; +}; + +struct lucene_fts_backend_update_context { + struct fts_backend_update_context ctx; + + struct mailbox *box; + uint32_t last_uid; + uint32_t last_indexed_uid; + char *first_box_vname; + + uint32_t uid, part_num; + char *hdr_name; + + unsigned int added_msgs; + struct fts_expunge_log_append_ctx *expunge_ctx; + + bool lucene_opened; + bool last_indexed_uid_set; + bool mime_parts; +}; + +static int fts_backend_lucene_mkdir(struct lucene_fts_backend *backend) +{ + if (backend->dir_created) + return 0; + + backend->dir_created = TRUE; + if (mailbox_list_mkdir_root(backend->backend.ns->list, + backend->dir_path, + MAILBOX_LIST_PATH_TYPE_INDEX) < 0) + return -1; + return 0; +} + +static int +fts_lucene_get_mailbox_guid(struct mailbox *box, guid_128_t guid_r) +{ + struct mailbox_metadata metadata; + + if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, + &metadata) < 0) { + i_error("lucene: Couldn't get mailbox %s GUID: %s", + box->vname, mailbox_get_last_internal_error(box, NULL)); + return -1; + } + memcpy(guid_r, metadata.guid, GUID_128_SIZE); + return 0; +} + +static int +fts_backend_select(struct lucene_fts_backend *backend, struct mailbox *box) +{ + guid_128_t guid; + unsigned char guid_hex[MAILBOX_GUID_HEX_LENGTH]; + wchar_t wguid_hex[MAILBOX_GUID_HEX_LENGTH]; + buffer_t buf; + unsigned int i; + + i_assert(box != NULL); + + if (backend->selected_box == box && + backend->selected_box_generation == box->generation_sequence) + return 0; + + if (fts_lucene_get_mailbox_guid(box, guid) < 0) + return -1; + buffer_create_from_data(&buf, guid_hex, MAILBOX_GUID_HEX_LENGTH); + binary_to_hex_append(&buf, guid, GUID_128_SIZE); + for (i = 0; i < N_ELEMENTS(wguid_hex); i++) + wguid_hex[i] = guid_hex[i]; + + lucene_index_select_mailbox(backend->index, wguid_hex); + + backend->selected_box = box; + memcpy(backend->selected_box_guid, guid, + sizeof(backend->selected_box_guid)); + backend->selected_box_generation = box->generation_sequence; + return 0; +} + +static struct fts_backend *fts_backend_lucene_alloc(void) +{ + struct lucene_fts_backend *backend; + + backend = i_new(struct lucene_fts_backend, 1); + backend->backend = fts_backend_lucene; + return &backend->backend; +} + +static int +fts_backend_lucene_init(struct fts_backend *_backend, const char **error_r) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + struct fts_lucene_user *fuser = + FTS_LUCENE_USER_CONTEXT(_backend->ns->user); + const char *path; + + if (fuser == NULL) { + /* invalid settings */ + *error_r = "Invalid fts_lucene settings"; + return -1; + } + /* fts already checked that index exists */ + + if (fuser->set.use_libfts) { + /* change our flags so we get proper input */ + _backend->flags &= ENUM_NEGATE(FTS_BACKEND_FLAG_FUZZY_SEARCH); + _backend->flags |= FTS_BACKEND_FLAG_TOKENIZED_INPUT; + } + path = mailbox_list_get_root_forced(_backend->ns->list, + MAILBOX_LIST_PATH_TYPE_INDEX); + + backend->dir_path = i_strconcat(path, "/"LUCENE_INDEX_DIR_NAME, NULL); + backend->index = lucene_index_init(backend->dir_path, + _backend->ns->list, + &fuser->set); + + path = t_strconcat(backend->dir_path, "/"LUCENE_EXPUNGE_LOG_NAME, NULL); + backend->expunge_log = fts_expunge_log_init(path); + return 0; +} + +static void fts_backend_lucene_deinit(struct fts_backend *_backend) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + + if (backend->index != NULL) + lucene_index_deinit(backend->index); + if (backend->expunge_log != NULL) + fts_expunge_log_deinit(&backend->expunge_log); + i_free(backend->dir_path); + i_free(backend); +} + +static int +fts_backend_lucene_get_last_uid(struct fts_backend *_backend, + struct mailbox *box, uint32_t *last_uid_r) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + struct fts_lucene_user *fuser = + FTS_LUCENE_USER_CONTEXT_REQUIRE(_backend->ns->user); + struct fts_index_header hdr; + uint32_t set_checksum; + int ret; + + if (fts_index_get_header(box, &hdr)) { + set_checksum = fts_lucene_settings_checksum(&fuser->set); + ret = fts_index_have_compatible_settings(_backend->ns->list, + set_checksum); + if (ret < 0) + return -1; + if (ret == 0) { + /* need to rebuild the index */ + *last_uid_r = 0; + } else { + *last_uid_r = hdr.last_indexed_uid; + } + return 0; + } + + /* either nothing has been indexed, or the index was corrupted. + do it the slow way. */ + if (fts_backend_select(backend, box) < 0) + return -1; + if (lucene_index_get_last_uid(backend->index, last_uid_r) < 0) + return -1; + + fts_index_set_last_uid(box, *last_uid_r); + return 0; +} + +static struct fts_backend_update_context * +fts_backend_lucene_update_init(struct fts_backend *_backend) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + struct lucene_fts_backend_update_context *ctx; + struct fts_lucene_user *fuser = + FTS_LUCENE_USER_CONTEXT_REQUIRE(_backend->ns->user); + + i_assert(!backend->updating); + + ctx = i_new(struct lucene_fts_backend_update_context, 1); + ctx->ctx.backend = _backend; + ctx->mime_parts = fuser->set.mime_parts; + backend->updating = TRUE; + return &ctx->ctx; +} + +static bool +fts_backend_lucene_need_optimize(struct lucene_fts_backend_update_context *ctx) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)ctx->ctx.backend; + unsigned int expunges; + uint32_t numdocs; + + if (ctx->added_msgs >= LUCENE_OPTIMIZE_BATCH_MSGS_COUNT) + return TRUE; + if (lucene_index_get_doc_count(backend->index, &numdocs) < 0) + return FALSE; + + if (fts_expunge_log_uid_count(backend->expunge_log, &expunges) < 0) + return FALSE; + return expunges > 0 && + numdocs / expunges <= 50; /* >2% of index has been expunged */ +} + +static int +fts_backend_lucene_update_deinit(struct fts_backend_update_context *_ctx) +{ + struct lucene_fts_backend_update_context *ctx = + (struct lucene_fts_backend_update_context *)_ctx; + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_ctx->backend; + int ret = _ctx->failed ? -1 : 0; + + i_assert(backend->updating); + + backend->updating = FALSE; + if (ctx->lucene_opened) { + if (lucene_index_build_deinit(backend->index) < 0) + ret = -1; + } + + if (ctx->expunge_ctx != NULL) { + if (fts_expunge_log_append_commit(&ctx->expunge_ctx) < 0) { + struct stat st; + ret = -1; + + if (stat(backend->dir_path, &st) < 0 && errno == ENOENT) { + /* lucene-indexes directory doesn't even exist, + so dovecot.index's last_index_uid is wrong. + rescan to update them. */ + (void)lucene_index_rescan(backend->index); + ret = 0; + } + } + } + + if (fts_backend_lucene_need_optimize(ctx)) { + if (ctx->lucene_opened) + (void)fts_backend_optimize(_ctx->backend); + else if (ctx->first_box_vname != NULL) { + struct mail_user *user = backend->backend.ns->user; + const char *cmd, *path; + int fd; + + /* the optimize affects all mailboxes within namespace, + so just use any mailbox name in it */ + cmd = t_strdup_printf("OPTIMIZE\t0\t%s\t%s\n", + str_tabescape(user->username), + str_tabescape(ctx->first_box_vname)); + fd = fts_indexer_cmd(user, cmd, &path); + i_close_fd(&fd); + } + } + + i_free(ctx->first_box_vname); + i_free(ctx); + return ret; +} + +static void +fts_backend_lucene_update_set_mailbox(struct fts_backend_update_context *_ctx, + struct mailbox *box) +{ + struct lucene_fts_backend_update_context *ctx = + (struct lucene_fts_backend_update_context *)_ctx; + + if (ctx->last_uid != 0) { + fts_index_set_last_uid(ctx->box, ctx->last_uid); + ctx->last_uid = 0; + } + if (ctx->first_box_vname == NULL && box != NULL) + ctx->first_box_vname = i_strdup(box->vname); + ctx->box = box; + ctx->last_indexed_uid_set = FALSE; +} + +static void +fts_backend_lucene_update_expunge(struct fts_backend_update_context *_ctx, + uint32_t uid) +{ + struct lucene_fts_backend_update_context *ctx = + (struct lucene_fts_backend_update_context *)_ctx; + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_ctx->backend; + struct fts_index_header hdr; + + if (!ctx->last_indexed_uid_set) { + if (!fts_index_get_header(ctx->box, &hdr)) + ctx->last_indexed_uid = 0; + else + ctx->last_indexed_uid = hdr.last_indexed_uid; + ctx->last_indexed_uid_set = TRUE; + } + if (ctx->last_indexed_uid == 0 || + uid > ctx->last_indexed_uid + 100) { + /* don't waste time adding expunge to log for a message that + isn't even indexed. this check is racy, because indexer may + just be in the middle of indexing this message. we'll + attempt to avoid that by skipping the expunging only if + indexing hasn't been done for a while (100 msgs). */ + return; + } + + if (ctx->expunge_ctx == NULL) { + ctx->expunge_ctx = + fts_expunge_log_append_begin(backend->expunge_log); + } + + if (fts_backend_select(backend, ctx->box) < 0) + _ctx->failed = TRUE; + + fts_expunge_log_append_next(ctx->expunge_ctx, + backend->selected_box_guid, uid); +} + +static bool +fts_backend_lucene_update_set_build_key(struct fts_backend_update_context *_ctx, + const struct fts_backend_build_key *key) +{ + struct lucene_fts_backend_update_context *ctx = + (struct lucene_fts_backend_update_context *)_ctx; + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_ctx->backend; + + if (!ctx->lucene_opened) { + if (fts_backend_lucene_mkdir(backend) < 0) + ctx->ctx.failed = TRUE; + if (lucene_index_build_init(backend->index) < 0) + ctx->ctx.failed = TRUE; + ctx->lucene_opened = TRUE; + } + + if (fts_backend_select(backend, ctx->box) < 0) + _ctx->failed = TRUE; + + switch (key->type) { + case FTS_BACKEND_BUILD_KEY_HDR: + case FTS_BACKEND_BUILD_KEY_MIME_HDR: + i_assert(key->hdr_name != NULL); + + i_free(ctx->hdr_name); + ctx->hdr_name = i_strdup(key->hdr_name); + break; + case FTS_BACKEND_BUILD_KEY_BODY_PART: + i_free_and_null(ctx->hdr_name); + break; + case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY: + i_unreached(); + } + + if (key->uid != ctx->last_uid) { + i_assert(key->uid >= ctx->last_uid); + ctx->last_uid = key->uid; + ctx->added_msgs++; + } + + ctx->uid = key->uid; + if (ctx->mime_parts) + ctx->part_num = message_part_to_idx(key->part); + return TRUE; +} + +static void +fts_backend_lucene_update_unset_build_key(struct fts_backend_update_context *_ctx) +{ + struct lucene_fts_backend_update_context *ctx = + (struct lucene_fts_backend_update_context *)_ctx; + + ctx->uid = 0; + ctx->part_num = 0; + i_free_and_null(ctx->hdr_name); +} + +static int +fts_backend_lucene_update_build_more(struct fts_backend_update_context *_ctx, + const unsigned char *data, size_t size) +{ + struct lucene_fts_backend_update_context *ctx = + (struct lucene_fts_backend_update_context *)_ctx; + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_ctx->backend; + int ret; + + i_assert(ctx->uid != 0); + + if (_ctx->failed) + return -1; + + T_BEGIN { + ret = lucene_index_build_more(backend->index, ctx->uid, + ctx->part_num, data, size, + ctx->hdr_name); + } T_END; + return ret; +} + +static int +fts_backend_lucene_refresh(struct fts_backend *_backend) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + + if (backend->index != NULL) + lucene_index_close(backend->index); + return 0; +} + +static int fts_backend_lucene_rescan(struct fts_backend *_backend) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + + if (lucene_index_rescan(backend->index) < 0) + return -1; + return lucene_index_optimize(backend->index); +} + +static int fts_backend_lucene_optimize(struct fts_backend *_backend) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + int ret; + + ret = lucene_index_expunge_from_log(backend->index, + backend->expunge_log); + if (ret == 0) { + /* log was corrupted, need to rescan */ + ret = lucene_index_rescan(backend->index); + } + if (ret >= 0) + ret = lucene_index_optimize(backend->index); + return ret; +} + +static int +fts_backend_lucene_lookup(struct fts_backend *_backend, struct mailbox *box, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_result *result) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + int ret; + + if (fts_backend_select(backend, box) < 0) + return -1; + T_BEGIN { + ret = lucene_index_lookup(backend->index, args, flags, result); + } T_END; + return ret; +} + +/* a char* hash function from ASU -- from glib */ +static unsigned int wstr_hash(const wchar_t *s) +{ + unsigned int g, h = 0; + + while (*s != '\0') { + h = (h << 4) + *s; + if ((g = h & 0xf0000000UL) != 0) { + h = h ^ (g >> 24); + h = h ^ g; + } + s++; + } + + return h; +} + +static int +mailboxes_get_guids(struct mailbox *const boxes[], + HASH_TABLE_TYPE(wguid_result) guids, + struct fts_multi_result *result) +{ + ARRAY(struct fts_result) box_results; + struct fts_result *box_result; + const char *guid; + wchar_t *guid_dup; + unsigned int i, j; + + p_array_init(&box_results, result->pool, 32); + /* first create the box_results - we'll be using pointers to them + later on and appending to the array changes the pointers */ + for (i = 0; boxes[i] != NULL; i++) { + box_result = array_append_space(&box_results); + box_result->box = boxes[i]; + } + for (i = 0; boxes[i] != NULL; i++) { + if (fts_mailbox_get_guid(boxes[i], &guid) < 0) + return -1; + + i_assert(strlen(guid) == MAILBOX_GUID_HEX_LENGTH); + guid_dup = t_new(wchar_t, MAILBOX_GUID_HEX_LENGTH + 1); + for (j = 0; j < MAILBOX_GUID_HEX_LENGTH; j++) + guid_dup[j] = guid[j]; + + box_result = array_idx_modifiable(&box_results, i); + hash_table_insert(guids, guid_dup, box_result); + } + + array_append_zero(&box_results); + result->box_results = array_front_modifiable(&box_results); + return 0; +} + +static int +fts_backend_lucene_lookup_multi(struct fts_backend *_backend, + struct mailbox *const boxes[], + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_multi_result *result) +{ + struct lucene_fts_backend *backend = + (struct lucene_fts_backend *)_backend; + int ret; + + T_BEGIN { + HASH_TABLE_TYPE(wguid_result) guids; + + hash_table_create(&guids, default_pool, 0, wstr_hash, wcscmp); + ret = mailboxes_get_guids(boxes, guids, result); + if (ret == 0) { + ret = lucene_index_lookup_multi(backend->index, + guids, args, flags, + result); + } + hash_table_destroy(&guids); + } T_END; + return ret; +} + +static void fts_backend_lucene_lookup_done(struct fts_backend *_backend) +{ + /* the next refresh is going to close the index anyway, so we might as + well do it now */ + (void)fts_backend_lucene_refresh(_backend); +} + +struct fts_backend fts_backend_lucene = { + .name = "lucene", + .flags = FTS_BACKEND_FLAG_BUILD_FULL_WORDS | + FTS_BACKEND_FLAG_FUZZY_SEARCH, + + { + fts_backend_lucene_alloc, + fts_backend_lucene_init, + fts_backend_lucene_deinit, + fts_backend_lucene_get_last_uid, + fts_backend_lucene_update_init, + fts_backend_lucene_update_deinit, + fts_backend_lucene_update_set_mailbox, + fts_backend_lucene_update_expunge, + fts_backend_lucene_update_set_build_key, + fts_backend_lucene_update_unset_build_key, + fts_backend_lucene_update_build_more, + fts_backend_lucene_refresh, + fts_backend_lucene_rescan, + fts_backend_lucene_optimize, + fts_backend_default_can_lookup, + fts_backend_lucene_lookup, + fts_backend_lucene_lookup_multi, + fts_backend_lucene_lookup_done + } +}; diff --git a/src/plugins/fts-lucene/fts-lucene-plugin.c b/src/plugins/fts-lucene/fts-lucene-plugin.c new file mode 100644 index 0000000..7c58fa7 --- /dev/null +++ b/src/plugins/fts-lucene/fts-lucene-plugin.c @@ -0,0 +1,146 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "crc32.h" +#include "mail-storage-hooks.h" +#include "lucene-wrapper.h" +#include "fts-user.h" +#include "fts-lucene-plugin.h" + +const char *fts_lucene_plugin_version = DOVECOT_ABI_VERSION; + +struct fts_lucene_user_module fts_lucene_user_module = + MODULE_CONTEXT_INIT(&mail_user_module_register); + +static int +fts_lucene_plugin_init_settings(struct mail_user *user, + struct fts_lucene_settings *set, + const char *str) +{ + const char *const *tmp; + + for (tmp = t_strsplit_spaces(str, " "); *tmp != NULL; tmp++) { + if (str_begins(*tmp, "default_language=")) { + set->default_language = + p_strdup(user->pool, *tmp + 17); + } else if (str_begins(*tmp, "textcat_conf=")) { + set->textcat_conf = p_strdup(user->pool, *tmp + 13); + } else if (str_begins(*tmp, "textcat_dir=")) { + set->textcat_dir = p_strdup(user->pool, *tmp + 12); + } else if (str_begins(*tmp, "whitespace_chars=")) { + set->whitespace_chars = p_strdup(user->pool, *tmp + 17); + } else if (strcmp(*tmp, "normalize") == 0) { + set->normalize = TRUE; + } else if (strcmp(*tmp, "no_snowball") == 0) { + set->no_snowball = TRUE; + } else if (strcmp(*tmp, "mime_parts") == 0) { + set->mime_parts = TRUE; + } else if (strcmp(*tmp, "use_libfts") == 0) { + set->use_libfts = TRUE; + } else { + i_error("fts_lucene: Invalid setting: %s", *tmp); + return -1; + } + } + if (set->textcat_conf != NULL && set->textcat_dir == NULL) { + i_error("fts_lucene: textcat_conf set, but textcat_dir unset"); + return -1; + } + if (set->textcat_conf == NULL && set->textcat_dir != NULL) { + i_error("fts_lucene: textcat_dir set, but textcat_conf unset"); + return -1; + } + if (set->whitespace_chars == NULL) + set->whitespace_chars = ""; +#ifndef HAVE_FTS_STEMMER + if (set->default_language != NULL) { + i_error("fts_lucene: default_language set, " + "but Dovecot built without stemmer support"); + return -1; + } +#else + if (set->default_language == NULL) + set->default_language = "english"; +#endif +#ifndef HAVE_FTS_TEXTCAT + if (set->textcat_conf != NULL) { + i_error("fts_lucene: textcat_dir set, " + "but Dovecot built without textcat support"); + return -1; + } +#endif + return 0; +} + +uint32_t fts_lucene_settings_checksum(const struct fts_lucene_settings *set) +{ + uint32_t crc; + + if (set->use_libfts) + return crc32_str("l"); + + /* checksum is always different when compiling with/without stemmer */ + crc = set->default_language == NULL ? 0 : + crc32_str(set->default_language); + crc = crc32_str_more(crc, set->whitespace_chars); + if (set->normalize) + crc = crc32_str_more(crc, "n"); + if (set->no_snowball) + crc = crc32_str_more(crc, "s"); + /* don't include mime_parts here, since changing it doesn't + necessarily need the index to be rebuilt */ + return crc; +} + +static void fts_lucene_mail_user_deinit(struct mail_user *user) +{ + struct fts_lucene_user *fuser = FTS_LUCENE_USER_CONTEXT_REQUIRE(user); + + fts_mail_user_deinit(user); + fuser->module_ctx.super.deinit(user); +} + +static void fts_lucene_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct fts_lucene_user *fuser; + const char *env, *error; + + fuser = p_new(user->pool, struct fts_lucene_user, 1); + env = mail_user_plugin_getenv(user, "fts_lucene"); + if (env == NULL) + env = ""; + + if (fts_lucene_plugin_init_settings(user, &fuser->set, env) < 0) { + /* invalid settings, disabling */ + return; + } + if (fts_mail_user_init(user, fuser->set.use_libfts, &error) < 0) { + i_error("fts_lucene: %s", error); + return; + } + + fuser->module_ctx.super = *v; + user->vlast = &fuser->module_ctx.super; + v->deinit = fts_lucene_mail_user_deinit; + MODULE_CONTEXT_SET(user, fts_lucene_user_module, fuser); +} + +static struct mail_storage_hooks fts_lucene_mail_storage_hooks = { + .mail_user_created = fts_lucene_mail_user_created +}; + +void fts_lucene_plugin_init(struct module *module ATTR_UNUSED) +{ + fts_backend_register(&fts_backend_lucene); + mail_storage_hooks_add(module, &fts_lucene_mail_storage_hooks); +} + +void fts_lucene_plugin_deinit(void) +{ + fts_backend_unregister(fts_backend_lucene.name); + mail_storage_hooks_remove(&fts_lucene_mail_storage_hooks); + lucene_shutdown(); +} + +const char *fts_lucene_plugin_dependencies[] = { "fts", NULL }; diff --git a/src/plugins/fts-lucene/fts-lucene-plugin.h b/src/plugins/fts-lucene/fts-lucene-plugin.h new file mode 100644 index 0000000..69440fb --- /dev/null +++ b/src/plugins/fts-lucene/fts-lucene-plugin.h @@ -0,0 +1,36 @@ +#ifndef FTS_LUCENE_PLUGIN_H +#define FTS_LUCENE_PLUGIN_H + +#include "module-context.h" +#include "mail-user.h" +#include "fts-api-private.h" + +#define FTS_LUCENE_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, fts_lucene_user_module) +#define FTS_LUCENE_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, fts_lucene_user_module) + +struct fts_lucene_settings { + const char *default_language; + const char *textcat_conf, *textcat_dir; + const char *whitespace_chars; + bool normalize; + bool no_snowball; + bool mime_parts; + bool use_libfts; +}; + +struct fts_lucene_user { + union mail_user_module_context module_ctx; + struct fts_lucene_settings set; +}; + +extern struct fts_backend fts_backend_lucene; +extern MODULE_CONTEXT_DEFINE(fts_lucene_user_module, &mail_user_module_register); + +uint32_t fts_lucene_settings_checksum(const struct fts_lucene_settings *set); + +void fts_lucene_plugin_init(struct module *module); +void fts_lucene_plugin_deinit(void); + +#endif diff --git a/src/plugins/fts-lucene/lucene-wrapper.cc b/src/plugins/fts-lucene/lucene-wrapper.cc new file mode 100644 index 0000000..7446693 --- /dev/null +++ b/src/plugins/fts-lucene/lucene-wrapper.cc @@ -0,0 +1,1639 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +extern "C" { +#include "lib.h" +#include "array.h" +#include "unichar.h" +#include "hash.h" +#include "hex-binary.h" +#include "ioloop.h" +#include "unlink-directory.h" +#include "ioloop.h" +#include "mail-index.h" +#include "mail-search.h" +#include "mail-namespace.h" +#include "mailbox-list-private.h" +#include "mail-storage.h" +#include "fts-expunge-log.h" +#include "fts-lucene-plugin.h" +#include "lucene-wrapper.h" + +#include <sys/stat.h> +#ifdef HAVE_LIBEXTTEXTCAT_TEXTCAT_H +# include <libexttextcat/textcat.h> +#elif defined (HAVE_LIBTEXTCAT_TEXTCAT_H) +# include <libtextcat/textcat.h> +#elif defined (HAVE_FTS_TEXTCAT) +# include <textcat.h> +#endif +}; +#include <CLucene.h> +#include <CLucene/util/CLStreams.h> +#include <CLucene/search/MultiPhraseQuery.h> +#include "SnowballAnalyzer.h" + +/* Lucene's default is 10000. Use it here also.. */ +#define MAX_TERMS_PER_DOCUMENT 10000 +#define FTS_LUCENE_MAX_SEARCH_TERMS 1000 + +#define LUCENE_LOCK_OVERRIDE_SECS 60 +#define LUCENE_INDEX_CLOSE_TIMEOUT_MSECS (120*1000) + +using namespace lucene::document; +using namespace lucene::index; +using namespace lucene::search; +using namespace lucene::queryParser; +using namespace lucene::analysis; +using namespace lucene::analysis; +using namespace lucene::util; + +struct lucene_query { + Query *query; + BooleanClause::Occur occur; +}; +ARRAY_DEFINE_TYPE(lucene_query, struct lucene_query); + +struct lucene_analyzer { + char *lang; + Analyzer *analyzer; +}; + +struct lucene_index { + char *path; + struct mailbox_list *list; + struct fts_lucene_settings set; + normalizer_func_t *normalizer; + + wchar_t mailbox_guid[MAILBOX_GUID_HEX_LENGTH + 1]; + + IndexReader *reader; + IndexWriter *writer; + IndexSearcher *searcher; + struct timeout *to_close; + + buffer_t *normalizer_buf; + Analyzer *default_analyzer, *cur_analyzer; + ARRAY(struct lucene_analyzer) analyzers; + + Document *doc; + uint32_t prev_uid, prev_part_idx; + bool no_analyzer; +}; + +struct rescan_context { + struct lucene_index *index; + + struct mailbox *box; + guid_128_t box_guid; + int box_ret; + + pool_t pool; + HASH_TABLE(uint8_t *, uint8_t *) seen_mailbox_guids; + + ARRAY_TYPE(seq_range) uids; + struct seq_range_iter uids_iter; + unsigned int uids_iter_n; + + uint32_t last_existing_uid; + bool warned; +}; + +static void *textcat = NULL; +#ifdef HAVE_FTS_TEXTCAT +static bool textcat_broken = FALSE; +#endif +static int textcat_refcount = 0; + +static void lucene_handle_error(struct lucene_index *index, CLuceneError &err, + const char *msg); +static void rescan_clear_unseen_mailboxes(struct lucene_index *index, + struct rescan_context *rescan_ctx); + +struct lucene_index *lucene_index_init(const char *path, + struct mailbox_list *list, + const struct fts_lucene_settings *set) +{ + struct lucene_index *index; + + index = i_new(struct lucene_index, 1); + index->path = i_strdup(path); + index->list = list; + if (set != NULL) { + index->set = *set; + index->normalizer = !set->normalize ? NULL : + mailbox_list_get_namespace(list)->user->default_normalizer; + } else { + /* this is valid only for doveadm dump, so it doesn't matter */ + index->set.default_language = ""; + } + if (index->set.use_libfts) { + index->default_analyzer = _CLNEW KeywordAnalyzer(); + } else +#ifdef HAVE_FTS_STEMMER + if (set == NULL || !set->no_snowball) { + index->default_analyzer = + _CLNEW snowball::SnowballAnalyzer(index->normalizer, + index->set.default_language); + } else +#endif + { + index->default_analyzer = _CLNEW standard::StandardAnalyzer(); + if (index->normalizer != NULL) { + index->normalizer_buf = + buffer_create_dynamic(default_pool, 1024); + } + } + + i_array_init(&index->analyzers, 32); + textcat_refcount++; + + return index; +} + +void lucene_index_close(struct lucene_index *index) +{ + timeout_remove(&index->to_close); + + _CLDELETE(index->searcher); + if (index->writer != NULL) { + try { + index->writer->close(); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "IndexWriter::close"); + } + _CLDELETE(index->writer); + } + if (index->reader != NULL) { + try { + index->reader->close(); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "IndexReader::close"); + } + _CLDELETE(index->reader); + } +} + +void lucene_index_deinit(struct lucene_index *index) +{ + struct lucene_analyzer *a; + + lucene_index_close(index); + array_foreach_modifiable(&index->analyzers, a) { + i_free(a->lang); + _CLDELETE(a->analyzer); + } + array_free(&index->analyzers); + if (--textcat_refcount == 0 && textcat != NULL) { +#ifdef HAVE_FTS_TEXTCAT + textcat_Done(textcat); +#endif + textcat = NULL; + } + _CLDELETE(index->default_analyzer); + if (index->normalizer_buf != NULL) + buffer_free(&index->normalizer_buf); + i_free(index->path); + i_free(index); +} + +static void lucene_data_translate(struct lucene_index *index, + wchar_t *data, unsigned int len) +{ + const char *whitespace_chars = index->set.whitespace_chars; + unsigned int i; + + if (*whitespace_chars == '\0' || index->set.use_libfts) + return; + + for (i = 0; i < len; i++) { + if (strchr(whitespace_chars, data[i]) != NULL) + data[i] = ' '; + } +} + +void lucene_utf8_n_to_tchar(const unsigned char *src, size_t srcsize, + wchar_t *dest, size_t destsize) +{ + ARRAY_TYPE(unichars) dest_arr; + buffer_t buf = { { 0, 0 } }; + + i_assert(sizeof(wchar_t) == sizeof(unichar_t)); + + buffer_create_from_data(&buf, dest, sizeof(wchar_t) * destsize); + array_create_from_buffer(&dest_arr, &buf, sizeof(wchar_t)); + if (uni_utf8_to_ucs4_n(src, srcsize, &dest_arr) < 0) + i_unreached(); + i_assert(array_count(&dest_arr)+1 == destsize); + dest[destsize-1] = 0; +} + +static const wchar_t * +t_lucene_utf8_to_tchar(struct lucene_index *index, const char *str) +{ + ARRAY_TYPE(unichars) dest_arr; + const unichar_t *chars; + wchar_t *ret; + unsigned int len; + + i_assert(sizeof(wchar_t) == sizeof(unichar_t)); + + t_array_init(&dest_arr, strlen(str) + 1); + if (uni_utf8_to_ucs4(str, &dest_arr) < 0) + i_unreached(); + (void)array_append_space(&dest_arr); + + chars = array_get_modifiable(&dest_arr, &len); + ret = (wchar_t *)chars; + lucene_data_translate(index, ret, len - 1); + return ret; +} + +void lucene_index_select_mailbox(struct lucene_index *index, + const wchar_t guid[MAILBOX_GUID_HEX_LENGTH]) +{ + memcpy(index->mailbox_guid, guid, + MAILBOX_GUID_HEX_LENGTH * sizeof(wchar_t)); + index->mailbox_guid[MAILBOX_GUID_HEX_LENGTH] = '\0'; +} + +void lucene_index_unselect_mailbox(struct lucene_index *index) +{ + memset(index->mailbox_guid, 0, sizeof(index->mailbox_guid)); +} + +static void lucene_handle_error(struct lucene_index *index, CLuceneError &err, + const char *msg) +{ + const char *error, *what = err.what(); + + i_error("lucene index %s: %s failed (#%d): %s", + index->path, msg, err.number(), what); + + if (index->list != NULL && + (err.number() == CL_ERR_CorruptIndex || + err.number() == CL_ERR_IO)) { + /* delete corrupted index. most IO errors are also about + missing files and other such corruption.. */ + if (unlink_directory(index->path, (enum unlink_directory_flags)0, &error) < 0) + i_error("unlink_directory(%s) failed: %s", index->path, error); + rescan_clear_unseen_mailboxes(index, NULL); + } +} + +static int lucene_index_open(struct lucene_index *index) +{ + if (index->reader != NULL) { + i_assert(index->to_close != NULL); + timeout_reset(index->to_close); + return 1; + } + + if (!IndexReader::indexExists(index->path)) + return 0; + + try { + index->reader = IndexReader::open(index->path); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "IndexReader::open()"); + return -1; + } + i_assert(index->to_close == NULL); + index->to_close = timeout_add(LUCENE_INDEX_CLOSE_TIMEOUT_MSECS, + lucene_index_close, index); + return 1; +} + +static int lucene_index_open_search(struct lucene_index *index) +{ + int ret; + + if (index->searcher != NULL) + return 1; + + if ((ret = lucene_index_open(index)) <= 0) + return ret; + + index->searcher = _CLNEW IndexSearcher(index->reader); + return 1; +} + +static int +lucene_doc_get_uid(struct lucene_index *index, Document *doc, uint32_t *uid_r) +{ + Field *field = doc->getField(_T("uid")); + const TCHAR *uid = field == NULL ? NULL : field->stringValue(); + if (uid == NULL) { + i_error("lucene: Corrupted FTS index %s: No UID for document", + index->path); + return -1; + } + + uint32_t num = 0; + while (*uid != 0) { + num = num*10 + (*uid - '0'); + uid++; + } + *uid_r = num; + return 0; +} + +static uint32_t +lucene_doc_get_part(struct lucene_index *index, Document *doc) +{ + Field *field = doc->getField(_T("part")); + const TCHAR *part = field == NULL ? NULL : field->stringValue(); + if (part == NULL) + return 0; + + uint32_t num = 0; + while (*part != 0) { + num = num*10 + (*part - '0'); + part++; + } + return num; +} + +int lucene_index_get_last_uid(struct lucene_index *index, uint32_t *last_uid_r) +{ + int ret = 0; + + *last_uid_r = 0; + + if ((ret = lucene_index_open_search(index)) <= 0) + return ret; + + Term mailbox_term(_T("box"), index->mailbox_guid); + TermQuery query(&mailbox_term); + + uint32_t last_uid = 0; + try { + Hits *hits = index->searcher->search(&query); + + for (size_t i = 0; i < hits->length(); i++) { + uint32_t uid; + + if (lucene_doc_get_uid(index, &hits->doc(i), + &uid) < 0) { + ret = -1; + break; + } + + if (uid > last_uid) + last_uid = uid; + } + _CLDELETE(hits); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "last_uid search"); + ret = -1; + } + *last_uid_r = last_uid; + return ret; +} + +int lucene_index_get_doc_count(struct lucene_index *index, uint32_t *count_r) +{ + int ret; + + if (index->reader == NULL) { + lucene_index_close(index); + if ((ret = lucene_index_open(index)) < 0) + return -1; + if (ret == 0) { + *count_r = 0; + return 0; + } + } + *count_r = index->reader->numDocs(); + return 0; +} + +static int lucene_settings_check(struct lucene_index *index) +{ + uint32_t set_checksum; + const char *error; + int ret = 0; + + set_checksum = fts_lucene_settings_checksum(&index->set); + ret = fts_index_have_compatible_settings(index->list, set_checksum); + if (ret != 0) + return ret; + + i_warning("fts-lucene: Settings have changed, rebuilding index for mailbox"); + + /* settings changed, rebuild index */ + if (unlink_directory(index->path, (enum unlink_directory_flags)0, &error) < 0) { + i_error("unlink_directory(%s) failed: %s", index->path, error); + ret = -1; + } else { + rescan_clear_unseen_mailboxes(index, NULL); + } + return ret; +} + +int lucene_index_build_init(struct lucene_index *index) +{ + const char *lock_path; + struct stat st; + + lucene_index_close(index); + + lock_path = t_strdup_printf("%s/write.lock", index->path); + if (stat(lock_path, &st) == 0 && + st.st_mtime < time(NULL) - LUCENE_LOCK_OVERRIDE_SECS) { + if (unlink(lock_path) < 0) + i_error("unlink(%s) failed: %m", lock_path); + } + + if (lucene_settings_check(index) < 0) + return -1; + + bool exists = IndexReader::indexExists(index->path); + try { + index->writer = _CLNEW IndexWriter(index->path, + index->default_analyzer, + !exists); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "IndexWriter()"); + return -1; + } + index->writer->setMaxFieldLength(MAX_TERMS_PER_DOCUMENT); + return 0; +} + +#ifdef HAVE_FTS_TEXTCAT +static Analyzer *get_analyzer(struct lucene_index *index, const char *lang) +{ + normalizer_func_t *normalizer = index->normalizer; + const struct lucene_analyzer *a; + struct lucene_analyzer new_analyzer; + Analyzer *analyzer; + + array_foreach(&index->analyzers, a) { + if (strcmp(a->lang, lang) == 0) + return a->analyzer; + } + + memset(&new_analyzer, 0, sizeof(new_analyzer)); + new_analyzer.lang = i_strdup(lang); + new_analyzer.analyzer = + _CLNEW snowball::SnowballAnalyzer(normalizer, lang); + array_append_i(&index->analyzers.arr, &new_analyzer, 1); + return new_analyzer.analyzer; +} + +static void *textcat_init(struct lucene_index *index) +{ + const char *textcat_dir = index->set.textcat_dir; + unsigned int len; + + if (textcat_dir == NULL) + return NULL; + + /* textcat really wants the '/' suffix */ + len = strlen(textcat_dir); + if (len > 0 && textcat_dir[len-1] != '/') + textcat_dir = t_strconcat(textcat_dir, "/", NULL); + + return special_textcat_Init(index->set.textcat_conf, textcat_dir); +} + +static Analyzer * +guess_analyzer(struct lucene_index *index, const void *data, size_t size) +{ + const char *lang; + + if (textcat_broken) + return NULL; + + if (textcat == NULL) { + textcat = textcat_init(index); + if (textcat == NULL) { + textcat_broken = TRUE; + return NULL; + } + } + + /* try to guess the language */ + lang = textcat_Classify(textcat, (const char *)data, + I_MIN(size, 500)); + const char *p = strchr(lang, ']'); + if (lang[0] != '[' || p == NULL) + return NULL; + lang = t_strdup_until(lang+1, p); + if (strcmp(lang, index->set.default_language) == 0) + return index->default_analyzer; + + return get_analyzer(index, lang); +} +#else +static Analyzer * +guess_analyzer(struct lucene_index *index ATTR_UNUSED, + const void *data ATTR_UNUSED, size_t size ATTR_UNUSED) +{ + return NULL; +} +#endif + +static int lucene_index_build_flush(struct lucene_index *index) +{ + int ret = 0; + + if (index->doc == NULL) + return 0; + + try { + CL_NS(analysis)::Analyzer *analyzer = NULL; + + if (!index->set.use_libfts) { + analyzer = index->cur_analyzer != NULL ? + index->cur_analyzer : index->default_analyzer; + } + index->writer->addDocument(index->doc, analyzer); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "IndexWriter::addDocument()"); + ret = -1; + } + + _CLDELETE(index->doc); + index->doc = NULL; + index->cur_analyzer = NULL; + return ret; +} + +int lucene_index_build_more(struct lucene_index *index, uint32_t uid, + uint32_t part_idx, const unsigned char *data, + size_t size, const char *hdr_name) +{ + wchar_t id[MAX_INT_STRLEN]; + size_t namesize, datasize; + + if (uid != index->prev_uid || part_idx != index->prev_part_idx) { + if (lucene_index_build_flush(index) < 0) + return -1; + index->prev_uid = uid; + index->prev_part_idx = part_idx; + + index->doc = _CLNEW Document(); + swprintf(id, N_ELEMENTS(id), L"%u", uid); + index->doc->add(*_CLNEW Field(_T("uid"), id, Field::STORE_YES | Field::INDEX_UNTOKENIZED)); + if (part_idx != 0) { + swprintf(id, N_ELEMENTS(id), L"%u", part_idx); + index->doc->add(*_CLNEW Field(_T("part"), id, Field::STORE_YES | Field::INDEX_UNTOKENIZED)); + } + index->doc->add(*_CLNEW Field(_T("box"), index->mailbox_guid, Field::STORE_YES | Field::INDEX_UNTOKENIZED)); + } + + if (index->normalizer_buf != NULL && !index->set.use_libfts) { + buffer_set_used_size(index->normalizer_buf, 0); + index->normalizer(data, size, index->normalizer_buf); + data = (const unsigned char *)index->normalizer_buf->data; + size = index->normalizer_buf->used; + } + + datasize = uni_utf8_strlen_n(data, size) + 1; + wchar_t *dest, *dest_free = NULL; + if (datasize < 4096) + dest = t_new(wchar_t, datasize); + else + dest = dest_free = i_new(wchar_t, datasize); + lucene_utf8_n_to_tchar(data, size, dest, datasize); + lucene_data_translate(index, dest, datasize-1); + + int token_flag = index->set.use_libfts ? + Field::INDEX_UNTOKENIZED : Field::INDEX_TOKENIZED; + if (hdr_name != NULL) { + /* hdr_name should be ASCII, but don't break in case it isn't */ + hdr_name = t_str_lcase(hdr_name); + namesize = uni_utf8_strlen(hdr_name) + 1; + wchar_t wname[namesize]; + lucene_utf8_n_to_tchar((const unsigned char *)hdr_name, + strlen(hdr_name), wname, namesize); + if (!index->set.use_libfts) + index->doc->add(*_CLNEW Field(_T("hdr"), wname, Field::STORE_NO | token_flag)); + index->doc->add(*_CLNEW Field(_T("hdr"), dest, Field::STORE_NO | token_flag)); + + if (fts_header_want_indexed(hdr_name)) + index->doc->add(*_CLNEW Field(wname, dest, Field::STORE_NO | token_flag)); + } else if (size > 0) { + if (index->cur_analyzer == NULL && !index->set.use_libfts) + index->cur_analyzer = guess_analyzer(index, data, size); + index->doc->add(*_CLNEW Field(_T("body"), dest, Field::STORE_NO | token_flag)); + } + i_free(dest_free); + return 0; +} + +int lucene_index_build_deinit(struct lucene_index *index) +{ + int ret = 0; + + if (index->prev_uid == 0) { + /* no changes. */ + return 0; + } + index->prev_uid = 0; + index->prev_part_idx = 0; + + if (index->writer == NULL) { + lucene_index_close(index); + return -1; + } + + if (lucene_index_build_flush(index) < 0) + ret = -1; + + try { + index->writer->close(); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "IndexWriter::close()"); + ret = -1; + } + + lucene_index_close(index); + return ret; +} + +static int +wcharguid_to_guid(guid_128_t dest, const wchar_t *src) +{ + buffer_t buf = { { 0, 0 } }; + char src_chars[GUID_128_SIZE*2 + 1]; + unsigned int i; + + for (i = 0; i < sizeof(src_chars)-1; i++) { + if ((src[i] >= '0' && src[i] <= '9') || + (src[i] >= 'a' && src[i] <= 'f')) + src_chars[i] = src[i]; + else + return -1; + } + if (src[i] != '\0') + return -1; + src_chars[i] = '\0'; + + buffer_create_from_data(&buf, dest, GUID_128_SIZE); + return hex_to_binary(src_chars, &buf); +} + +static int +rescan_get_uids(struct mailbox *box, ARRAY_TYPE(seq_range) *uids) +{ + struct mailbox_status status; + + if (mailbox_get_status(box, STATUS_MESSAGES, &status) < 0) + return -1; + + if (status.messages > 0) T_BEGIN { + ARRAY_TYPE(seq_range) seqs; + + t_array_init(&seqs, 2); + seq_range_array_add_range(&seqs, 1, status.messages); + mailbox_get_uid_range(box, &seqs, uids); + } T_END; + return 0; +} + +static int rescan_finish(struct rescan_context *ctx) +{ + int ret; + + ret = fts_index_set_last_uid(ctx->box, ctx->last_existing_uid); + mailbox_free(&ctx->box); + return ret; +} + +static int +fts_lucene_get_mailbox_guid(struct lucene_index *index, Document *doc, + guid_128_t guid_r) +{ + Field *field = doc->getField(_T("box")); + const TCHAR *box_guid = field == NULL ? NULL : field->stringValue(); + if (box_guid == NULL) { + i_error("lucene: Corrupted FTS index %s: No mailbox for document", + index->path); + return -1; + } + + if (wcharguid_to_guid(guid_r, box_guid) < 0) { + i_error("lucene: Corrupted FTS index %s: " + "box field not in expected format", index->path); + return -1; + } + return 0; +} + +static int +rescan_open_mailbox(struct rescan_context *ctx, Document *doc) +{ + guid_128_t guid, *guidp; + int ret; + + if (fts_lucene_get_mailbox_guid(ctx->index, doc, guid) < 0) + return 0; + + if (memcmp(guid, ctx->box_guid, sizeof(guid)) == 0) { + /* same as last one */ + return ctx->box_ret; + } + memcpy(ctx->box_guid, guid, sizeof(ctx->box_guid)); + + guidp = p_new(ctx->pool, guid_128_t, 1); + memcpy(guidp, guid, sizeof(*guidp)); + hash_table_insert(ctx->seen_mailbox_guids, guidp, guidp); + + if (ctx->box != NULL) + rescan_finish(ctx); + ctx->box = mailbox_alloc_guid(ctx->index->list, guid, + (enum mailbox_flags)0); + if (mailbox_open(ctx->box) < 0) { + enum mail_error error; + const char *errstr; + + errstr = mailbox_get_last_internal_error(ctx->box, &error); + if (error == MAIL_ERROR_NOTFOUND) + ret = 0; + else { + i_error("lucene: Couldn't open mailbox %s: %s", + mailbox_get_vname(ctx->box), errstr); + ret = -1; + } + mailbox_free(&ctx->box); + ctx->box_ret = ret; + return ret; + } + if (mailbox_sync(ctx->box, (enum mailbox_sync_flags)0) < 0) { + i_error("lucene: Failed to sync mailbox %s: %s", + mailbox_get_vname(ctx->box), + mailbox_get_last_internal_error(ctx->box, NULL)); + mailbox_free(&ctx->box); + ctx->box_ret = -1; + return -1; + } + + array_clear(&ctx->uids); + rescan_get_uids(ctx->box, &ctx->uids); + + ctx->warned = FALSE; + ctx->last_existing_uid = 0; + ctx->uids_iter_n = 0; + seq_range_array_iter_init(&ctx->uids_iter, &ctx->uids); + + ctx->box_ret = 1; + return 1; +} + +static int +rescan_next(struct rescan_context *ctx, Document *doc) +{ + uint32_t lucene_uid, idx_uid; + + if (lucene_doc_get_uid(ctx->index, doc, &lucene_uid) < 0) + return 0; + + if (seq_range_array_iter_nth(&ctx->uids_iter, ctx->uids_iter_n, + &idx_uid)) { + if (idx_uid == lucene_uid) { + ctx->uids_iter_n++; + ctx->last_existing_uid = idx_uid; + return 1; + } + if (idx_uid < lucene_uid) { + /* lucene is missing an UID from the middle. delete + the rest of the messages from this mailbox and + reindex. */ + if (!ctx->warned) { + i_warning("lucene: Mailbox %s " + "missing UIDs in the middle", + mailbox_get_vname(ctx->box)); + ctx->warned = TRUE; + } + } else { + /* UID has been expunged from index. delete from + lucene as well. */ + } + return 0; + } else { + /* the rest of the messages have been expunged from index */ + return 0; + } +} + +static void +rescan_clear_unseen_mailbox(struct lucene_index *index, + struct rescan_context *rescan_ctx, + const char *vname, + const struct fts_index_header *hdr) +{ + struct mailbox *box; + struct mailbox_metadata metadata; + + box = mailbox_alloc(index->list, vname, + (enum mailbox_flags)0); + if (mailbox_open(box) == 0 && + mailbox_get_metadata(box, MAILBOX_METADATA_GUID, + &metadata) == 0 && + (rescan_ctx == NULL || + hash_table_lookup(rescan_ctx->seen_mailbox_guids, + metadata.guid) == NULL)) { + /* this mailbox had no records in lucene index. + make sure its last indexed uid is 0 */ + (void)fts_index_set_header(box, hdr); + } + mailbox_free(&box); +} + +static void rescan_clear_unseen_mailboxes(struct lucene_index *index, + struct rescan_context *rescan_ctx) +{ + const enum mailbox_list_iter_flags iter_flags = + (enum mailbox_list_iter_flags) + (MAILBOX_LIST_ITER_NO_AUTO_BOXES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + struct fts_index_header hdr; + struct mail_namespace *ns = index->list->ns; + const char *vname; + + memset(&hdr, 0, sizeof(hdr)); + hdr.settings_checksum = fts_lucene_settings_checksum(&index->set); + + iter = mailbox_list_iter_init(index->list, "*", iter_flags); + while ((info = mailbox_list_iter_next(iter)) != NULL) + rescan_clear_unseen_mailbox(index, rescan_ctx, info->vname, &hdr); + (void)mailbox_list_iter_deinit(&iter); + + if (ns->prefix_len > 0 && + ns->prefix[ns->prefix_len-1] == mail_namespace_get_sep(ns)) { + /* namespace prefix itself isn't returned by the listing */ + vname = t_strndup(index->list->ns->prefix, + index->list->ns->prefix_len-1); + rescan_clear_unseen_mailbox(index, rescan_ctx, vname, &hdr); + } +} + +int lucene_index_rescan(struct lucene_index *index) +{ + static const TCHAR *sort_fields[] = { _T("box"), _T("uid"), NULL }; + struct rescan_context ctx; + bool failed = false; + int ret; + + i_assert(index->list != NULL); + + if ((ret = lucene_index_open_search(index)) < 0) + return ret; + + Term term(_T("box"), _T("*")); + WildcardQuery query(&term); + Sort sort(sort_fields); + + memset(&ctx, 0, sizeof(ctx)); + ctx.index = index; + ctx.pool = pool_alloconly_create("guids", 1024); + hash_table_create(&ctx.seen_mailbox_guids, ctx.pool, 0, + guid_128_hash, guid_128_cmp); + i_array_init(&ctx.uids, 128); + + if (ret > 0) try { + Hits *hits = index->searcher->search(&query, &sort); + + for (size_t i = 0; i < hits->length(); i++) { + ret = rescan_open_mailbox(&ctx, &hits->doc(i)); + if (ret > 0) + ret = rescan_next(&ctx, &hits->doc(i)); + if (ret < 0) + failed = true; + else if (ret == 0) + index->reader->deleteDocument(hits->id(i)); + } + _CLDELETE(hits); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "rescan search"); + failed = true; + } + lucene_index_close(index); + if (ctx.box != NULL) + rescan_finish(&ctx); + array_free(&ctx.uids); + + rescan_clear_unseen_mailboxes(index, &ctx); + hash_table_destroy(&ctx.seen_mailbox_guids); + pool_unref(&ctx.pool); + return failed ? -1 : 0; +} + +static void guid128_to_wguid(const guid_128_t guid, + wchar_t wguid_hex[MAILBOX_GUID_HEX_LENGTH + 1]) +{ + buffer_t buf = { { 0, 0 } }; + unsigned char guid_hex[MAILBOX_GUID_HEX_LENGTH]; + unsigned int i; + + buffer_create_from_data(&buf, guid_hex, MAILBOX_GUID_HEX_LENGTH); + binary_to_hex_append(&buf, guid, GUID_128_SIZE); + for (i = 0; i < MAILBOX_GUID_HEX_LENGTH; i++) + wguid_hex[i] = guid_hex[i]; + wguid_hex[i] = '\0'; +} + +static bool +lucene_index_add_uid_filter(BooleanQuery *query, + const struct fts_expunge_log_read_record *rec) +{ + struct seq_range_iter iter; + wchar_t wuid[MAX_INT_STRLEN]; + unsigned int n; + uint32_t uid; + + /* RangeQuery and WildcardQuery work by enumerating through all terms + that match them, and then adding TermQueries for them. So we can + simply do the same directly, and if it looks like there are too + many terms just go through everything. */ + + if (seq_range_count(&rec->uids) > FTS_LUCENE_MAX_SEARCH_TERMS) + return false; + + seq_range_array_iter_init(&iter, &rec->uids); n = 0; + while (seq_range_array_iter_nth(&iter, n++, &uid)) { + swprintf(wuid, N_ELEMENTS(wuid), L"%u", uid); + + Term *term = _CLNEW Term(_T("uid"), wuid); + query->add(_CLNEW TermQuery(term), true, BooleanClause::SHOULD); + _CLDECDELETE(term); + } + return true; +} + +static int +lucene_index_expunge_record(struct lucene_index *index, + const struct fts_expunge_log_read_record *rec) +{ + int ret; + + if ((ret = lucene_index_open_search(index)) <= 0) + return ret; + + BooleanQuery query; + BooleanQuery uids_query; + + if (lucene_index_add_uid_filter(&uids_query, rec)) + query.add(&uids_query, BooleanClause::MUST); + + wchar_t wguid[MAILBOX_GUID_HEX_LENGTH + 1]; + guid128_to_wguid(rec->mailbox_guid, wguid); + Term term(_T("box"), wguid); + TermQuery mailbox_query(&term); + query.add(&mailbox_query, BooleanClause::MUST); + + try { + Hits *hits = index->searcher->search(&query); + + for (size_t i = 0; i < hits->length(); i++) { + uint32_t uid; + + if (lucene_doc_get_uid(index, &hits->doc(i), + &uid) < 0 || + seq_range_exists(&rec->uids, uid)) + index->reader->deleteDocument(hits->id(i)); + } + _CLDELETE(hits); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "expunge search"); + ret = -1; + } + return ret < 0 ? -1 : 0; +} + +int lucene_index_expunge_from_log(struct lucene_index *index, + struct fts_expunge_log *log) +{ + struct fts_expunge_log_read_ctx *ctx; + const struct fts_expunge_log_read_record *rec; + int ret = 0, ret2; + + ctx = fts_expunge_log_read_begin(log); + while ((rec = fts_expunge_log_read_next(ctx)) != NULL) { + if (lucene_index_expunge_record(index, rec) < 0) { + ret = -1; + break; + } + } + + lucene_index_close(index); + + ret2 = fts_expunge_log_read_end(&ctx); + if (ret < 0 || ret2 < 0) + return -1; + return ret2; +} + +int lucene_index_optimize(struct lucene_index *index) +{ + int ret = 0; + + if (!IndexReader::indexExists(index->path)) + return 0; + if (IndexReader::isLocked(index->path)) + IndexReader::unlock(index->path); + + IndexWriter *writer = NULL; + try { + writer = _CLNEW IndexWriter(index->path, index->default_analyzer, false); + writer->optimize(); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "IndexWriter::optimize()"); + ret = -1; + } + try { + writer->close(); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "IndexWriter::close()"); + ret = -1; + } + if (writer != NULL) + _CLDELETE(writer); + return ret; +} + +// Mostly copy&pasted from CLucene's QueryParser +static Query* getFieldQuery(Analyzer *analyzer, const TCHAR* _field, const TCHAR* queryText, bool fuzzy) { + // Use the analyzer to get all the tokens, and then build a TermQuery, + // PhraseQuery, or nothing based on the term count + + StringReader reader(queryText); + TokenStream* source = analyzer->tokenStream(_field, &reader); + + CLVector<CL_NS(analysis)::Token*, Deletor::Object<CL_NS(analysis)::Token> > v; + CL_NS(analysis)::Token* t = NULL; + int32_t positionCount = 0; + bool severalTokensAtSamePosition = false; + + while (true) { + t = _CLNEW Token(); + try { + Token* _t = source->next(t); + if (_t == NULL) _CLDELETE(t); + }_CLCATCH_ERR(CL_ERR_IO, _CLLDELETE(source);_CLLDELETE(t);,{ + t = NULL; + }); + if (t == NULL) + break; + v.push_back(t); + if (t->getPositionIncrement() != 0) + positionCount += t->getPositionIncrement(); + else + severalTokensAtSamePosition = true; + } + try { + source->close(); + } + _CLCATCH_ERR_CLEANUP(CL_ERR_IO, {_CLLDELETE(source);_CLLDELETE(t);} ); /* cleanup */ + _CLLDELETE(source); + + if (v.size() == 0) + return NULL; + else if (v.size() == 1) { + Term* tm = _CLNEW Term(_field, v.at(0)->termBuffer()); + Query* ret; + if (fuzzy) + ret = _CLNEW FuzzyQuery( tm ); + else + ret = _CLNEW TermQuery( tm ); + _CLDECDELETE(tm); + return ret; + } else { + if (severalTokensAtSamePosition) { + if (positionCount == 1) { + // no phrase query: + BooleanQuery* q = _CLNEW BooleanQuery(true); + for(size_t i=0; i<v.size(); i++ ){ + Term* tm = _CLNEW Term(_field, v.at(i)->termBuffer()); + q->add(_CLNEW TermQuery(tm), true, BooleanClause::SHOULD); + _CLDECDELETE(tm); + } + return q; + }else { + MultiPhraseQuery* mpq = _CLNEW MultiPhraseQuery(); + CLArrayList<Term*> multiTerms; + int32_t position = -1; + for (size_t i = 0; i < v.size(); i++) { + t = v.at(i); + if (t->getPositionIncrement() > 0 && multiTerms.size() > 0) { + ValueArray<Term*> termsArray(multiTerms.size()); + multiTerms.toArray(termsArray.values); + mpq->add(&termsArray,position); + multiTerms.clear(); + } + position += t->getPositionIncrement(); + multiTerms.push_back(_CLNEW Term(_field, t->termBuffer())); + } + ValueArray<Term*> termsArray(multiTerms.size()); + multiTerms.toArray(termsArray.values); + mpq->add(&termsArray,position); + return mpq; + } + }else { + PhraseQuery* pq = _CLNEW PhraseQuery(); + int32_t position = -1; + + for (size_t i = 0; i < v.size(); i++) { + t = v.at(i); + Term* tm = _CLNEW Term(_field, t->termBuffer()); + position += t->getPositionIncrement(); + pq->add(tm,position); + _CLDECDELETE(tm); + } + return pq; + } + } +} + +static Query * +lucene_get_query_str(struct lucene_index *index, + const TCHAR *key, const char *str, bool fuzzy) +{ + const TCHAR *wvalue; + Analyzer *analyzer; + + if (index->set.use_libfts) { + const wchar_t *wstr = t_lucene_utf8_to_tchar(index, str); + Term* tm = _CLNEW Term(key, wstr); + Query* ret; + if (fuzzy) + ret = _CLNEW FuzzyQuery( tm ); + else + ret = _CLNEW TermQuery( tm ); + _CLDECDELETE(tm); + return ret; + } + + if (index->normalizer_buf != NULL) { + buffer_set_used_size(index->normalizer_buf, 0); + index->normalizer(str, strlen(str), index->normalizer_buf); + buffer_append_c(index->normalizer_buf, '\0'); + str = (const char *)index->normalizer_buf->data; + } + + wvalue = t_lucene_utf8_to_tchar(index, str); + analyzer = guess_analyzer(index, str, strlen(str)); + if (analyzer == NULL) { + analyzer = index->default_analyzer; + i_assert(analyzer != NULL); + } + + return getFieldQuery(analyzer, key, wvalue, fuzzy); +} + +static Query * +lucene_get_query(struct lucene_index *index, + const TCHAR *key, const struct mail_search_arg *arg) +{ + return lucene_get_query_str(index, key, arg->value.str, arg->fuzzy); +} + +static bool +lucene_add_definite_query(struct lucene_index *index, + ARRAY_TYPE(lucene_query) &queries, + struct mail_search_arg *arg, + enum fts_lookup_flags flags) +{ + bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0; + Query *q; + + if (arg->no_fts) + return false; + + if (arg->match_not && !and_args) { + /* FIXME: we could handle this by doing multiple queries.. */ + return false; + } + + switch (arg->type) { + case SEARCH_TEXT: { + Query *q1 = lucene_get_query(index, _T("hdr"), arg); + Query *q2 = lucene_get_query(index, _T("body"), arg); + + if (q1 == NULL && q2 == NULL) + q = NULL; + else { + BooleanQuery *bq = _CLNEW BooleanQuery(); + if (q1 != NULL) + bq->add(q1, true, BooleanClause::SHOULD); + if (q2 != NULL) + bq->add(q2, true, BooleanClause::SHOULD); + q = bq; + } + break; + } + case SEARCH_BODY: + q = lucene_get_query(index, _T("body"), arg); + break; + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + if (!fts_header_want_indexed(arg->hdr_field_name) || + *arg->value.str == '\0') + return false; + + q = lucene_get_query(index, + t_lucene_utf8_to_tchar(index, t_str_lcase(arg->hdr_field_name)), + arg); + break; + default: + return false; + } + + if (q == NULL) { + /* couldn't handle this search after all (e.g. trying to search + a stop word) */ + return false; + } + + struct lucene_query *lq = array_append_space(&queries); + lq->query = q; + if (!and_args) + lq->occur = BooleanClause::SHOULD; + else if (!arg->match_not) + lq->occur = BooleanClause::MUST; + else + lq->occur = BooleanClause::MUST_NOT; + return true; +} + +static bool +lucene_add_maybe_query(struct lucene_index *index, + ARRAY_TYPE(lucene_query) &queries, + struct mail_search_arg *arg, + enum fts_lookup_flags flags) +{ + bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0; + Query *q = NULL; + + if (arg->no_fts) + return false; + + if (arg->match_not) { + /* FIXME: we could handle this by doing multiple queries.. */ + return false; + } + + switch (arg->type) { + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + if (*arg->value.str == '\0' && !index->set.use_libfts) { + /* checking potential existence of the header name */ + q = lucene_get_query_str(index, _T("hdr"), + t_str_lcase(arg->hdr_field_name), FALSE); + break; + } + + if (fts_header_want_indexed(arg->hdr_field_name)) + return false; + + /* we can check if the search key exists in some header and + filter out the messages that have no chance of matching */ + q = lucene_get_query(index, _T("hdr"), arg); + break; + default: + return false; + } + + if (q == NULL) { + /* couldn't handle this search after all (e.g. trying to search + a stop word) */ + return false; + } + struct lucene_query *lq = array_append_space(&queries); + lq->query = q; + if (!and_args) + lq->occur = BooleanClause::SHOULD; + else if (!arg->match_not) + lq->occur = BooleanClause::MUST; + else + lq->occur = BooleanClause::MUST_NOT; + return true; +} + +static bool queries_have_non_must_nots(ARRAY_TYPE(lucene_query) &queries) +{ + const struct lucene_query *lq; + + array_foreach(&queries, lq) { + if (lq->occur != BooleanClause::MUST_NOT) + return TRUE; + } + return FALSE; +} + +static void search_query_add(BooleanQuery &query, + ARRAY_TYPE(lucene_query) &queries) +{ + BooleanQuery *search_query = _CLNEW BooleanQuery(); + const struct lucene_query *lq; + + if (queries_have_non_must_nots(queries)) { + array_foreach(&queries, lq) + search_query->add(lq->query, true, lq->occur); + query.add(search_query, true, BooleanClause::MUST); + } else { + array_foreach(&queries, lq) + search_query->add(lq->query, true, BooleanClause::SHOULD); + query.add(search_query, true, BooleanClause::MUST_NOT); + } +} + +static int +lucene_index_search(struct lucene_index *index, + ARRAY_TYPE(lucene_query) &queries, + struct fts_result *result, ARRAY_TYPE(seq_range) *uids_r) +{ + struct fts_score_map *score; + int ret = 0; + + BooleanQuery query; + search_query_add(query, queries); + + Term mailbox_term(_T("box"), index->mailbox_guid); + TermQuery mailbox_query(&mailbox_term); + query.add(&mailbox_query, BooleanClause::MUST); + + try { + Hits *hits = index->searcher->search(&query); + + uint32_t last_uid = 0; + if (result != NULL) + result->scores_sorted = true; + + for (size_t i = 0; i < hits->length(); i++) { + uint32_t uid; + + if (lucene_doc_get_uid(index, &hits->doc(i), + &uid) < 0) { + ret = -1; + break; + } + + if (seq_range_array_add(uids_r, uid)) { + /* duplicate result */ + } else if (result != NULL) { + if (uid < last_uid) + result->scores_sorted = false; + last_uid = uid; + + score = array_append_space(&result->scores); + score->uid = uid; + score->score = hits->score(i); + } + } + _CLDELETE(hits); + return ret; + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "search"); + return -1; + } +} + +int lucene_index_lookup(struct lucene_index *index, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_result *result) +{ + struct mail_search_arg *arg; + + if (lucene_index_open_search(index) <= 0) + return -1; + + ARRAY_TYPE(lucene_query) def_queries; + t_array_init(&def_queries, 16); + bool have_definites = false; + + for (arg = args; arg != NULL; arg = arg->next) { + if (lucene_add_definite_query(index, def_queries, arg, flags)) { + arg->match_always = true; + have_definites = true; + } + } + + if (have_definites) { + ARRAY_TYPE(seq_range) *uids_arr = + (flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0 ? + &result->definite_uids : &result->maybe_uids; + if (lucene_index_search(index, def_queries, result, + uids_arr) < 0) + return -1; + } + + if (have_definites) { + /* FIXME: mixing up definite + maybe queries is broken. if the + definite query matched, it'll just assume that the maybe + queries matched as well */ + return 0; + } + + ARRAY_TYPE(lucene_query) maybe_queries; + t_array_init(&maybe_queries, 16); + bool have_maybies = false; + + for (arg = args; arg != NULL; arg = arg->next) { + if (lucene_add_maybe_query(index, maybe_queries, arg, flags)) { + arg->match_always = true; + have_maybies = true; + } + } + + if (have_maybies) { + if (lucene_index_search(index, maybe_queries, NULL, + &result->maybe_uids) < 0) + return -1; + } + return 0; +} + +static int +lucene_index_search_multi(struct lucene_index *index, + HASH_TABLE_TYPE(wguid_result) guids, + ARRAY_TYPE(lucene_query) &queries, + enum fts_lookup_flags flags, + struct fts_multi_result *result) +{ + struct fts_score_map *score; + int ret = 0; + + BooleanQuery query; + search_query_add(query, queries); + + BooleanQuery mailbox_query; + struct hash_iterate_context *iter; + void *key, *value; + iter = hash_table_iterate_init(guids); + while (hash_table_iterate(iter, guids, &key, &value)) { + Term *term = _CLNEW Term(_T("box"), (wchar_t *)key); + TermQuery *q = _CLNEW TermQuery(term); + mailbox_query.add(q, true, BooleanClause::SHOULD); + } + hash_table_iterate_deinit(&iter); + + query.add(&mailbox_query, BooleanClause::MUST); + try { + Hits *hits = index->searcher->search(&query); + + for (size_t i = 0; i < hits->length(); i++) { + uint32_t uid; + + Field *field = hits->doc(i).getField(_T("box")); + const TCHAR *box_guid = field == NULL ? NULL : field->stringValue(); + if (box_guid == NULL) { + i_error("lucene: Corrupted FTS index %s: No mailbox for document", + index->path); + ret = -1; + break; + } + struct fts_result *br = + hash_table_lookup(guids, box_guid); + if (br == NULL) { + i_warning("lucene: Returned unexpected mailbox with GUID %ls", box_guid); + continue; + } + + if (lucene_doc_get_uid(index, &hits->doc(i), + &uid) < 0) { + ret = -1; + break; + } + + ARRAY_TYPE(seq_range) *uids_arr = + (flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0 ? + &br->maybe_uids : &br->definite_uids; + if (!array_is_created(uids_arr)) { + p_array_init(uids_arr, result->pool, 32); + p_array_init(&br->scores, result->pool, 32); + } + if (seq_range_array_add(uids_arr, uid)) { + /* duplicate result */ + } else { + score = array_append_space(&br->scores); + score->uid = uid; + score->score = hits->score(i); + } + } + _CLDELETE(hits); + return ret; + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "multi search"); + return -1; + } +} + +int lucene_index_lookup_multi(struct lucene_index *index, + HASH_TABLE_TYPE(wguid_result) guids, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_multi_result *result) +{ + struct mail_search_arg *arg; + + if (lucene_index_open_search(index) <= 0) + return -1; + + ARRAY_TYPE(lucene_query) def_queries; + t_array_init(&def_queries, 16); + bool have_definites = false; + + for (arg = args; arg != NULL; arg = arg->next) { + if (lucene_add_definite_query(index, def_queries, arg, flags)) { + arg->match_always = true; + have_definites = true; + } + } + + if (have_definites) { + if (lucene_index_search_multi(index, guids, def_queries, flags, + result) < 0) + return -1; + } + return 0; +} + +struct lucene_index_iter { + struct lucene_index *index; + struct lucene_index_record rec; + + Term *term; + WildcardQuery *query; + Sort *sort; + + Hits *hits; + size_t i; + bool failed; +}; + +struct lucene_index_iter * +lucene_index_iter_init(struct lucene_index *index) +{ + static const TCHAR *sort_fields[] = { _T("box"), _T("uid"), NULL }; + struct lucene_index_iter *iter; + int ret; + + iter = i_new(struct lucene_index_iter, 1); + iter->index = index; + if ((ret = lucene_index_open_search(index)) <= 0) { + if (ret < 0) + iter->failed = true; + return iter; + } + + iter->term = _CLNEW Term(_T("box"), _T("*")); + iter->query = _CLNEW WildcardQuery(iter->term); + iter->sort = _CLNEW Sort(sort_fields); + + try { + iter->hits = index->searcher->search(iter->query, iter->sort); + } catch (CLuceneError &err) { + lucene_handle_error(index, err, "rescan search"); + iter->failed = true; + } + return iter; +} + +const struct lucene_index_record * +lucene_index_iter_next(struct lucene_index_iter *iter) +{ + if (iter->hits == NULL) + return NULL; + if (iter->i == iter->hits->length()) + return NULL; + + Document *doc = &iter->hits->doc(iter->i); + iter->i++; + + memset(&iter->rec, 0, sizeof(iter->rec)); + (void)fts_lucene_get_mailbox_guid(iter->index, doc, + iter->rec.mailbox_guid); + (void)lucene_doc_get_uid(iter->index, doc, &iter->rec.uid); + iter->rec.part_num = lucene_doc_get_part(iter->index, doc); + return &iter->rec; +} + +int lucene_index_iter_deinit(struct lucene_index_iter **_iter) +{ + struct lucene_index_iter *iter = *_iter; + int ret = iter->failed ? -1 : 0; + + *_iter = NULL; + if (iter->hits != NULL) + _CLDELETE(iter->hits); + if (iter->query != NULL) { + _CLDELETE(iter->query); + _CLDELETE(iter->sort); + _CLDELETE(iter->term); + } + i_free(iter); + return ret; +} + +void lucene_shutdown(void) +{ + _lucene_shutdown(); +} diff --git a/src/plugins/fts-lucene/lucene-wrapper.h b/src/plugins/fts-lucene/lucene-wrapper.h new file mode 100644 index 0000000..270e902 --- /dev/null +++ b/src/plugins/fts-lucene/lucene-wrapper.h @@ -0,0 +1,67 @@ +#ifndef LUCENE_WRAPPER_H +#define LUCENE_WRAPPER_H + +#include "fts-api-private.h" +#include "guid.h" + +struct mailbox_list; +struct fts_expunge_log; +struct fts_lucene_settings; + +#define MAILBOX_GUID_HEX_LENGTH (GUID_128_SIZE*2) + +struct lucene_index_record { + guid_128_t mailbox_guid; + uint32_t uid, part_num; +}; + +HASH_TABLE_DEFINE_TYPE(wguid_result, wchar_t *, struct fts_result *); + +struct lucene_index * +lucene_index_init(const char *path, struct mailbox_list *list, + const struct fts_lucene_settings *set) + ATTR_NULL(2, 3); +void lucene_index_deinit(struct lucene_index *index); + +void lucene_index_select_mailbox(struct lucene_index *index, + const wchar_t guid[MAILBOX_GUID_HEX_LENGTH]); +void lucene_index_unselect_mailbox(struct lucene_index *index); +int lucene_index_get_last_uid(struct lucene_index *index, uint32_t *last_uid_r); +int lucene_index_get_doc_count(struct lucene_index *index, uint32_t *count_r); + +int lucene_index_build_init(struct lucene_index *index); +int lucene_index_build_more(struct lucene_index *index, uint32_t uid, + uint32_t part_num, const unsigned char *data, + size_t size, const char *hdr_name); +int lucene_index_build_deinit(struct lucene_index *index); + +void lucene_index_close(struct lucene_index *index); +int lucene_index_rescan(struct lucene_index *index); +int lucene_index_expunge_from_log(struct lucene_index *index, + struct fts_expunge_log *log); +int lucene_index_optimize(struct lucene_index *index); + +int lucene_index_lookup(struct lucene_index *index, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_result *result); + +int lucene_index_lookup_multi(struct lucene_index *index, + HASH_TABLE_TYPE(wguid_result) guids, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_multi_result *result); + +struct lucene_index_iter * +lucene_index_iter_init(struct lucene_index *index); +const struct lucene_index_record * +lucene_index_iter_next(struct lucene_index_iter *iter); +int lucene_index_iter_deinit(struct lucene_index_iter **iter); + +/* internal: */ +void lucene_utf8_n_to_tchar(const unsigned char *src, size_t srcsize, + wchar_t *dest, size_t destsize); + +void lucene_shutdown(void); + +#endif diff --git a/src/plugins/fts-lucene/textcat.conf b/src/plugins/fts-lucene/textcat.conf new file mode 100644 index 0000000..d75c4fe --- /dev/null +++ b/src/plugins/fts-lucene/textcat.conf @@ -0,0 +1,25 @@ +# +# A sample config file for the language models +# provided with Gertjan van Noords language guesser +# (http://odur.let.rug.nl/~vannoord/TextCat/) +# +# Notes: +# - You may consider eliminating a couple of small languages from this +# list because they cause false positives with big languages and are +# bad for performance. (Do you really want to recognize Drents?) +# - Putting the most probable languages at the top of the list +# improves performance, because this will raise the threshold for +# likely candidates more quickly. +# +LM/english.lm english +LM/italian.lm italian +LM/danish.lm danish +LM/dutch.lm dutch +LM/finnish.lm finnish +LM/french.lm french +LM/german.lm german +LM/norwegian.lm norwegian +LM/portuguese.lm portuguese +LM/russian.lm russian +LM/spanish.lm spanish +LM/swedish.lm swedish diff --git a/src/plugins/fts-solr/Makefile.am b/src/plugins/fts-solr/Makefile.am new file mode 100644 index 0000000..cd1e17b --- /dev/null +++ b/src/plugins/fts-solr/Makefile.am @@ -0,0 +1,64 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/fts + +NOPLUGIN_LDFLAGS = +lib21_fts_solr_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib21_fts_solr_plugin.la + +if DOVECOT_PLUGIN_DEPS +fts_plugin_dep = ../fts/lib20_fts_plugin.la +endif + +lib21_fts_solr_plugin_la_LIBADD = \ + $(fts_plugin_dep) \ + -lexpat + +lib21_fts_solr_plugin_la_SOURCES = \ + fts-backend-solr.c \ + fts-backend-solr-old.c \ + fts-solr-plugin.c \ + solr-response.c \ + solr-connection.c + +noinst_HEADERS = \ + fts-solr-plugin.h \ + solr-response.h \ + solr-connection.h + +test_programs = \ + test-solr-response + +test_libs = \ + ../../lib-test/libtest.la \ + ../../lib-charset/libcharset.la \ + ../../lib/liblib.la \ + $(MODULE_LIBS) + +noinst_PROGRAMS = test-solr-response + +test_solr_response_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir)/src/lib-test +test_solr_response_SOURCES = \ + solr-response.c \ + test-solr-response.c +test_solr_response_LDADD = \ + $(test_libs) -lexpat + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +check: check-am check-test +check-test: all-am + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/plugins/fts-solr/Makefile.in b/src/plugins/fts-solr/Makefile.in new file mode 100644 index 0000000..ee248f8 --- /dev/null +++ b/src/plugins/fts-solr/Makefile.in @@ -0,0 +1,965 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = test-solr-response$(EXEEXT) +subdir = src/plugins/fts-solr +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(pkginc_lib_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" \ + "$(DESTDIR)$(pkginc_libdir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib21_fts_solr_plugin_la_DEPENDENCIES = $(fts_plugin_dep) +am_lib21_fts_solr_plugin_la_OBJECTS = fts-backend-solr.lo \ + fts-backend-solr-old.lo fts-solr-plugin.lo solr-response.lo \ + solr-connection.lo +lib21_fts_solr_plugin_la_OBJECTS = \ + $(am_lib21_fts_solr_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib21_fts_solr_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib21_fts_solr_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_solr_response_OBJECTS = \ + test_solr_response-solr-response.$(OBJEXT) \ + test_solr_response-test-solr-response.$(OBJEXT) +test_solr_response_OBJECTS = $(am_test_solr_response_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = ../../lib-test/libtest.la \ + ../../lib-charset/libcharset.la ../../lib/liblib.la \ + $(am__DEPENDENCIES_1) +test_solr_response_DEPENDENCIES = $(am__DEPENDENCIES_2) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/fts-backend-solr-old.Plo \ + ./$(DEPDIR)/fts-backend-solr.Plo \ + ./$(DEPDIR)/fts-solr-plugin.Plo \ + ./$(DEPDIR)/solr-connection.Plo ./$(DEPDIR)/solr-response.Plo \ + ./$(DEPDIR)/test_solr_response-solr-response.Po \ + ./$(DEPDIR)/test_solr_response-test-solr-response.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib21_fts_solr_plugin_la_SOURCES) \ + $(test_solr_response_SOURCES) +DIST_SOURCES = $(lib21_fts_solr_plugin_la_SOURCES) \ + $(test_solr_response_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/fts + +lib21_fts_solr_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib21_fts_solr_plugin.la + +@DOVECOT_PLUGIN_DEPS_TRUE@fts_plugin_dep = ../fts/lib20_fts_plugin.la +lib21_fts_solr_plugin_la_LIBADD = \ + $(fts_plugin_dep) \ + -lexpat + +lib21_fts_solr_plugin_la_SOURCES = \ + fts-backend-solr.c \ + fts-backend-solr-old.c \ + fts-solr-plugin.c \ + solr-response.c \ + solr-connection.c + +noinst_HEADERS = \ + fts-solr-plugin.h \ + solr-response.h \ + solr-connection.h + +test_programs = \ + test-solr-response + +test_libs = \ + ../../lib-test/libtest.la \ + ../../lib-charset/libcharset.la \ + ../../lib/liblib.la \ + $(MODULE_LIBS) + +test_solr_response_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir)/src/lib-test + +test_solr_response_SOURCES = \ + solr-response.c \ + test-solr-response.c + +test_solr_response_LDADD = \ + $(test_libs) -lexpat + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fts-solr/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/fts-solr/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib21_fts_solr_plugin.la: $(lib21_fts_solr_plugin_la_OBJECTS) $(lib21_fts_solr_plugin_la_DEPENDENCIES) $(EXTRA_lib21_fts_solr_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib21_fts_solr_plugin_la_LINK) -rpath $(moduledir) $(lib21_fts_solr_plugin_la_OBJECTS) $(lib21_fts_solr_plugin_la_LIBADD) $(LIBS) + +test-solr-response$(EXEEXT): $(test_solr_response_OBJECTS) $(test_solr_response_DEPENDENCIES) $(EXTRA_test_solr_response_DEPENDENCIES) + @rm -f test-solr-response$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_solr_response_OBJECTS) $(test_solr_response_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-backend-solr-old.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-backend-solr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-solr-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/solr-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/solr-response.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_solr_response-solr-response.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_solr_response-test-solr-response.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +test_solr_response-solr-response.o: solr-response.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_solr_response-solr-response.o -MD -MP -MF $(DEPDIR)/test_solr_response-solr-response.Tpo -c -o test_solr_response-solr-response.o `test -f 'solr-response.c' || echo '$(srcdir)/'`solr-response.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_solr_response-solr-response.Tpo $(DEPDIR)/test_solr_response-solr-response.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='solr-response.c' object='test_solr_response-solr-response.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_solr_response-solr-response.o `test -f 'solr-response.c' || echo '$(srcdir)/'`solr-response.c + +test_solr_response-solr-response.obj: solr-response.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_solr_response-solr-response.obj -MD -MP -MF $(DEPDIR)/test_solr_response-solr-response.Tpo -c -o test_solr_response-solr-response.obj `if test -f 'solr-response.c'; then $(CYGPATH_W) 'solr-response.c'; else $(CYGPATH_W) '$(srcdir)/solr-response.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_solr_response-solr-response.Tpo $(DEPDIR)/test_solr_response-solr-response.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='solr-response.c' object='test_solr_response-solr-response.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_solr_response-solr-response.obj `if test -f 'solr-response.c'; then $(CYGPATH_W) 'solr-response.c'; else $(CYGPATH_W) '$(srcdir)/solr-response.c'; fi` + +test_solr_response-test-solr-response.o: test-solr-response.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_solr_response-test-solr-response.o -MD -MP -MF $(DEPDIR)/test_solr_response-test-solr-response.Tpo -c -o test_solr_response-test-solr-response.o `test -f 'test-solr-response.c' || echo '$(srcdir)/'`test-solr-response.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_solr_response-test-solr-response.Tpo $(DEPDIR)/test_solr_response-test-solr-response.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-solr-response.c' object='test_solr_response-test-solr-response.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_solr_response-test-solr-response.o `test -f 'test-solr-response.c' || echo '$(srcdir)/'`test-solr-response.c + +test_solr_response-test-solr-response.obj: test-solr-response.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_solr_response-test-solr-response.obj -MD -MP -MF $(DEPDIR)/test_solr_response-test-solr-response.Tpo -c -o test_solr_response-test-solr-response.obj `if test -f 'test-solr-response.c'; then $(CYGPATH_W) 'test-solr-response.c'; else $(CYGPATH_W) '$(srcdir)/test-solr-response.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_solr_response-test-solr-response.Tpo $(DEPDIR)/test_solr_response-test-solr-response.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-solr-response.c' object='test_solr_response-test-solr-response.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_solr_response_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_solr_response-test-solr-response.obj `if test -f 'test-solr-response.c'; then $(CYGPATH_W) 'test-solr-response.c'; else $(CYGPATH_W) '$(srcdir)/test-solr-response.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/fts-backend-solr-old.Plo + -rm -f ./$(DEPDIR)/fts-backend-solr.Plo + -rm -f ./$(DEPDIR)/fts-solr-plugin.Plo + -rm -f ./$(DEPDIR)/solr-connection.Plo + -rm -f ./$(DEPDIR)/solr-response.Plo + -rm -f ./$(DEPDIR)/test_solr_response-solr-response.Po + -rm -f ./$(DEPDIR)/test_solr_response-test-solr-response.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/fts-backend-solr-old.Plo + -rm -f ./$(DEPDIR)/fts-backend-solr.Plo + -rm -f ./$(DEPDIR)/fts-solr-plugin.Plo + -rm -f ./$(DEPDIR)/solr-connection.Plo + -rm -f ./$(DEPDIR)/solr-response.Plo + -rm -f ./$(DEPDIR)/test_solr_response-solr-response.Po + -rm -f ./$(DEPDIR)/test_solr_response-test-solr-response.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +check: check-am check-test +check-test: all-am + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/fts-solr/fts-backend-solr-old.c b/src/plugins/fts-solr/fts-backend-solr-old.c new file mode 100644 index 0000000..20b891b --- /dev/null +++ b/src/plugins/fts-solr/fts-backend-solr-old.c @@ -0,0 +1,879 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hash.h" +#include "strescape.h" +#include "unichar.h" +#include "iostream-ssl.h" +#include "http-url.h" +#include "imap-utf7.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "mail-search.h" +#include "fts-api.h" +#include "solr-connection.h" +#include "fts-solr-plugin.h" + +#include <ctype.h> + +#define SOLR_CMDBUF_SIZE (1024*64) +#define SOLR_MAX_MULTI_ROWS 100000 + +struct solr_fts_backend { + struct fts_backend backend; + struct solr_connection *solr_conn; + char *id_username, *id_namespace; + struct mail_namespace *default_ns; +}; + +struct solr_fts_backend_update_context { + struct fts_backend_update_context ctx; + + struct mailbox *cur_box; + char *id_box_name; + + struct solr_connection_post *post; + uint32_t prev_uid, uid_validity; + string_t *cmd, *hdr; + + bool headers_open; + bool body_open; + bool documents_added; +}; + +static const char *solr_escape_chars = "+-&|!(){}[]^\"~*?:\\/ "; + +static bool is_valid_xml_char(unichar_t chr) +{ + /* Valid characters in XML: + + #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | + [#x10000-#x10FFFF] + + This function gets called only for #x80 and higher */ + if (chr > 0xd7ff && chr < 0xe000) + return FALSE; + if (chr > 0xfffd && chr < 0x10000) + return FALSE; + return chr < 0x10ffff; +} + +static void +xml_encode_data(string_t *dest, const unsigned char *data, size_t len) +{ + unichar_t chr; + size_t i; + + for (i = 0; i < len; i++) { + switch (data[i]) { + case '&': + str_append(dest, "&"); + break; + case '<': + str_append(dest, "<"); + break; + case '>': + str_append(dest, ">"); + break; + case '\t': + case '\n': + case '\r': + /* exceptions to the following control char check */ + str_append_c(dest, data[i]); + break; + default: + if (data[i] < 32) { + /* SOLR doesn't like control characters. + replace them with spaces. */ + str_append_c(dest, ' '); + } else if (data[i] >= 0x80) { + /* make sure the character is valid for XML + so we don't get XML parser errors */ + int char_len = + uni_utf8_get_char_n(data + i, len - i, &chr); + i_assert(char_len > 0); /* input is valid UTF8 */ + if (is_valid_xml_char(chr)) + str_append_data(dest, data + i, char_len); + else { + str_append_data(dest, utf8_replacement_char, + UTF8_REPLACEMENT_CHAR_LEN); + } + i += char_len - 1; + } else { + str_append_c(dest, data[i]); + } + break; + } + } +} + +static void xml_encode(string_t *dest, const char *str) +{ + xml_encode_data(dest, (const unsigned char *)str, strlen(str)); +} + +static const char *solr_escape_id_str(const char *str) +{ + string_t *tmp; + const char *p; + + for (p = str; *p != '\0'; p++) { + if (*p == '/' || *p == '!') + break; + } + if (*p == '\0') + return str; + + tmp = t_str_new(64); + for (p = str; *p != '\0'; p++) { + switch (*p) { + case '/': + str_append(tmp, "!\\"); + break; + case '!': + str_append(tmp, "!!"); + break; + default: + str_append_c(tmp, *p); + break; + } + } + return str_c(tmp); +} + +static const char *solr_escape(const char *str) +{ + string_t *ret; + unsigned int i; + + if (str[0] == '\0') + return "\"\""; + + ret = t_str_new(strlen(str) + 16); + for (i = 0; str[i] != '\0'; i++) { + if (strchr(solr_escape_chars, str[i]) != NULL) + str_append_c(ret, '\\'); + str_append_c(ret, str[i]); + } + return str_c(ret); +} + +static void solr_quote(string_t *dest, const char *str) +{ + str_append(dest, solr_escape(str)); +} + +static void solr_quote_http(string_t *dest, const char *str) +{ + http_url_escape_param(dest, solr_escape(str)); +} + +static void fts_solr_set_default_ns(struct solr_fts_backend *backend) +{ + struct mail_namespace *ns = backend->backend.ns; + struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT_REQUIRE(ns->user); + const struct fts_solr_settings *set = &fuser->set; + const char *str; + + if (backend->default_ns != NULL) + return; + + if (set->default_ns_prefix != NULL) { + backend->default_ns = + mail_namespace_find_prefix(ns->user->namespaces, + set->default_ns_prefix); + if (backend->default_ns == NULL) { + i_error("fts_solr: default_ns setting points to " + "nonexistent namespace"); + } + } + if (backend->default_ns == NULL) { + backend->default_ns = + mail_namespace_find_inbox(ns->user->namespaces); + } + while (backend->default_ns->alias_for != NULL) + backend->default_ns = backend->default_ns->alias_for; + + if (ns != backend->default_ns) { + str = solr_escape_id_str(ns->prefix); + backend->id_namespace = i_strdup(str); + } +} + +static void fts_box_name_get_root(struct mail_namespace **ns, const char **name) +{ + struct mail_namespace *orig_ns = *ns; + + while ((*ns)->alias_for != NULL) + *ns = (*ns)->alias_for; + + if (**name == '\0' && *ns != orig_ns && + ((*ns)->flags & NAMESPACE_FLAG_INBOX_USER) != 0) { + /* ugly workaround to allow selecting INBOX from a Maildir/ + when it's not in the inbox=yes namespace. */ + *name = "INBOX"; + } +} + +static const char * +fts_box_get_root(struct mailbox *box, struct mail_namespace **ns_r) +{ + struct mail_namespace *ns = mailbox_get_namespace(box); + const char *name; + + if (t_imap_utf8_to_utf7(box->name, &name) < 0) + i_unreached(); + + fts_box_name_get_root(&ns, &name); + *ns_r = ns; + return name; +} + +static struct fts_backend *fts_backend_solr_alloc(void) +{ + struct solr_fts_backend *backend; + + backend = i_new(struct solr_fts_backend, 1); + backend->backend = fts_backend_solr_old; + return &backend->backend; +} + +static int +fts_backend_solr_init(struct fts_backend *_backend, const char **error_r) +{ + struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend; + struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(_backend->ns->user); + struct ssl_iostream_settings ssl_set; + const char *str; + + if (fuser == NULL) { + *error_r = "Invalid fts_solr setting"; + return -1; + } + + mail_user_init_ssl_client_settings(_backend->ns->user, &ssl_set); + if (solr_connection_init(&fuser->set, &ssl_set, + _backend->ns->user->event, + &backend->solr_conn, error_r) < 0) + return -1; + + str = solr_escape_id_str(_backend->ns->user->username); + backend->id_username = i_strdup(str); + return 0; +} + +static void fts_backend_solr_deinit(struct fts_backend *_backend) +{ + struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend; + + solr_connection_deinit(&backend->solr_conn); + i_free(backend->id_namespace); + i_free(backend->id_username); + i_free(backend); +} + +static void +solr_add_ns_query(string_t *str, struct solr_fts_backend *backend, + struct mail_namespace *ns, bool neg) +{ + while (ns->alias_for != NULL) + ns = ns->alias_for; + + if (ns == backend->default_ns || *ns->prefix == '\0') { + if (!neg) + str_append(str, " -ns:[* TO *]"); + else + str_append(str, " +ns:[* TO *]"); + } else { + if (!neg) + str_append(str, " +ns:"); + else + str_append(str, " -ns:"); + solr_quote(str, ns->prefix); + } +} + +static void +solr_add_ns_query_http(string_t *str, struct solr_fts_backend *backend, + struct mail_namespace *ns) +{ + string_t *tmp; + + tmp = t_str_new(64); + solr_add_ns_query(tmp, backend, ns, FALSE); + http_url_escape_param(str, str_c(tmp)); +} + +static int +fts_backend_solr_get_last_uid_fallback(struct solr_fts_backend *backend, + struct mailbox *box, + uint32_t *last_uid_r) +{ + struct mail_namespace *ns; + struct mailbox_status status; + struct solr_result **results; + const struct seq_range *uidvals; + const char *box_name; + unsigned int count; + string_t *str; + pool_t pool; + int ret = 0; + + str = t_str_new(256); + str_append(str, "fl=uid&rows=1&sort=uid+desc&q="); + + box_name = fts_box_get_root(box, &ns); + + mailbox_get_open_status(box, STATUS_UIDVALIDITY, &status); + str_printfa(str, "uidv:%u+AND+box:", status.uidvalidity); + solr_quote_http(str, box_name); + solr_add_ns_query_http(str, backend, ns); + str_append(str, "+AND+user:"); + solr_quote_http(str, ns->user->username); + + pool = pool_alloconly_create("solr last uid lookup", 1024); + if (solr_connection_select(backend->solr_conn, str_c(str), + pool, &results) < 0) + ret = -1; + else if (results[0] == NULL) { + /* no UIDs */ + *last_uid_r = 0; + } else { + uidvals = array_get(&results[0]->uids, &count); + i_assert(count > 0); + if (count == 1 && uidvals[0].seq1 == uidvals[0].seq2) { + *last_uid_r = uidvals[0].seq1; + } else { + i_error("fts_solr: Last UID lookup returned multiple rows"); + ret = -1; + } + } + pool_unref(&pool); + return ret; +} + +static int +fts_backend_solr_get_last_uid(struct fts_backend *_backend, + struct mailbox *box, uint32_t *last_uid_r) +{ + struct solr_fts_backend *backend = + (struct solr_fts_backend *)_backend; + struct fts_index_header hdr; + + if (fts_index_get_header(box, &hdr)) { + *last_uid_r = hdr.last_indexed_uid; + return 0; + } + + /* either nothing has been indexed, or the index was corrupted. + do it the slow way. */ + if (fts_backend_solr_get_last_uid_fallback(backend, box, last_uid_r) < 0) + return -1; + + fts_index_set_last_uid(box, *last_uid_r); + return 0; +} + +static struct fts_backend_update_context * +fts_backend_solr_update_init(struct fts_backend *_backend) +{ + struct solr_fts_backend *backend = + (struct solr_fts_backend *)_backend; + struct solr_fts_backend_update_context *ctx; + + ctx = i_new(struct solr_fts_backend_update_context, 1); + ctx->ctx.backend = _backend; + ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE); + ctx->hdr = str_new(default_pool, 4096); + fts_solr_set_default_ns(backend); + return &ctx->ctx; +} + +static void xml_encode_id(struct solr_fts_backend_update_context *ctx, + string_t *str, uint32_t uid) +{ + struct solr_fts_backend *backend = + (struct solr_fts_backend *)ctx->ctx.backend; + + if (uid != 0) + str_printfa(str, "%u/", uid); + else + str_append(str, "L/"); + + if (backend->id_namespace != NULL) { + xml_encode(str, backend->id_namespace); + str_append_c(str, '/'); + } + str_printfa(str, "%u/", ctx->uid_validity); + xml_encode(str, backend->id_username); + str_append_c(str, '/'); + xml_encode(str, ctx->id_box_name); +} + +static void +fts_backend_solr_add_doc_prefix(struct solr_fts_backend_update_context *ctx, + uint32_t uid) +{ + struct solr_fts_backend *backend = + (struct solr_fts_backend *)ctx->ctx.backend; + struct mailbox *box = ctx->cur_box; + struct mail_namespace *ns; + const char *box_name; + + ctx->documents_added = TRUE; + + str_printfa(ctx->cmd, "<doc>" + "<field name=\"uid\">%u</field>" + "<field name=\"uidv\">%u</field>", + uid, ctx->uid_validity); + + box_name = fts_box_get_root(box, &ns); + + if (ns != backend->default_ns) { + str_append(ctx->cmd, "<field name=\"ns\">"); + xml_encode(ctx->cmd, ns->prefix); + str_append(ctx->cmd, "</field>"); + } + str_append(ctx->cmd, "<field name=\"box\">"); + xml_encode(ctx->cmd, box_name); + str_append(ctx->cmd, "</field><field name=\"user\">"); + xml_encode(ctx->cmd, ns->user->username); + str_append(ctx->cmd, "</field>"); +} + +static int +fts_backed_solr_build_commit(struct solr_fts_backend_update_context *ctx) +{ + if (ctx->post == NULL) + return 0; + + str_append(ctx->cmd, "</doc></add>"); + + solr_connection_post_more(ctx->post, str_data(ctx->cmd), + str_len(ctx->cmd)); + return solr_connection_post_end(&ctx->post); +} + +static int +fts_backend_solr_update_deinit(struct fts_backend_update_context *_ctx) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + struct solr_fts_backend *backend = + (struct solr_fts_backend *)_ctx->backend; + const char *str; + int ret; + + ret = fts_backed_solr_build_commit(ctx); + + /* commit and wait until the documents we just indexed are + visible to the following search */ + str = t_strdup_printf("<commit waitFlush=\"false\" " + "waitSearcher=\"%s\"/>", + ctx->documents_added ? "true" : "false"); + if (solr_connection_post(backend->solr_conn, str) < 0) + ret = -1; + + str_free(&ctx->cmd); + str_free(&ctx->hdr); + i_free(ctx->id_box_name); + i_free(ctx); + return ret; +} + +static void +fts_backend_solr_update_set_mailbox(struct fts_backend_update_context *_ctx, + struct mailbox *box) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + struct mailbox_status status; + struct mail_namespace *ns; + + if (ctx->prev_uid != 0) { + fts_index_set_last_uid(ctx->cur_box, ctx->prev_uid); + ctx->prev_uid = 0; + } + + ctx->cur_box = box; + ctx->uid_validity = 0; + i_free_and_null(ctx->id_box_name); + + if (box != NULL) { + ctx->id_box_name = i_strdup(fts_box_get_root(box, &ns)); + + mailbox_get_open_status(box, STATUS_UIDVALIDITY, &status); + ctx->uid_validity = status.uidvalidity; + } +} + +static void +fts_backend_solr_update_expunge(struct fts_backend_update_context *_ctx, + uint32_t uid) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + struct solr_fts_backend *backend = + (struct solr_fts_backend *)_ctx->backend; + + T_BEGIN { + string_t *cmd; + + cmd = t_str_new(256); + str_append(cmd, "<delete><id>"); + xml_encode_id(ctx, cmd, uid); + str_append(cmd, "</id></delete>"); + + (void)solr_connection_post(backend->solr_conn, str_c(cmd)); + } T_END; +} + +static void +fts_backend_solr_uid_changed(struct solr_fts_backend_update_context *ctx, + uint32_t uid) +{ + struct solr_fts_backend *backend = + (struct solr_fts_backend *)ctx->ctx.backend; + + if (ctx->post == NULL) { + i_assert(ctx->prev_uid == 0); + + ctx->post = solr_connection_post_begin(backend->solr_conn); + str_append(ctx->cmd, "<add>"); + } else { + ctx->headers_open = FALSE; + if (ctx->body_open) { + ctx->body_open = FALSE; + str_append(ctx->cmd, "</field>"); + } + str_append(ctx->cmd, "<field name=\"hdr\">"); + str_append_str(ctx->cmd, ctx->hdr); + str_append(ctx->cmd, "</field>"); + str_truncate(ctx->hdr, 0); + + str_append(ctx->cmd, "</doc>"); + } + ctx->prev_uid = uid; + + fts_backend_solr_add_doc_prefix(ctx, uid); + str_printfa(ctx->cmd, "<field name=\"id\">"); + xml_encode_id(ctx, ctx->cmd, uid); + str_append(ctx->cmd, "</field>"); +} + +static bool +fts_backend_solr_update_set_build_key(struct fts_backend_update_context *_ctx, + const struct fts_backend_build_key *key) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + + if (key->uid != ctx->prev_uid) + fts_backend_solr_uid_changed(ctx, key->uid); + + switch (key->type) { + case FTS_BACKEND_BUILD_KEY_HDR: + case FTS_BACKEND_BUILD_KEY_MIME_HDR: + xml_encode(ctx->hdr, key->hdr_name); + str_append(ctx->hdr, ": "); + ctx->headers_open = TRUE; + break; + case FTS_BACKEND_BUILD_KEY_BODY_PART: + ctx->headers_open = FALSE; + if (!ctx->body_open) { + ctx->body_open = TRUE; + str_append(ctx->cmd, "<field name=\"body\">"); + } + break; + case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY: + i_unreached(); + } + return TRUE; +} + +static void +fts_backend_solr_update_unset_build_key(struct fts_backend_update_context *_ctx) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + + if (ctx->headers_open) + str_append_c(ctx->hdr, '\n'); + else { + i_assert(ctx->body_open); + str_append_c(ctx->cmd, '\n'); + } +} + +static int +fts_backend_solr_update_build_more(struct fts_backend_update_context *_ctx, + const unsigned char *data, size_t size) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + + xml_encode_data(ctx->cmd, data, size); + if (str_len(ctx->cmd) > SOLR_CMDBUF_SIZE-128) { + solr_connection_post_more(ctx->post, str_data(ctx->cmd), + str_len(ctx->cmd)); + str_truncate(ctx->cmd, 0); + } + return 0; +} + +static int fts_backend_solr_refresh(struct fts_backend *backend ATTR_UNUSED) +{ + return 0; +} + +static int fts_backend_solr_optimize(struct fts_backend *backend ATTR_UNUSED) +{ + return 0; +} + +static bool +solr_add_definite_query(string_t *str, struct mail_search_arg *arg) +{ + if (arg->no_fts) + return FALSE; + switch (arg->type) { + case SEARCH_TEXT: { + if (arg->match_not) + str_append_c(str, '-'); + str_append(str, "(hdr:"); + solr_quote_http(str, arg->value.str); + str_append(str, "+OR+body:"); + solr_quote_http(str, arg->value.str); + str_append(str, ")"); + break; + } + case SEARCH_BODY: + if (arg->match_not) + str_append_c(str, '-'); + str_append(str, "body:"); + solr_quote_http(str, arg->value.str); + break; + default: + return FALSE; + } + return TRUE; +} + +static bool +solr_add_definite_query_args(string_t *str, struct mail_search_arg *arg, + bool and_args) +{ + size_t last_len; + + last_len = str_len(str); + for (; arg != NULL; arg = arg->next) { + if (solr_add_definite_query(str, arg)) { + arg->match_always = TRUE; + last_len = str_len(str); + if (and_args) + str_append(str, "+AND+"); + else + str_append(str, "+OR+"); + } + } + if (str_len(str) == last_len) + return FALSE; + + str_truncate(str, last_len); + return TRUE; +} + +static int +fts_backend_solr_lookup(struct fts_backend *_backend, struct mailbox *box, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_result *result) +{ + struct solr_fts_backend *backend = + (struct solr_fts_backend *)_backend; + bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0; + struct mail_namespace *ns; + struct mailbox_status status; + string_t *str; + const char *box_name; + pool_t pool; + struct solr_result **results; + int ret; + + fts_solr_set_default_ns(backend); + mailbox_get_open_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT, + &status); + + str = t_str_new(256); + str_printfa(str, "fl=uid,score&rows=%u&sort=uid+asc&q=%%7b!lucene+q.op%%3dAND%%7d", + status.uidnext); + + if (!solr_add_definite_query_args(str, args, and_args)) { + /* can't search this query */ + return 0; + } + + /* use a separate filter query for selecting the mailbox. it shouldn't + affect the score and there could be some caching benefits too. */ + str_append(str, "&fq=%2Buser:"); + solr_quote_http(str, box->storage->user->username); + box_name = fts_box_get_root(box, &ns); + str_printfa(str, "+%%2Buidv:%u+%%2Bbox:", status.uidvalidity); + solr_quote_http(str, box_name); + solr_add_ns_query_http(str, backend, ns); + + pool = pool_alloconly_create("fts solr search", 1024); + ret = solr_connection_select(backend->solr_conn, str_c(str), + pool, &results); + if (ret == 0 && results[0] != NULL) { + if ((flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0) + array_append_array(&result->definite_uids, &results[0]->uids); + else + array_append_array(&result->maybe_uids, &results[0]->uids); + array_append_array(&result->scores, &results[0]->scores); + } + result->scores_sorted = TRUE; + pool_unref(&pool); + return ret; +} + +static char * +mailbox_get_id(struct solr_fts_backend *backend, struct mail_namespace *ns, + const char *mailbox, uint32_t uidvalidity) +{ + string_t *str = t_str_new(64); + + str_printfa(str, "%u\001", uidvalidity); + str_append(str, mailbox); + if (ns != backend->default_ns) + str_printfa(str, "\001%s", ns->prefix); + return str_c_modifiable(str); +} + +static int +solr_search_multi(struct solr_fts_backend *backend, string_t *str, + struct mailbox *const boxes[], + enum fts_lookup_flags flags, + struct fts_multi_result *result) +{ + struct solr_result **solr_results; + struct fts_result *fts_result; + ARRAY(struct fts_result) fts_results; + struct mail_namespace *ns; + struct mailbox_status status; + HASH_TABLE(char *, struct mailbox *) mailboxes; + struct mailbox *box; + const char *box_name; + char *box_id; + unsigned int i; + size_t len; + + /* use a separate filter query for selecting the mailbox. it shouldn't + affect the score and there could be some caching benefits too. */ + str_append(str, "&fq=%2Buser:"); + if (backend->backend.ns->owner != NULL) + solr_quote_http(str, backend->backend.ns->owner->username); + else + str_append(str, "%22%22"); + + hash_table_create(&mailboxes, default_pool, 0, str_hash, strcmp); + str_append(str, "%2B("); + len = str_len(str); + for (i = 0; boxes[i] != NULL; i++) { + if (str_len(str) != len) + str_append(str, "+OR+"); + + box_name = fts_box_get_root(boxes[i], &ns); + mailbox_get_open_status(boxes[i], STATUS_UIDVALIDITY, &status); + str_printfa(str, "%%2B(%%2Buidv:%u+%%2Bbox:", status.uidvalidity); + solr_quote_http(str, box_name); + solr_add_ns_query_http(str, backend, ns); + str_append_c(str, ')'); + + box_id = mailbox_get_id(backend, ns, box_name, status.uidvalidity); + hash_table_insert(mailboxes, box_id, boxes[i]); + } + str_append_c(str, ')'); + + if (solr_connection_select(backend->solr_conn, str_c(str), + result->pool, &solr_results) < 0) { + hash_table_destroy(&mailboxes); + return -1; + } + + p_array_init(&fts_results, result->pool, 32); + for (i = 0; solr_results[i] != NULL; i++) { + box = hash_table_lookup(mailboxes, solr_results[i]->box_id); + if (box == NULL) { + i_warning("fts_solr: Lookup returned unexpected mailbox " + "with id=%s", solr_results[i]->box_id); + continue; + } + fts_result = array_append_space(&fts_results); + fts_result->box = box; + if ((flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0) + fts_result->definite_uids = solr_results[i]->uids; + else + fts_result->maybe_uids = solr_results[i]->uids; + fts_result->scores = solr_results[i]->scores; + fts_result->scores_sorted = TRUE; + } + array_append_zero(&fts_results); + result->box_results = array_front_modifiable(&fts_results); + hash_table_destroy(&mailboxes); + return 0; +} + +static int +fts_backend_solr_lookup_multi(struct fts_backend *_backend, + struct mailbox *const boxes[], + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_multi_result *result) +{ + bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0; + struct solr_fts_backend *backend = + (struct solr_fts_backend *)_backend; + string_t *str; + + fts_solr_set_default_ns(backend); + + str = t_str_new(256); + str_printfa(str, "fl=ns,box,uidv,uid,score&rows=%u&sort=box+asc,uid+asc&q=%%7b!lucene+q.op%%3dAND%%7d", + SOLR_MAX_MULTI_ROWS); + + if (solr_add_definite_query_args(str, args, and_args)) { + if (solr_search_multi(backend, str, boxes, flags, result) < 0) + return -1; + } + /* FIXME: maybe_uids could be handled also with some more work.. */ + return 0; +} + +struct fts_backend fts_backend_solr_old = { + .name = "solr_old", + .flags = 0, + + { + fts_backend_solr_alloc, + fts_backend_solr_init, + fts_backend_solr_deinit, + fts_backend_solr_get_last_uid, + fts_backend_solr_update_init, + fts_backend_solr_update_deinit, + fts_backend_solr_update_set_mailbox, + fts_backend_solr_update_expunge, + fts_backend_solr_update_set_build_key, + fts_backend_solr_update_unset_build_key, + fts_backend_solr_update_build_more, + fts_backend_solr_refresh, + NULL, + fts_backend_solr_optimize, + fts_backend_default_can_lookup, + fts_backend_solr_lookup, + fts_backend_solr_lookup_multi, + NULL + } +}; diff --git a/src/plugins/fts-solr/fts-backend-solr.c b/src/plugins/fts-solr/fts-backend-solr.c new file mode 100644 index 0000000..0ac0f18 --- /dev/null +++ b/src/plugins/fts-solr/fts-backend-solr.c @@ -0,0 +1,984 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hash.h" +#include "strescape.h" +#include "unichar.h" +#include "iostream-ssl.h" +#include "http-url.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "mail-search.h" +#include "fts-api.h" +#include "solr-connection.h" +#include "fts-solr-plugin.h" + +#include <ctype.h> + +#define SOLR_CMDBUF_SIZE (1024*64) +#define SOLR_CMDBUF_FLUSH_SIZE (SOLR_CMDBUF_SIZE-128) +#define SOLR_MAX_MULTI_ROWS 100000 + +/* If header is larger than this, truncate it. */ +#define SOLR_HEADER_MAX_SIZE (1024*1024) +/* If SOLR_HEADER_MAX_SIZE was already reached, write still to individual + header fields as long as they're smaller than this */ +#define SOLR_HEADER_LINE_MAX_TRUNC_SIZE 1024 + +#define SOLR_QUERY_MAX_MAILBOX_COUNT 10 + +struct solr_fts_backend { + struct fts_backend backend; + struct solr_connection *solr_conn; +}; + +struct solr_fts_field { + char *key; + string_t *value; +}; + +struct solr_fts_backend_update_context { + struct fts_backend_update_context ctx; + + struct mailbox *cur_box; + char box_guid[MAILBOX_GUID_HEX_LENGTH+1]; + + struct solr_connection_post *post; + uint32_t prev_uid; + string_t *cmd, *cur_value, *cur_value2; + string_t *cmd_expunge; + ARRAY(struct solr_fts_field) fields; + + uint32_t last_indexed_uid; + unsigned int mails_since_flush; + + bool tokenized_input:1; + bool last_indexed_uid_set:1; + bool body_open:1; + bool documents_added:1; + bool expunges:1; + bool truncate_header:1; +}; + +static const char *solr_escape_chars = "+-&|!(){}[]^\"~*?:\\/ "; + +static bool is_valid_xml_char(unichar_t chr) +{ + /* Valid characters in XML: + + #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | + [#x10000-#x10FFFF] + + This function gets called only for #x80 and higher */ + if (chr > 0xd7ff && chr < 0xe000) + return FALSE; + if (chr > 0xfffd && chr < 0x10000) + return FALSE; + return chr < 0x10ffff; +} + +static size_t +xml_encode_data_max(string_t *dest, const unsigned char *data, size_t len, + unsigned int max_len) +{ + unichar_t chr; + size_t i; + + i_assert(max_len > 0 || len == 0); + + if (max_len > len) + max_len = len; + for (i = 0; i < max_len; i++) { + switch (data[i]) { + case '&': + str_append(dest, "&"); + break; + case '<': + str_append(dest, "<"); + break; + case '>': + str_append(dest, ">"); + break; + case '\t': + case '\n': + case '\r': + /* exceptions to the following control char check */ + str_append_c(dest, data[i]); + break; + default: + if (data[i] < 32) { + /* SOLR doesn't like control characters. + replace them with spaces. */ + str_append_c(dest, ' '); + } else if (data[i] >= 0x80) { + /* make sure the character is valid for XML + so we don't get XML parser errors */ + int char_len = + uni_utf8_get_char_n(data + i, len - i, &chr); + i_assert(char_len > 0); /* input is valid UTF8 */ + if (is_valid_xml_char(chr)) + str_append_data(dest, data + i, char_len); + else { + str_append_data(dest, utf8_replacement_char, + UTF8_REPLACEMENT_CHAR_LEN); + } + i += char_len - 1; + } else { + str_append_c(dest, data[i]); + } + break; + } + } + return i; +} + +static void +xml_encode_data(string_t *dest, const unsigned char *data, size_t len) +{ + (void)xml_encode_data_max(dest, data, len, len); +} + +static void xml_encode(string_t *dest, const char *str) +{ + xml_encode_data(dest, (const unsigned char *)str, strlen(str)); +} + +static const char *solr_escape(const char *str) +{ + string_t *ret; + unsigned int i; + + ret = t_str_new(strlen(str) + 16); + for (i = 0; str[i] != '\0'; i++) { + if (strchr(solr_escape_chars, str[i]) != NULL) + str_append_c(ret, '\\'); + str_append_c(ret, str[i]); + } + return str_c(ret); +} + +static void solr_quote_http(string_t *dest, const char *str) +{ + if (str[0] != '\0') + http_url_escape_param(dest, solr_escape(str)); + else + str_append(dest, "%22%22"); +} + +static struct fts_backend *fts_backend_solr_alloc(void) +{ + struct solr_fts_backend *backend; + + backend = i_new(struct solr_fts_backend, 1); + backend->backend = fts_backend_solr; + return &backend->backend; +} + +static int +fts_backend_solr_init(struct fts_backend *_backend, const char **error_r) +{ + struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend; + struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(_backend->ns->user); + struct ssl_iostream_settings ssl_set; + + if (fuser == NULL) { + *error_r = "Invalid fts_solr setting"; + return -1; + } + if (fuser->set.use_libfts) { + /* change our flags so we get proper input */ + _backend->flags &= ENUM_NEGATE(FTS_BACKEND_FLAG_FUZZY_SEARCH); + _backend->flags |= FTS_BACKEND_FLAG_TOKENIZED_INPUT; + } + + mail_user_init_ssl_client_settings(_backend->ns->user, &ssl_set); + return solr_connection_init(&fuser->set, &ssl_set, + _backend->ns->user->event, + &backend->solr_conn, error_r); +} + +static void fts_backend_solr_deinit(struct fts_backend *_backend) +{ + struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend; + + solr_connection_deinit(&backend->solr_conn); + i_free(backend); +} + +static int +get_last_uid_fallback(struct fts_backend *_backend, struct mailbox *box, + uint32_t *last_uid_r) +{ + struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend; + const struct seq_range *uidvals; + const char *box_guid; + unsigned int count; + struct solr_result **results; + string_t *str; + pool_t pool; + int ret = 0; + + str = t_str_new(256); + str_append(str, "wt=xml&fl=uid&rows=1&sort=uid+desc&q="); + + if (fts_mailbox_get_guid(box, &box_guid) < 0) + return -1; + + str_printfa(str, "box:%s+AND+user:", box_guid); + if (_backend->ns->owner != NULL) + solr_quote_http(str, _backend->ns->owner->username); + else + str_append(str, "%22%22"); + + pool = pool_alloconly_create("solr last uid lookup", 1024); + if (solr_connection_select(backend->solr_conn, str_c(str), + pool, &results) < 0) + ret = -1; + else if (results[0] == NULL) { + /* no UIDs */ + *last_uid_r = 0; + } else { + uidvals = array_get(&results[0]->uids, &count); + i_assert(count > 0); + if (count == 1 && uidvals[0].seq1 == uidvals[0].seq2) { + *last_uid_r = uidvals[0].seq1; + } else { + i_error("fts_solr: Last UID lookup returned multiple rows"); + ret = -1; + } + } + pool_unref(&pool); + return ret; +} + +static int +fts_backend_solr_get_last_uid(struct fts_backend *_backend, + struct mailbox *box, uint32_t *last_uid_r) +{ + struct fts_index_header hdr; + + if (fts_index_get_header(box, &hdr)) { + *last_uid_r = hdr.last_indexed_uid; + return 0; + } + + /* either nothing has been indexed, or the index was corrupted. + do it the slow way. */ + if (get_last_uid_fallback(_backend, box, last_uid_r) < 0) + return -1; + + fts_index_set_last_uid(box, *last_uid_r); + return 0; +} + +static struct fts_backend_update_context * +fts_backend_solr_update_init(struct fts_backend *_backend) +{ + struct solr_fts_backend_update_context *ctx; + + ctx = i_new(struct solr_fts_backend_update_context, 1); + ctx->ctx.backend = _backend; + ctx->tokenized_input = + (_backend->flags & FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0; + i_array_init(&ctx->fields, 16); + return &ctx->ctx; +} + +static void xml_encode_id(struct solr_fts_backend_update_context *ctx, + string_t *str, uint32_t uid) +{ + str_printfa(str, "%u/%s", uid, ctx->box_guid); + if (ctx->ctx.backend->ns->owner != NULL) { + str_append_c(str, '/'); + xml_encode(str, ctx->ctx.backend->ns->owner->username); + } +} + +static void +fts_backend_solr_doc_open(struct solr_fts_backend_update_context *ctx, + uint32_t uid) +{ + ctx->documents_added = TRUE; + + str_printfa(ctx->cmd, "<doc>" + "<field name=\"uid\">%u</field>" + "<field name=\"box\">%s</field>", + uid, ctx->box_guid); + str_append(ctx->cmd, "<field name=\"user\">"); + if (ctx->ctx.backend->ns->owner != NULL) + xml_encode(ctx->cmd, ctx->ctx.backend->ns->owner->username); + str_append(ctx->cmd, "</field>"); + + str_printfa(ctx->cmd, "<field name=\"id\">"); + xml_encode_id(ctx, ctx->cmd, uid); + str_append(ctx->cmd, "</field>"); +} + +static string_t * +fts_solr_field_get(struct solr_fts_backend_update_context *ctx, const char *key) +{ + const struct solr_fts_field *field; + struct solr_fts_field new_field; + + /* there are only a few fields. this lookup is fast enough. */ + array_foreach(&ctx->fields, field) { + if (strcasecmp(field->key, key) == 0) + return field->value; + } + + i_zero(&new_field); + new_field.key = str_lcase(i_strdup(key)); + new_field.value = str_new(default_pool, 128); + array_push_back(&ctx->fields, &new_field); + return new_field.value; +} + +static void +fts_backend_solr_doc_close(struct solr_fts_backend_update_context *ctx) +{ + struct solr_fts_field *field; + + if (ctx->body_open) { + ctx->body_open = FALSE; + str_append(ctx->cmd, "</field>"); + } + array_foreach_modifiable(&ctx->fields, field) { + str_printfa(ctx->cmd, "<field name=\"%s\">", field->key); + /* the values are already xml-escaped */ + str_append_str(ctx->cmd, field->value); + str_append(ctx->cmd, "</field>"); + str_truncate(field->value, 0); + } + str_append(ctx->cmd, "</doc>"); +} + +static int +fts_backed_solr_build_flush(struct solr_fts_backend_update_context *ctx) +{ + if (ctx->post == NULL) + return 0; + + fts_backend_solr_doc_close(ctx); + str_append(ctx->cmd, "</add>"); + ctx->mails_since_flush = 0; + + solr_connection_post_more(ctx->post, str_data(ctx->cmd), + str_len(ctx->cmd)); + str_truncate(ctx->cmd, 0); + return solr_connection_post_end(&ctx->post); +} + +static void +fts_backend_solr_expunge_flush(struct solr_fts_backend_update_context *ctx) +{ + struct solr_fts_backend *backend = + (struct solr_fts_backend *)ctx->ctx.backend; + + str_append(ctx->cmd_expunge, "</delete>"); + (void)solr_connection_post(backend->solr_conn, str_c(ctx->cmd_expunge)); + str_truncate(ctx->cmd_expunge, 0); + str_append(ctx->cmd_expunge, "<delete>"); +} + +static int +fts_backend_solr_update_deinit(struct fts_backend_update_context *_ctx) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + struct solr_fts_backend *backend = + (struct solr_fts_backend *)_ctx->backend; + struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(_ctx->backend->ns->user); + struct solr_fts_field *field; + const char *str; + int ret = _ctx->failed ? -1 : 0; + + if (fts_backed_solr_build_flush(ctx) < 0) + ret = -1; + + if (ctx->documents_added || ctx->expunges) { + /* commit and wait until the documents we just indexed are + visible to the following search */ + if (ctx->expunges) + fts_backend_solr_expunge_flush(ctx); + if (fuser->set.soft_commit) { + str = t_strdup_printf("<commit softCommit=\"true\" waitSearcher=\"%s\"/>", + ctx->documents_added ? "true" : "false"); + if (solr_connection_post(backend->solr_conn, str) < 0) + ret = -1; + } + } + + str_free(&ctx->cmd); + str_free(&ctx->cmd_expunge); + array_foreach_modifiable(&ctx->fields, field) { + str_free(&field->value); + i_free(field->key); + } + array_free(&ctx->fields); + i_free(ctx); + return ret; +} + +static void +fts_backend_solr_update_set_mailbox(struct fts_backend_update_context *_ctx, + struct mailbox *box) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + const char *box_guid; + + if (ctx->prev_uid != 0) { + i_assert(ctx->cur_box != NULL); + + /* flush solr between mailboxes, so we don't wrongly update + last_uid before we know it has succeeded */ + if (fts_backed_solr_build_flush(ctx) < 0) + _ctx->failed = TRUE; + else if (!_ctx->failed) + fts_index_set_last_uid(ctx->cur_box, ctx->prev_uid); + ctx->prev_uid = 0; + } + + if (box != NULL) { + if (fts_mailbox_get_guid(box, &box_guid) < 0) + _ctx->failed = TRUE; + + i_assert(strlen(box_guid) == sizeof(ctx->box_guid)-1); + memcpy(ctx->box_guid, box_guid, sizeof(ctx->box_guid)-1); + } else { + memset(ctx->box_guid, 0, sizeof(ctx->box_guid)); + } + ctx->cur_box = box; +} + +static void +fts_backend_solr_update_expunge(struct fts_backend_update_context *_ctx, + uint32_t uid) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + struct fts_index_header hdr; + + if (!ctx->last_indexed_uid_set) { + if (!fts_index_get_header(ctx->cur_box, &hdr)) + ctx->last_indexed_uid = 0; + else + ctx->last_indexed_uid = hdr.last_indexed_uid; + ctx->last_indexed_uid_set = TRUE; + } + if (ctx->last_indexed_uid == 0 || + uid > ctx->last_indexed_uid + 100) { + /* don't waste time asking Solr to expunge a message that is + highly unlikely to be indexed at this time. */ + return; + } + if (!ctx->expunges) { + ctx->expunges = TRUE; + ctx->cmd_expunge = str_new(default_pool, 1024); + str_append(ctx->cmd_expunge, "<delete>"); + } + + if (str_len(ctx->cmd_expunge) >= SOLR_CMDBUF_FLUSH_SIZE) + fts_backend_solr_expunge_flush(ctx); + + str_append(ctx->cmd_expunge, "<id>"); + xml_encode_id(ctx, ctx->cmd_expunge, uid); + str_append(ctx->cmd_expunge, "</id>"); +} + +static void +fts_backend_solr_uid_changed(struct solr_fts_backend_update_context *ctx, + uint32_t uid) +{ + struct solr_fts_backend *backend = + (struct solr_fts_backend *)ctx->ctx.backend; + struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(ctx->ctx.backend->ns->user); + + if (ctx->mails_since_flush >= fuser->set.batch_size) { + if (fts_backed_solr_build_flush(ctx) < 0) + ctx->ctx.failed = TRUE; + } + ctx->mails_since_flush++; + if (ctx->post == NULL) { + if (ctx->cmd == NULL) + ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE); + ctx->post = solr_connection_post_begin(backend->solr_conn); + str_append(ctx->cmd, "<add>"); + } else { + fts_backend_solr_doc_close(ctx); + } + ctx->prev_uid = uid; + ctx->truncate_header = FALSE; + fts_backend_solr_doc_open(ctx, uid); +} + +static bool +fts_backend_solr_update_set_build_key(struct fts_backend_update_context *_ctx, + const struct fts_backend_build_key *key) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + + if (key->uid != ctx->prev_uid) + fts_backend_solr_uid_changed(ctx, key->uid); + + switch (key->type) { + case FTS_BACKEND_BUILD_KEY_HDR: + if (fts_header_want_indexed(key->hdr_name)) { + ctx->cur_value2 = + fts_solr_field_get(ctx, key->hdr_name); + } + /* fall through */ + case FTS_BACKEND_BUILD_KEY_MIME_HDR: + ctx->cur_value = fts_solr_field_get(ctx, "hdr"); + xml_encode(ctx->cur_value, key->hdr_name); + str_append(ctx->cur_value, ": "); + break; + case FTS_BACKEND_BUILD_KEY_BODY_PART: + if (!ctx->body_open) { + ctx->body_open = TRUE; + str_append(ctx->cmd, "<field name=\"body\">"); + } + ctx->cur_value = ctx->cmd; + break; + case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY: + i_unreached(); + } + return TRUE; +} + +static void +fts_backend_solr_update_unset_build_key(struct fts_backend_update_context *_ctx) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + + /* There can be multiple duplicate keys (duplicate header lines, + multiple MIME body parts). Make sure they are separated by + whitespace. */ + str_append_c(ctx->cur_value, '\n'); + ctx->cur_value = NULL; + if (ctx->cur_value2 != NULL) { + str_append_c(ctx->cur_value2, '\n'); + ctx->cur_value2 = NULL; + } +} + +static int +fts_backend_solr_update_build_more(struct fts_backend_update_context *_ctx, + const unsigned char *data, size_t size) +{ + struct solr_fts_backend_update_context *ctx = + (struct solr_fts_backend_update_context *)_ctx; + size_t len; + + if (_ctx->failed) + return -1; + + if (ctx->cur_value2 == NULL && ctx->cur_value == ctx->cmd) { + /* we're writing to message body. if size is huge, + flush it once in a while */ + while (size >= SOLR_CMDBUF_FLUSH_SIZE) { + if (str_len(ctx->cmd) >= SOLR_CMDBUF_FLUSH_SIZE) { + solr_connection_post_more(ctx->post, + str_data(ctx->cmd), + str_len(ctx->cmd)); + str_truncate(ctx->cmd, 0); + } + len = xml_encode_data_max(ctx->cmd, data, size, + SOLR_CMDBUF_FLUSH_SIZE - + str_len(ctx->cmd)); + i_assert(len > 0); + i_assert(len <= size); + data += len; + size -= len; + } + xml_encode_data(ctx->cmd, data, size); + if (ctx->tokenized_input) + str_append_c(ctx->cmd, ' '); + } else { + if (!ctx->truncate_header) { + xml_encode_data(ctx->cur_value, data, size); + if (ctx->tokenized_input) + str_append_c(ctx->cur_value, ' '); + } + if (ctx->cur_value2 != NULL && + (!ctx->truncate_header || + str_len(ctx->cur_value2) < SOLR_HEADER_LINE_MAX_TRUNC_SIZE)) { + xml_encode_data(ctx->cur_value2, data, size); + if (ctx->tokenized_input) + str_append_c(ctx->cur_value2, ' '); + } + } + + if (str_len(ctx->cmd) >= SOLR_CMDBUF_FLUSH_SIZE) { + solr_connection_post_more(ctx->post, str_data(ctx->cmd), + str_len(ctx->cmd)); + str_truncate(ctx->cmd, 0); + } + if (!ctx->truncate_header && + str_len(ctx->cur_value) >= SOLR_HEADER_MAX_SIZE) { + /* a large header */ + i_assert(ctx->cur_value != ctx->cmd); + + i_warning("fts-solr(%s): Mailbox %s UID=%u header size is huge, truncating", + ctx->cur_box->storage->user->username, + mailbox_get_vname(ctx->cur_box), ctx->prev_uid); + ctx->truncate_header = TRUE; + } + return 0; +} + +static int fts_backend_solr_refresh(struct fts_backend *backend ATTR_UNUSED) +{ + return 0; +} + +static int fts_backend_solr_rescan(struct fts_backend *backend) +{ + /* FIXME: proper rescan needed. for now we'll just reset the + last-uids */ + return fts_backend_reset_last_uids(backend); +} + +static int fts_backend_solr_optimize(struct fts_backend *backend ATTR_UNUSED) +{ + return 0; +} + +static bool solr_need_escaping(const char *str) +{ + for (; *str != '\0'; str++) { + if (strchr(solr_escape_chars, *str) != NULL) + return TRUE; + } + return FALSE; +} + +static void solr_add_str_arg(string_t *str, struct mail_search_arg *arg) +{ + /* currently we'll just disable fuzzy searching if there are any + parameters that need escaping. solr doesn't seem to give good + fuzzy results even if we did escape them.. */ + if (!arg->fuzzy || arg->value.str[0] == '\0' || + solr_need_escaping(arg->value.str)) + solr_quote_http(str, arg->value.str); + else { + http_url_escape_param(str, arg->value.str); + str_append_c(str, '~'); + } +} + +static bool +solr_add_definite_query(string_t *str, struct mail_search_arg *arg) +{ + if (arg->no_fts) + return FALSE; + switch (arg->type) { + case SEARCH_TEXT: { + if (arg->match_not) + str_append_c(str, '-'); + str_append(str, "(hdr:"); + solr_add_str_arg(str, arg); + str_append(str, "+OR+body:"); + solr_add_str_arg(str, arg); + str_append(str, ")"); + break; + } + case SEARCH_BODY: + if (arg->match_not) + str_append_c(str, '-'); + str_append(str, "body:"); + solr_add_str_arg(str, arg); + break; + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + if (!fts_header_want_indexed(arg->hdr_field_name)) + return FALSE; + + if (arg->match_not) + str_append_c(str, '-'); + str_append(str, t_str_lcase(arg->hdr_field_name)); + str_append_c(str, ':'); + solr_add_str_arg(str, arg); + break; + default: + return FALSE; + } + return TRUE; +} + +static bool +solr_add_definite_query_args(string_t *str, struct mail_search_arg *arg, + bool and_args) +{ + size_t last_len; + + last_len = str_len(str); + for (; arg != NULL; arg = arg->next) { + if (solr_add_definite_query(str, arg)) { + arg->match_always = TRUE; + last_len = str_len(str); + if (and_args) + str_append(str, "+AND+"); + else + str_append(str, "+OR+"); + } + } + if (str_len(str) == last_len) + return FALSE; + + str_truncate(str, last_len); + return TRUE; +} + +static bool +solr_add_maybe_query(string_t *str, struct mail_search_arg *arg) +{ + if (arg->no_fts) + return FALSE; + switch (arg->type) { + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + if (fts_header_want_indexed(arg->hdr_field_name)) + return FALSE; + if (arg->match_not) { + /* all matches would be definite, but all non-matches + would be maybies. too much trouble to optimize. */ + return FALSE; + } + + /* we can check if the search key exists in some header and + filter out the messages that have no chance of matching */ + str_append(str, "hdr:"); + if (*arg->value.str != '\0') + solr_quote_http(str, arg->value.str); + else { + /* checking potential existence of the header name */ + solr_quote_http(str, t_str_lcase(arg->hdr_field_name)); + } + break; + default: + return FALSE; + } + return TRUE; +} + +static bool +solr_add_maybe_query_args(string_t *str, struct mail_search_arg *arg, + bool and_args) +{ + size_t last_len; + + last_len = str_len(str); + for (; arg != NULL; arg = arg->next) { + if (solr_add_maybe_query(str, arg)) { + arg->match_always = TRUE; + last_len = str_len(str); + if (and_args) + str_append(str, "+AND+"); + else + str_append(str, "+OR+"); + } + } + if (str_len(str) == last_len) + return FALSE; + + str_truncate(str, last_len); + return TRUE; +} + +static int solr_search(struct fts_backend *_backend, string_t *str, + const char *box_guid, ARRAY_TYPE(seq_range) *uids_r, + ARRAY_TYPE(fts_score_map) *scores_r) +{ + struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend; + pool_t pool = pool_alloconly_create("fts solr search", 1024); + struct solr_result **results; + int ret; + + /* use a separate filter query for selecting the mailbox. it shouldn't + affect the score and there could be some caching benefits too. */ + str_printfa(str, "&fq=%%2Bbox:%s+%%2Buser:", box_guid); + if (_backend->ns->owner != NULL) + solr_quote_http(str, _backend->ns->owner->username); + else + str_append(str, "%22%22"); + + ret = solr_connection_select(backend->solr_conn, str_c(str), + pool, &results); + if (ret == 0 && results[0] != NULL) { + array_append_array(uids_r, &results[0]->uids); + array_append_array(scores_r, &results[0]->scores); + } + pool_unref(&pool); + return ret; +} + +static int +fts_backend_solr_lookup(struct fts_backend *_backend, struct mailbox *box, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_result *result) +{ + bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0; + struct mailbox_status status; + string_t *str; + const char *box_guid; + size_t prefix_len; + + if (fts_mailbox_get_guid(box, &box_guid) < 0) + return -1; + mailbox_get_open_status(box, STATUS_UIDNEXT, &status); + + str = t_str_new(256); + str_printfa(str, "wt=xml&fl=uid,score&rows=%u&sort=uid+asc&q=%%7b!lucene+q.op%%3dAND%%7d", + status.uidnext); + prefix_len = str_len(str); + + if (solr_add_definite_query_args(str, args, and_args)) { + ARRAY_TYPE(seq_range) *uids_arr = + (flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0 ? + &result->definite_uids : &result->maybe_uids; + if (solr_search(_backend, str, box_guid, + uids_arr, &result->scores) < 0) + return -1; + } + str_truncate(str, prefix_len); + if (solr_add_maybe_query_args(str, args, and_args)) { + if (solr_search(_backend, str, box_guid, + &result->maybe_uids, &result->scores) < 0) + return -1; + } + result->scores_sorted = TRUE; + return 0; +} + +static int +solr_search_multi(struct fts_backend *_backend, string_t *str, + struct mailbox *const boxes[], enum fts_lookup_flags flags, + struct fts_multi_result *result) +{ + struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend; + struct solr_result **solr_results; + struct fts_result *fts_result; + ARRAY(struct fts_result) fts_results; + HASH_TABLE(char *, struct mailbox *) mailboxes; + struct mailbox *box; + const char *box_guid; + unsigned int i; + size_t len; + bool search_all_mailboxes; + + /* use a separate filter query for selecting the mailbox. it shouldn't + affect the score and there could be some caching benefits too. */ + str_append(str, "&fq=%2Buser:"); + if (_backend->ns->owner != NULL) + solr_quote_http(str, _backend->ns->owner->username); + else + str_append(str, "%22%22"); + + hash_table_create(&mailboxes, default_pool, 0, str_hash, strcmp); + for (i = 0; boxes[i] != NULL; i++) ; + search_all_mailboxes = i > SOLR_QUERY_MAX_MAILBOX_COUNT; + if (!search_all_mailboxes) + str_append(str, "+%2B("); + len = str_len(str); + + for (i = 0; boxes[i] != NULL; i++) { + if (fts_mailbox_get_guid(boxes[i], &box_guid) < 0) + continue; + + if (!search_all_mailboxes) { + if (str_len(str) != len) + str_append(str, "+OR+"); + str_printfa(str, "box:%s", box_guid); + } + hash_table_insert(mailboxes, t_strdup_noconst(box_guid), + boxes[i]); + } + if (!search_all_mailboxes) + str_append_c(str, ')'); + + if (solr_connection_select(backend->solr_conn, str_c(str), + result->pool, &solr_results) < 0) { + hash_table_destroy(&mailboxes); + return -1; + } + + p_array_init(&fts_results, result->pool, 32); + for (i = 0; solr_results[i] != NULL; i++) { + box = hash_table_lookup(mailboxes, solr_results[i]->box_id); + if (box == NULL) { + if (!search_all_mailboxes) { + i_warning("fts_solr: Lookup returned unexpected mailbox " + "with guid=%s", solr_results[i]->box_id); + } + continue; + } + fts_result = array_append_space(&fts_results); + fts_result->box = box; + if ((flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0) + fts_result->definite_uids = solr_results[i]->uids; + else + fts_result->maybe_uids = solr_results[i]->uids; + fts_result->scores = solr_results[i]->scores; + fts_result->scores_sorted = TRUE; + } + array_append_zero(&fts_results); + result->box_results = array_front_modifiable(&fts_results); + hash_table_destroy(&mailboxes); + return 0; +} + +static int +fts_backend_solr_lookup_multi(struct fts_backend *backend, + struct mailbox *const boxes[], + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_multi_result *result) +{ + bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0; + string_t *str; + + str = t_str_new(256); + str_printfa(str, "wt=xml&fl=box,uid,score&rows=%u&sort=box+asc,uid+asc&q=%%7b!lucene+q.op%%3dAND%%7d", + SOLR_MAX_MULTI_ROWS); + + if (solr_add_definite_query_args(str, args, and_args)) { + if (solr_search_multi(backend, str, boxes, flags, result) < 0) + return -1; + } + /* FIXME: maybe_uids could be handled also with some more work.. */ + return 0; +} + +struct fts_backend fts_backend_solr = { + .name = "solr", + .flags = FTS_BACKEND_FLAG_FUZZY_SEARCH, + + { + fts_backend_solr_alloc, + fts_backend_solr_init, + fts_backend_solr_deinit, + fts_backend_solr_get_last_uid, + fts_backend_solr_update_init, + fts_backend_solr_update_deinit, + fts_backend_solr_update_set_mailbox, + fts_backend_solr_update_expunge, + fts_backend_solr_update_set_build_key, + fts_backend_solr_update_unset_build_key, + fts_backend_solr_update_build_more, + fts_backend_solr_refresh, + fts_backend_solr_rescan, + fts_backend_solr_optimize, + fts_backend_default_can_lookup, + fts_backend_solr_lookup, + fts_backend_solr_lookup_multi, + NULL + } +}; diff --git a/src/plugins/fts-solr/fts-solr-plugin.c b/src/plugins/fts-solr/fts-solr-plugin.c new file mode 100644 index 0000000..5899784 --- /dev/null +++ b/src/plugins/fts-solr/fts-solr-plugin.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "http-client.h" +#include "mail-user.h" +#include "mail-storage-hooks.h" +#include "solr-connection.h" +#include "fts-user.h" +#include "fts-solr-plugin.h" + +#define DEFAULT_SOLR_BATCH_SIZE 1000 + +const char *fts_solr_plugin_version = DOVECOT_ABI_VERSION; +struct http_client *solr_http_client = NULL; + +struct fts_solr_user_module fts_solr_user_module = + MODULE_CONTEXT_INIT(&mail_user_module_register); + +static int +fts_solr_plugin_init_settings(struct mail_user *user, + struct fts_solr_settings *set, const char *str) +{ + const char *const *tmp; + + if (str == NULL) + str = ""; + + set->batch_size = DEFAULT_SOLR_BATCH_SIZE; + set->soft_commit = TRUE; + + for (tmp = t_strsplit_spaces(str, " "); *tmp != NULL; tmp++) { + if (str_begins(*tmp, "url=")) { + set->url = p_strdup(user->pool, *tmp + 4); + } else if (strcmp(*tmp, "debug") == 0) { + set->debug = TRUE; + } else if (strcmp(*tmp, "use_libfts") == 0) { + set->use_libfts = TRUE; + } else if (str_begins(*tmp, "default_ns=")) { + set->default_ns_prefix = + p_strdup(user->pool, *tmp + 11); + } else if (str_begins(*tmp, "rawlog_dir=")) { + set->rawlog_dir = p_strdup(user->pool, *tmp + 11); + } else if (str_begins(*tmp, "batch_size=")) { + if (str_to_uint(*tmp+11, &set->batch_size) < 0 || + set->batch_size == 0) { + i_error("fts_solr: batch_size must be a positive integer"); + return -1; + } + } else if (str_begins(*tmp, "soft_commit=")) { + if (strcmp(*tmp + 12, "yes") == 0) { + set->soft_commit = TRUE; + } else if (strcmp(*tmp + 12, "no") == 0) { + set->soft_commit = FALSE; + } else { + i_error("fts_solr: Invalid setting for soft_commit: %s", *tmp+12); + return -1; + } + } else { + i_error("fts_solr: Invalid setting: %s", *tmp); + return -1; + } + } + if (set->url == NULL) { + i_error("fts_solr: url setting missing"); + return -1; + } + return 0; +} + +static void fts_solr_mail_user_deinit(struct mail_user *user) +{ + struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT_REQUIRE(user); + + fts_mail_user_deinit(user); + fuser->module_ctx.super.deinit(user); +} + +static void fts_solr_mail_user_create(struct mail_user *user, const char *env) +{ + struct mail_user_vfuncs *v = user->vlast; + struct fts_solr_user *fuser; + const char *error; + + fuser = p_new(user->pool, struct fts_solr_user, 1); + if (fts_solr_plugin_init_settings(user, &fuser->set, env) < 0) { + /* invalid settings, disabling */ + return; + } + if (fts_mail_user_init(user, fuser->set.use_libfts, &error) < 0) { + i_error("fts-solr: %s", error); + return; + } + + fuser->module_ctx.super = *v; + user->vlast = &fuser->module_ctx.super; + v->deinit = fts_solr_mail_user_deinit; + MODULE_CONTEXT_SET(user, fts_solr_user_module, fuser); +} + +static void fts_solr_mail_user_created(struct mail_user *user) +{ + const char *env; + + env = mail_user_plugin_getenv(user, "fts_solr"); + if (env != NULL) + fts_solr_mail_user_create(user, env); +} + +static struct mail_storage_hooks fts_solr_mail_storage_hooks = { + .mail_user_created = fts_solr_mail_user_created +}; + +void fts_solr_plugin_init(struct module *module) +{ + fts_backend_register(&fts_backend_solr); + fts_backend_register(&fts_backend_solr_old); + mail_storage_hooks_add(module, &fts_solr_mail_storage_hooks); +} + +void fts_solr_plugin_deinit(void) +{ + fts_backend_unregister(fts_backend_solr.name); + fts_backend_unregister(fts_backend_solr_old.name); + mail_storage_hooks_remove(&fts_solr_mail_storage_hooks); + if (solr_http_client != NULL) + http_client_deinit(&solr_http_client); + +} + +const char *fts_solr_plugin_dependencies[] = { "fts", NULL }; diff --git a/src/plugins/fts-solr/fts-solr-plugin.h b/src/plugins/fts-solr/fts-solr-plugin.h new file mode 100644 index 0000000..abc1b66 --- /dev/null +++ b/src/plugins/fts-solr/fts-solr-plugin.h @@ -0,0 +1,35 @@ +#ifndef FTS_SOLR_PLUGIN_H +#define FTS_SOLR_PLUGIN_H + +#include "module-context.h" +#include "mail-user.h" +#include "fts-api-private.h" + +#define FTS_SOLR_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, fts_solr_user_module) +#define FTS_SOLR_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, fts_solr_user_module) + +struct fts_solr_settings { + const char *url, *default_ns_prefix, *rawlog_dir; + unsigned int batch_size; + bool use_libfts; + bool debug; + bool soft_commit; +}; + +struct fts_solr_user { + union mail_user_module_context module_ctx; + struct fts_solr_settings set; +}; + +extern const char *fts_solr_plugin_dependencies[]; +extern struct fts_backend fts_backend_solr; +extern struct fts_backend fts_backend_solr_old; +extern MODULE_CONTEXT_DEFINE(fts_solr_user_module, &mail_user_module_register); +extern struct http_client *solr_http_client; + +void fts_solr_plugin_init(struct module *module); +void fts_solr_plugin_deinit(void); + +#endif diff --git a/src/plugins/fts-solr/solr-connection.c b/src/plugins/fts-solr/solr-connection.c new file mode 100644 index 0000000..41a4fee --- /dev/null +++ b/src/plugins/fts-solr/solr-connection.c @@ -0,0 +1,327 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "str.h" +#include "strescape.h" +#include "ioloop.h" +#include "istream.h" +#include "http-url.h" +#include "http-client.h" +#include "fts-solr-plugin.h" +#include "solr-connection.h" + +#include <expat.h> + +struct solr_lookup_context { + pool_t result_pool; + struct istream *payload; + struct io *io; + + int request_status; + + struct solr_response_parser *parser; + struct solr_result **results; +}; + +struct solr_connection_post { + struct solr_connection *conn; + + struct http_client_request *http_req; + int request_status; + + bool failed:1; +}; + +struct solr_connection { + struct event *event; + char *http_host; + in_port_t http_port; + char *http_base_url; + char *http_failure; + char *http_user; + char *http_password; + + bool debug:1; + bool posting:1; + bool http_ssl:1; +}; + +/* Regardless of the specified URL, make sure path ends in '/' */ +static char *solr_connection_create_http_base_url(struct http_url *http_url) +{ + if (http_url->path == NULL) + return i_strconcat("/", http_url->enc_query, NULL); + size_t len = strlen(http_url->path); + if (len > 0 && http_url->path[len-1] != '/') + return i_strconcat(http_url->path, "/", + http_url->enc_query, NULL); + /* http_url->path is NULL on empty path, so this is impossible. */ + i_assert(len != 0); + return i_strconcat(http_url->path, http_url->enc_query, NULL); +} + +int solr_connection_init(const struct fts_solr_settings *solr_set, + const struct ssl_iostream_settings *ssl_client_set, + struct event *event_parent, + struct solr_connection **conn_r, const char **error_r) +{ + struct http_client_settings http_set; + struct solr_connection *conn; + struct http_url *http_url; + const char *error; + + if (http_url_parse(solr_set->url, NULL, HTTP_URL_ALLOW_USERINFO_PART, + pool_datastack_create(), &http_url, &error) < 0) { + *error_r = t_strdup_printf( + "fts_solr: Failed to parse HTTP url: %s", error); + return -1; + } + + conn = i_new(struct solr_connection, 1); + conn->event = event_create(event_parent); + conn->http_host = i_strdup(http_url->host.name); + conn->http_port = http_url->port; + conn->http_base_url = solr_connection_create_http_base_url(http_url); + conn->http_ssl = http_url->have_ssl; + if (http_url->user != NULL) { + conn->http_user = i_strdup(http_url->user); + /* allow empty password */ + conn->http_password = i_strdup(http_url->password != NULL ? + http_url->password : ""); + } + + conn->debug = solr_set->debug; + + if (solr_http_client == NULL) { + i_zero(&http_set); + http_set.max_idle_time_msecs = 5*1000; + http_set.max_parallel_connections = 1; + http_set.max_pipelined_requests = 1; + http_set.max_redirects = 1; + http_set.max_attempts = 3; + http_set.connect_timeout_msecs = 5*1000; + http_set.request_timeout_msecs = 60*1000; + http_set.ssl = ssl_client_set; + http_set.debug = solr_set->debug; + http_set.rawlog_dir = solr_set->rawlog_dir; + http_set.event_parent = conn->event; + + /* FIXME: We should initialize a shared client instead. However, + this is currently not possible due to an obscure bug + in the blocking HTTP payload API, which causes + conflicts with other HTTP applications like FTS Tika. + Using a private client will provide a quick fix for + now. */ + solr_http_client = http_client_init_private(&http_set); + } + + *conn_r = conn; + return 0; +} + +void solr_connection_deinit(struct solr_connection **_conn) +{ + struct solr_connection *conn = *_conn; + + *_conn = NULL; + event_unref(&conn->event); + i_free(conn->http_host); + i_free(conn->http_base_url); + i_free(conn->http_user); + i_free(conn->http_password); + i_free(conn); +} + +static void solr_connection_payload_input(struct solr_lookup_context *lctx) +{ + int ret; + + /* read payload */ + ret = solr_response_parse(lctx->parser, &lctx->results); + + if (ret == 0) { + /* we will be called again for more data */ + } else { + if (lctx->payload->stream_errno != 0) { + i_assert(ret < 0); + i_error("fts_solr: " + "failed to read payload from HTTP server: %s", + i_stream_get_error(lctx->payload)); + } + if (ret < 0) + lctx->request_status = -1; + solr_response_parser_deinit(&lctx->parser); + io_remove(&lctx->io); + } +} + +static void +solr_connection_select_response(const struct http_response *response, + struct solr_lookup_context *lctx) +{ + if (response->status / 100 != 2) { + i_error("fts_solr: Lookup failed: %s", + http_response_get_message(response)); + lctx->request_status = -1; + return; + } + + if (response->payload == NULL) { + i_error("fts_solr: Lookup failed: Empty response payload"); + lctx->request_status = -1; + return; + } + + lctx->parser = solr_response_parser_init(lctx->result_pool, + response->payload); + lctx->payload = response->payload; + lctx->io = io_add_istream(response->payload, + solr_connection_payload_input, lctx); + solr_connection_payload_input(lctx); +} + +int solr_connection_select(struct solr_connection *conn, const char *query, + pool_t pool, struct solr_result ***box_results_r) +{ + struct solr_lookup_context lctx; + struct http_client_request *http_req; + const char *url; + + i_zero(&lctx); + lctx.result_pool = pool; + + i_free_and_null(conn->http_failure); + url = t_strconcat(conn->http_base_url, "select?", query, NULL); + + http_req = http_client_request(solr_http_client, "GET", + conn->http_host, url, + solr_connection_select_response, + &lctx); + if (conn->http_user != NULL) { + http_client_request_set_auth_simple( + http_req, conn->http_user, conn->http_password); + } + http_client_request_set_port(http_req, conn->http_port); + http_client_request_set_ssl(http_req, conn->http_ssl); + http_client_request_submit(http_req); + + lctx.request_status = 0; + http_client_wait(solr_http_client); + + if (lctx.request_status < 0) + return -1; + + *box_results_r = lctx.results; + return 0; +} + +static void +solr_connection_update_response(const struct http_response *response, + struct solr_connection_post *post) +{ + if (response->status / 100 != 2) { + i_error("fts_solr: Indexing failed: %s", + http_response_get_message(response)); + post->request_status = -1; + } +} + +static struct http_client_request * +solr_connection_post_request(struct solr_connection_post *post) +{ + struct solr_connection *conn = post->conn; + struct http_client_request *http_req; + const char *url; + + url = t_strconcat(conn->http_base_url, "update", NULL); + + http_req = http_client_request(solr_http_client, "POST", + conn->http_host, url, + solr_connection_update_response, post); + if (conn->http_user != NULL) { + http_client_request_set_auth_simple( + http_req, conn->http_user, conn->http_password); + } + http_client_request_set_port(http_req, conn->http_port); + http_client_request_set_ssl(http_req, conn->http_ssl); + http_client_request_add_header(http_req, "Content-Type", "text/xml"); + return http_req; +} + +struct solr_connection_post * +solr_connection_post_begin(struct solr_connection *conn) +{ + struct solr_connection_post *post; + + i_assert(!conn->posting); + conn->posting = TRUE; + + post = i_new(struct solr_connection_post, 1); + post->conn = conn; + post->http_req = solr_connection_post_request(post); + return post; +} + +void solr_connection_post_more(struct solr_connection_post *post, + const unsigned char *data, size_t size) +{ + i_assert(post->conn->posting); + + if (post->failed) + return; + + if (post->request_status == 0) { + (void)http_client_request_send_payload( + &post->http_req, data, size); + } + if (post->request_status < 0) + post->failed = TRUE; +} + +int solr_connection_post_end(struct solr_connection_post **_post) +{ + struct solr_connection_post *post = *_post; + struct solr_connection *conn = post->conn; + int ret = post->failed ? -1 : 0; + + i_assert(conn->posting); + + *_post = NULL; + + if (!post->failed) { + if (http_client_request_finish_payload(&post->http_req) < 0 || + post->request_status < 0) { + ret = -1; + } + } else { + http_client_request_abort(&post->http_req); + } + i_free(post); + + conn->posting = FALSE; + return ret; +} + +int solr_connection_post(struct solr_connection *conn, const char *cmd) +{ + struct istream *post_payload; + struct solr_connection_post post; + + i_assert(!conn->posting); + + i_zero(&post); + post.conn = conn; + + post.http_req = solr_connection_post_request(&post); + post_payload = i_stream_create_from_data(cmd, strlen(cmd)); + http_client_request_set_payload(post.http_req, post_payload, TRUE); + i_stream_unref(&post_payload); + http_client_request_submit(post.http_req); + + post.request_status = 0; + http_client_wait(solr_http_client); + + return post.request_status; +} diff --git a/src/plugins/fts-solr/solr-connection.h b/src/plugins/fts-solr/solr-connection.h new file mode 100644 index 0000000..ebad8be --- /dev/null +++ b/src/plugins/fts-solr/solr-connection.h @@ -0,0 +1,26 @@ +#ifndef SOLR_CONNECTION_H +#define SOLR_CONNECTION_H + +#include "solr-response.h" + +struct solr_connection; +struct fts_solr_settings; + +int solr_connection_init(const struct fts_solr_settings *solr_set, + const struct ssl_iostream_settings *ssl_client_set, + struct event *event_parent, + struct solr_connection **conn_r, + const char **error_r); +void solr_connection_deinit(struct solr_connection **conn); + +int solr_connection_select(struct solr_connection *conn, const char *query, + pool_t pool, struct solr_result ***box_results_r); +int solr_connection_post(struct solr_connection *conn, const char *cmd); + +struct solr_connection_post * +solr_connection_post_begin(struct solr_connection *conn); +void solr_connection_post_more(struct solr_connection_post *post, + const unsigned char *data, size_t size); +int solr_connection_post_end(struct solr_connection_post **post); + +#endif diff --git a/src/plugins/fts-solr/solr-response.c b/src/plugins/fts-solr/solr-response.c new file mode 100644 index 0000000..65a6a1f --- /dev/null +++ b/src/plugins/fts-solr/solr-response.c @@ -0,0 +1,372 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "str.h" +#include "istream.h" +#include "solr-response.h" + +#include <expat.h> + +#define MAX_VALUE_LEN 2048 + +enum solr_xml_response_state { + SOLR_XML_RESPONSE_STATE_ROOT, + SOLR_XML_RESPONSE_STATE_RESPONSE, + SOLR_XML_RESPONSE_STATE_RESULT, + SOLR_XML_RESPONSE_STATE_DOC, + SOLR_XML_RESPONSE_STATE_CONTENT +}; + +enum solr_xml_content_state { + SOLR_XML_CONTENT_STATE_NONE = 0, + SOLR_XML_CONTENT_STATE_UID, + SOLR_XML_CONTENT_STATE_SCORE, + SOLR_XML_CONTENT_STATE_MAILBOX, + SOLR_XML_CONTENT_STATE_NAMESPACE, + SOLR_XML_CONTENT_STATE_UIDVALIDITY, + SOLR_XML_CONTENT_STATE_ERROR +}; + +struct solr_response_parser { + XML_Parser xml_parser; + struct istream *input; + + enum solr_xml_response_state state; + enum solr_xml_content_state content_state; + int depth; + string_t *buffer; + + uint32_t uid, uidvalidity; + float score; + char *mailbox, *ns; + + pool_t result_pool; + /* box_id -> solr_result */ + HASH_TABLE(char *, struct solr_result *) mailboxes; + ARRAY(struct solr_result *) results; + + bool xml_failed:1; +}; + +static int +solr_xml_parse(struct solr_response_parser *parser, + const void *data, size_t size, bool done) +{ + enum XML_Error err; + int line, col; + + if (parser->xml_failed) + return -1; + + if (XML_Parse(parser->xml_parser, data, size, done ? 1 : 0) != 0) + return 0; + + err = XML_GetErrorCode(parser->xml_parser); + if (err != XML_ERROR_FINISHED) { + line = XML_GetCurrentLineNumber(parser->xml_parser); + col = XML_GetCurrentColumnNumber(parser->xml_parser); + i_error("fts_solr: Invalid XML input at %d:%d: %s " + "(near: %.*s)", line, col, XML_ErrorString(err), + (int)I_MIN(size, 128), (const char *)data); + parser->xml_failed = TRUE; + return -1; + } + return 0; +} + +static const char *attrs_get_name(const char **attrs) +{ + for (; *attrs != NULL; attrs += 2) { + if (strcmp(attrs[0], "name") == 0) + return attrs[1]; + } + return ""; +} + +static void +solr_lookup_xml_start(void *context, const char *name, const char **attrs) +{ + struct solr_response_parser *parser = context; + const char *name_attr; + + i_assert(parser->depth >= (int)parser->state); + + parser->depth++; + if (parser->depth - 1 > (int)parser->state) { + /* skipping over unwanted elements */ + return; + } + + str_truncate(parser->buffer, 0); + + /* response -> result -> doc */ + switch (parser->state) { + case SOLR_XML_RESPONSE_STATE_ROOT: + if (strcmp(name, "response") == 0) + parser->state++; + break; + case SOLR_XML_RESPONSE_STATE_RESPONSE: + if (strcmp(name, "result") == 0) + parser->state++; + break; + case SOLR_XML_RESPONSE_STATE_RESULT: + if (strcmp(name, "doc") == 0) { + parser->state++; + parser->uid = 0; + parser->score = 0; + i_free_and_null(parser->mailbox); + i_free_and_null(parser->ns); + parser->uidvalidity = 0; + } + break; + case SOLR_XML_RESPONSE_STATE_DOC: + name_attr = attrs_get_name(attrs); + if (strcmp(name_attr, "uid") == 0) + parser->content_state = SOLR_XML_CONTENT_STATE_UID; + else if (strcmp(name_attr, "score") == 0) + parser->content_state = SOLR_XML_CONTENT_STATE_SCORE; + else if (strcmp(name_attr, "box") == 0) + parser->content_state = SOLR_XML_CONTENT_STATE_MAILBOX; + else if (strcmp(name_attr, "ns") == 0) + parser->content_state = SOLR_XML_CONTENT_STATE_NAMESPACE; + else if (strcmp(name_attr, "uidv") == 0) + parser->content_state = SOLR_XML_CONTENT_STATE_UIDVALIDITY; + else + break; + parser->state++; + break; + case SOLR_XML_RESPONSE_STATE_CONTENT: + break; + } +} + +static struct solr_result * +solr_result_get(struct solr_response_parser *parser, const char *box_id) +{ + struct solr_result *result; + char *box_id_dup; + + result = hash_table_lookup(parser->mailboxes, box_id); + if (result != NULL) + return result; + + box_id_dup = p_strdup(parser->result_pool, box_id); + result = p_new(parser->result_pool, struct solr_result, 1); + result->box_id = box_id_dup; + p_array_init(&result->uids, parser->result_pool, 32); + p_array_init(&result->scores, parser->result_pool, 32); + hash_table_insert(parser->mailboxes, box_id_dup, result); + array_push_back(&parser->results, &result); + return result; +} + +static int solr_lookup_add_doc(struct solr_response_parser *parser) +{ + struct fts_score_map *score; + struct solr_result *result; + const char *box_id; + + if (parser->uid == 0) { + i_error("fts_solr: uid missing from inside doc"); + return -1; + } + + if (parser->mailbox == NULL) { + /* looking up from a single mailbox only */ + box_id = ""; + } else if (parser->uidvalidity != 0) { + /* old style lookup */ + string_t *str = t_str_new(64); + str_printfa(str, "%u\001", parser->uidvalidity); + str_append(str, parser->mailbox); + if (parser->ns != NULL) + str_printfa(str, "\001%s", parser->ns); + box_id = str_c(str); + } else { + /* new style lookup */ + box_id = parser->mailbox; + } + result = solr_result_get(parser, box_id); + + if (seq_range_array_add(&result->uids, parser->uid)) { + /* duplicate result */ + } else if (parser->score != 0) { + score = array_append_space(&result->scores); + score->uid = parser->uid; + score->score = parser->score; + } + return 0; +} + +static void solr_lookup_xml_end(void *context, const char *name ATTR_UNUSED) +{ + struct solr_response_parser *parser = context; + string_t *buf = parser->buffer; + int ret; + + switch (parser->content_state) { + case SOLR_XML_CONTENT_STATE_NONE: + break; + case SOLR_XML_CONTENT_STATE_UID: + if (str_to_uint32(str_c(buf), &parser->uid) < 0 || + parser->uid == 0) { + i_error("fts_solr: received invalid uid '%s'", + str_c(buf)); + parser->content_state = SOLR_XML_CONTENT_STATE_ERROR; + } + break; + case SOLR_XML_CONTENT_STATE_SCORE: + parser->score = strtod(str_c(buf), NULL); + break; + case SOLR_XML_CONTENT_STATE_MAILBOX: + parser->mailbox = i_strdup(str_c(buf)); + break; + case SOLR_XML_CONTENT_STATE_NAMESPACE: + parser->ns = i_strdup(str_c(buf)); + break; + case SOLR_XML_CONTENT_STATE_UIDVALIDITY: + if (str_to_uint32(str_c(buf), &parser->uidvalidity) < 0) + i_error("fts_solr: received invalid uidvalidity"); + break; + case SOLR_XML_CONTENT_STATE_ERROR: + return; + } + + i_assert(parser->depth >= (int)parser->state); + + if (parser->state == SOLR_XML_RESPONSE_STATE_CONTENT && + parser->content_state == SOLR_XML_CONTENT_STATE_MAILBOX && + parser->mailbox == NULL) { + /* mailbox is namespace prefix */ + parser->mailbox = i_strdup(""); + } + + if (parser->depth == (int)parser->state) { + ret = 0; + if (parser->state == SOLR_XML_RESPONSE_STATE_DOC) { + T_BEGIN { + ret = solr_lookup_add_doc(parser); + } T_END; + } + parser->state--; + if (ret < 0) + parser->content_state = SOLR_XML_CONTENT_STATE_ERROR; + else + parser->content_state = SOLR_XML_CONTENT_STATE_NONE; + } + parser->depth--; +} + +static void solr_lookup_xml_data(void *context, const char *str, int len) +{ + struct solr_response_parser *parser = context; + + switch (parser->content_state) { + case SOLR_XML_CONTENT_STATE_NONE: + case SOLR_XML_CONTENT_STATE_ERROR: + /* ignore element data */ + return; + case SOLR_XML_CONTENT_STATE_UID: + case SOLR_XML_CONTENT_STATE_SCORE: + case SOLR_XML_CONTENT_STATE_MAILBOX: + case SOLR_XML_CONTENT_STATE_NAMESPACE: + case SOLR_XML_CONTENT_STATE_UIDVALIDITY: + break; + } + + if (str_len(parser->buffer) + len > MAX_VALUE_LEN) { + i_error("fts_solr: XML element data length out of range"); + parser->content_state = SOLR_XML_CONTENT_STATE_ERROR; + return; + } + + str_append_data(parser->buffer, str, len); +} + +struct solr_response_parser * +solr_response_parser_init(pool_t result_pool, struct istream *input) +{ + struct solr_response_parser *parser; + + parser = i_new(struct solr_response_parser, 1); + + parser->xml_parser = XML_ParserCreate("UTF-8"); + if (parser->xml_parser == NULL) { + i_fatal_status(FATAL_OUTOFMEM, + "fts_solr: Failed to allocate XML parser"); + } + + parser->buffer = str_new(default_pool, 256); + hash_table_create(&parser->mailboxes, default_pool, 0, + str_hash, strcmp); + + parser->result_pool = result_pool; + pool_ref(result_pool); + p_array_init(&parser->results, result_pool, 32); + + parser->input = input; + i_stream_ref(input); + + parser->xml_failed = FALSE; + XML_SetElementHandler(parser->xml_parser, + solr_lookup_xml_start, solr_lookup_xml_end); + XML_SetCharacterDataHandler(parser->xml_parser, solr_lookup_xml_data); + XML_SetUserData(parser->xml_parser, parser); + + return parser; +} + +void solr_response_parser_deinit(struct solr_response_parser **_parser) +{ + struct solr_response_parser *parser = *_parser; + + *_parser = NULL; + + if (parser == NULL) + return; + + str_free(&parser->buffer); + hash_table_destroy(&parser->mailboxes); + XML_ParserFree(parser->xml_parser); + i_stream_unref(&parser->input); + pool_unref(&parser->result_pool); + i_free(parser); +} + +int solr_response_parse(struct solr_response_parser *parser, + struct solr_result ***box_results_r) +{ + const unsigned char *data; + size_t size; + int stream_errno, ret; + + i_assert(parser->input != NULL); + i_zero(box_results_r); + + /* read payload */ + while ((ret = i_stream_read_more(parser->input, &data, &size)) > 0) { + (void)solr_xml_parse(parser, data, size, FALSE); + i_stream_skip(parser->input, size); + } + + if (ret == 0) { + /* we will be called again for more data */ + return 0; + } + + stream_errno = parser->input->stream_errno; + i_stream_unref(&parser->input); + + if (parser->content_state == SOLR_XML_CONTENT_STATE_ERROR) + return -1; + if (stream_errno != 0) + return -1; + + ret = solr_xml_parse(parser, "", 0, TRUE); + + array_append_zero(&parser->results); + *box_results_r = array_front_modifiable(&parser->results); + return (ret == 0 ? 1 : -1); +} diff --git a/src/plugins/fts-solr/solr-response.h b/src/plugins/fts-solr/solr-response.h new file mode 100644 index 0000000..1d5cdd5 --- /dev/null +++ b/src/plugins/fts-solr/solr-response.h @@ -0,0 +1,23 @@ +#ifndef SOLR_RESPONSE_H +#define SOLR_RESPONSE_H + +#include "seq-range-array.h" +#include "fts-api.h" + +struct solr_response_parser; + +struct solr_result { + const char *box_id; + + ARRAY_TYPE(seq_range) uids; + ARRAY_TYPE(fts_score_map) scores; +}; + +struct solr_response_parser * +solr_response_parser_init(pool_t result_pool, struct istream *input); +void solr_response_parser_deinit(struct solr_response_parser **_parser); + +int solr_response_parse(struct solr_response_parser *parser, + struct solr_result ***box_results_r); + +#endif diff --git a/src/plugins/fts-solr/test-solr-response.c b/src/plugins/fts-solr/test-solr-response.c new file mode 100644 index 0000000..8add6db --- /dev/null +++ b/src/plugins/fts-solr/test-solr-response.c @@ -0,0 +1,295 @@ +/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "istream.h" +#include "solr-response.h" +#include "test-common.h" + +#include <unistd.h> + +static bool debug = FALSE; + +struct solr_response_test_result { + const char *box_id; + struct fts_score_map *scores; +}; + +struct solr_response_test { + const char *input; + + struct solr_response_test_result *results; +}; + +struct fts_score_map test_results1_scores[] = { + { .score = 0.042314477, .uid = 1 }, + { .score = 0.06996078, .uid = 2, }, + { .score = 0.020381179, .uid = 3 }, + { .score = 0.020381179, .uid = 4 }, + { .score = 5.510487E-4, .uid = 6 }, + { .score = 0.0424253, .uid = 7 }, + { .score = 0.04215967, .uid = 8 }, + { .score = 0.02470572, .uid = 9 }, + { .score = 0.05936369, .uid = 10 }, + { .score = 0.048221838, .uid = 11 }, + { .score = 7.793006E-4, .uid = 12 }, + { .score = 2.7900032E-4, .uid = 13 }, + { .score = 0.02088323, .uid = 14 }, + { .score = 0.011646388, .uid = 15 }, + { .score = 1.3776218E-4, .uid = 17 }, + { .score = 2.386111E-4, .uid = 19 }, + { .score = 2.7552436E-4, .uid = 20 }, + { .score = 4.772222E-4, .uid = 23 }, + { .score = 4.772222E-4, .uid = 24 }, + { .score = 5.965277E-4, .uid = 25 }, + { .score = 0.0471366, .uid = 26 }, + { .score = 0.0471366, .uid = 50 }, + { .score = 0.047274362, .uid = 51 }, + { .score = 0.053303234, .uid = 56 }, + { .score = 5.445528E-4, .uid = 62 }, + { .score = 2.922377E-4, .uid = 66 }, + { .score = 0.02623833, .uid = 68 }, + { .score = 3.4440547E-4, .uid = 70 }, + { .score = 2.922377E-4, .uid = 74 }, + { .score = 2.7552436E-4, .uid = 76 }, + { .score = 1.3776218E-4, .uid = 77 }, + { .score = 0, .uid = 0 }, +}; + +struct solr_response_test_result test_results1[] = { + { + .box_id = "", + .scores = test_results1_scores, + }, + { + .box_id = NULL + } +}; + +static const struct solr_response_test tests[] = { + { + .input = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<response>\n" + "<lst name=\"responseHeader\"><int name=\"status\"" + ">0</int><int name=\"QTime\">3</int><lst name=\"pa" + "rams\"><str name=\"wt\">xml</str><str name=\"fl\"" + ">uid,score</str><str name=\"rows\">4023</str><str" + " name=\"sort\">uid asc</str><str name=\"q\">{!luc" + "ene q.op=AND}subject:pierreserveur OR from:pierre" + "serveur OR to:pierreserveur OR cc:pierreserveur O" + "R bcc:pierreserveur OR body:pierreserveur</str><s" + "tr name=\"fq\">+box:fa74101044cb607d5f0900001de14" + "712 +user:jpierreserveur</str></lst></lst><result" + " name=\"response\" numFound=\"31\" start=\"0\" ma" + "xScore=\"0.06996078\"><doc><float name=\"score\">" + "0.042314477</float><long name=\"uid\">1</long></d" + "oc><doc><float name=\"score\">0.06996078</float><" + "long name=\"uid\">2</long></doc><doc><float name=" + "\"score\">0.020381179</float><long name=\"uid\">3" + "</long></doc><doc><float name=\"score\">0.0203811" + "79</float><long name=\"uid\">4</long></doc><doc><" + "float name=\"score\">5.510487E-4</float><long nam" + "e=\"uid\">6</long></doc><doc><float name=\"score\"" + ">0.0424253</float><long name=\"uid\">7</long></do" + "c><doc><float name=\"score\">0.04215967</float><l" + "ong name=\"uid\">8</long></doc><doc><float name=\"" + "score\">0.02470572</float><long name=\"uid\">9</l" + "ong></doc><doc><float name=\"score\">0.05936369</" + "float><long name=\"uid\">10</long></doc><doc><flo" + "at name=\"score\">0.048221838</float><long name=\"" + "uid\">11</long></doc><doc><float name=\"score\">7" + ".793006E-4</float><long name=\"uid\">12</long></d" + "oc><doc><float name=\"score\">2.7900032E-4</float" + "><long name=\"uid\">13</long></doc><doc><float na" + "me=\"score\">0.02088323</float><long name=\"uid\"" + ">14</long></doc><doc><float name=\"score\">0.0116" + "46388</float><long name=\"uid\">15</long></doc><d" + "oc><float name=\"score\">1.3776218E-4</float><lon" + "g name=\"uid\">17</long></doc><doc><float name=\"" + "score\">2.386111E-4</float><long name=\"uid\">19<" + "/long></doc><doc><float name=\"score\">2.7552436E" + "-4</float><long name=\"uid\">20</long></doc><doc>" + "<float name=\"score\">4.772222E-4</float><long na" + "me=\"uid\">23</long></doc><doc><float name=\"scor" + "e\">4.772222E-4</float><long name=\"uid\">24</lon" + "g></doc><doc><float name=\"score\">5.965277E-4</f" + "loat><long name=\"uid\">25</long></doc><doc><floa" + "t name=\"score\">0.0471366</float><long name=\"ui" + "d\">26</long></doc><doc><float name=\"score\">0.0" + "471366</float><long name=\"uid\">50</long></doc><" + "doc><float name=\"score\">0.047274362</float><lon" + "g name=\"uid\">51</long></doc><doc><float name=\"" + "score\">0.053303234</float><long name=\"uid\">56<" + "/long></doc><doc><float name=\"score\">5.445528E-" + "4</float><long name=\"uid\">62</long></doc><doc><" + "float name=\"score\">2.922377E-4</float><long nam" + "e=\"uid\">66</long></doc><doc><float name=\"score" + "\">0.02623833</float><long name=\"uid\">68</long>" + "</doc><doc><float name=\"score\">3.4440547E-4</fl" + "oat><long name=\"uid\">70</long></doc><doc><float" + " name=\"score\">2.922377E-4</float><long name=\"u" + "id\">74</long></doc><doc><float name=\"score\">2." + "7552436E-4</float><long name=\"uid\">76</long></d" + "oc><doc><float name=\"score\">1.3776218E-4</float" + "><long name=\"uid\">77</long></doc></result>\n" + "</response>\n", + .results = test_results1, + }, +}; + +static const unsigned tests_count = N_ELEMENTS(tests); + +static void +test_solr_result(const struct solr_response_test_result *test_results, + struct solr_result **parse_results) +{ + unsigned int rcount, i; + + for (i = 0; test_results[i].box_id != NULL; i++); + rcount = i; + + for (i = 0; parse_results[i] != NULL; i++); + + test_out_quiet("result count equal", i == rcount); + if (test_has_failed()) + return; + + for (i = 0; i < rcount && parse_results[i] != NULL; i++) { + unsigned int scount, j; + const struct fts_score_map *tscores = test_results[i].scores; + const struct fts_score_map *pscores = + array_get(&parse_results[i]->scores, &scount); + + test_out_quiet(t_strdup_printf("box id equal[%u]", i), + strcmp(test_results[i].box_id, + parse_results[i]->box_id) == 0); + + for (j = 0; tscores[j].uid != 0; j++); + test_out_quiet(t_strdup_printf("scores count equal[%u]", i), + j == scount); + if (j != scount) + continue; + + for (j = 0; j < scount; j++) { + test_out_quiet( + t_strdup_printf("score uid equal[%u/%u]", i, j), + pscores[j].uid == tscores[j].uid); + test_out_quiet( + t_strdup_printf("score value equal[%u/%u]", i, j), + pscores[j].score == tscores[j].score); + } + } +} + +static void test_solr_response_parser(void) +{ + unsigned int i; + + for (i = 0; i < tests_count; i++) T_BEGIN { + const struct solr_response_test *test; + const char *text; + unsigned int pos, text_len; + struct istream *input; + struct solr_response_parser *parser; + struct solr_result **box_results; + const char *error = NULL; + pool_t pool; + int ret = 0; + + test = &tests[i]; + text = test->input; + text_len = strlen(text); + + test_begin(t_strdup_printf("solr response [%d]", i)); + + input = test_istream_create_data(text, text_len); + pool = pool_alloconly_create("solr response", 4096); + parser = solr_response_parser_init(pool, input); + + ret = solr_response_parse(parser, &box_results); + + test_out_reason("parse ok (buffer)", ret > 0, error); + if (ret > 0) + test_solr_result(test->results, box_results); + + solr_response_parser_deinit(&parser); + pool_unref(&pool); + i_stream_unref(&input); + + input = test_istream_create_data(text, text_len); + pool = pool_alloconly_create("solr response", 4096); + parser = solr_response_parser_init(pool, input); + + ret = 0; + for (pos = 0; pos <= text_len && ret == 0; pos++) { + test_istream_set_size(input, pos); + ret = solr_response_parse(parser, &box_results); + } + + test_out_reason("parse ok (trickle)", ret > 0, error); + if (ret > 0) + test_solr_result(test->results, box_results); + + solr_response_parser_deinit(&parser); + pool_unref(&pool); + i_stream_unref(&input); + + test_end(); + + } T_END; +} + +static void test_solr_response_file(const char *file) +{ + pool_t pool; + struct istream *input; + struct solr_response_parser *parser; + struct solr_result **box_results; + int ret = 0; + + pool = pool_alloconly_create("solr response", 4096); + input = i_stream_create_file(file, 1024); + parser = solr_response_parser_init(pool, input); + + while ((ret = solr_response_parse(parser, &box_results)) == 0); + + if (ret < 0) + i_fatal("Failed to read response"); + + solr_response_parser_deinit(&parser); + i_stream_unref(&input); + pool_unref(&pool); +} + +int main(int argc, char *argv[]) +{ + int c; + + static void (*test_functions[])(void) = { + test_solr_response_parser, + NULL + }; + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + argc -= optind; + argv += optind; + + if (argc > 0) { + test_solr_response_file(argv[0]); + return 0; + } + + return test_run(test_functions); +} + + diff --git a/src/plugins/fts-squat/Makefile.am b/src/plugins/fts-squat/Makefile.am new file mode 100644 index 0000000..6c8181c --- /dev/null +++ b/src/plugins/fts-squat/Makefile.am @@ -0,0 +1,47 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/fts + +NOPLUGIN_LDFLAGS = +lib21_fts_squat_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib21_fts_squat_plugin.la + +if DOVECOT_PLUGIN_DEPS +lib21_fts_squat_plugin_la_LIBADD = \ + ../fts/lib20_fts_plugin.la +endif + +lib21_fts_squat_plugin_la_SOURCES = \ + fts-squat-plugin.c \ + fts-backend-squat.c \ + squat-trie.c \ + squat-uidlist.c + +noinst_HEADERS = \ + fts-squat-plugin.h \ + squat-trie.h \ + squat-trie-private.h \ + squat-uidlist.h + +noinst_PROGRAMS = squat-test + +squat_test_SOURCES = \ + squat-test.c + +common_objects = \ + squat-trie.lo \ + squat-uidlist.lo + +squat_test_LDADD = \ + $(common_objects) \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) +squat_test_DEPENDENCIES = \ + $(common_objects) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) diff --git a/src/plugins/fts-squat/Makefile.in b/src/plugins/fts-squat/Makefile.in new file mode 100644 index 0000000..443a7d0 --- /dev/null +++ b/src/plugins/fts-squat/Makefile.in @@ -0,0 +1,883 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = squat-test$(EXEEXT) +subdir = src/plugins/fts-squat +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +@DOVECOT_PLUGIN_DEPS_TRUE@lib21_fts_squat_plugin_la_DEPENDENCIES = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../fts/lib20_fts_plugin.la +am_lib21_fts_squat_plugin_la_OBJECTS = fts-squat-plugin.lo \ + fts-backend-squat.lo squat-trie.lo squat-uidlist.lo +lib21_fts_squat_plugin_la_OBJECTS = \ + $(am_lib21_fts_squat_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib21_fts_squat_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib21_fts_squat_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_squat_test_OBJECTS = squat-test.$(OBJEXT) +squat_test_OBJECTS = $(am_squat_test_OBJECTS) +am__DEPENDENCIES_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/fts-backend-squat.Plo \ + ./$(DEPDIR)/fts-squat-plugin.Plo ./$(DEPDIR)/squat-test.Po \ + ./$(DEPDIR)/squat-trie.Plo ./$(DEPDIR)/squat-uidlist.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib21_fts_squat_plugin_la_SOURCES) $(squat_test_SOURCES) +DIST_SOURCES = $(lib21_fts_squat_plugin_la_SOURCES) \ + $(squat_test_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/fts + +lib21_fts_squat_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib21_fts_squat_plugin.la + +@DOVECOT_PLUGIN_DEPS_TRUE@lib21_fts_squat_plugin_la_LIBADD = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../fts/lib20_fts_plugin.la + +lib21_fts_squat_plugin_la_SOURCES = \ + fts-squat-plugin.c \ + fts-backend-squat.c \ + squat-trie.c \ + squat-uidlist.c + +noinst_HEADERS = \ + fts-squat-plugin.h \ + squat-trie.h \ + squat-trie-private.h \ + squat-uidlist.h + +squat_test_SOURCES = \ + squat-test.c + +common_objects = \ + squat-trie.lo \ + squat-uidlist.lo + +squat_test_LDADD = \ + $(common_objects) \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) + +squat_test_DEPENDENCIES = \ + $(common_objects) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fts-squat/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/fts-squat/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib21_fts_squat_plugin.la: $(lib21_fts_squat_plugin_la_OBJECTS) $(lib21_fts_squat_plugin_la_DEPENDENCIES) $(EXTRA_lib21_fts_squat_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib21_fts_squat_plugin_la_LINK) -rpath $(moduledir) $(lib21_fts_squat_plugin_la_OBJECTS) $(lib21_fts_squat_plugin_la_LIBADD) $(LIBS) + +squat-test$(EXEEXT): $(squat_test_OBJECTS) $(squat_test_DEPENDENCIES) $(EXTRA_squat_test_DEPENDENCIES) + @rm -f squat-test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(squat_test_OBJECTS) $(squat_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-backend-squat.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-squat-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/squat-test.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/squat-trie.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/squat-uidlist.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/fts-backend-squat.Plo + -rm -f ./$(DEPDIR)/fts-squat-plugin.Plo + -rm -f ./$(DEPDIR)/squat-test.Po + -rm -f ./$(DEPDIR)/squat-trie.Plo + -rm -f ./$(DEPDIR)/squat-uidlist.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/fts-backend-squat.Plo + -rm -f ./$(DEPDIR)/fts-squat-plugin.Plo + -rm -f ./$(DEPDIR)/squat-test.Po + -rm -f ./$(DEPDIR)/squat-trie.Plo + -rm -f ./$(DEPDIR)/squat-uidlist.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/fts-squat/fts-backend-squat.c b/src/plugins/fts-squat/fts-backend-squat.c new file mode 100644 index 0000000..fbd7bbe --- /dev/null +++ b/src/plugins/fts-squat/fts-backend-squat.c @@ -0,0 +1,497 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "unichar.h" +#include "mail-user.h" +#include "mail-namespace.h" +#include "mail-storage-private.h" +#include "mail-search-build.h" +#include "squat-trie.h" +#include "fts-squat-plugin.h" + + +#define SQUAT_FILE_PREFIX "dovecot.index.search" + +struct squat_fts_backend { + struct fts_backend backend; + + struct mailbox *box; + struct squat_trie *trie; + + unsigned int partial_len, full_len; + bool refresh; +}; + +struct squat_fts_backend_update_context { + struct fts_backend_update_context ctx; + struct squat_trie_build_context *build_ctx; + + enum squat_index_type squat_type; + uint32_t uid; + string_t *hdr; + + bool failed; +}; + +static struct fts_backend *fts_backend_squat_alloc(void) +{ + struct squat_fts_backend *backend; + + backend = i_new(struct squat_fts_backend, 1); + backend->backend = fts_backend_squat; + return &backend->backend; +} + +static int +fts_backend_squat_init(struct fts_backend *_backend, const char **error_r) +{ + struct squat_fts_backend *backend = + (struct squat_fts_backend *)_backend; + const char *const *tmp, *env; + unsigned int len; + + env = mail_user_plugin_getenv(_backend->ns->user, "fts_squat"); + if (env == NULL) + return 0; + + for (tmp = t_strsplit_spaces(env, " "); *tmp != NULL; tmp++) { + if (str_begins(*tmp, "partial=")) { + if (str_to_uint(*tmp + 8, &len) < 0 || len == 0) { + *error_r = t_strdup_printf( + "Invalid partial length: %s", *tmp + 8); + return -1; + } + backend->partial_len = len; + } else if (str_begins(*tmp, "full=")) { + if (str_to_uint(*tmp + 5, &len) < 0 || len == 0) { + *error_r = t_strdup_printf( + "Invalid full length: %s", *tmp + 5); + return -1; + } + backend->full_len = len; + } else { + *error_r = t_strdup_printf("Invalid setting: %s", *tmp); + return -1; + } + } + return 0; +} + +static void +fts_backend_squat_unset_box(struct squat_fts_backend *backend) +{ + if (backend->trie != NULL) + squat_trie_deinit(&backend->trie); + backend->box = NULL; +} + +static void fts_backend_squat_deinit(struct fts_backend *_backend) +{ + struct squat_fts_backend *backend = + (struct squat_fts_backend *)_backend; + + fts_backend_squat_unset_box(backend); + i_free(backend); +} + +static int +fts_backend_squat_set_box(struct squat_fts_backend *backend, + struct mailbox *box) +{ + const struct mailbox_permissions *perm; + struct mail_storage *storage; + struct mailbox_status status; + const char *path; + enum squat_index_flags flags = 0; + int ret; + + if (backend->box == box) + { + if (backend->refresh) { + ret = squat_trie_refresh(backend->trie); + if (ret < 0) + return ret; + backend->refresh = FALSE; + } + return 0; + } + fts_backend_squat_unset_box(backend); + backend->refresh = FALSE; + if (box == NULL) + return 0; + + perm = mailbox_get_permissions(box); + storage = mailbox_get_storage(box); + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX, &path) <= 0) + i_unreached(); /* fts already checked this */ + + mailbox_get_open_status(box, STATUS_UIDVALIDITY, &status); + if (storage->set->mmap_disable) + flags |= SQUAT_INDEX_FLAG_MMAP_DISABLE; + if (storage->set->mail_nfs_index) + flags |= SQUAT_INDEX_FLAG_NFS_FLUSH; + if (storage->set->dotlock_use_excl) + flags |= SQUAT_INDEX_FLAG_DOTLOCK_USE_EXCL; + + backend->trie = + squat_trie_init(t_strconcat(path, "/"SQUAT_FILE_PREFIX, NULL), + status.uidvalidity, + storage->set->parsed_lock_method, + flags, perm->file_create_mode, + perm->file_create_gid); + + if (backend->partial_len != 0) + squat_trie_set_partial_len(backend->trie, backend->partial_len); + if (backend->full_len != 0) + squat_trie_set_full_len(backend->trie, backend->full_len); + backend->box = box; + return squat_trie_open(backend->trie); +} + +static int +fts_backend_squat_get_last_uid(struct fts_backend *_backend, + struct mailbox *box, uint32_t *last_uid_r) +{ + struct squat_fts_backend *backend = + (struct squat_fts_backend *)_backend; + + int ret = fts_backend_squat_set_box(backend, box); + if (ret < 0) + return -1; + return squat_trie_get_last_uid(backend->trie, last_uid_r); +} + +static struct fts_backend_update_context * +fts_backend_squat_update_init(struct fts_backend *_backend) +{ + struct squat_fts_backend_update_context *ctx; + + ctx = i_new(struct squat_fts_backend_update_context, 1); + ctx->ctx.backend = _backend; + ctx->hdr = str_new(default_pool, 1024*32); + return &ctx->ctx; +} + +static int get_all_msg_uids(struct mailbox *box, ARRAY_TYPE(seq_range) *uids) +{ + struct mailbox_transaction_context *t; + struct mail_search_context *search_ctx; + struct mail_search_args *search_args; + struct mail *mail; + int ret; + + t = mailbox_transaction_begin(box, 0, __func__); + + search_args = mail_search_build_init(); + mail_search_build_add_all(search_args); + search_ctx = mailbox_search_init(t, search_args, NULL, 0, NULL); + mail_search_args_unref(&search_args); + + while (mailbox_search_next(search_ctx, &mail)) { + /* *2 because even/odd is for body/header */ + seq_range_array_add_range(uids, mail->uid * 2, + mail->uid * 2 + 1); + } + ret = mailbox_search_deinit(&search_ctx); + (void)mailbox_transaction_commit(&t); + return ret; +} + +static int +fts_backend_squat_update_uid_changed(struct squat_fts_backend_update_context *ctx) +{ + int ret = 0; + + if (ctx->uid == 0) + return 0; + + if (squat_trie_build_more(ctx->build_ctx, ctx->uid, + SQUAT_INDEX_TYPE_HEADER, + str_data(ctx->hdr), str_len(ctx->hdr)) < 0) + ret = -1; + str_truncate(ctx->hdr, 0); + return ret; +} + +static int +fts_backend_squat_build_deinit(struct squat_fts_backend_update_context *ctx) +{ + struct squat_fts_backend *backend = + (struct squat_fts_backend *)ctx->ctx.backend; + ARRAY_TYPE(seq_range) uids; + int ret = 0; + + if (ctx->build_ctx == NULL) + return 0; + + if (fts_backend_squat_update_uid_changed(ctx) < 0) + ret = -1; + + i_array_init(&uids, 1024); + if (get_all_msg_uids(backend->box, &uids) < 0) { + (void)squat_trie_build_deinit(&ctx->build_ctx, NULL); + ret = -1; + } else { + seq_range_array_invert(&uids, 2, (uint32_t)-2); + if (squat_trie_build_deinit(&ctx->build_ctx, &uids) < 0) + ret = -1; + } + array_free(&uids); + return ret; +} + +static int +fts_backend_squat_update_deinit(struct fts_backend_update_context *_ctx) +{ + struct squat_fts_backend_update_context *ctx = + (struct squat_fts_backend_update_context *)_ctx; + int ret = ctx->failed ? -1 : 0; + + if (fts_backend_squat_build_deinit(ctx) < 0) + ret = -1; + str_free(&ctx->hdr); + i_free(ctx); + return ret; +} + +static void +fts_backend_squat_update_set_mailbox(struct fts_backend_update_context *_ctx, + struct mailbox *box) +{ + struct squat_fts_backend_update_context *ctx = + (struct squat_fts_backend_update_context *)_ctx; + struct squat_fts_backend *backend = + (struct squat_fts_backend *)ctx->ctx.backend; + + if (fts_backend_squat_build_deinit(ctx) < 0) + ctx->failed = TRUE; + if (fts_backend_squat_set_box(backend, box) < 0) + ctx->failed = TRUE; + else if (box != NULL) { + if (squat_trie_build_init(backend->trie, &ctx->build_ctx) < 0) + ctx->failed = TRUE; + } +} + +static void +fts_backend_squat_update_expunge(struct fts_backend_update_context *_ctx ATTR_UNUSED, + uint32_t last_uid ATTR_UNUSED) +{ + /* FIXME */ +} + +static bool +fts_backend_squat_update_set_build_key(struct fts_backend_update_context *_ctx, + const struct fts_backend_build_key *key) +{ + struct squat_fts_backend_update_context *ctx = + (struct squat_fts_backend_update_context *)_ctx; + + if (ctx->failed) + return FALSE; + + if (key->uid != ctx->uid) { + if (fts_backend_squat_update_uid_changed(ctx) < 0) + ctx->failed = TRUE; + } + + switch (key->type) { + case FTS_BACKEND_BUILD_KEY_HDR: + case FTS_BACKEND_BUILD_KEY_MIME_HDR: + str_printfa(ctx->hdr, "%s: ", key->hdr_name); + ctx->squat_type = SQUAT_INDEX_TYPE_HEADER; + break; + case FTS_BACKEND_BUILD_KEY_BODY_PART: + ctx->squat_type = SQUAT_INDEX_TYPE_BODY; + break; + case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY: + i_unreached(); + } + ctx->uid = key->uid; + return TRUE; +} + +static void +fts_backend_squat_update_unset_build_key(struct fts_backend_update_context *_ctx) +{ + struct squat_fts_backend_update_context *ctx = + (struct squat_fts_backend_update_context *)_ctx; + + if (ctx->squat_type == SQUAT_INDEX_TYPE_HEADER) + str_append_c(ctx->hdr, '\n'); +} + +static int +fts_backend_squat_update_build_more(struct fts_backend_update_context *_ctx, + const unsigned char *data, size_t size) +{ + struct squat_fts_backend_update_context *ctx = + (struct squat_fts_backend_update_context *)_ctx; + + if (ctx->squat_type == SQUAT_INDEX_TYPE_HEADER) { + str_append_data(ctx->hdr, data, size); + return 0; + } + return squat_trie_build_more(ctx->build_ctx, ctx->uid, ctx->squat_type, + data, size); +} + +static int fts_backend_squat_refresh(struct fts_backend *_backend) +{ + struct squat_fts_backend *backend = + (struct squat_fts_backend *)_backend; + + backend->refresh = TRUE; + return 0; +} + +static int fts_backend_squat_optimize(struct fts_backend *_backend ATTR_UNUSED) +{ + /* FIXME: drop expunged messages */ + return 0; +} + +static int squat_lookup_arg(struct squat_fts_backend *backend, + const struct mail_search_arg *arg, bool and_args, + ARRAY_TYPE(seq_range) *definite_uids, + ARRAY_TYPE(seq_range) *maybe_uids) +{ + enum squat_index_type squat_type; + ARRAY_TYPE(seq_range) tmp_definite_uids, tmp_maybe_uids; + string_t *dtc; + uint32_t last_uid; + int ret; + + switch (arg->type) { + case SEARCH_TEXT: + squat_type = SQUAT_INDEX_TYPE_HEADER | + SQUAT_INDEX_TYPE_BODY; + break; + case SEARCH_BODY: + squat_type = SQUAT_INDEX_TYPE_BODY; + break; + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + squat_type = SQUAT_INDEX_TYPE_HEADER; + break; + default: + return 0; + } + + i_array_init(&tmp_definite_uids, 128); + i_array_init(&tmp_maybe_uids, 128); + + dtc = t_str_new(128); + if (backend->backend.ns->user-> + default_normalizer(arg->value.str, strlen(arg->value.str), dtc) < 0) + i_panic("squat: search key not utf8"); + + ret = squat_trie_lookup(backend->trie, str_c(dtc), squat_type, + &tmp_definite_uids, &tmp_maybe_uids); + if (arg->match_not) { + /* definite -> non-match + maybe -> maybe + non-match -> maybe */ + array_clear(&tmp_maybe_uids); + + if (squat_trie_get_last_uid(backend->trie, &last_uid) < 0) + i_unreached(); + seq_range_array_add_range(&tmp_maybe_uids, 1, last_uid); + seq_range_array_remove_seq_range(&tmp_maybe_uids, + &tmp_definite_uids); + array_clear(&tmp_definite_uids); + } + + if (and_args) { + /* AND: + definite && definite -> definite + definite && maybe -> maybe + maybe && maybe -> maybe */ + + /* put definites among maybies, so they can be intersected */ + seq_range_array_merge(maybe_uids, definite_uids); + seq_range_array_merge(&tmp_maybe_uids, &tmp_definite_uids); + + seq_range_array_intersect(maybe_uids, &tmp_maybe_uids); + seq_range_array_intersect(definite_uids, &tmp_definite_uids); + /* remove duplicate maybies that are also definites */ + seq_range_array_remove_seq_range(maybe_uids, definite_uids); + } else { + /* OR: + definite || definite -> definite + definite || maybe -> definite + maybe || maybe -> maybe */ + + /* remove maybies that are now definites */ + seq_range_array_remove_seq_range(&tmp_maybe_uids, + definite_uids); + seq_range_array_remove_seq_range(maybe_uids, + &tmp_definite_uids); + + seq_range_array_merge(definite_uids, &tmp_definite_uids); + seq_range_array_merge(maybe_uids, &tmp_maybe_uids); + } + + array_free(&tmp_definite_uids); + array_free(&tmp_maybe_uids); + return ret < 0 ? -1 : 1; +} + +static int +fts_backend_squat_lookup(struct fts_backend *_backend, struct mailbox *box, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_result *result) +{ + struct squat_fts_backend *backend = + (struct squat_fts_backend *)_backend; + bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0; + bool first = TRUE; + int ret; + + ret = fts_backend_squat_set_box(backend, box); + if (ret < 0) + return -1; + + for (; args != NULL; args = args->next) { + ret = squat_lookup_arg(backend, args, first ? FALSE : and_args, + &result->definite_uids, + &result->maybe_uids); + if (ret < 0) + return -1; + if (ret > 0) { + args->match_always = TRUE; + first = FALSE; + } + } + return 0; +} + +struct fts_backend fts_backend_squat = { + .name = "squat", + .flags = FTS_BACKEND_FLAG_NORMALIZE_INPUT, + + { + fts_backend_squat_alloc, + fts_backend_squat_init, + fts_backend_squat_deinit, + fts_backend_squat_get_last_uid, + fts_backend_squat_update_init, + fts_backend_squat_update_deinit, + fts_backend_squat_update_set_mailbox, + fts_backend_squat_update_expunge, + fts_backend_squat_update_set_build_key, + fts_backend_squat_update_unset_build_key, + fts_backend_squat_update_build_more, + fts_backend_squat_refresh, + NULL, + fts_backend_squat_optimize, + fts_backend_default_can_lookup, + fts_backend_squat_lookup, + NULL, + NULL + } +}; diff --git a/src/plugins/fts-squat/fts-squat-plugin.c b/src/plugins/fts-squat/fts-squat-plugin.c new file mode 100644 index 0000000..59d9383 --- /dev/null +++ b/src/plugins/fts-squat/fts-squat-plugin.c @@ -0,0 +1,18 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "fts-squat-plugin.h" + +const char *fts_squat_plugin_version = DOVECOT_ABI_VERSION; + +void fts_squat_plugin_init(struct module *module ATTR_UNUSED) +{ + fts_backend_register(&fts_backend_squat); +} + +void fts_squat_plugin_deinit(void) +{ + fts_backend_unregister(fts_backend_squat.name); +} + +const char *fts_squat_plugin_dependencies[] = { "fts", NULL }; diff --git a/src/plugins/fts-squat/fts-squat-plugin.h b/src/plugins/fts-squat/fts-squat-plugin.h new file mode 100644 index 0000000..0d6bfcb --- /dev/null +++ b/src/plugins/fts-squat/fts-squat-plugin.h @@ -0,0 +1,14 @@ +#ifndef FTS_SQUAT_PLUGIN_H +#define FTS_SQUAT_PLUGIN_H + +#include "fts-api-private.h" + +struct module; + +extern const char *fts_squat_plugin_dependencies[]; +extern struct fts_backend fts_backend_squat; + +void fts_squat_plugin_init(struct module *module); +void fts_squat_plugin_deinit(void); + +#endif diff --git a/src/plugins/fts-squat/squat-test.c b/src/plugins/fts-squat/squat-test.c new file mode 100644 index 0000000..b55646c --- /dev/null +++ b/src/plugins/fts-squat/squat-test.c @@ -0,0 +1,197 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "file-lock.h" +#include "istream.h" +#include "time-util.h" +#include "unichar.h" +#include "squat-trie.h" +#include "squat-uidlist.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <sys/time.h> + +static void result_print(ARRAY_TYPE(seq_range) *result) +{ + const struct seq_range *range; + unsigned int i, count; + + range = array_get(result, &count); + for (i = 0; i < count; i++) { + if (i != 0) + printf(","); + printf("%u", range[i].seq1); + if (range[i].seq1 != range[i].seq2) + printf("-%u", range[i].seq2); + } + printf("\n"); +} + +int main(int argc ATTR_UNUSED, char *argv[]) +{ + const char *trie_path = "/tmp/squat-test-index.search"; + const char *uidlist_path = "/tmp/squat-test-index.search.uids"; + struct squat_trie *trie; + struct squat_trie_build_context *build_ctx; + struct istream *input; + struct stat trie_st, uidlist_st; + ARRAY_TYPE(seq_range) definite_uids, maybe_uids; + char *line, *str, buf[4096]; + buffer_t *valid; + int ret, fd; + unsigned int last = 0, seq = 1, node_count, uidlist_count; + size_t len; + enum squat_index_type index_type; + bool data_header = TRUE, first = TRUE, skip_body = FALSE; + bool mime_header = TRUE; + size_t trie_mem, uidlist_mem; + clock_t clock_start, clock_end; + struct timeval tv_start, tv_end; + double cputime; + + lib_init(); + i_unlink_if_exists(trie_path); + i_unlink_if_exists(uidlist_path); + trie = squat_trie_init(trie_path, time(NULL), + FILE_LOCK_METHOD_FCNTL, 0, 0600, (gid_t)-1); + + clock_start = clock(); + i_gettimeofday(&tv_start); + + fd = open(argv[1], O_RDONLY); + if (fd == -1) + return 1; + + if (squat_trie_build_init(trie, &build_ctx) < 0) + return 1; + + valid = buffer_create_dynamic(default_pool, 4096); + input = i_stream_create_fd(fd, SIZE_MAX); + ret = 0; + while (ret == 0 && (line = i_stream_read_next_line(input)) != NULL) { + if (last != input->v_offset/(1024*100)) { + fprintf(stderr, "\r%ukB", (unsigned)(input->v_offset/1024)); + fflush(stderr); + last = input->v_offset/(1024*100); + } + if (str_begins(line, "From ")) { + if (!first) + seq++; + data_header = TRUE; + skip_body = FALSE; + mime_header = TRUE; + continue; + } + first = FALSE; + + if (str_begins(line, "--")) { + skip_body = FALSE; + mime_header = TRUE; + } + + if (mime_header) { + if (*line == '\0') { + data_header = FALSE; + mime_header = FALSE; + continue; + } + + if (strncasecmp(line, "Content-Type:", 13) == 0 && + strncasecmp(line, "Content-Type: text/", 19) != 0 && + strncasecmp(line, "Content-Type: message/", 22) != 0) + skip_body = TRUE; + else if (strncasecmp(line, "Content-Transfer-Encoding: base64", 33) == 0) + skip_body = TRUE; + } else if (skip_body) + continue; + if (*line == '\0') + continue; + + /* we're actually indexing here headers as bodies and bodies + as headers. it doesn't really matter in this test, and + fixing it would require storing headers temporarily + elsewhere and index them only after the body */ + index_type = !data_header ? SQUAT_INDEX_TYPE_HEADER : + SQUAT_INDEX_TYPE_BODY; + + buffer_set_used_size(valid, 0); + len = strlen(line); + if (uni_utf8_get_valid_data((const unsigned char *)line, + len, valid)) { + ret = squat_trie_build_more(build_ctx, seq, index_type, + (const void *)line, len); + } else if (valid->used > 0) { + ret = squat_trie_build_more(build_ctx, seq, index_type, + valid->data, valid->used); + } + } + buffer_free(&valid); + if (squat_trie_build_deinit(&build_ctx, NULL) < 0) + ret = -1; + if (ret < 0) { + printf("build broken\n"); + return 1; + } + + clock_end = clock(); + i_gettimeofday(&tv_end); + + cputime = (double)(clock_end - clock_start) / CLOCKS_PER_SEC; + fprintf(stderr, "\n - Index time: %.2f CPU seconds, " + "%.2f real seconds (%.02fMB/CPUs)\n", cputime, + timeval_diff_msecs(&tv_end, &tv_start)/1000.0, + input->v_offset / cputime / (1024*1024)); + + if (stat(trie_path, &trie_st) < 0) + i_error("stat(%s) failed: %m", trie_path); + if (stat(uidlist_path, &uidlist_st) < 0) + i_error("stat(%s) failed: %m", uidlist_path); + + trie_mem = squat_trie_mem_used(trie, &node_count); + uidlist_mem = squat_uidlist_mem_used(squat_trie_get_uidlist(trie), + &uidlist_count); + fprintf(stderr, " - memory: %uk for trie, %uk for uidlist\n", + (unsigned)(trie_mem/1024), (unsigned)(uidlist_mem/1024)); + fprintf(stderr, " - %"PRIuUOFF_T" bytes in %u nodes (%.02f%%)\n", + trie_st.st_size, node_count, + trie_st.st_size / (float)input->v_offset * 100.0); + fprintf(stderr, " - %"PRIuUOFF_T" bytes in %u UID lists (%.02f%%)\n", + uidlist_st.st_size, uidlist_count, + uidlist_st.st_size / (float)input->v_offset * 100.0); + fprintf(stderr, " - %"PRIuUOFF_T" bytes total of %" + PRIuUOFF_T" (%.02f%%)\n", + (trie_st.st_size + uidlist_st.st_size), input->v_offset, + (trie_st.st_size + uidlist_st.st_size) / + (float)input->v_offset * 100.0); + + i_stream_unref(&input); + i_close_fd(&fd); + + i_array_init(&definite_uids, 128); + i_array_init(&maybe_uids, 128); + while ((str = fgets(buf, sizeof(buf), stdin)) != NULL) { + ret = strlen(str)-1; + str[ret] = 0; + + i_gettimeofday(&tv_start); + ret = squat_trie_lookup(trie, str, SQUAT_INDEX_TYPE_HEADER | + SQUAT_INDEX_TYPE_BODY, + &definite_uids, &maybe_uids); + if (ret < 0) + printf("error\n"); + else { + i_gettimeofday(&tv_end); + printf(" - Search took %.05f CPU seconds\n", + timeval_diff_usecs(&tv_end, &tv_start)/1000000.0); + printf(" - definite uids: "); + result_print(&definite_uids); + printf(" - maybe uids: "); + result_print(&maybe_uids); + } + } + return 0; +} diff --git a/src/plugins/fts-squat/squat-trie-private.h b/src/plugins/fts-squat/squat-trie-private.h new file mode 100644 index 0000000..b079554 --- /dev/null +++ b/src/plugins/fts-squat/squat-trie-private.h @@ -0,0 +1,192 @@ +#ifndef SQUAT_TRIE_PRIVATE_H +#define SQUAT_TRIE_PRIVATE_H + +#include "file-dotlock.h" +#include "squat-trie.h" + +#define SQUAT_TRIE_VERSION 2 +#define SQUAT_TRIE_LOCK_TIMEOUT 60 +#define SQUAT_TRIE_DOTLOCK_STALE_TIMEOUT (15*60) + +struct squat_file_header { + uint8_t version; + uint8_t unused[3]; + + uint32_t indexid; + uint32_t uidvalidity; + uint32_t used_file_size; + uint32_t deleted_space; + uint32_t node_count; + + uint32_t root_offset; + uint32_t root_unused_uids; + uint32_t root_next_uid; + uint32_t root_uidlist_idx; + + uint8_t partial_len; + uint8_t full_len; + uint8_t normalize_map[256]; +}; + +/* + node file: FIXME: no up-to-date + + struct squat_file_header; + + // children are written before their parents + node[] { + uint8_t child_count; + unsigned char chars[child_count]; + packed neg_diff_to_first_child_offset; // relative to node + packed diff_to_prev_offset[child_count-1]; + packed[child_count] { + // unused_uids_count == uid if have_uid_offset bit is zero + (unused_uids_count << 1) | (have_uid_offset); + [diff_to_prev_uid_offset;] // first one is relative to zero + } + } +*/ + +struct squat_node { + unsigned int child_count:8; + + /* children.leaf_string contains this many bytes */ + unsigned int leaf_string_length:16; + + /* TRUE = children.data contains our children. + FALSE = children.offset contains offset to our children in the + index file. */ + bool children_not_mapped:1; + /* When allocating our children, use a sequential array. */ + bool want_sequential:1; + /* This node's children are in a sequential array, meaning that the + first SEQUENTIAL_COUNT children have chars[n] = n. */ + bool have_sequential:1; + + /* Number of UIDs that exists in parent node but not in this one + (i.e. number of UIDs [0..next_uid-1] not in this node's uidlist). + This is mainly used when adding new UIDs to our children to set + the UID to be relative to this node's UID list. */ + uint32_t unused_uids; + + /* next_uid=0 means there are no UIDs in this node, otherwise + next_uid-1 is the last UID added to this node. */ + uint32_t next_uid; + uint32_t uid_list_idx; + + /* + struct { + unsigned char chars[child_count]; + struct squat_node[child_count]; + } *children; + */ + union { + /* children_not_mapped determines if data or offset should + be used. */ + void *data; + unsigned char *leaf_string; + unsigned char static_leaf_string[sizeof(void *)]; + uint32_t offset; + } children; +}; +/* Return pointer to node.children.chars[] */ +#define NODE_CHILDREN_CHARS(node) \ + ((unsigned char *)(node)->children.data) +/* Return pointer to node.children.node[] */ +#define NODE_CHILDREN_NODES(_node) \ + ((struct squat_node *)(NODE_CHILDREN_CHARS(_node) + \ + MEM_ALIGN((_node)->child_count))) +/* Return number of bytes allocated in node.children.data */ +#define NODE_CHILDREN_ALLOC_SIZE(child_count) \ + (MEM_ALIGN(child_count) + \ + ((child_count) / 8 + 1) * 8 * sizeof(struct squat_node)) +/* Return TRUE if children.leaf_string is set. */ +#define NODE_IS_DYNAMIC_LEAF(node) \ + ((node)->leaf_string_length > \ + sizeof((node)->children.static_leaf_string)) +/* Return node's leaf string. Assumes that it is set. */ +#define NODE_LEAF_STRING(node) \ + (NODE_IS_DYNAMIC_LEAF(node) ? \ + (node)->children.leaf_string : (node)->children.static_leaf_string) +struct squat_trie { + struct squat_node root; + struct squat_uidlist *uidlist; + + struct squat_file_header hdr; + size_t node_alloc_size; + unsigned int unmapped_child_count; + + enum squat_index_flags flags; + enum file_lock_method lock_method; + mode_t create_mode; + gid_t create_gid; + uint32_t uidvalidity; + + char *path; + int fd; + struct file_cache *file_cache; + struct dotlock_settings dotlock_set; + + uoff_t locked_file_size; + const void *data; + size_t data_size; + + void *mmap_base; + size_t mmap_size; + + unsigned char default_normalize_map[256]; + unsigned int default_partial_len; + unsigned int default_full_len; + + bool corrupted:1; +}; + +#define SQUAT_PACK_MAX_SIZE ((sizeof(uint32_t) * 8 + 7) / 7) + +static inline void squat_pack_num(uint8_t **p, uint32_t num) +{ + /* number continues as long as the highest bit is set */ + while (num >= 0x80) { + **p = (num & 0x7f) | 0x80; + *p += 1; + num >>= 7; + } + + **p = num; + *p += 1; +} + +static inline uint32_t squat_unpack_num(const uint8_t **p, const uint8_t *end) +{ + const uint8_t *c = *p; + uint32_t value = 0; + unsigned int bits = 0; + + for (;;) { + if (unlikely(c == end)) { + /* we should never see EOF */ + return 0; + } + + value |= (*c & 0x7f) << bits; + if (*c < 0x80) + break; + + bits += 7; + c++; + } + + if (unlikely(bits >= 32)) { + /* broken input */ + *p = end; + return 0; + } + + *p = c + 1; + return value; +} + +int squat_trie_create_fd(struct squat_trie *trie, const char *path, int flags); +void squat_trie_delete(struct squat_trie *trie); + +#endif diff --git a/src/plugins/fts-squat/squat-trie.c b/src/plugins/fts-squat/squat-trie.c new file mode 100644 index 0000000..d006817 --- /dev/null +++ b/src/plugins/fts-squat/squat-trie.c @@ -0,0 +1,2096 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "read-full.h" +#include "istream.h" +#include "ostream.h" +#include "unichar.h" +#include "nfs-workarounds.h" +#include "file-cache.h" +#include "seq-range-array.h" +#include "squat-uidlist.h" +#include "squat-trie-private.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/mman.h> + +#define DEFAULT_NORMALIZE_MAP_CHARS \ + "EOTIRSACDNLMVUGPHBFWYXKJQZ0123456789@.-+#$%_&" +#define DEFAULT_PARTIAL_LEN 4 +#define DEFAULT_FULL_LEN 4 + +#define MAX_FAST_LEVEL 3 +#define SEQUENTIAL_COUNT 46 + +#define TRIE_BYTES_LEFT(n) \ + ((n) * SQUAT_PACK_MAX_SIZE) +#define TRIE_READAHEAD_SIZE \ + I_MAX(4096, 1 + 256 + TRIE_BYTES_LEFT(256)) + +struct squat_trie_build_context { + struct squat_trie *trie; + struct ostream *output; + struct squat_uidlist_build_context *uidlist_build_ctx; + + struct file_lock *file_lock; + struct dotlock *dotlock; + + uint32_t first_uid; + bool compress_nodes:1; +}; + +struct squat_trie_iterate_node { + struct squat_node *node; + ARRAY_TYPE(seq_range) shifts; + unsigned int idx; +}; + +struct squat_trie_iterate_context { + struct squat_trie *trie; + struct squat_trie_iterate_node cur; + ARRAY(struct squat_trie_iterate_node) parents; + bool failed; +}; + +static int squat_trie_map(struct squat_trie *trie, bool building); + +void squat_trie_delete(struct squat_trie *trie) +{ + i_unlink_if_exists(trie->path); + squat_uidlist_delete(trie->uidlist); +} + +static void squat_trie_set_corrupted(struct squat_trie *trie) +{ + trie->corrupted = TRUE; + i_error("Corrupted file %s", trie->path); + squat_trie_delete(trie); +} + +static void squat_trie_normalize_map_build(struct squat_trie *trie) +{ + static unsigned char valid_chars[] = + DEFAULT_NORMALIZE_MAP_CHARS; + unsigned int i, j; + + memset(trie->default_normalize_map, 0, + sizeof(trie->default_normalize_map)); + +#if 1 + for (i = 0, j = 1; i < sizeof(valid_chars)-1; i++) { + unsigned char chr = valid_chars[i]; + + if (chr >= 'A' && chr <= 'Z') + trie->default_normalize_map[chr-'A'+'a'] = j; + trie->default_normalize_map[chr] = j++; + } + i_assert(j <= SEQUENTIAL_COUNT); + + for (i = 128; i < 256; i++) + trie->default_normalize_map[i] = j++; +#else + for (i = 0; i < sizeof(valid_chars)-1; i++) { + unsigned char chr = valid_chars[i]; + + if (chr >= 'A' && chr <= 'Z') + trie->default_normalize_map[chr-'A'+'a'] = chr; + trie->default_normalize_map[chr] = chr; + } + for (i = 128; i < 256; i++) + trie->default_normalize_map[i] = i_toupper(i); +#endif +} + +static void node_free(struct squat_trie *trie, struct squat_node *node) +{ + struct squat_node *children; + unsigned int i; + + if (node->leaf_string_length > 0) { + if (NODE_IS_DYNAMIC_LEAF(node)) + i_free(node->children.leaf_string); + } else if (!node->children_not_mapped) { + children = NODE_CHILDREN_NODES(node); + + trie->node_alloc_size -= + NODE_CHILDREN_ALLOC_SIZE(node->child_count); + for (i = 0; i < node->child_count; i++) + node_free(trie, &children[i]); + + i_free(node->children.data); + } +} + +struct squat_trie * +squat_trie_init(const char *path, uint32_t uidvalidity, + enum file_lock_method lock_method, enum squat_index_flags flags, + mode_t mode, gid_t gid) +{ + struct squat_trie *trie; + + trie = i_new(struct squat_trie, 1); + trie->path = i_strdup(path); + trie->uidlist = squat_uidlist_init(trie); + trie->fd = -1; + trie->lock_method = lock_method; + trie->uidvalidity = uidvalidity; + trie->flags = flags; + trie->create_mode = mode; + trie->create_gid = gid; + squat_trie_normalize_map_build(trie); + + trie->dotlock_set.use_excl_lock = + (flags & SQUAT_INDEX_FLAG_DOTLOCK_USE_EXCL) != 0; + trie->dotlock_set.nfs_flush = (flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0; + trie->dotlock_set.timeout = SQUAT_TRIE_LOCK_TIMEOUT; + trie->dotlock_set.stale_timeout = SQUAT_TRIE_DOTLOCK_STALE_TIMEOUT; + trie->default_partial_len = DEFAULT_PARTIAL_LEN; + trie->default_full_len = DEFAULT_FULL_LEN; + return trie; +} + +static void squat_trie_close_fd(struct squat_trie *trie) +{ + trie->data = NULL; + trie->data_size = 0; + + if (trie->mmap_size != 0) { + if (munmap(trie->mmap_base, trie->mmap_size) < 0) + i_error("munmap(%s) failed: %m", trie->path); + trie->mmap_base = NULL; + trie->mmap_size = 0; + } + i_close_fd_path(&trie->fd, trie->path); +} + +static void squat_trie_close(struct squat_trie *trie) +{ + trie->corrupted = FALSE; + node_free(trie, &trie->root); + i_zero(&trie->root); + i_zero(&trie->hdr); + + squat_trie_close_fd(trie); + if (trie->file_cache != NULL) + file_cache_free(&trie->file_cache); + trie->locked_file_size = 0; +} + +void squat_trie_deinit(struct squat_trie **_trie) +{ + struct squat_trie *trie = *_trie; + + *_trie = NULL; + squat_trie_close(trie); + squat_uidlist_deinit(trie->uidlist); + i_free(trie->path); + i_free(trie); +} + +void squat_trie_set_partial_len(struct squat_trie *trie, unsigned int len) +{ + trie->default_partial_len = len; +} + +void squat_trie_set_full_len(struct squat_trie *trie, unsigned int len) +{ + trie->default_full_len = len; +} + +static void squat_trie_header_init(struct squat_trie *trie) +{ + i_zero(&trie->hdr); + trie->hdr.version = SQUAT_TRIE_VERSION; + trie->hdr.indexid = time(NULL); + trie->hdr.uidvalidity = trie->uidvalidity; + trie->hdr.partial_len = trie->default_partial_len; + trie->hdr.full_len = trie->default_full_len; + + i_assert(sizeof(trie->hdr.normalize_map) == + sizeof(trie->default_normalize_map)); + memcpy(trie->hdr.normalize_map, trie->default_normalize_map, + sizeof(trie->hdr.normalize_map)); +} + +static int squat_trie_open_fd(struct squat_trie *trie) +{ + trie->fd = open(trie->path, O_RDWR); + if (trie->fd == -1) { + if (errno == ENOENT) { + squat_trie_header_init(trie); + return 0; + } + i_error("open(%s) failed: %m", trie->path); + return -1; + } + if (trie->file_cache != NULL) + file_cache_set_fd(trie->file_cache, trie->fd); + return 0; +} + +int squat_trie_open(struct squat_trie *trie) +{ + squat_trie_close(trie); + + if (squat_trie_open_fd(trie) < 0) + return -1; + return squat_trie_map(trie, FALSE); +} + +static int squat_trie_is_file_stale(struct squat_trie *trie) +{ + struct stat st, st2; + + if ((trie->flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0) + nfs_flush_file_handle_cache(trie->path); + if (nfs_safe_stat(trie->path, &st) < 0) { + if (errno == ENOENT) + return 1; + + i_error("stat(%s) failed: %m", trie->path); + return -1; + } + if (fstat(trie->fd, &st2) < 0) { + if (errno == ESTALE) + return 1; + i_error("fstat(%s) failed: %m", trie->path); + return -1; + } + trie->locked_file_size = st2.st_size; + + if (st.st_ino == st2.st_ino && CMP_DEV_T(st.st_dev, st2.st_dev)) { + i_assert(trie->locked_file_size >= trie->data_size); + return 0; + } + return 1; +} + +int squat_trie_refresh(struct squat_trie *trie) +{ + int ret; + + ret = squat_trie_is_file_stale(trie); + if (ret > 0) + ret = squat_trie_open(trie); + return ret; +} + +static int squat_trie_lock(struct squat_trie *trie, int lock_type, + struct file_lock **file_lock_r, + struct dotlock **dotlock_r) +{ + const char *error; + int ret; + + i_assert(trie->fd != -1); + + *file_lock_r = NULL; + *dotlock_r = NULL; + + for (;;) { + if (trie->lock_method != FILE_LOCK_METHOD_DOTLOCK) { + struct file_lock_settings lock_set = { + .lock_method = trie->lock_method, + }; + ret = file_wait_lock(trie->fd, trie->path, lock_type, + &lock_set, SQUAT_TRIE_LOCK_TIMEOUT, + file_lock_r, &error); + if (ret < 0) { + i_error("squat trie %s: %s", + trie->path, error); + } + } else { + ret = file_dotlock_create(&trie->dotlock_set, + trie->path, 0, dotlock_r); + } + if (ret == 0) { + i_error("squat trie %s: Locking timed out", trie->path); + return 0; + } + if (ret < 0) + return -1; + + /* if the trie has been compressed, we need to reopen the + file and try to lock again */ + ret = squat_trie_is_file_stale(trie); + if (ret == 0) + break; + + if (*file_lock_r != NULL) + file_unlock(file_lock_r); + else + file_dotlock_delete(dotlock_r); + if (ret < 0) + return -1; + + squat_trie_close(trie); + if (squat_trie_open_fd(trie) < 0) + return -1; + if (trie->fd == -1) + return 0; + } + + if ((trie->flags & SQUAT_INDEX_FLAG_NFS_FLUSH) != 0) + nfs_flush_read_cache_locked(trie->path, trie->fd); + return 1; +} + +static void +node_make_sequential(struct squat_trie *trie, struct squat_node *node, int level) +{ + const unsigned int alloc_size = + NODE_CHILDREN_ALLOC_SIZE(SEQUENTIAL_COUNT); + struct squat_node *children; + unsigned char *chars; + unsigned int i; + + i_assert(node->child_count == 0); + + trie->node_alloc_size += alloc_size; + + node->want_sequential = FALSE; + node->have_sequential = TRUE; + + node->child_count = SEQUENTIAL_COUNT; + node->children.data = i_malloc(alloc_size); + + chars = NODE_CHILDREN_CHARS(node); + for (i = 0; i < SEQUENTIAL_COUNT; i++) + chars[i] = i; + + if (level < MAX_FAST_LEVEL) { + children = NODE_CHILDREN_NODES(node); + for (i = 0; i < SEQUENTIAL_COUNT; i++) + children[i].want_sequential = TRUE; + } +} + +static unsigned int +node_add_child(struct squat_trie *trie, struct squat_node *node, + unsigned char chr, int level) +{ + unsigned int old_child_count = node->child_count; + struct squat_node *children, *old_children; + unsigned char *chars; + size_t old_size, new_size; + + i_assert(node->leaf_string_length == 0); + + if (node->want_sequential) { + node_make_sequential(trie, node, level); + + if (chr < SEQUENTIAL_COUNT) + return chr; + old_child_count = SEQUENTIAL_COUNT; + } + + node->child_count++; + new_size = NODE_CHILDREN_ALLOC_SIZE(node->child_count); + + if (old_child_count == 0) { + /* first child */ + node->children.data = i_malloc(new_size); + trie->node_alloc_size += new_size; + } else { + old_size = NODE_CHILDREN_ALLOC_SIZE(old_child_count); + if (old_size != new_size) { + trie->node_alloc_size += new_size - old_size; + node->children.data = i_realloc(node->children.data, + old_size, new_size); + } + + children = NODE_CHILDREN_NODES(node); + old_children = (void *)(NODE_CHILDREN_CHARS(node) + + MEM_ALIGN(old_child_count)); + if (children != old_children) { + memmove(children, old_children, + old_child_count * sizeof(struct squat_node)); + } + } + + chars = NODE_CHILDREN_CHARS(node); + i_assert(chars != NULL); + chars[node->child_count - 1] = chr; + return node->child_count - 1; +} + +static int +trie_file_cache_read(struct squat_trie *trie, size_t offset, size_t size) +{ + if (trie->file_cache == NULL) + return 0; + + if (file_cache_read(trie->file_cache, offset, size) < 0) { + i_error("read(%s) failed: %m", trie->path); + return -1; + } + trie->data = file_cache_get_map(trie->file_cache, &trie->data_size); + return 0; +} + +static int +node_read_children(struct squat_trie *trie, struct squat_node *node, int level) +{ + const uint8_t *data, *end; + const unsigned char *child_chars; + struct squat_node *child, *children = NULL; + uoff_t node_offset; + unsigned int i, child_idx, child_count; + uoff_t base_offset; + uint32_t num; + + i_assert(node->children_not_mapped); + i_assert(!node->have_sequential); + i_assert(trie->unmapped_child_count > 0); + i_assert(trie->data_size <= trie->locked_file_size); + + trie->unmapped_child_count--; + node_offset = node->children.offset; + node->children_not_mapped = FALSE; + node->children.data = NULL; + + if (trie_file_cache_read(trie, node_offset, TRIE_READAHEAD_SIZE) < 0) + return -1; + if (unlikely(node_offset >= trie->data_size)) { + squat_trie_set_corrupted(trie); + return -1; + } + + data = CONST_PTR_OFFSET(trie->data, node_offset); + end = CONST_PTR_OFFSET(trie->data, trie->data_size); + child_count = *data++; + if (unlikely(node_offset + child_count >= trie->data_size)) { + squat_trie_set_corrupted(trie); + return -1; + } + + if (child_count == 0) + return 0; + + child_chars = data; + data += child_count; + + /* get child offsets */ + base_offset = node_offset; + for (i = 0; i < child_count; i++) { + /* we always start with !have_sequential, so at i=0 this + check always goes to add the first child */ + if (node->have_sequential && child_chars[i] < SEQUENTIAL_COUNT) + child_idx = child_chars[i]; + else { + child_idx = node_add_child(trie, node, child_chars[i], + level); + children = NODE_CHILDREN_NODES(node); + } + + i_assert(children != NULL); + + child = &children[child_idx]; + + /* 1) child offset */ + num = squat_unpack_num(&data, end); + if (num == 0) { + /* no children */ + } else { + if ((num & 1) != 0) { + base_offset += num >> 1; + } else { + base_offset -= num >> 1; + } + if (base_offset >= trie->locked_file_size) { + squat_trie_set_corrupted(trie); + return -1; + } + trie->unmapped_child_count++; + child->children_not_mapped = TRUE; + child->children.offset = base_offset; + } + + /* 2) uidlist */ + child->uid_list_idx = squat_unpack_num(&data, end); + if (child->uid_list_idx == 0) { + /* we don't write nodes with empty uidlists */ + squat_trie_set_corrupted(trie); + return -1; + } + if (!UIDLIST_IS_SINGLETON(child->uid_list_idx)) { + /* 3) next uid */ + child->next_uid = squat_unpack_num(&data, end) + 1; + } else { + uint32_t idx = child->uid_list_idx; + + child->next_uid = 1 + + squat_uidlist_singleton_last_uid(idx); + } + + /* 4) unused uids + leaf string flag */ + num = squat_unpack_num(&data, end); + child->unused_uids = num >> 1; + if ((num & 1) != 0) { + /* leaf string */ + unsigned int len; + unsigned char *dest; + + /* 5) leaf string length */ + len = child->leaf_string_length = + squat_unpack_num(&data, end) + 1; + if (!NODE_IS_DYNAMIC_LEAF(child)) + dest = child->children.static_leaf_string; + else { + dest = child->children.leaf_string = + i_malloc(len); + } + + if (trie->file_cache != NULL) { + /* the string may be long - + recalculate the end pos */ + size_t offset, size; + + offset = (const char *)data - + (const char *)trie->data; + size = len + TRIE_BYTES_LEFT(child_count - i); + + if (trie_file_cache_read(trie, offset, + size) < 0) + return -1; + data = CONST_PTR_OFFSET(trie->data, offset); + end = CONST_PTR_OFFSET(trie->data, + trie->data_size); + child_chars = CONST_PTR_OFFSET(trie->data, + node_offset + 1); + } + + if ((size_t)(end - data) < len) { + squat_trie_set_corrupted(trie); + return -1; + } + memcpy(dest, data, len); + data += len; + } + } + if (unlikely(data == end)) { + /* we should never get this far */ + squat_trie_set_corrupted(trie); + return -1; + } + return 0; +} + +static void +node_write_children(struct squat_trie_build_context *ctx, + struct squat_node *node, const uoff_t *node_offsets) +{ + struct squat_node *children; + const unsigned char *chars; + uint8_t child_count, buf[SQUAT_PACK_MAX_SIZE * 5], *bufp; + uoff_t base_offset; + unsigned int i; + + chars = NODE_CHILDREN_CHARS(node); + children = NODE_CHILDREN_NODES(node); + + base_offset = ctx->output->offset; + child_count = node->child_count; + o_stream_nsend(ctx->output, &child_count, 1); + o_stream_nsend(ctx->output, chars, child_count); + + for (i = 0; i < child_count; i++) { + bufp = buf; + /* 1) child offset */ + if (node_offsets[i] == 0) + *bufp++ = 0; + else if (node_offsets[i] >= base_offset) { + squat_pack_num(&bufp, + ((node_offsets[i] - base_offset) << 1) | 1); + base_offset = node_offsets[i]; + } else { + squat_pack_num(&bufp, + (base_offset - node_offsets[i]) << 1); + base_offset = node_offsets[i]; + } + + /* 2) uidlist */ + squat_pack_num(&bufp, children[i].uid_list_idx); + if (!UIDLIST_IS_SINGLETON(children[i].uid_list_idx)) { + /* 3) next uid */ + squat_pack_num(&bufp, children[i].next_uid - 1); + } + + if (children[i].leaf_string_length == 0) { + /* 4a) unused uids */ + squat_pack_num(&bufp, children[i].unused_uids << 1); + o_stream_nsend(ctx->output, buf, bufp - buf); + } else { + i_assert(node_offsets[i] == 0); + /* 4b) unused uids + flag */ + squat_pack_num(&bufp, (children[i].unused_uids << 1) | 1); + /* 5) leaf string length */ + squat_pack_num(&bufp, children[i].leaf_string_length - 1); + o_stream_nsend(ctx->output, buf, bufp - buf); + o_stream_nsend(ctx->output, + NODE_LEAF_STRING(&children[i]), + children[i].leaf_string_length); + } + } +} + +static inline void +node_add_uid(struct squat_trie_build_context *ctx, uint32_t uid, + struct squat_node *node) +{ + if (uid < node->next_uid) { + /* duplicate */ + return; + } + node->unused_uids += uid - node->next_uid; + node->next_uid = uid + 1; + + node->uid_list_idx = + squat_uidlist_build_add_uid(ctx->uidlist_build_ctx, + node->uid_list_idx, uid); +} + +static void +node_split_string(struct squat_trie_build_context *ctx, struct squat_node *node) +{ + struct squat_node *child; + unsigned char *str; + unsigned int uid, idx, leafstr_len = node->leaf_string_length; + + i_assert(leafstr_len > 0); + + /* make a copy of the leaf string and convert to normal node by + removing it. */ + str = t_malloc_no0(leafstr_len); + if (!NODE_IS_DYNAMIC_LEAF(node)) + memcpy(str, node->children.static_leaf_string, leafstr_len); + else { + memcpy(str, node->children.leaf_string, leafstr_len); + i_free(node->children.leaf_string); + } + node->leaf_string_length = 0; + + /* create a new child node for the rest of the string */ + idx = node_add_child(ctx->trie, node, str[0], MAX_FAST_LEVEL); + child = NODE_CHILDREN_NODES(node) + idx; + + /* update uidlist to contain all of parent's UIDs */ + child->next_uid = node->next_uid - node->unused_uids; + for (uid = 0; uid < child->next_uid; uid++) { + child->uid_list_idx = + squat_uidlist_build_add_uid(ctx->uidlist_build_ctx, + child->uid_list_idx, uid); + } + + i_assert(!child->have_sequential && child->children.data == NULL); + if (leafstr_len > 1) { + /* make the child a leaf string */ + leafstr_len--; + child->leaf_string_length = leafstr_len; + if (!NODE_IS_DYNAMIC_LEAF(child)) { + memcpy(child->children.static_leaf_string, + str + 1, leafstr_len); + } else { + child->children.leaf_string = i_malloc(leafstr_len); + memcpy(child->children.leaf_string, + str + 1, leafstr_len); + } + } +} + +static bool +node_leaf_string_add_or_split(struct squat_trie_build_context *ctx, + struct squat_node *node, + const unsigned char *data, unsigned int data_len) +{ + const unsigned char *str = NODE_LEAF_STRING(node); + const unsigned int leafstr_len = node->leaf_string_length; + unsigned int i; + + if (data_len != leafstr_len) { + /* different lengths, can't match */ + T_BEGIN { + node_split_string(ctx, node); + } T_END; + return FALSE; + } + + for (i = 0; i < data_len; i++) { + if (data[i] != str[i]) { + /* non-match */ + T_BEGIN { + node_split_string(ctx, node); + } T_END; + return FALSE; + } + } + return TRUE; +} + +static int squat_build_add(struct squat_trie_build_context *ctx, uint32_t uid, + const unsigned char *data, unsigned int size) +{ + struct squat_trie *trie = ctx->trie; + struct squat_node *node = &trie->root; + const unsigned char *end = data + size; + unsigned char *chars; + unsigned int idx; + int level = 0; + + for (;;) { + if (node->children_not_mapped) { + if (unlikely(node_read_children(trie, node, level) < 0)) + return -1; + } + + if (node->leaf_string_length != 0) { + /* the whole string must match or we need to split + the node */ + if (node_leaf_string_add_or_split(ctx, node, data, + end - data)) { + node_add_uid(ctx, uid, node); + return 0; + } + } + + node_add_uid(ctx, uid, node); + + if (unlikely(uid < node->unused_uids)) { + squat_trie_set_corrupted(trie); + return -1; + } + /* child node's UIDs are relative to ours. so for example if + we're adding UID 4 and this node now has [2,4] UIDs, + unused_uids=3 and so the child node will be adding + UID 4-3 = 1. */ + uid -= node->unused_uids; + + if (data == end) + return 0; + level++; + + if (node->have_sequential) { + i_assert(node->child_count >= SEQUENTIAL_COUNT); + if (*data < SEQUENTIAL_COUNT) { + idx = *data; + goto found; + } + idx = SEQUENTIAL_COUNT; + } else { + idx = 0; + } + chars = NODE_CHILDREN_CHARS(node); + for (; idx < node->child_count; idx++) { + if (chars[idx] == *data) + goto found; + } + break; + found: + data++; + node = NODE_CHILDREN_NODES(node) + idx; + } + + /* create new children */ + i_assert(node->leaf_string_length == 0); + + for (;;) { + idx = node_add_child(trie, node, *data, + size - (end - data) + 1); + node = NODE_CHILDREN_NODES(node) + idx; + + node_add_uid(ctx, uid, node); + uid = 0; + + if (++data == end) + break; + + if (!node->have_sequential) { + /* convert the node into a leaf string */ + unsigned int len = end - data; + + i_assert(node->children.data == NULL); + node->leaf_string_length = len; + if (!NODE_IS_DYNAMIC_LEAF(node)) { + memcpy(node->children.static_leaf_string, + data, len); + } else { + node->children.leaf_string = i_malloc(len); + memcpy(node->children.leaf_string, data, len); + } + break; + } + } + return 0; +} + +static int +squat_build_word_bytes(struct squat_trie_build_context *ctx, uint32_t uid, + const unsigned char *data, unsigned int size) +{ + struct squat_trie *trie = ctx->trie; + unsigned int i; + + if (trie->hdr.full_len <= trie->hdr.partial_len) + i = 0; + else { + /* the first word is longer than others */ + if (squat_build_add(ctx, uid, data, + I_MIN(size, trie->hdr.full_len)) < 0) + return -1; + i = 1; + } + + for (; i < size; i++) { + if (squat_build_add(ctx, uid, data + i, + I_MIN(trie->hdr.partial_len, size-i)) < 0) + return -1; + } + return 0; +} + +static int +squat_build_word(struct squat_trie_build_context *ctx, uint32_t uid, + const unsigned char *data, const uint8_t *char_lengths, + unsigned int size) +{ + struct squat_trie *trie = ctx->trie; + unsigned int i, j, bytelen; + + if (char_lengths == NULL) { + /* optimization path: all characters are bytes */ + return squat_build_word_bytes(ctx, uid, data, size); + } + + if (trie->hdr.full_len <= trie->hdr.partial_len) + i = 0; + else { + /* the first word is longer than others */ + bytelen = 0; + for (j = 0; j < trie->hdr.full_len && bytelen < size; j++) + bytelen += char_lengths[bytelen]; + i_assert(bytelen <= size); + + if (squat_build_add(ctx, uid, data, bytelen) < 0) + return -1; + i = char_lengths[0]; + } + + for (; i < size; i += char_lengths[i]) { + bytelen = 0; + for (j = 0; j < trie->hdr.partial_len && i+bytelen < size; j++) + bytelen += char_lengths[i + bytelen]; + i_assert(i + bytelen <= size); + + if (squat_build_add(ctx, uid, data + i, bytelen) < 0) + return -1; + } + return 0; +} + +static unsigned char * +squat_data_normalize(struct squat_trie *trie, const unsigned char *data, + unsigned int size) +{ + static const unsigned char replacement_utf8[] = { 0xef, 0xbf, 0xbd }; + unsigned char *dest; + unsigned int i; + + dest = t_malloc_no0(size); + for (i = 0; i < size; i++) { + if (data[i] == replacement_utf8[0] && i + 2 < size && + data[i+1] == replacement_utf8[1] && + data[i+2] == replacement_utf8[2]) { + /* Don't index replacement character */ + dest[i++] = 0; + dest[i++] = 0; + dest[i] = 0; + } else { + dest[i] = trie->hdr.normalize_map[data[i]]; + } + } + return dest; +} + +static int +squat_trie_build_more_real(struct squat_trie_build_context *ctx, + uint32_t uid, enum squat_index_type type, + const unsigned char *input, unsigned int size) +{ + struct squat_trie *trie = ctx->trie; + const unsigned char *data; + uint8_t *char_lengths; + unsigned int i, start = 0; + bool multibyte_chars = FALSE; + int ret = 0; + + uid = uid * 2 + (type == SQUAT_INDEX_TYPE_HEADER ? 1 : 0); + + char_lengths = t_malloc_no0(size); + data = squat_data_normalize(trie, input, size); + for (i = 0; i < size; i++) { + char_lengths[i] = uni_utf8_char_bytes(input[i]); + if (char_lengths[i] != 1) + multibyte_chars = TRUE; + if (data[i] != '\0') + continue; + + while (start < i && data[start] == '\0') + start++; + if (i != start) { + if (squat_build_word(ctx, uid, data + start, + !multibyte_chars ? NULL : + char_lengths + start, + i - start) < 0) { + ret = -1; + start = i; + break; + } + } + start = i + 1; + } + while (start < i && data[start] == '\0') + start++; + if (i != start) { + if (squat_build_word(ctx, uid, data + start, + !multibyte_chars ? NULL : + char_lengths + start, i - start) < 0) + ret = -1; + } + return ret; +} + +int squat_trie_build_more(struct squat_trie_build_context *ctx, + uint32_t uid, enum squat_index_type type, + const unsigned char *input, unsigned int size) +{ + int ret = 0; + + if (size != 0) T_BEGIN { + ret = squat_trie_build_more_real(ctx, uid, type, input, size); + } T_END; + return ret; +} + +static void +node_drop_unused_children(struct squat_trie *trie, struct squat_node *node) +{ + unsigned char *chars; + struct squat_node *children_src, *children_dest; + unsigned int i, j, orig_child_count = node->child_count; + + chars = NODE_CHILDREN_CHARS(node); + children_src = NODE_CHILDREN_NODES(node); + + /* move chars */ + for (i = j = 0; i < orig_child_count; i++) { + if (children_src[i].next_uid != 0) + chars[j++] = chars[i]; + } + node->child_count = j; + + /* move children. note that children_dest may point to different + location than children_src, although they both point to the + same node. */ + children_dest = NODE_CHILDREN_NODES(node); + for (i = j = 0; i < orig_child_count; i++) { + if (children_src[i].next_uid != 0) + children_dest[j++] = children_src[i]; + else + node_free(trie, &children_src[i]); + } +} + +static int +squat_write_node(struct squat_trie_build_context *ctx, struct squat_node *node, + uoff_t *node_offset_r, int level) +{ + struct squat_trie *trie = ctx->trie; + struct squat_node *children; + unsigned int i; + uoff_t *node_offsets; + uint8_t child_count; + int ret; + + i_assert(node->next_uid != 0); + + if (node->children_not_mapped && ctx->compress_nodes) { + if (node_read_children(trie, node, MAX_FAST_LEVEL) < 0) + return -1; + } + + node->have_sequential = FALSE; + node_drop_unused_children(trie, node); + + child_count = node->child_count; + if (child_count == 0) { + i_assert(!node->children_not_mapped || + node->leaf_string_length == 0); + *node_offset_r = !node->children_not_mapped ? 0 : + node->children.offset; + return 0; + } + i_assert(!node->children_not_mapped); + + trie->hdr.node_count++; + + children = NODE_CHILDREN_NODES(node); + node_offsets = t_new(uoff_t, child_count); + for (i = 0; i < child_count; i++) { + T_BEGIN { + ret = squat_write_node(ctx, &children[i], + &node_offsets[i], level + 1); + } T_END; + if (ret < 0) + return -1; + } + + *node_offset_r = ctx->output->offset; + node_write_children(ctx, node, node_offsets); + return 0; +} + +static int squat_write_nodes(struct squat_trie_build_context *ctx) +{ + struct squat_trie *trie = ctx->trie; + uoff_t node_offset; + int ret; + + if (ctx->trie->root.next_uid == 0) + return 0; + + T_BEGIN { + ret = squat_write_node(ctx, &ctx->trie->root, &node_offset, 0); + } T_END; + if (ret < 0) + return -1; + + trie->hdr.root_offset = node_offset; + trie->hdr.root_unused_uids = trie->root.unused_uids; + trie->hdr.root_next_uid = trie->root.next_uid; + trie->hdr.root_uidlist_idx = trie->root.uid_list_idx; + return 0; +} + +static struct squat_trie_iterate_context * +squat_trie_iterate_init(struct squat_trie *trie) +{ + struct squat_trie_iterate_context *ctx; + + ctx = i_new(struct squat_trie_iterate_context, 1); + ctx->trie = trie; + ctx->cur.node = &trie->root; + i_array_init(&ctx->parents, trie->hdr.partial_len*2); + return ctx; +} + +static int +squat_trie_iterate_deinit(struct squat_trie_iterate_context *ctx) +{ + struct squat_trie_iterate_node *node; + int ret = ctx->failed ? -1 : 0; + + if (array_is_created(&ctx->cur.shifts)) { + array_foreach_modifiable(&ctx->parents, node) + array_free(&node->shifts); + array_free(&ctx->cur.shifts); + } + array_free(&ctx->parents); + i_free(ctx); + return ret; +} + +static struct squat_node * +squat_trie_iterate_first(struct squat_trie_iterate_context *ctx) +{ + if (ctx->cur.node->children_not_mapped) { + if (node_read_children(ctx->trie, ctx->cur.node, 1) < 0) { + ctx->failed = TRUE; + return NULL; + } + } + return ctx->cur.node; +} + +static struct squat_node * +squat_trie_iterate_next(struct squat_trie_iterate_context *ctx, + ARRAY_TYPE(seq_range) *shifts_r) +{ + struct squat_trie_iterate_node *iter_nodes; + struct squat_node *children; + unsigned int count, shift_count = 0; + + while (ctx->cur.idx == ctx->cur.node->child_count || + ctx->cur.node->uid_list_idx == 0) + { + iter_nodes = array_get_modifiable(&ctx->parents, &count); + if (count == 0) + return NULL; + + if (array_is_created(&ctx->cur.shifts)) + array_free(&ctx->cur.shifts); + ctx->cur = iter_nodes[count-1]; + array_delete(&ctx->parents, count-1, 1); + } + + *shifts_r = ctx->cur.shifts; + if (array_is_created(&ctx->cur.shifts)) + shift_count = array_count(&ctx->cur.shifts); + + children = NODE_CHILDREN_NODES(ctx->cur.node); + while (children[ctx->cur.idx++].uid_list_idx == 0) { + if (ctx->cur.idx == ctx->cur.node->child_count) { + /* no more non-empty children in this node */ + return squat_trie_iterate_next(ctx, shifts_r); + } + } + array_push_back(&ctx->parents, &ctx->cur); + ctx->cur.node = &children[ctx->cur.idx-1]; + ctx->cur.idx = 0; + if (shift_count != 0) + i_array_init(&ctx->cur.shifts, shift_count); + else + i_zero(&ctx->cur.shifts); + return squat_trie_iterate_first(ctx); +} + +static void +squat_uidlist_update_expunged_uids(const ARRAY_TYPE(seq_range) *shifts_arr, + ARRAY_TYPE(seq_range) *child_shifts, + ARRAY_TYPE(seq_range) *uids_arr, + struct squat_trie *trie, + struct squat_node *node, bool do_shifts) +{ + const struct seq_range *shifts; + struct seq_range *uids, shift; + unsigned int i, uid_idx, uid_count, shift_count; + uint32_t child_shift_seq1, child_shift_count, seq_high; + unsigned int shift_sum = 0, child_sum = 0; + + if (!array_is_created(shifts_arr)) { + i_assert(node->uid_list_idx != 0 || node->child_count == 0); + return; + } + + /* we'll recalculate this */ + node->unused_uids = 0; + + uids = array_get_modifiable(uids_arr, &uid_count); + shifts = array_get(shifts_arr, &shift_count); + for (i = 0, uid_idx = 0, seq_high = 0;; ) { + /* skip UID ranges until we skip/overlap shifts */ + while (uid_idx < uid_count && + (i == shift_count || + I_MAX(shifts[i].seq1, seq_high) > uids[uid_idx].seq2)) + { + i_assert(uids[uid_idx].seq1 >= shift_sum); + uids[uid_idx].seq1 -= shift_sum; + uids[uid_idx].seq2 -= shift_sum; + child_sum += uids[uid_idx].seq2 - + uids[uid_idx].seq1 + 1; + + if (uid_idx > 0 && + uids[uid_idx-1].seq2 >= uids[uid_idx].seq1 - 1) { + /* we can merge this and the previous range */ + i_assert(uids[uid_idx-1].seq2 == + uids[uid_idx].seq1 - 1); + uids[uid_idx-1].seq2 = uids[uid_idx].seq2; + array_delete(uids_arr, uid_idx, 1); + uids = array_get_modifiable(uids_arr, + &uid_count); + } else { + if (uid_idx == 0) + node->unused_uids += uids[0].seq1; + else { + node->unused_uids += + uids[uid_idx].seq1 - + uids[uid_idx-1].seq2 - 1; + } + uid_idx++; + } + } + if (uid_idx == uid_count) + break; + + shift.seq1 = I_MAX(shifts[i].seq1, seq_high); + shift.seq2 = shifts[i].seq2; + if (shift.seq2 < uids[uid_idx].seq1) { + /* shift is entirely before UID range */ + shift_sum += shift.seq2 - shift.seq1 + 1; + i++; + } else { + /* handle shifts before UID range */ + if (shift.seq1 < uids[uid_idx].seq1) { + shift_sum += uids[uid_idx].seq1 - shift.seq1; + shift.seq1 = uids[uid_idx].seq1; + } + /* update child shifts */ + child_shift_seq1 = child_sum + + shift.seq1 - uids[uid_idx].seq1; + child_shift_count = + I_MIN(shift.seq2, uids[uid_idx].seq2) - + shift.seq1 + 1; + seq_range_array_add_range(child_shifts, + child_shift_seq1, + child_shift_seq1 + + child_shift_count - 1); + child_sum += child_shift_count; + + /* if the shifts continue after the UID range, + treat it in the next loop iteration */ + if (shift.seq2 <= uids[uid_idx].seq2) + i++; + else + seq_high = uids[uid_idx].seq2 + 1; + + /* update UIDs - no matter where within the UID range + the shifts happened, the result is the same: + shift number of UIDs are removed, and the rest + are decreased by shift_sum. + + 123 uids child_shifts + a) s -> 12 1 + b) s -> 12 2 + c) s -> 12 3 + */ + if (uids[uid_idx].seq1 + + child_shift_count > uids[uid_idx].seq2) { + /* removed completely */ + array_delete(uids_arr, uid_idx, 1); + uids = array_get_modifiable(uids_arr, + &uid_count); + } else if (do_shifts) { + /* the next loop iteration fixes the UIDs */ + uids[uid_idx].seq1 += child_shift_count; + } else { + seq_range_array_remove_range(uids_arr, + shift.seq1, + I_MIN(shift.seq2, uids[uid_idx].seq2)); + uids = array_get_modifiable(uids_arr, + &uid_count); + } + shift_sum += child_shift_count; + } + if (!do_shifts) { + /* root node - UIDs are only removed, not shifted */ + shift_sum = 0; + } + } + + if (uid_count == 0) { + /* no UIDs left, delete the node's children and mark it + unused */ + if (!NODE_IS_DYNAMIC_LEAF(node)) + node_free(trie, node); + + node->child_count = 0; + node->have_sequential = FALSE; + node->next_uid = 0; + } else { + if (do_shifts) + node->next_uid = uids[uid_count-1].seq2 + 1; + else { + node->unused_uids += (node->next_uid - 1) - + uids[uid_count-1].seq2; + } + } +} + +static int +squat_trie_expunge_uidlists(struct squat_trie_build_context *ctx, + struct squat_uidlist_rebuild_context *rebuild_ctx, + struct squat_trie_iterate_context *iter, + const ARRAY_TYPE(seq_range) *expunged_uids) +{ + struct squat_node *node; + ARRAY_TYPE(seq_range) uid_range, root_shifts, shifts; + bool shift = FALSE; + int ret = 0; + + node = squat_trie_iterate_first(iter); + if (node->uid_list_idx == 0) + return 0; + + i_array_init(&uid_range, 1024); + i_array_init(&root_shifts, array_count(expunged_uids)); + array_append_array(&root_shifts, expunged_uids); + + if (array_count(expunged_uids) > 0) + i_array_init(&iter->cur.shifts, array_count(expunged_uids)); + + shifts = root_shifts; + do { + i_assert(node->uid_list_idx != 0); + array_clear(&uid_range); + if (squat_uidlist_get_seqrange(ctx->trie->uidlist, + node->uid_list_idx, + &uid_range) < 0) { + ret = -1; + break; + } + squat_uidlist_update_expunged_uids(&shifts, &iter->cur.shifts, + &uid_range, ctx->trie, node, + shift); + node->uid_list_idx = + squat_uidlist_rebuild_nextu(rebuild_ctx, &uid_range); + i_assert(node->uid_list_idx != 0 || node->next_uid == 0); + + node = squat_trie_iterate_next(iter, &shifts); + shift = TRUE; + } while (node != NULL); + array_free(&uid_range); + array_free(&root_shifts); + return ret; +} + +static int +squat_trie_renumber_uidlists2(struct squat_trie_build_context *ctx, + struct squat_uidlist_rebuild_context *rebuild_ctx, + struct squat_trie_iterate_context *iter) +{ + struct squat_node *node; + ARRAY_TYPE(seq_range) shifts; + ARRAY_TYPE(uint32_t) uids; + int ret = 0; + + node = squat_trie_iterate_first(iter); + if (node->uid_list_idx == 0) + return 0; + + i_array_init(&uids, 1024); + while (node != NULL) { + i_assert(node->uid_list_idx != 0); + if (!UIDLIST_IS_SINGLETON(node->uid_list_idx)) { + /* rebuild the uidlist */ + array_clear(&uids); + if (squat_uidlist_get(ctx->trie->uidlist, + node->uid_list_idx, &uids) < 0) { + ret = -1; + break; + } + node->uid_list_idx = + squat_uidlist_rebuild_next(rebuild_ctx, &uids); + } + node = squat_trie_iterate_next(iter, &shifts); + } + array_free(&uids); + return ret; +} + +static int +squat_trie_renumber_uidlists(struct squat_trie_build_context *ctx, + const ARRAY_TYPE(seq_range) *expunged_uids, + bool compress) +{ + struct squat_trie_iterate_context *iter; + struct squat_uidlist_rebuild_context *rebuild_ctx; + time_t now; + int ret = 0; + + if ((ret = squat_uidlist_rebuild_init(ctx->uidlist_build_ctx, + compress, &rebuild_ctx)) <= 0) + return ret; + + now = time(NULL); + ctx->trie->hdr.indexid = + I_MAX((unsigned int)now, ctx->trie->hdr.indexid + 1); + + iter = squat_trie_iterate_init(ctx->trie); + if (expunged_uids != NULL) { + ret = squat_trie_expunge_uidlists(ctx, rebuild_ctx, iter, + expunged_uids); + } else { + ret = squat_trie_renumber_uidlists2(ctx, rebuild_ctx, iter); + } + if (squat_trie_iterate_deinit(iter) < 0) + ret = -1; + + /* lock the trie before we rename uidlist */ + i_assert(ctx->file_lock == NULL && ctx->dotlock == NULL); + if (squat_trie_lock(ctx->trie, F_WRLCK, + &ctx->file_lock, &ctx->dotlock) <= 0) + ret = -1; + return squat_uidlist_rebuild_finish(rebuild_ctx, ret < 0); +} + +static bool squat_trie_check_header(struct squat_trie *trie) +{ + if (trie->hdr.version != SQUAT_TRIE_VERSION || + trie->hdr.uidvalidity != trie->uidvalidity) + return FALSE; + + if (trie->hdr.partial_len > trie->hdr.full_len) { + i_error("Corrupted %s: partial len > full len", trie->path); + return FALSE; + } + if (trie->hdr.full_len == 0) { + i_error("Corrupted %s: full len=0", trie->path); + return FALSE; + } + return TRUE; +} + +static int squat_trie_map_header(struct squat_trie *trie) +{ + int ret; + + if (trie->locked_file_size == 0) { + /* newly created file */ + squat_trie_header_init(trie); + return 1; + } + i_assert(trie->fd != -1); + + if ((trie->flags & SQUAT_INDEX_FLAG_MMAP_DISABLE) != 0) { + ret = pread_full(trie->fd, &trie->hdr, sizeof(trie->hdr), 0); + if (ret <= 0) { + if (ret < 0) { + i_error("pread(%s) failed: %m", trie->path); + return -1; + } + i_error("Corrupted %s: File too small", trie->path); + return 0; + } + trie->data = NULL; + trie->data_size = 0; + } else { + if (trie->locked_file_size < sizeof(trie->hdr)) { + i_error("Corrupted %s: File too small", trie->path); + return 0; + } + if (trie->mmap_size != 0) { + if (munmap(trie->mmap_base, trie->mmap_size) < 0) + i_error("munmap(%s) failed: %m", trie->path); + } + + trie->mmap_size = trie->locked_file_size; + trie->mmap_base = mmap(NULL, trie->mmap_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, trie->fd, 0); + if (trie->mmap_base == MAP_FAILED) { + trie->data = trie->mmap_base = NULL; + trie->data_size = trie->mmap_size = 0; + i_error("mmap(%s) failed: %m", trie->path); + return -1; + } + memcpy(&trie->hdr, trie->mmap_base, sizeof(trie->hdr)); + trie->data = trie->mmap_base; + trie->data_size = trie->mmap_size; + } + + return squat_trie_check_header(trie) ? 1 : 0; +} + +static int squat_trie_map(struct squat_trie *trie, bool building) +{ + struct file_lock *file_lock = NULL; + struct dotlock *dotlock = NULL; + bool changed; + int ret; + + if (trie->fd != -1) { + if (squat_trie_lock(trie, F_RDLCK, &file_lock, &dotlock) <= 0) + return -1; + if ((trie->flags & SQUAT_INDEX_FLAG_MMAP_DISABLE) != 0 && + trie->file_cache == NULL) + trie->file_cache = file_cache_new_path(trie->fd, trie->path); + } + + ret = squat_trie_map_header(trie); + if (ret == 0) { + if (file_lock != NULL) + file_unlock(&file_lock); + else + file_dotlock_delete(&dotlock); + squat_trie_delete(trie); + squat_trie_close(trie); + squat_trie_header_init(trie); + } + changed = trie->root.children.offset != trie->hdr.root_offset; + + if (changed || trie->hdr.root_offset == 0) { + node_free(trie, &trie->root); + i_zero(&trie->root); + trie->root.want_sequential = TRUE; + trie->root.unused_uids = trie->hdr.root_unused_uids; + trie->root.next_uid = trie->hdr.root_next_uid; + trie->root.uid_list_idx = trie->hdr.root_uidlist_idx; + trie->root.children.offset = trie->hdr.root_offset; + + if (trie->hdr.root_offset == 0) { + trie->unmapped_child_count = 0; + trie->root.children_not_mapped = FALSE; + } else { + trie->unmapped_child_count = 1; + trie->root.children_not_mapped = TRUE; + } + } + + if (ret >= 0 && !building) { + /* do this while we're still locked */ + ret = squat_uidlist_refresh(trie->uidlist); + } + + if (file_lock != NULL) + file_unlock(&file_lock); + if (dotlock != NULL) + file_dotlock_delete(&dotlock); + if (ret < 0) + return -1; + + return trie->hdr.root_offset == 0 || !changed ? 0 : + node_read_children(trie, &trie->root, 1); +} + +int squat_trie_create_fd(struct squat_trie *trie, const char *path, int flags) +{ + mode_t old_mask; + int fd; + + old_mask = umask(0); + fd = open(path, O_RDWR | O_CREAT | flags, trie->create_mode); + umask(old_mask); + if (fd == -1) { + i_error("creat(%s) failed: %m", path); + return -1; + } + if (trie->create_gid != (gid_t)-1) { + if (fchown(fd, (uid_t)-1, trie->create_gid) < 0) { + i_error("fchown(%s, -1, %ld) failed: %m", + path, (long)trie->create_gid); + i_close_fd(&fd); + return -1; + } + } + return fd; +} + +int squat_trie_build_init(struct squat_trie *trie, + struct squat_trie_build_context **ctx_r) +{ + struct squat_trie_build_context *ctx; + struct squat_uidlist_build_context *uidlist_build_ctx; + + if (trie->fd == -1) { + trie->fd = squat_trie_create_fd(trie, trie->path, 0); + if (trie->fd == -1) + return -1; + + if (trie->file_cache != NULL) + file_cache_set_fd(trie->file_cache, trie->fd); + i_assert(trie->locked_file_size == 0); + } + + /* uidlist locks building */ + if (squat_uidlist_build_init(trie->uidlist, &uidlist_build_ctx) < 0) + return -1; + + if (squat_trie_map(trie, TRUE) < 0) { + squat_uidlist_build_deinit(&uidlist_build_ctx); + return -1; + } + + ctx = i_new(struct squat_trie_build_context, 1); + ctx->trie = trie; + ctx->uidlist_build_ctx = uidlist_build_ctx; + ctx->first_uid = trie->root.next_uid; + + *ctx_r = ctx; + return 0; +} + +static int squat_trie_write_lock(struct squat_trie_build_context *ctx) +{ + if (ctx->file_lock != NULL || ctx->dotlock != NULL) + return 0; + + if (squat_trie_lock(ctx->trie, F_WRLCK, + &ctx->file_lock, &ctx->dotlock) <= 0) + return -1; + return 0; +} + +static int squat_trie_write(struct squat_trie_build_context *ctx) +{ + struct squat_trie *trie = ctx->trie; + struct file_lock *file_lock = NULL; + struct ostream *output; + const char *path, *error; + int fd = -1, ret = 0; + + if ((trie->hdr.used_file_size > sizeof(trie->hdr) && + trie->unmapped_child_count < trie->hdr.node_count/4) || 1) { + /* we might as well recreate the file */ + ctx->compress_nodes = TRUE; + + path = t_strconcat(trie->path, ".tmp", NULL); + fd = squat_trie_create_fd(trie, path, O_TRUNC); + if (fd == -1) + return -1; + + if (trie->lock_method != FILE_LOCK_METHOD_DOTLOCK) { + struct file_lock_settings lock_set = { + .lock_method = trie->lock_method, + }; + ret = file_wait_lock(fd, path, F_WRLCK, &lock_set, + SQUAT_TRIE_LOCK_TIMEOUT, + &file_lock, &error); + if (ret <= 0) { + i_error("file_wait_lock(%s) failed: %s", + path, error); + i_close_fd(&fd); + return -1; + } + } + + output = o_stream_create_fd(fd, 0); + o_stream_cork(output); + o_stream_nsend(output, &trie->hdr, sizeof(trie->hdr)); + } else { + /* we need to lock only while header is being written */ + path = trie->path; + ctx->compress_nodes = + trie->hdr.used_file_size == sizeof(trie->hdr); + + if (trie->hdr.used_file_size == 0) { + /* lock before opening the file, in case we reopen it */ + if (squat_trie_write_lock(ctx) < 0) + return -1; + } + output = o_stream_create_fd(trie->fd, 0); + o_stream_cork(output); + + if (trie->hdr.used_file_size != 0) + (void)o_stream_seek(output, trie->hdr.used_file_size); + else + o_stream_nsend(output, &trie->hdr, sizeof(trie->hdr)); + } + + ctx->output = output; + ret = squat_write_nodes(ctx); + ctx->output = NULL; + + /* write 1 byte guard at the end of file, so that we can verify broken + squat_unpack_num() input by checking if data==end */ + o_stream_nsend(output, "", 1); + + if (trie->corrupted) + ret = -1; + if (ret == 0) + ret = squat_trie_write_lock(ctx); + if (ret == 0) { + trie->hdr.used_file_size = output->offset; + (void)o_stream_seek(output, 0); + o_stream_nsend(output, &trie->hdr, sizeof(trie->hdr)); + } + if (o_stream_finish(output) < 0) { + i_error("write(%s) failed: %s", path, + o_stream_get_error(output)); + ret = -1; + } + o_stream_destroy(&output); + + if (fd == -1) { + /* appended to the existing file */ + i_assert(file_lock == NULL); + return ret; + } + + /* recreating the trie file */ + if (ret < 0) { + if (close(fd) < 0) + i_error("close(%s) failed: %m", path); + fd = -1; + } else if (rename(path, trie->path) < 0) { + i_error("rename(%s, %s) failed: %m", path, trie->path); + ret = -1; + } + + if (ret < 0) { + i_unlink_if_exists(path); + file_lock_free(&file_lock); + } else { + squat_trie_close_fd(trie); + trie->fd = fd; + trie->locked_file_size = trie->hdr.used_file_size; + if (trie->file_cache != NULL) + file_cache_set_fd(trie->file_cache, trie->fd); + + file_lock_free(&ctx->file_lock); + ctx->file_lock = file_lock; + } + return ret; +} + +int squat_trie_build_deinit(struct squat_trie_build_context **_ctx, + const ARRAY_TYPE(seq_range) *expunged_uids) +{ + struct squat_trie_build_context *ctx = *_ctx; + bool compress, unlock = TRUE; + int ret; + + *_ctx = NULL; + + compress = (ctx->trie->root.next_uid - ctx->first_uid) > 10; + + /* keep trie locked while header is being written and when files are + being renamed, so that while trie is read locked, uidlist can't + change under. */ + squat_uidlist_build_flush(ctx->uidlist_build_ctx); + ret = squat_trie_renumber_uidlists(ctx, expunged_uids, compress); + if (ret == 0) { + ret = squat_trie_write(ctx); + if (ret < 0) + unlock = FALSE; + } + + if (ret == 0) + ret = squat_uidlist_build_finish(ctx->uidlist_build_ctx); + if (ctx->file_lock != NULL) { + if (unlock) + file_unlock(&ctx->file_lock); + else + file_lock_free(&ctx->file_lock); + } + if (ctx->dotlock != NULL) + file_dotlock_delete(&ctx->dotlock); + squat_uidlist_build_deinit(&ctx->uidlist_build_ctx); + + i_free(ctx); + return ret; +} + +int squat_trie_get_last_uid(struct squat_trie *trie, uint32_t *last_uid_r) +{ + if (trie->fd == -1) { + if (squat_trie_open(trie) < 0) + return -1; + } + + *last_uid_r = I_MAX((trie->root.next_uid+1)/2, 1) - 1; + return 0; +} + +static int +squat_trie_lookup_data(struct squat_trie *trie, const unsigned char *data, + unsigned int size, ARRAY_TYPE(seq_range) *uids) +{ + struct squat_node *node = &trie->root; + unsigned char *chars; + unsigned int idx; + int level = 0; + + array_clear(uids); + + for (;;) { + if (node->children_not_mapped) { + if (node_read_children(trie, node, level) < 0) + return -1; + } + if (node->leaf_string_length != 0) { + unsigned int len = node->leaf_string_length; + const unsigned char *str; + + if (len > sizeof(node->children.static_leaf_string)) + str = node->children.leaf_string; + else + str = node->children.static_leaf_string; + + if (size > len || memcmp(data, str, size) != 0) + return 0; + + /* match */ + break; + } + + if (size == 0) + break; + level++; + + if (node->have_sequential) { + if (*data < SEQUENTIAL_COUNT) { + idx = *data; + goto found; + } + idx = SEQUENTIAL_COUNT; + } else { + idx = 0; + } + chars = NODE_CHILDREN_CHARS(node); + for (; idx < node->child_count; idx++) { + if (chars[idx] == *data) + goto found; + } + return 0; + found: + /* follow to children */ + if (level == 1) { + /* root level, add all UIDs */ + if (squat_uidlist_get_seqrange(trie->uidlist, + node->uid_list_idx, + uids) < 0) + return -1; + } else { + if (squat_uidlist_filter(trie->uidlist, + node->uid_list_idx, uids) < 0) + return -1; + } + data++; + size--; + node = NODE_CHILDREN_NODES(node) + idx; + } + + if (squat_uidlist_filter(trie->uidlist, node->uid_list_idx, uids) < 0) + return -1; + return 1; +} + +static void +squat_trie_filter_type(enum squat_index_type type, + const ARRAY_TYPE(seq_range) *src, + ARRAY_TYPE(seq_range) *dest) +{ + const struct seq_range *src_range; + struct seq_range new_range; + unsigned int i, count, mask; + uint32_t next_seq, uid; + + array_clear(dest); + src_range = array_get(src, &count); + if (count == 0) + return; + + if ((type & SQUAT_INDEX_TYPE_HEADER) != 0 && + (type & SQUAT_INDEX_TYPE_BODY) != 0) { + /* everything is fine, just fix the UIDs */ + new_range.seq1 = src_range[0].seq1 / 2; + new_range.seq2 = src_range[0].seq2 / 2; + for (i = 1; i < count; i++) { + next_seq = src_range[i].seq1 / 2; + if (next_seq == new_range.seq2 + 1) { + /* we can continue the previous range */ + } else { + array_push_back(dest, &new_range); + new_range.seq1 = src_range[i].seq1 / 2; + } + new_range.seq2 = src_range[i].seq2 / 2; + } + array_push_back(dest, &new_range); + return; + } + + /* we'll have to drop either header or body UIDs */ + mask = (type & SQUAT_INDEX_TYPE_HEADER) != 0 ? 1 : 0; + for (i = 0; i < count; i++) { + for (uid = src_range[i].seq1; uid <= src_range[i].seq2; uid++) { + if ((uid & 1) == mask) + seq_range_array_add(dest, uid/2); + } + } +} + +struct squat_trie_lookup_context { + struct squat_trie *trie; + enum squat_index_type type; + + ARRAY_TYPE(seq_range) *definite_uids, *maybe_uids; + ARRAY_TYPE(seq_range) tmp_uids, tmp_uids2; + bool first; +}; + +static int +squat_trie_lookup_partial(struct squat_trie_lookup_context *ctx, + const unsigned char *data, uint8_t *char_lengths, + unsigned int size) +{ + const unsigned int partial_len = ctx->trie->hdr.partial_len; + unsigned int char_idx, max_chars, i, j, bytelen; + int ret; + + for (i = 0, max_chars = 0; i < size; max_chars++) + i += char_lengths[i]; + i_assert(max_chars > 0); + + i = 0; char_idx = 0; + do { + bytelen = 0; + for (j = 0; j < partial_len && i+bytelen < size; j++) + bytelen += char_lengths[i + bytelen]; + + ret = squat_trie_lookup_data(ctx->trie, data + i, bytelen, + &ctx->tmp_uids); + if (ret <= 0) { + array_clear(ctx->maybe_uids); + return ret; + } + + if (ctx->first) { + squat_trie_filter_type(ctx->type, &ctx->tmp_uids, + ctx->maybe_uids); + ctx->first = FALSE; + } else { + squat_trie_filter_type(ctx->type, &ctx->tmp_uids, + &ctx->tmp_uids2); + seq_range_array_intersect(ctx->maybe_uids, + &ctx->tmp_uids2); + } + i += char_lengths[i]; + char_idx++; + } while (max_chars - char_idx >= partial_len); + return 1; +} + +static void squat_trie_add_unknown(struct squat_trie *trie, + ARRAY_TYPE(seq_range) *maybe_uids) +{ + struct seq_range *range, new_range; + unsigned int count; + uint32_t last_uid; + + last_uid = I_MAX((trie->root.next_uid+1)/2, 1) - 1; + + range = array_get_modifiable(maybe_uids, &count); + if (count > 0 && range[count-1].seq2 == last_uid) { + /* increase the range */ + range[count-1].seq2 = (uint32_t)-1; + } else { + new_range.seq1 = last_uid + 1; + new_range.seq2 = (uint32_t)-1; + array_push_back(maybe_uids, &new_range); + } +} + +static int +squat_trie_lookup_real(struct squat_trie *trie, const char *str, + enum squat_index_type type, + ARRAY_TYPE(seq_range) *definite_uids, + ARRAY_TYPE(seq_range) *maybe_uids) +{ + struct squat_trie_lookup_context ctx; + unsigned char *data; + uint8_t *char_lengths; + unsigned int i, start, bytes, str_bytelen, str_charlen; + bool searched = FALSE; + int ret = 0; + + array_clear(definite_uids); + array_clear(maybe_uids); + + i_zero(&ctx); + ctx.trie = trie; + ctx.type = type; + ctx.definite_uids = definite_uids; + ctx.maybe_uids = maybe_uids; + i_array_init(&ctx.tmp_uids, 128); + i_array_init(&ctx.tmp_uids2, 128); + ctx.first = TRUE; + + str_bytelen = strlen(str); + char_lengths = str_bytelen == 0 ? NULL : t_malloc0(str_bytelen); + for (i = 0, str_charlen = 0; i < str_bytelen; str_charlen++) { + bytes = uni_utf8_char_bytes(str[i]); + char_lengths[i] = bytes; + i += bytes; + } + data = squat_data_normalize(trie, (const unsigned char *)str, + str_bytelen); + + for (i = start = 0; i < str_bytelen && ret >= 0; i += char_lengths[i]) { + if (data[i] != '\0') + continue; + + /* string has nonindexed characters. + search it in parts. */ + if (i != start) { + ret = squat_trie_lookup_partial(&ctx, data + start, + char_lengths + start, + i - start); + searched = TRUE; + } + start = i + char_lengths[i]; + } + + if (start == 0) { + if (str_charlen <= trie->hdr.partial_len || + trie->hdr.full_len > trie->hdr.partial_len) { + ret = squat_trie_lookup_data(trie, data, str_bytelen, + &ctx.tmp_uids); + if (ret > 0) { + squat_trie_filter_type(type, &ctx.tmp_uids, + definite_uids); + } + } else { + array_clear(definite_uids); + } + + if (str_charlen <= trie->hdr.partial_len || + trie->hdr.partial_len == 0) { + /* we have the result */ + array_clear(maybe_uids); + } else { + ret = squat_trie_lookup_partial(&ctx, data + start, + char_lengths + start, + i - start); + } + } else if (str_bytelen > 0) { + /* string has nonindexed characters. finish the search. */ + array_clear(definite_uids); + if (i != start && ret >= 0) { + ret = squat_trie_lookup_partial(&ctx, data + start, + char_lengths + start, + i - start); + } else if (!searched) { + /* string has only nonindexed chars, + list all root UIDs as maybes */ + ret = squat_uidlist_get_seqrange(trie->uidlist, + trie->root.uid_list_idx, + &ctx.tmp_uids); + squat_trie_filter_type(type, &ctx.tmp_uids, + maybe_uids); + } + } else { + /* zero string length - list all root UIDs as definite + answers */ +#if 0 /* FIXME: this code is never actually reached now. */ + ret = squat_uidlist_get_seqrange(trie->uidlist, + trie->root.uid_list_idx, + &ctx.tmp_uids); + squat_trie_filter_type(type, &ctx.tmp_uids, + definite_uids); +#else + i_unreached(); +#endif + } + seq_range_array_remove_seq_range(maybe_uids, definite_uids); + squat_trie_add_unknown(trie, maybe_uids); + array_free(&ctx.tmp_uids); + array_free(&ctx.tmp_uids2); + return ret < 0 ? -1 : 0; +} + +int squat_trie_lookup(struct squat_trie *trie, const char *str, + enum squat_index_type type, + ARRAY_TYPE(seq_range) *definite_uids, + ARRAY_TYPE(seq_range) *maybe_uids) +{ + int ret; + + T_BEGIN { + ret = squat_trie_lookup_real(trie, str, type, + definite_uids, maybe_uids); + } T_END; + return ret; +} + +struct squat_uidlist *squat_trie_get_uidlist(struct squat_trie *trie) +{ + return trie->uidlist; +} + +size_t squat_trie_mem_used(struct squat_trie *trie, unsigned int *count_r) +{ + *count_r = trie->hdr.node_count; + return trie->node_alloc_size; +} diff --git a/src/plugins/fts-squat/squat-trie.h b/src/plugins/fts-squat/squat-trie.h new file mode 100644 index 0000000..91530b8 --- /dev/null +++ b/src/plugins/fts-squat/squat-trie.h @@ -0,0 +1,54 @@ +#ifndef SQUAT_TRIE_H +#define SQUAT_TRIE_H + +#include "file-lock.h" +#include "seq-range-array.h" + +enum squat_index_flags { + SQUAT_INDEX_FLAG_MMAP_DISABLE = 0x01, + SQUAT_INDEX_FLAG_NFS_FLUSH = 0x02, + SQUAT_INDEX_FLAG_DOTLOCK_USE_EXCL = 0x04 +}; + +enum squat_index_type { + SQUAT_INDEX_TYPE_HEADER = 0x01, + SQUAT_INDEX_TYPE_BODY = 0x02 +}; + +struct squat_trie_build_context; + +struct squat_trie * +squat_trie_init(const char *path, uint32_t uidvalidity, + enum file_lock_method lock_method, + enum squat_index_flags flags, mode_t mode, gid_t gid); +void squat_trie_deinit(struct squat_trie **trie); + +void squat_trie_set_partial_len(struct squat_trie *trie, unsigned int len); +void squat_trie_set_full_len(struct squat_trie *trie, unsigned int len); + +int squat_trie_open(struct squat_trie *trie); +int squat_trie_refresh(struct squat_trie *trie); + +int squat_trie_build_init(struct squat_trie *trie, + struct squat_trie_build_context **ctx_r); +/* bodies must be added before headers */ +int squat_trie_build_more(struct squat_trie_build_context *ctx, + uint32_t uid, enum squat_index_type type, + const unsigned char *data, unsigned int size); +/* if expunged_uids is non-NULL, they may be removed from the index if they + still exist. */ +int squat_trie_build_deinit(struct squat_trie_build_context **ctx, + const ARRAY_TYPE(seq_range) *expunged_uids) + ATTR_NULL(2); + +int squat_trie_get_last_uid(struct squat_trie *trie, uint32_t *last_uid_r); +/* type specifies if we're looking at header, body or both */ +int squat_trie_lookup(struct squat_trie *trie, const char *str, + enum squat_index_type type, + ARRAY_TYPE(seq_range) *definite_uids, + ARRAY_TYPE(seq_range) *maybe_uids); + +struct squat_uidlist *squat_trie_get_uidlist(struct squat_trie *trie); +size_t squat_trie_mem_used(struct squat_trie *trie, unsigned int *count_r); + +#endif diff --git a/src/plugins/fts-squat/squat-uidlist.c b/src/plugins/fts-squat/squat-uidlist.c new file mode 100644 index 0000000..facb8d0 --- /dev/null +++ b/src/plugins/fts-squat/squat-uidlist.c @@ -0,0 +1,1624 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "sort.h" +#include "bsearch-insert-pos.h" +#include "file-cache.h" +#include "file-lock.h" +#include "read-full.h" +#include "write-full.h" +#include "ostream.h" +#include "mmap-util.h" +#include "squat-trie-private.h" +#include "squat-uidlist.h" + +#include <stdio.h> +#include <sys/stat.h> + +#define UIDLIST_LIST_SIZE 31 +#define UIDLIST_BLOCK_LIST_COUNT 100 +#define UID_LIST_MASK_RANGE 0x80000000U + +/* set = points to uidlist index number, unset = points to uidlist offset */ +#define UID_LIST_POINTER_MASK_LIST_IDX 0x80000000U + +#define UIDLIST_PACKED_FLAG_BITMASK 1 +#define UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER 2 + +struct uidlist_list { + unsigned int uid_count:31; + bool uid_begins_with_pointer:1; + uint32_t uid_list[UIDLIST_LIST_SIZE]; +}; + +struct squat_uidlist { + struct squat_trie *trie; + + char *path; + int fd; + struct file_cache *file_cache; + + struct file_lock *file_lock; + struct dotlock *dotlock; + uoff_t locked_file_size; + + void *mmap_base; + size_t mmap_size; + struct squat_uidlist_file_header hdr; + + const void *data; + size_t data_size; + + unsigned int cur_block_count; + const uint32_t *cur_block_offsets; + const uint32_t *cur_block_end_indexes; + + size_t max_size; + bool corrupted:1; + bool building:1; +}; + +struct squat_uidlist_build_context { + struct squat_uidlist *uidlist; + struct ostream *output; + + ARRAY_TYPE(uint32_t) block_offsets; + ARRAY_TYPE(uint32_t) block_end_indexes; + + ARRAY(struct uidlist_list) lists; + uint32_t list_start_idx; + + struct squat_uidlist_file_header build_hdr; + bool need_reopen:1; +}; + +struct squat_uidlist_rebuild_context { + struct squat_uidlist *uidlist; + struct squat_uidlist_build_context *build_ctx; + + int fd; + struct ostream *output; + + ARRAY_TYPE(uint32_t) new_block_offsets, new_block_end_indexes; + uoff_t cur_block_start_offset; + + uint32_t list_sizes[UIDLIST_BLOCK_LIST_COUNT]; + uint32_t next_uid_list_idx; + unsigned int list_idx; + unsigned int new_count; +}; + +static void squat_uidlist_close(struct squat_uidlist *uidlist); + +void squat_uidlist_delete(struct squat_uidlist *uidlist) +{ + i_unlink_if_exists(uidlist->path); +} + +static void squat_uidlist_set_corrupted(struct squat_uidlist *uidlist, + const char *reason) +{ + if (uidlist->corrupted) + return; + uidlist->corrupted = TRUE; + + i_error("Corrupted squat uidlist file %s: %s", uidlist->path, reason); + squat_trie_delete(uidlist->trie); +} + +static int +uidlist_write_array(struct ostream *output, const uint32_t *uid_list, + unsigned int uid_count, uint32_t packed_flags, + uint32_t offset, bool write_size, uint32_t *size_r) +{ + uint8_t *uidbuf, *bufp, sizebuf[SQUAT_PACK_MAX_SIZE], *sizebufp; + uint8_t listbuf[SQUAT_PACK_MAX_SIZE], *listbufp = listbuf; + uint32_t uid, uid2, prev, base_uid, size_value; + unsigned int i, bitmask_len, uid_list_len; + unsigned int idx, max_idx, mask; + bool datastack; + int num; + + if ((packed_flags & UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER) != 0) + squat_pack_num(&listbufp, offset); + + /* @UNSAFE */ + base_uid = uid_list[0] & ~UID_LIST_MASK_RANGE; + datastack = uid_count < 1024*8/SQUAT_PACK_MAX_SIZE; + if (datastack) + uidbuf = t_malloc_no0(SQUAT_PACK_MAX_SIZE * uid_count); + else + uidbuf = i_malloc(SQUAT_PACK_MAX_SIZE * uid_count); + bufp = uidbuf; + squat_pack_num(&bufp, base_uid); + + bitmask_len = (uid_list[uid_count-1] - base_uid + 7) / 8 + + (bufp - uidbuf); + if (bitmask_len < uid_count) { + bitmask_build: + i_assert(bitmask_len < SQUAT_PACK_MAX_SIZE*uid_count); + + memset(bufp, 0, bitmask_len - (bufp - uidbuf)); + if ((uid_list[0] & UID_LIST_MASK_RANGE) == 0) { + i = 1; + uid = i == uid_count ? 0 : uid_list[i]; + } else { + i = 0; + uid = uid_list[0] + 1; + } + base_uid++; + + for (; i < uid_count; i++) { + i_assert((uid & ~UID_LIST_MASK_RANGE) >= base_uid); + if ((uid & UID_LIST_MASK_RANGE) == 0) { + uid -= base_uid; + uid2 = uid; + } else { + uid &= ~UID_LIST_MASK_RANGE; + uid -= base_uid; + uid2 = uid_list[i+1] - base_uid; + i++; + } + + if (uid2 - uid < 3*8) { + for (; uid <= uid2; uid++) + bufp[uid / 8] |= 1 << (uid % 8); + } else { + /* first byte */ + idx = uid / 8; + num = uid % 8; + if (num != 0) { + uid += 8 - num; + for (mask = 0; num < 8; num++) + mask |= 1 << num; + bufp[idx++] |= mask; + } + + /* middle bytes */ + num = uid2 % 8; + max_idx = idx + (uid2 - num - uid)/8; + for (; idx < max_idx; idx++, uid += 8) + bufp[idx] = 0xff; + + /* last byte */ + for (mask = 0; num >= 0; num--) + mask |= 1 << num; + bufp[idx] |= mask; + } + uid = i+1 == uid_count ? 0 : uid_list[i+1]; + } + uid_list_len = bitmask_len; + packed_flags |= UIDLIST_PACKED_FLAG_BITMASK; + } else { + bufp = uidbuf; + prev = 0; + for (i = 0; i < uid_count; i++) { + uid = uid_list[i]; + if (unlikely((uid & ~UID_LIST_MASK_RANGE) < prev)) { + if (!datastack) + i_free(uidbuf); + return -1; + } + if ((uid & UID_LIST_MASK_RANGE) == 0) { + squat_pack_num(&bufp, (uid - prev) << 1); + prev = uid + 1; + } else { + uid &= ~UID_LIST_MASK_RANGE; + squat_pack_num(&bufp, 1 | (uid - prev) << 1); + squat_pack_num(&bufp, uid_list[i+1] - uid - 1); + prev = uid_list[i+1] + 1; + i++; + } + } + uid_list_len = bufp - uidbuf; + if (uid_list_len > bitmask_len) { + bufp = uidbuf; + squat_pack_num(&bufp, base_uid); + goto bitmask_build; + } + } + + size_value = ((uid_list_len + + (listbufp - listbuf)) << 2) | packed_flags; + if (write_size) { + sizebufp = sizebuf; + squat_pack_num(&sizebufp, size_value); + o_stream_nsend(output, sizebuf, sizebufp - sizebuf); + } + o_stream_nsend(output, listbuf, listbufp - listbuf); + o_stream_nsend(output, uidbuf, uid_list_len); + if (!datastack) + i_free(uidbuf); + + *size_r = size_value; + return 0; +} + +static int +uidlist_write(struct ostream *output, const struct uidlist_list *list, + bool write_size, uint32_t *size_r) +{ + const uint32_t *uid_list = list->uid_list; + uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp; + uint32_t uid_count = list->uid_count; + uint32_t packed_flags = 0; + uint32_t offset = 0; + int ret; + + if (list->uid_begins_with_pointer) { + /* continued UID list */ + packed_flags |= UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER; + if ((uid_list[0] & UID_LIST_POINTER_MASK_LIST_IDX) != 0) { + offset = ((uid_list[0] & ~UID_LIST_POINTER_MASK_LIST_IDX) << 1) | 1; + if (list->uid_count == 1) { + bufp = buf; + squat_pack_num(&bufp, offset); + o_stream_nsend(output, buf, bufp - buf); + *size_r = (bufp - buf) << 2 | packed_flags; + return 0; + } + } else if (unlikely(output->offset <= uid_list[0])) { + i_assert(output->closed); + return -1; + } else { + i_assert(list->uid_count > 1); + offset = (output->offset - uid_list[0]) << 1; + } + uid_list++; + uid_count--; + } + + T_BEGIN { + ret = uidlist_write_array(output, uid_list, uid_count, + packed_flags, offset, + write_size, size_r); + } T_END; + return ret; +} + +static void squat_uidlist_map_blocks_set_pointers(struct squat_uidlist *uidlist) +{ + const void *base; + size_t end_index_size, end_size; + + base = CONST_PTR_OFFSET(uidlist->data, uidlist->hdr.block_list_offset + + sizeof(uint32_t)); + + end_index_size = uidlist->cur_block_count * sizeof(uint32_t); + end_size = end_index_size + uidlist->cur_block_count * sizeof(uint32_t); + if (end_size <= uidlist->data_size) { + uidlist->cur_block_end_indexes = base; + uidlist->cur_block_offsets = + CONST_PTR_OFFSET(base, end_index_size); + } else { + uidlist->cur_block_end_indexes = NULL; + uidlist->cur_block_offsets = NULL; + } +} + +static int uidlist_file_cache_read(struct squat_uidlist *uidlist, + size_t offset, size_t size) +{ + if (uidlist->file_cache == NULL) + return 0; + + if (file_cache_read(uidlist->file_cache, offset, size) < 0) { + i_error("read(%s) failed: %m", uidlist->path); + return -1; + } + uidlist->data = file_cache_get_map(uidlist->file_cache, + &uidlist->data_size); + squat_uidlist_map_blocks_set_pointers(uidlist); + return 0; +} + +static int squat_uidlist_map_blocks(struct squat_uidlist *uidlist) +{ + const struct squat_uidlist_file_header *hdr = &uidlist->hdr; + const void *base; + uint32_t block_count, blocks_offset, blocks_size, i, verify_count; + + if (hdr->block_list_offset == 0) { + /* empty file */ + uidlist->cur_block_count = 0; + return 1; + } + + /* get number of blocks */ + if (uidlist_file_cache_read(uidlist, hdr->block_list_offset, + sizeof(block_count)) < 0) + return -1; + blocks_offset = hdr->block_list_offset + sizeof(block_count); + if (blocks_offset > uidlist->data_size) { + squat_uidlist_set_corrupted(uidlist, "block list outside file"); + return 0; + } + + i_assert(uidlist->data != NULL); + base = CONST_PTR_OFFSET(uidlist->data, hdr->block_list_offset); + memcpy(&block_count, base, sizeof(block_count)); + + /* map the blocks */ + blocks_size = block_count * sizeof(uint32_t)*2; + if (uidlist_file_cache_read(uidlist, blocks_offset, blocks_size) < 0) + return -1; + if (blocks_offset + blocks_size > uidlist->data_size) { + squat_uidlist_set_corrupted(uidlist, "block list outside file"); + return 0; + } + + uidlist->cur_block_count = block_count; + squat_uidlist_map_blocks_set_pointers(uidlist); + + i_assert(uidlist->cur_block_end_indexes != NULL); + + /* verify just a couple of the end indexes to make sure they + look correct */ + verify_count = I_MIN(block_count, 8); + for (i = 1; i < verify_count; i++) { + if (unlikely(uidlist->cur_block_end_indexes[i-1] >= + uidlist->cur_block_end_indexes[i])) { + squat_uidlist_set_corrupted(uidlist, + "block list corrupted"); + return 0; + } + } + return 1; +} + +static int squat_uidlist_map_header(struct squat_uidlist *uidlist) +{ + if (uidlist->hdr.indexid == 0) { + /* still being built */ + return 1; + } + if (uidlist->hdr.indexid != uidlist->trie->hdr.indexid) { + /* see if trie was recreated */ + (void)squat_trie_open(uidlist->trie); + } + if (uidlist->hdr.indexid != uidlist->trie->hdr.indexid) { + squat_uidlist_set_corrupted(uidlist, "wrong indexid"); + return 0; + } + if (uidlist->hdr.used_file_size < sizeof(uidlist->hdr) || + (uidlist->hdr.used_file_size > uidlist->mmap_size && + uidlist->mmap_base != NULL)) { + squat_uidlist_set_corrupted(uidlist, "broken used_file_size"); + return 0; + } + return squat_uidlist_map_blocks(uidlist); +} + +static void squat_uidlist_unmap(struct squat_uidlist *uidlist) +{ + if (uidlist->mmap_size != 0) { + if (munmap(uidlist->mmap_base, uidlist->mmap_size) < 0) + i_error("munmap(%s) failed: %m", uidlist->path); + uidlist->mmap_base = NULL; + uidlist->mmap_size = 0; + } + uidlist->cur_block_count = 0; + uidlist->cur_block_end_indexes = NULL; + uidlist->cur_block_offsets = NULL; +} + +static int squat_uidlist_mmap(struct squat_uidlist *uidlist) +{ + struct stat st; + + if (fstat(uidlist->fd, &st) < 0) { + i_error("fstat(%s) failed: %m", uidlist->path); + return -1; + } + if (st.st_size < (off_t)sizeof(uidlist->hdr)) { + squat_uidlist_set_corrupted(uidlist, "File too small"); + return -1; + } + + squat_uidlist_unmap(uidlist); + uidlist->mmap_size = st.st_size; + uidlist->mmap_base = mmap(NULL, uidlist->mmap_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, uidlist->fd, 0); + if (uidlist->mmap_base == MAP_FAILED) { + uidlist->data = uidlist->mmap_base = NULL; + uidlist->data_size = uidlist->mmap_size = 0; + i_error("mmap(%s) failed: %m", uidlist->path); + return -1; + } + uidlist->data = uidlist->mmap_base; + uidlist->data_size = uidlist->mmap_size; + return 0; +} + +static int squat_uidlist_map(struct squat_uidlist *uidlist) +{ + const struct squat_uidlist_file_header *mmap_hdr = uidlist->mmap_base; + int ret; + + if (mmap_hdr != NULL && !uidlist->building && + uidlist->hdr.block_list_offset == mmap_hdr->block_list_offset) { + /* file hasn't changed */ + return 1; + } + + if ((uidlist->trie->flags & SQUAT_INDEX_FLAG_MMAP_DISABLE) == 0) { + if (mmap_hdr == NULL || uidlist->building || + uidlist->mmap_size < mmap_hdr->used_file_size) { + if (squat_uidlist_mmap(uidlist) < 0) + return -1; + } + + if (!uidlist->building) { + memcpy(&uidlist->hdr, uidlist->mmap_base, + sizeof(uidlist->hdr)); + } + } else if (uidlist->building) { + /* we want to update blocks mapping, but using the header + in memory */ + } else { + ret = pread_full(uidlist->fd, &uidlist->hdr, + sizeof(uidlist->hdr), 0); + if (ret <= 0) { + if (ret < 0) { + i_error("pread(%s) failed: %m", uidlist->path); + return -1; + } + i_error("Corrupted %s: File too small", uidlist->path); + return 0; + } + uidlist->data = NULL; + uidlist->data_size = 0; + } + if (uidlist->file_cache == NULL && + (uidlist->trie->flags & SQUAT_INDEX_FLAG_MMAP_DISABLE) != 0) + uidlist->file_cache = file_cache_new_path(uidlist->fd, uidlist->path); + return squat_uidlist_map_header(uidlist); +} + +static int squat_uidlist_read_to_memory(struct squat_uidlist *uidlist) +{ + size_t i, page_size = mmap_get_page_size(); + + if (uidlist->file_cache != NULL) { + return uidlist_file_cache_read(uidlist, 0, + uidlist->hdr.used_file_size); + } + /* Tell the kernel we're going to use the uidlist data, so it loads + it into memory and keeps it there. */ + (void)madvise(uidlist->mmap_base, uidlist->mmap_size, MADV_WILLNEED); + /* It also speeds up a bit for us to sequentially load everything + into memory, although at least Linux catches up quite fast even + without this code. Compiler can quite easily optimize away this + entire for loop, but volatile seems to help with gcc 4.2. */ + for (i = 0; i < uidlist->mmap_size; i += page_size) + ((const volatile char *)uidlist->data)[i]; + return 0; +} + +static void squat_uidlist_free_from_memory(struct squat_uidlist *uidlist) +{ + size_t page_size = mmap_get_page_size(); + + if (uidlist->file_cache != NULL) { + file_cache_invalidate(uidlist->file_cache, + page_size, UOFF_T_MAX); + } else { + (void)madvise(uidlist->mmap_base, uidlist->mmap_size, + MADV_DONTNEED); + } +} + +struct squat_uidlist *squat_uidlist_init(struct squat_trie *trie) +{ + struct squat_uidlist *uidlist; + + uidlist = i_new(struct squat_uidlist, 1); + uidlist->trie = trie; + uidlist->path = i_strconcat(trie->path, ".uids", NULL); + uidlist->fd = -1; + + return uidlist; +} + +void squat_uidlist_deinit(struct squat_uidlist *uidlist) +{ + squat_uidlist_close(uidlist); + + i_free(uidlist->path); + i_free(uidlist); +} + +static int squat_uidlist_open(struct squat_uidlist *uidlist) +{ + squat_uidlist_close(uidlist); + + uidlist->fd = open(uidlist->path, O_RDWR); + if (uidlist->fd == -1) { + if (errno == ENOENT) { + i_zero(&uidlist->hdr); + return 0; + } + i_error("open(%s) failed: %m", uidlist->path); + return -1; + } + return squat_uidlist_map(uidlist) <= 0 ? -1 : 0; +} + +static void squat_uidlist_close(struct squat_uidlist *uidlist) +{ + i_assert(!uidlist->building); + + squat_uidlist_unmap(uidlist); + if (uidlist->file_cache != NULL) + file_cache_free(&uidlist->file_cache); + file_lock_free(&uidlist->file_lock); + if (uidlist->dotlock != NULL) + file_dotlock_delete(&uidlist->dotlock); + i_close_fd_path(&uidlist->fd, uidlist->path); + uidlist->corrupted = FALSE; +} + +int squat_uidlist_refresh(struct squat_uidlist *uidlist) +{ + /* we assume here that trie is locked, so that we don't need to worry + about it when reading the header */ + if (uidlist->fd == -1 || + uidlist->hdr.indexid != uidlist->trie->hdr.indexid) { + if (squat_uidlist_open(uidlist) < 0) + return -1; + } else { + if (squat_uidlist_map(uidlist) <= 0) + return -1; + } + return 0; +} + +static int squat_uidlist_is_file_stale(struct squat_uidlist *uidlist) +{ + struct stat st, st2; + + i_assert(uidlist->fd != -1); + + if (stat(uidlist->path, &st) < 0) { + if (errno == ENOENT) + return 1; + + i_error("stat(%s) failed: %m", uidlist->path); + return -1; + } + if (fstat(uidlist->fd, &st2) < 0) { + i_error("fstat(%s) failed: %m", uidlist->path); + return -1; + } + uidlist->locked_file_size = st2.st_size; + + return st.st_ino == st2.st_ino && + CMP_DEV_T(st.st_dev, st2.st_dev) ? 0 : 1; +} + +static int squat_uidlist_lock(struct squat_uidlist *uidlist) +{ + const char *error; + int ret; + + for (;;) { + i_assert(uidlist->fd != -1); + i_assert(uidlist->file_lock == NULL); + i_assert(uidlist->dotlock == NULL); + + if (uidlist->trie->lock_method != FILE_LOCK_METHOD_DOTLOCK) { + struct file_lock_settings lock_set = { + .lock_method = uidlist->trie->lock_method, + }; + ret = file_wait_lock(uidlist->fd, uidlist->path, + F_WRLCK, &lock_set, + SQUAT_TRIE_LOCK_TIMEOUT, + &uidlist->file_lock, &error); + if (ret < 0) { + i_error("squat uidlist %s: %s", + uidlist->path, error); + } + } else { + ret = file_dotlock_create(&uidlist->trie->dotlock_set, + uidlist->path, 0, + &uidlist->dotlock); + } + if (ret == 0) { + i_error("squat uidlist %s: Locking timed out", + uidlist->path); + return 0; + } + if (ret < 0) + return -1; + + ret = squat_uidlist_is_file_stale(uidlist); + if (ret == 0) + break; + + if (uidlist->file_lock != NULL) + file_unlock(&uidlist->file_lock); + else + file_dotlock_delete(&uidlist->dotlock); + if (ret < 0) + return -1; + + squat_uidlist_close(uidlist); + uidlist->fd = squat_trie_create_fd(uidlist->trie, + uidlist->path, 0); + if (uidlist->fd == -1) + return -1; + } + return 1; +} + +static int squat_uidlist_open_or_create(struct squat_uidlist *uidlist) +{ + int ret; + + if (uidlist->fd == -1) { + uidlist->fd = squat_trie_create_fd(uidlist->trie, + uidlist->path, 0); + if (uidlist->fd == -1) + return -1; + } + if (squat_uidlist_lock(uidlist) <= 0) + return -1; + + if (uidlist->locked_file_size != 0) { + if ((ret = squat_uidlist_map(uidlist)) < 0) + return -1; + if (ret == 0) { + /* broken file, truncate */ + if (ftruncate(uidlist->fd, 0) < 0) { + i_error("ftruncate(%s) failed: %m", + uidlist->path); + return -1; + } + uidlist->locked_file_size = 0; + } + } + if (uidlist->locked_file_size == 0) { + /* write using 0 until we're finished */ + i_zero(&uidlist->hdr); + if (write_full(uidlist->fd, &uidlist->hdr, + sizeof(uidlist->hdr)) < 0) { + i_error("write(%s) failed: %m", uidlist->path); + return -1; + } + } + return 0; +} + +int squat_uidlist_build_init(struct squat_uidlist *uidlist, + struct squat_uidlist_build_context **ctx_r) +{ + struct squat_uidlist_build_context *ctx; + int ret; + + i_assert(!uidlist->building); + + ret = squat_uidlist_open_or_create(uidlist); + if (ret == 0 && + lseek(uidlist->fd, uidlist->hdr.used_file_size, SEEK_SET) < 0) { + i_error("lseek(%s) failed: %m", uidlist->path); + ret = -1; + } + + if (ret < 0) { + if (uidlist->file_lock != NULL) + file_unlock(&uidlist->file_lock); + if (uidlist->dotlock != NULL) + file_dotlock_delete(&uidlist->dotlock); + return -1; + } + + ctx = i_new(struct squat_uidlist_build_context, 1); + ctx->uidlist = uidlist; + ctx->output = o_stream_create_fd(uidlist->fd, 0); + if (ctx->output->offset == 0) { + struct squat_uidlist_file_header hdr; + + i_zero(&hdr); + o_stream_nsend(ctx->output, &hdr, sizeof(hdr)); + } + o_stream_cork(ctx->output); + i_array_init(&ctx->lists, 10240); + i_array_init(&ctx->block_offsets, 128); + i_array_init(&ctx->block_end_indexes, 128); + ctx->list_start_idx = uidlist->hdr.count; + ctx->build_hdr = uidlist->hdr; + + uidlist->building = TRUE; + *ctx_r = ctx; + return 0; +} + +static void +uidlist_write_block_list_and_header(struct squat_uidlist_build_context *ctx, + struct ostream *output, + ARRAY_TYPE(uint32_t) *block_offsets, + ARRAY_TYPE(uint32_t) *block_end_indexes, + bool write_old_blocks) +{ + struct squat_uidlist *uidlist = ctx->uidlist; + unsigned int align, old_block_count, new_block_count; + uint32_t block_offset_count; + uoff_t block_list_offset; + + i_assert(uidlist->trie->hdr.indexid != 0); + ctx->build_hdr.indexid = uidlist->trie->hdr.indexid; + + if (array_count(block_end_indexes) == 0) { + ctx->build_hdr.used_file_size = output->offset; + ctx->build_hdr.block_list_offset = 0; + uidlist->hdr = ctx->build_hdr; + return; + } + + align = output->offset % sizeof(uint32_t); + if (align != 0) { + static char null[sizeof(uint32_t)-1] = { 0, }; + + o_stream_nsend(output, null, sizeof(uint32_t) - align); + } + block_list_offset = output->offset; + + new_block_count = array_count(block_offsets); + old_block_count = write_old_blocks ? uidlist->cur_block_count : 0; + + block_offset_count = new_block_count + old_block_count; + o_stream_nsend(output, &block_offset_count, sizeof(block_offset_count)); + /* write end indexes */ + o_stream_nsend(output, uidlist->cur_block_end_indexes, + old_block_count * sizeof(uint32_t)); + o_stream_nsend(output, array_front(block_end_indexes), + new_block_count * sizeof(uint32_t)); + /* write offsets */ + o_stream_nsend(output, uidlist->cur_block_offsets, + old_block_count * sizeof(uint32_t)); + o_stream_nsend(output, array_front(block_offsets), + new_block_count * sizeof(uint32_t)); + (void)o_stream_flush(output); + + /* update header - it's written later when trie is locked */ + ctx->build_hdr.block_list_offset = block_list_offset; + ctx->build_hdr.used_file_size = output->offset; + uidlist->hdr = ctx->build_hdr; +} + +void squat_uidlist_build_flush(struct squat_uidlist_build_context *ctx) +{ + struct uidlist_list *lists; + uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp; + unsigned int i, j, count, max; + uint32_t block_offset, block_end_idx, start_offset; + uint32_t list_sizes[UIDLIST_BLOCK_LIST_COUNT]; + size_t mem_size; + + if (ctx->uidlist->corrupted) + return; + + lists = array_get_modifiable(&ctx->lists, &count); + if (count == 0) + return; + + /* write the lists and save the written sizes to uid_list[0] */ + for (i = 0; i < count; i += UIDLIST_BLOCK_LIST_COUNT) { + start_offset = ctx->output->offset; + max = I_MIN(count - i, UIDLIST_BLOCK_LIST_COUNT); + for (j = 0; j < max; j++) { + if (uidlist_write(ctx->output, &lists[i+j], + FALSE, &list_sizes[j]) < 0) { + squat_uidlist_set_corrupted(ctx->uidlist, + "Broken uidlists"); + return; + } + } + + block_offset = ctx->output->offset; + block_end_idx = ctx->list_start_idx + i + max; + array_push_back(&ctx->block_offsets, &block_offset); + array_push_back(&ctx->block_end_indexes, &block_end_idx); + + /* write the full size of the uidlists */ + bufp = buf; + squat_pack_num(&bufp, block_offset - start_offset); + o_stream_nsend(ctx->output, buf, bufp - buf); + + /* write the sizes/flags */ + for (j = 0; j < max; j++) { + bufp = buf; + squat_pack_num(&bufp, list_sizes[j]); + o_stream_nsend(ctx->output, buf, bufp - buf); + } + } + + mem_size = ctx->lists.arr.buffer->used + + ctx->block_offsets.arr.buffer->used + + ctx->block_end_indexes.arr.buffer->used; + if (ctx->uidlist->max_size < mem_size) + ctx->uidlist->max_size = mem_size; + + ctx->list_start_idx += count; + array_clear(&ctx->lists); + + uidlist_write_block_list_and_header(ctx, ctx->output, + &ctx->block_offsets, + &ctx->block_end_indexes, TRUE); + + (void)squat_uidlist_map(ctx->uidlist); + + array_clear(&ctx->block_offsets); + array_clear(&ctx->block_end_indexes); +} + +int squat_uidlist_build_finish(struct squat_uidlist_build_context *ctx) +{ + if (ctx->uidlist->corrupted) + return -1; + + if (!ctx->output->closed) { + (void)o_stream_seek(ctx->output, 0); + o_stream_nsend(ctx->output, + &ctx->build_hdr, sizeof(ctx->build_hdr)); + (void)o_stream_seek(ctx->output, ctx->build_hdr.used_file_size); + } + + if (o_stream_finish(ctx->output) < 0) { + i_error("write() to %s failed: %s", ctx->uidlist->path, + o_stream_get_error(ctx->output)); + return -1; + } + return 0; +} + +void squat_uidlist_build_deinit(struct squat_uidlist_build_context **_ctx) +{ + struct squat_uidlist_build_context *ctx = *_ctx; + + *_ctx = NULL; + + i_assert(array_count(&ctx->lists) == 0 || ctx->uidlist->corrupted); + i_assert(ctx->uidlist->building); + ctx->uidlist->building = FALSE; + + if (ctx->uidlist->file_lock != NULL) + file_unlock(&ctx->uidlist->file_lock); + else + file_dotlock_delete(&ctx->uidlist->dotlock); + + if (ctx->need_reopen) + (void)squat_uidlist_open(ctx->uidlist); + + array_free(&ctx->block_offsets); + array_free(&ctx->block_end_indexes); + array_free(&ctx->lists); + o_stream_ignore_last_errors(ctx->output); + o_stream_unref(&ctx->output); + i_free(ctx); +} + +int squat_uidlist_rebuild_init(struct squat_uidlist_build_context *build_ctx, + bool compress, + struct squat_uidlist_rebuild_context **ctx_r) +{ + struct squat_uidlist_rebuild_context *ctx; + struct squat_uidlist_file_header hdr; + const char *temp_path; + int fd; + + if (build_ctx->build_hdr.link_count == 0) + return 0; + + if (!compress) { + if (build_ctx->build_hdr.link_count < + build_ctx->build_hdr.count*2/3) + return 0; + } + + /* make sure the entire uidlist is in memory before beginning, + otherwise the pages are faulted to memory in random order which + takes forever. */ + if (squat_uidlist_read_to_memory(build_ctx->uidlist) < 0) + return -1; + + temp_path = t_strconcat(build_ctx->uidlist->path, ".tmp", NULL); + fd = squat_trie_create_fd(build_ctx->uidlist->trie, temp_path, O_TRUNC); + if (fd == -1) + return -1; + + ctx = i_new(struct squat_uidlist_rebuild_context, 1); + ctx->uidlist = build_ctx->uidlist; + ctx->build_ctx = build_ctx; + ctx->fd = fd; + ctx->output = o_stream_create_fd(ctx->fd, 0); + ctx->next_uid_list_idx = 0x100; + o_stream_cork(ctx->output); + + i_zero(&hdr); + o_stream_nsend(ctx->output, &hdr, sizeof(hdr)); + + ctx->cur_block_start_offset = ctx->output->offset; + i_array_init(&ctx->new_block_offsets, + build_ctx->build_hdr.count / UIDLIST_BLOCK_LIST_COUNT); + i_array_init(&ctx->new_block_end_indexes, + build_ctx->build_hdr.count / UIDLIST_BLOCK_LIST_COUNT); + *ctx_r = ctx; + return 1; +} + +static void +uidlist_rebuild_flush_block(struct squat_uidlist_rebuild_context *ctx) +{ + uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp; + uint32_t block_offset, block_end_idx; + unsigned int i; + + ctx->new_count += ctx->list_idx; + + block_offset = ctx->output->offset; + block_end_idx = ctx->new_count; + array_push_back(&ctx->new_block_offsets, &block_offset); + array_push_back(&ctx->new_block_end_indexes, &block_end_idx); + + /* this block's contents started from cur_block_start_offset and + ended to current offset. write the size of this area. */ + bufp = buf; + squat_pack_num(&bufp, block_offset - ctx->cur_block_start_offset); + o_stream_nsend(ctx->output, buf, bufp - buf); + + /* write the sizes/flags */ + for (i = 0; i < ctx->list_idx; i++) { + bufp = buf; + squat_pack_num(&bufp, ctx->list_sizes[i]); + o_stream_nsend(ctx->output, buf, bufp - buf); + } + ctx->cur_block_start_offset = ctx->output->offset; +} + +uint32_t squat_uidlist_rebuild_next(struct squat_uidlist_rebuild_context *ctx, + const ARRAY_TYPE(uint32_t) *uids) +{ + int ret; + + T_BEGIN { + ret = uidlist_write_array(ctx->output, array_front(uids), + array_count(uids), 0, 0, FALSE, + &ctx->list_sizes[ctx->list_idx]); + } T_END; + if (ret < 0) + squat_uidlist_set_corrupted(ctx->uidlist, "Broken uidlists"); + + if (++ctx->list_idx == UIDLIST_BLOCK_LIST_COUNT) { + uidlist_rebuild_flush_block(ctx); + ctx->list_idx = 0; + } + return ctx->next_uid_list_idx++ << 1; +} + +uint32_t squat_uidlist_rebuild_nextu(struct squat_uidlist_rebuild_context *ctx, + const ARRAY_TYPE(seq_range) *uids) +{ + const struct seq_range *range; + ARRAY_TYPE(uint32_t) tmp_uids; + uint32_t seq, uid1, ret; + unsigned int i, count; + + range = array_get(uids, &count); + if (count == 0) + return 0; + + if (range[count-1].seq2 < 8) { + /* we can use a singleton bitmask */ + ret = 0; + for (i = 0; i < count; i++) { + for (seq = range[i].seq1; seq <= range[i].seq2; seq++) + ret |= 1 << (seq+1); + } + return ret; + } + if (count == 1 && range[0].seq1 == range[0].seq2) { + /* single UID */ + return (range[0].seq1 << 1) | 1; + } + + /* convert seq range to our internal representation and use the + normal _rebuild_next() to write it */ + i_array_init(&tmp_uids, 128); + for (i = 0; i < count; i++) { + if (range[i].seq1 == range[i].seq2) + array_push_back(&tmp_uids, &range[i].seq1); + else { + uid1 = range[i].seq1 | UID_LIST_MASK_RANGE; + array_push_back(&tmp_uids, &uid1); + array_push_back(&tmp_uids, &range[i].seq2); + } + } + ret = squat_uidlist_rebuild_next(ctx, &tmp_uids); + array_free(&tmp_uids); + return ret; +} + +int squat_uidlist_rebuild_finish(struct squat_uidlist_rebuild_context *ctx, + bool cancel) +{ + const char *temp_path; + int ret = 1; + + if (ctx->list_idx != 0) + uidlist_rebuild_flush_block(ctx); + if (cancel || ctx->uidlist->corrupted) + ret = 0; + + temp_path = t_strconcat(ctx->uidlist->path, ".tmp", NULL); + if (ret > 0) { + ctx->build_ctx->build_hdr.indexid = + ctx->uidlist->trie->hdr.indexid; + ctx->build_ctx->build_hdr.count = ctx->new_count; + ctx->build_ctx->build_hdr.link_count = 0; + uidlist_write_block_list_and_header(ctx->build_ctx, ctx->output, + &ctx->new_block_offsets, + &ctx->new_block_end_indexes, + FALSE); + (void)o_stream_seek(ctx->output, 0); + o_stream_nsend(ctx->output, &ctx->build_ctx->build_hdr, + sizeof(ctx->build_ctx->build_hdr)); + (void)o_stream_seek(ctx->output, + ctx->build_ctx->build_hdr.used_file_size); + + if (ctx->uidlist->corrupted) + ret = -1; + else if (o_stream_finish(ctx->output) < 0) { + i_error("write(%s) failed: %s", temp_path, + o_stream_get_error(ctx->output)); + ret = -1; + } else if (rename(temp_path, ctx->uidlist->path) < 0) { + i_error("rename(%s, %s) failed: %m", + temp_path, ctx->uidlist->path); + ret = -1; + } + ctx->build_ctx->need_reopen = TRUE; + } else { + o_stream_abort(ctx->output); + } + + /* we no longer require the entire uidlist to be in memory, + let it be used for something more useful. */ + squat_uidlist_free_from_memory(ctx->uidlist); + + o_stream_unref(&ctx->output); + if (close(ctx->fd) < 0) + i_error("close(%s) failed: %m", temp_path); + + if (ret <= 0) + i_unlink(temp_path); + array_free(&ctx->new_block_offsets); + array_free(&ctx->new_block_end_indexes); + i_free(ctx); + return ret < 0 ? -1 : 0; +} + +static void +uidlist_flush(struct squat_uidlist_build_context *ctx, + struct uidlist_list *list, uint32_t uid) +{ + uint32_t size, offset = ctx->output->offset; + + ctx->build_hdr.link_count++; + if (uidlist_write(ctx->output, list, TRUE, &size) < 0) + squat_uidlist_set_corrupted(ctx->uidlist, "Broken uidlists"); + + list->uid_count = 2; + list->uid_begins_with_pointer = TRUE; + + list->uid_list[0] = offset; + list->uid_list[1] = uid; +} + +static struct uidlist_list * +uidlist_add_new(struct squat_uidlist_build_context *ctx, unsigned int count, + uint32_t *uid_list_idx_r) +{ + struct uidlist_list *list; + + i_assert(array_count(&ctx->lists) + + ctx->list_start_idx == ctx->build_hdr.count); + *uid_list_idx_r = (ctx->build_hdr.count + 0x100) << 1; + list = array_append_space(&ctx->lists); + ctx->build_hdr.count++; + + list->uid_count = count; + return list; +} + +uint32_t squat_uidlist_build_add_uid(struct squat_uidlist_build_context *ctx, + uint32_t uid_list_idx, uint32_t uid) +{ + struct uidlist_list *list; + unsigned int idx, mask; + uint32_t *p; + + if ((uid_list_idx & 1) != 0) { + /* adding second UID */ + uint32_t prev_uid = uid_list_idx >> 1; + + i_assert(prev_uid != uid); + list = uidlist_add_new(ctx, 2, &uid_list_idx); + list->uid_list[0] = prev_uid; + if (prev_uid + 1 == uid) + list->uid_list[0] |= UID_LIST_MASK_RANGE; + list->uid_list[1] = uid; + return uid_list_idx; + } else if (uid_list_idx < (0x100 << 1)) { + uint32_t old_list_idx; + + if (uid < 8) { + /* UID lists containing only UIDs 0-7 are saved as + uidlist values 2..511. think of it as a bitmask. */ + uid_list_idx |= 1 << (uid + 1); + i_assert((uid_list_idx & 1) == 0); + return uid_list_idx; + } + + if (uid_list_idx == 0) { + /* first UID */ + return (uid << 1) | 1; + } + + /* create a new list */ + old_list_idx = uid_list_idx >> 1; + list = uidlist_add_new(ctx, 1, &uid_list_idx); + /* add the first UID ourself */ + idx = 0; + i_assert((old_list_idx & 0xff) != 0); + for (mask = 1; mask <= 128; mask <<= 1, idx++) { + if ((old_list_idx & mask) != 0) { + list->uid_list[0] = idx; + idx++; mask <<= 1; + break; + } + } + for (; mask <= 128; mask <<= 1, idx++) { + if ((old_list_idx & mask) != 0) { + (void)squat_uidlist_build_add_uid(ctx, + uid_list_idx, idx); + } + } + } + + /* add to existing list */ + idx = (uid_list_idx >> 1) - 0x100; + if (idx < ctx->list_start_idx) { + list = uidlist_add_new(ctx, 2, &uid_list_idx); + list->uid_list[0] = UID_LIST_POINTER_MASK_LIST_IDX | idx; + list->uid_list[1] = uid; + list->uid_begins_with_pointer = TRUE; + ctx->build_hdr.link_count++; + return uid_list_idx; + } + + idx -= ctx->list_start_idx; + if (idx >= array_count(&ctx->lists)) { + squat_uidlist_set_corrupted(ctx->uidlist, + "missing/broken uidlist"); + return 0; + } + list = array_idx_modifiable(&ctx->lists, idx); + i_assert(list->uid_count > 0); + + p = &list->uid_list[list->uid_count-1]; + i_assert(uid != *p || ctx->uidlist->corrupted || + (list->uid_count == 1 && list->uid_begins_with_pointer)); + if (uid == *p + 1 && + (list->uid_count > 1 || !list->uid_begins_with_pointer)) { + /* use a range */ + if (list->uid_count > 1 && (p[-1] & UID_LIST_MASK_RANGE) != 0 && + (list->uid_count > 2 || !list->uid_begins_with_pointer)) { + /* increase the existing range */ + *p += 1; + return uid_list_idx; + } + + if (list->uid_count == UIDLIST_LIST_SIZE) { + uidlist_flush(ctx, list, uid); + return uid_list_idx; + } + /* create a new range */ + *p |= UID_LIST_MASK_RANGE; + } else { + if (list->uid_count == UIDLIST_LIST_SIZE) { + uidlist_flush(ctx, list, uid); + return uid_list_idx; + } + } + + p++; + list->uid_count++; + + *p = uid; + return uid_list_idx; +} + +static void uidlist_array_append(ARRAY_TYPE(uint32_t) *uids, uint32_t uid) +{ + uint32_t *uidlist; + unsigned int count; + + uidlist = array_get_modifiable(uids, &count); + if (count == 0) { + array_push_back(uids, &uid); + return; + } + if (uidlist[count-1] + 1 == uid) { + if (count > 1 && (uidlist[count-2] & + UID_LIST_MASK_RANGE) != 0) { + uidlist[count-1]++; + return; + } + uidlist[count-1] |= UID_LIST_MASK_RANGE; + } + array_push_back(uids, &uid); +} + +static void uidlist_array_append_range(ARRAY_TYPE(uint32_t) *uids, + uint32_t uid1, uint32_t uid2) +{ + uint32_t *uidlist; + unsigned int count; + + i_assert(uid1 < uid2); + + uidlist = array_get_modifiable(uids, &count); + if (count == 0) { + uid1 |= UID_LIST_MASK_RANGE; + array_push_back(uids, &uid1); + array_push_back(uids, &uid2); + return; + } + if (uidlist[count-1] + 1 == uid1) { + if (count > 1 && (uidlist[count-2] & + UID_LIST_MASK_RANGE) != 0) { + uidlist[count-1] = uid2; + return; + } + uidlist[count-1] |= UID_LIST_MASK_RANGE; + } else { + uid1 |= UID_LIST_MASK_RANGE; + array_push_back(uids, &uid1); + } + array_push_back(uids, &uid2); +} + +static int +squat_uidlist_get_at_offset(struct squat_uidlist *uidlist, uoff_t offset, + uint32_t num, ARRAY_TYPE(uint32_t) *uids) +{ + const uint32_t *uid_list; + const uint8_t *p, *end; + uint32_t size, base_uid, next_uid, flags, prev; + uoff_t uidlist_data_offset; + unsigned int i, j, count; + + if (num != 0) + uidlist_data_offset = offset; + else { + /* not given, read it */ + if (uidlist_file_cache_read(uidlist, offset, + SQUAT_PACK_MAX_SIZE) < 0) + return -1; + + p = CONST_PTR_OFFSET(uidlist->data, offset); + end = CONST_PTR_OFFSET(uidlist->data, uidlist->data_size); + num = squat_unpack_num(&p, end); + uidlist_data_offset = p - (const uint8_t *)uidlist->data; + } + size = num >> 2; + + if (uidlist_file_cache_read(uidlist, uidlist_data_offset, size) < 0) + return -1; + if (uidlist_data_offset + size > uidlist->data_size) { + squat_uidlist_set_corrupted(uidlist, + "size points outside file"); + return -1; + } + + p = CONST_PTR_OFFSET(uidlist->data, uidlist_data_offset); + end = p + size; + + flags = num; + if ((flags & UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER) != 0) { + /* link to the file */ + prev = squat_unpack_num(&p, end); + + if ((prev & 1) != 0) { + /* pointer to uidlist */ + prev = ((prev >> 1) + 0x100) << 1; + if (squat_uidlist_get(uidlist, prev, uids) < 0) + return -1; + } else { + prev = offset - (prev >> 1); + if (squat_uidlist_get_at_offset(uidlist, prev, + 0, uids) < 0) + return -1; + } + uid_list = array_get(uids, &count); + next_uid = count == 0 ? 0 : uid_list[count-1] + 1; + } else { + next_uid = 0; + } + + num = base_uid = squat_unpack_num(&p, end); + if ((flags & UIDLIST_PACKED_FLAG_BITMASK) == 0) + base_uid >>= 1; + if (base_uid < next_uid) { + squat_uidlist_set_corrupted(uidlist, + "broken continued uidlist"); + return -1; + } + + if ((flags & UIDLIST_PACKED_FLAG_BITMASK) != 0) { + /* bitmask */ + size = end - p; + + uidlist_array_append(uids, base_uid++); + for (i = 0; i < size; i++) { + for (j = 0; j < 8; j++, base_uid++) { + if ((p[i] & (1 << j)) != 0) + uidlist_array_append(uids, base_uid); + } + } + } else { + /* range */ + for (;;) { + if ((num & 1) == 0) { + uidlist_array_append(uids, base_uid); + } else { + /* range */ + uint32_t seq1 = base_uid; + base_uid += squat_unpack_num(&p, end) + 1; + uidlist_array_append_range(uids, seq1, + base_uid); + } + if (p == end) + break; + + num = squat_unpack_num(&p, end); + base_uid += (num >> 1) + 1; + } + } + return 0; +} + +static int +squat_uidlist_get_offset(struct squat_uidlist *uidlist, uint32_t uid_list_idx, + uint32_t *offset_r, uint32_t *num_r) +{ + const uint8_t *p, *end; + unsigned int idx; + uint32_t num, skip_bytes, uidlists_offset; + size_t max_map_size; + + if (uidlist->fd == -1) { + squat_uidlist_set_corrupted(uidlist, "no uidlists"); + return -1; + } + + if (bsearch_insert_pos(&uid_list_idx, uidlist->cur_block_end_indexes, + uidlist->cur_block_count, + sizeof(uint32_t), uint32_cmp, &idx)) + idx++; + if (unlikely(idx == uidlist->cur_block_count)) { + squat_uidlist_set_corrupted(uidlist, "uidlist not found"); + return -1; + } + i_assert(uidlist->cur_block_end_indexes != NULL); + if (unlikely(idx > 0 && + uidlist->cur_block_end_indexes[idx-1] > uid_list_idx)) { + squat_uidlist_set_corrupted(uidlist, "broken block list"); + return -1; + } + + /* make sure everything is mapped */ + uid_list_idx -= idx == 0 ? 0 : uidlist->cur_block_end_indexes[idx-1]; + max_map_size = SQUAT_PACK_MAX_SIZE * (1+uid_list_idx); + if (uidlist_file_cache_read(uidlist, uidlist->cur_block_offsets[idx], + max_map_size) < 0) + return -1; + + /* find the uidlist inside the block */ + i_assert(uidlist->cur_block_offsets != NULL); + p = CONST_PTR_OFFSET(uidlist->data, uidlist->cur_block_offsets[idx]); + end = CONST_PTR_OFFSET(uidlist->data, uidlist->data_size); + + uidlists_offset = uidlist->cur_block_offsets[idx] - + squat_unpack_num(&p, end); + for (skip_bytes = 0; uid_list_idx > 0; uid_list_idx--) { + num = squat_unpack_num(&p, end); + skip_bytes += num >> 2; + } + *offset_r = uidlists_offset + skip_bytes; + *num_r = squat_unpack_num(&p, end); + + if (unlikely(p == end)) { + squat_uidlist_set_corrupted(uidlist, "broken file"); + return -1; + } + if (unlikely(*offset_r > uidlist->mmap_size && + uidlist->mmap_base != NULL)) { + squat_uidlist_set_corrupted(uidlist, "broken offset"); + return -1; + } + return 0; +} + +int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx, + ARRAY_TYPE(uint32_t) *uids) +{ + unsigned int mask; + uint32_t uid, offset, num; + + if ((uid_list_idx & 1) != 0) { + /* single UID */ + uid = uid_list_idx >> 1; + uidlist_array_append(uids, uid); + return 0; + } else if (uid_list_idx < (0x100 << 1)) { + /* bitmask */ + for (uid = 0, mask = 2; mask <= 256; mask <<= 1, uid++) { + if ((uid_list_idx & mask) != 0) + uidlist_array_append(uids, uid); + } + return 0; + } + + uid_list_idx = (uid_list_idx >> 1) - 0x100; + if (squat_uidlist_get_offset(uidlist, uid_list_idx, &offset, &num) < 0) + return -1; + return squat_uidlist_get_at_offset(uidlist, offset, num, uids); +} + +uint32_t squat_uidlist_singleton_last_uid(uint32_t uid_list_idx) +{ + unsigned int idx, mask; + + if ((uid_list_idx & 1) != 0) { + /* single UID */ + return uid_list_idx >> 1; + } else if (uid_list_idx < (0x100 << 1)) { + /* bitmask */ + if (uid_list_idx == 2) { + /* just a quick optimization */ + return 0; + } + for (idx = 7, mask = 256; mask > 2; mask >>= 1, idx--) { + if ((uid_list_idx & mask) != 0) + return idx; + } + } + + i_unreached(); + return 0; +} + +int squat_uidlist_get_seqrange(struct squat_uidlist *uidlist, + uint32_t uid_list_idx, + ARRAY_TYPE(seq_range) *seq_range_arr) +{ + ARRAY_TYPE(uint32_t) tmp_uid_arr; + struct seq_range range; + const uint32_t *tmp_uids; + unsigned int i, count; + int ret; + + i_array_init(&tmp_uid_arr, 128); + ret = squat_uidlist_get(uidlist, uid_list_idx, &tmp_uid_arr); + if (ret == 0) { + tmp_uids = array_get(&tmp_uid_arr, &count); + for (i = 0; i < count; i++) { + if ((tmp_uids[i] & UID_LIST_MASK_RANGE) == 0) + range.seq1 = range.seq2 = tmp_uids[i]; + else { + range.seq1 = tmp_uids[i] & ~UID_LIST_MASK_RANGE; + range.seq2 = tmp_uids[++i]; + } + array_push_back(seq_range_arr, &range); + } + } + array_free(&tmp_uid_arr); + return ret; +} + +int squat_uidlist_filter(struct squat_uidlist *uidlist, uint32_t uid_list_idx, + ARRAY_TYPE(seq_range) *uids) +{ + const struct seq_range *parent_range; + ARRAY_TYPE(seq_range) dest_uids; + ARRAY_TYPE(uint32_t) relative_uids; + const uint32_t *rel_range; + unsigned int i, rel_count, parent_idx, parent_count, diff, parent_uid; + uint32_t prev_seq, seq1, seq2; + int ret = 0; + + parent_range = array_get(uids, &parent_count); + if (parent_count == 0) + return 0; + + i_array_init(&relative_uids, 128); + i_array_init(&dest_uids, 128); + if (squat_uidlist_get(uidlist, uid_list_idx, &relative_uids) < 0) + ret = -1; + + parent_idx = 0; + rel_range = array_get(&relative_uids, &rel_count); + prev_seq = 0; parent_uid = parent_range[0].seq1; + for (i = 0; i < rel_count; i++) { + if (unlikely(parent_uid == (uint32_t)-1)) { + i_error("broken UID ranges"); + ret = -1; + break; + } + if ((rel_range[i] & UID_LIST_MASK_RANGE) == 0) + seq1 = seq2 = rel_range[i]; + else { + seq1 = (rel_range[i] & ~UID_LIST_MASK_RANGE); + seq2 = rel_range[++i]; + } + i_assert(seq1 >= prev_seq); + diff = seq1 - prev_seq; + while (diff > 0) { + if (unlikely(parent_uid == (uint32_t)-1)) { + i_error("broken UID ranges"); + ret = -1; + break; + } + + for (; parent_idx < parent_count; parent_idx++) { + if (parent_range[parent_idx].seq2 <= parent_uid) + continue; + if (parent_uid < parent_range[parent_idx].seq1) + parent_uid = parent_range[parent_idx].seq1; + else + parent_uid++; + break; + } + diff--; + } + diff = seq2 - seq1 + 1; + while (diff > 0) { + if (unlikely(parent_uid == (uint32_t)-1)) { + i_error("broken UID ranges"); + ret = -1; + break; + } + seq_range_array_add(&dest_uids, parent_uid); + for (; parent_idx < parent_count; parent_idx++) { + if (parent_range[parent_idx].seq2 <= parent_uid) + continue; + if (parent_uid < parent_range[parent_idx].seq1) + parent_uid = parent_range[parent_idx].seq1; + else + parent_uid++; + break; + } + diff--; + } + + prev_seq = seq2 + 1; + } + + buffer_set_used_size(uids->arr.buffer, 0); + array_append_array(uids, &dest_uids); + + array_free(&relative_uids); + array_free(&dest_uids); + return ret; +} + +size_t squat_uidlist_mem_used(struct squat_uidlist *uidlist, + unsigned int *count_r) +{ + *count_r = uidlist->hdr.count; + return uidlist->max_size; +} diff --git a/src/plugins/fts-squat/squat-uidlist.h b/src/plugins/fts-squat/squat-uidlist.h new file mode 100644 index 0000000..79ed791 --- /dev/null +++ b/src/plugins/fts-squat/squat-uidlist.h @@ -0,0 +1,71 @@ +#ifndef SQUAT_UIDLIST_H +#define SQUAT_UIDLIST_H + +struct squat_trie; +struct squat_uidlist_build_context; +struct squat_uidlist_rebuild_context; + +struct squat_uidlist_file_header { + uint32_t indexid; + uint32_t used_file_size; + uint32_t block_list_offset; + uint32_t count, link_count; +}; + +/* + uidlist file: + + struct uidlist_header; + + // size includes both prev_offset and uidlist + packed (size << 2) | packed_flags; // UIDLIST_PACKED_FLAG_* + [packed prev_offset;] // If UIDLIST_PACKED_FLAG_BEGINS_WITH_OFFSET is set + if (UIDLIST_PACKED_FLAG_BITMASK) { + packed base_uid; // first UID in uidlist + uint8_t bitmask[]; // first bit is base_uid+1 + } else { + // FIXME: packed range + } +*/ + +#define UIDLIST_IS_SINGLETON(idx) \ + (((idx) & 1) != 0 || (idx) < (0x100 << 1)) + +struct squat_uidlist *squat_uidlist_init(struct squat_trie *trie); +void squat_uidlist_deinit(struct squat_uidlist *uidlist); + +int squat_uidlist_refresh(struct squat_uidlist *uidlist); + +int squat_uidlist_build_init(struct squat_uidlist *uidlist, + struct squat_uidlist_build_context **ctx_r); +uint32_t squat_uidlist_build_add_uid(struct squat_uidlist_build_context *ctx, + uint32_t uid_list_idx, uint32_t uid); +void squat_uidlist_build_flush(struct squat_uidlist_build_context *ctx); +int squat_uidlist_build_finish(struct squat_uidlist_build_context *ctx); +void squat_uidlist_build_deinit(struct squat_uidlist_build_context **ctx); + +int squat_uidlist_rebuild_init(struct squat_uidlist_build_context *build_ctx, + bool compress, + struct squat_uidlist_rebuild_context **ctx_r); +uint32_t squat_uidlist_rebuild_next(struct squat_uidlist_rebuild_context *ctx, + const ARRAY_TYPE(uint32_t) *uids); +uint32_t squat_uidlist_rebuild_nextu(struct squat_uidlist_rebuild_context *ctx, + const ARRAY_TYPE(seq_range) *uids); +int squat_uidlist_rebuild_finish(struct squat_uidlist_rebuild_context *ctx, + bool cancel); + +int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx, + ARRAY_TYPE(uint32_t) *uids); +uint32_t squat_uidlist_singleton_last_uid(uint32_t uid_list_idx); + +int squat_uidlist_get_seqrange(struct squat_uidlist *uidlist, + uint32_t uid_list_idx, + ARRAY_TYPE(seq_range) *seq_range_arr); +int squat_uidlist_filter(struct squat_uidlist *uidlist, uint32_t uid_list_idx, + ARRAY_TYPE(seq_range) *uids); + +void squat_uidlist_delete(struct squat_uidlist *uidlist); +size_t squat_uidlist_mem_used(struct squat_uidlist *uidlist, + unsigned int *count_r); + +#endif diff --git a/src/plugins/fts/Makefile.am b/src/plugins/fts/Makefile.am new file mode 100644 index 0000000..2e7753c --- /dev/null +++ b/src/plugins/fts/Makefile.am @@ -0,0 +1,74 @@ +pkglibexecdir = $(libexecdir)/dovecot +doveadm_moduledir = $(moduledir)/doveadm + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-fts \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/doveadm + +NOPLUGIN_LDFLAGS = +lib20_doveadm_fts_plugin_la_LDFLAGS = -module -avoid-version +lib20_fts_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_fts_plugin.la + +lib20_fts_plugin_la_LIBADD = ../../lib-fts/libfts.la + +lib20_fts_plugin_la_SOURCES = \ + fts-api.c \ + fts-build-mail.c \ + fts-expunge-log.c \ + fts-indexer.c \ + fts-parser.c \ + fts-parser-html.c \ + fts-parser-script.c \ + fts-parser-tika.c \ + fts-plugin.c \ + fts-search.c \ + fts-search-args.c \ + fts-search-serialize.c \ + fts-storage.c \ + fts-user.c + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = \ + fts-api.h \ + fts-api-private.h \ + fts-expunge-log.h \ + fts-indexer.h \ + fts-parser.h \ + fts-storage.h \ + fts-user.h + +noinst_HEADERS = \ + doveadm-fts.h \ + fts-build-mail.h \ + fts-plugin.h \ + fts-search-args.h \ + fts-search-serialize.h + +pkglibexec_PROGRAMS = xml2text + +xml2text_SOURCES = xml2text.c fts-parser-html.c +xml2text_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) +xml2text_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS) +xml2text_DEPENDENCIES = $(module_LTLIBRARIES) $(LIBDOVECOT_DEPS) + +pkglibexec_SCRIPTS = decode2text.sh +EXTRA_DIST = $(pkglibexec_SCRIPTS) + +doveadm_module_LTLIBRARIES = \ + lib20_doveadm_fts_plugin.la + +lib20_doveadm_fts_plugin_la_SOURCES = \ + doveadm-fts.c \ + doveadm-dump-fts-expunge-log.c diff --git a/src/plugins/fts/Makefile.in b/src/plugins/fts/Makefile.in new file mode 100644 index 0000000..624f69f --- /dev/null +++ b/src/plugins/fts/Makefile.in @@ -0,0 +1,1140 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +pkglibexec_PROGRAMS = xml2text$(EXEEXT) +subdir = src/plugins/fts +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(pkginc_lib_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \ + "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" \ + "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)" +PROGRAMS = $(pkglibexec_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES) +lib20_doveadm_fts_plugin_la_LIBADD = +am_lib20_doveadm_fts_plugin_la_OBJECTS = doveadm-fts.lo \ + doveadm-dump-fts-expunge-log.lo +lib20_doveadm_fts_plugin_la_OBJECTS = \ + $(am_lib20_doveadm_fts_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_doveadm_fts_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_doveadm_fts_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +lib20_fts_plugin_la_DEPENDENCIES = ../../lib-fts/libfts.la +am_lib20_fts_plugin_la_OBJECTS = fts-api.lo fts-build-mail.lo \ + fts-expunge-log.lo fts-indexer.lo fts-parser.lo \ + fts-parser-html.lo fts-parser-script.lo fts-parser-tika.lo \ + fts-plugin.lo fts-search.lo fts-search-args.lo \ + fts-search-serialize.lo fts-storage.lo fts-user.lo +lib20_fts_plugin_la_OBJECTS = $(am_lib20_fts_plugin_la_OBJECTS) +lib20_fts_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_fts_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_xml2text_OBJECTS = xml2text-xml2text.$(OBJEXT) \ + xml2text-fts-parser-html.$(OBJEXT) +xml2text_OBJECTS = $(am_xml2text_OBJECTS) +am__DEPENDENCIES_1 = +SCRIPTS = $(pkglibexec_SCRIPTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/doveadm-dump-fts-expunge-log.Plo \ + ./$(DEPDIR)/doveadm-fts.Plo ./$(DEPDIR)/fts-api.Plo \ + ./$(DEPDIR)/fts-build-mail.Plo ./$(DEPDIR)/fts-expunge-log.Plo \ + ./$(DEPDIR)/fts-indexer.Plo ./$(DEPDIR)/fts-parser-html.Plo \ + ./$(DEPDIR)/fts-parser-script.Plo \ + ./$(DEPDIR)/fts-parser-tika.Plo ./$(DEPDIR)/fts-parser.Plo \ + ./$(DEPDIR)/fts-plugin.Plo ./$(DEPDIR)/fts-search-args.Plo \ + ./$(DEPDIR)/fts-search-serialize.Plo \ + ./$(DEPDIR)/fts-search.Plo ./$(DEPDIR)/fts-storage.Plo \ + ./$(DEPDIR)/fts-user.Plo \ + ./$(DEPDIR)/xml2text-fts-parser-html.Po \ + ./$(DEPDIR)/xml2text-xml2text.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_doveadm_fts_plugin_la_SOURCES) \ + $(lib20_fts_plugin_la_SOURCES) $(xml2text_SOURCES) +DIST_SOURCES = $(lib20_doveadm_fts_plugin_la_SOURCES) \ + $(lib20_fts_plugin_la_SOURCES) $(xml2text_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +pkglibexecdir = $(libexecdir)/dovecot +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +doveadm_moduledir = $(moduledir)/doveadm +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-fts \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/doveadm + +lib20_doveadm_fts_plugin_la_LDFLAGS = -module -avoid-version +lib20_fts_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_fts_plugin.la + +lib20_fts_plugin_la_LIBADD = ../../lib-fts/libfts.la +lib20_fts_plugin_la_SOURCES = \ + fts-api.c \ + fts-build-mail.c \ + fts-expunge-log.c \ + fts-indexer.c \ + fts-parser.c \ + fts-parser-html.c \ + fts-parser-script.c \ + fts-parser-tika.c \ + fts-plugin.c \ + fts-search.c \ + fts-search-args.c \ + fts-search-serialize.c \ + fts-storage.c \ + fts-user.c + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = \ + fts-api.h \ + fts-api-private.h \ + fts-expunge-log.h \ + fts-indexer.h \ + fts-parser.h \ + fts-storage.h \ + fts-user.h + +noinst_HEADERS = \ + doveadm-fts.h \ + fts-build-mail.h \ + fts-plugin.h \ + fts-search-args.h \ + fts-search-serialize.h + +xml2text_SOURCES = xml2text.c fts-parser-html.c +xml2text_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) +xml2text_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS) +xml2text_DEPENDENCIES = $(module_LTLIBRARIES) $(LIBDOVECOT_DEPS) +pkglibexec_SCRIPTS = decode2text.sh +EXTRA_DIST = $(pkglibexec_SCRIPTS) +doveadm_module_LTLIBRARIES = \ + lib20_doveadm_fts_plugin.la + +lib20_doveadm_fts_plugin_la_SOURCES = \ + doveadm-fts.c \ + doveadm-dump-fts-expunge-log.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/fts/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/fts/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-pkglibexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files + +clean-pkglibexecPROGRAMS: + @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \ + } + +uninstall-doveadm_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \ + done + +clean-doveadm_moduleLTLIBRARIES: + -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES) + @list='$(doveadm_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_doveadm_fts_plugin.la: $(lib20_doveadm_fts_plugin_la_OBJECTS) $(lib20_doveadm_fts_plugin_la_DEPENDENCIES) $(EXTRA_lib20_doveadm_fts_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_doveadm_fts_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib20_doveadm_fts_plugin_la_OBJECTS) $(lib20_doveadm_fts_plugin_la_LIBADD) $(LIBS) + +lib20_fts_plugin.la: $(lib20_fts_plugin_la_OBJECTS) $(lib20_fts_plugin_la_DEPENDENCIES) $(EXTRA_lib20_fts_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_fts_plugin_la_LINK) -rpath $(moduledir) $(lib20_fts_plugin_la_OBJECTS) $(lib20_fts_plugin_la_LIBADD) $(LIBS) + +xml2text$(EXEEXT): $(xml2text_OBJECTS) $(xml2text_DEPENDENCIES) $(EXTRA_xml2text_DEPENDENCIES) + @rm -f xml2text$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(xml2text_OBJECTS) $(xml2text_LDADD) $(LIBS) +install-pkglibexecSCRIPTS: $(pkglibexec_SCRIPTS) + @$(NORMAL_INSTALL) + @list='$(pkglibexec_SCRIPTS)'; test -n "$(pkglibexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n' \ + -e 'h;s|.*|.|' \ + -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) { files[d] = files[d] " " $$1; \ + if (++n[d] == $(am__install_max)) { \ + print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ + else { print "f", d "/" $$4, $$1 } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ + $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-pkglibexecSCRIPTS: + @$(NORMAL_UNINSTALL) + @list='$(pkglibexec_SCRIPTS)'; test -n "$(pkglibexecdir)" || exit 0; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 's,.*/,,;$(transform)'`; \ + dir='$(DESTDIR)$(pkglibexecdir)'; $(am__uninstall_files_from_dir) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-fts-expunge-log.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-fts.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-api.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-build-mail.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-expunge-log.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-indexer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-parser-html.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-parser-script.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-parser-tika.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-search-args.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-search-serialize.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-search.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fts-user.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xml2text-fts-parser-html.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xml2text-xml2text.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +xml2text-xml2text.o: xml2text.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xml2text-xml2text.o -MD -MP -MF $(DEPDIR)/xml2text-xml2text.Tpo -c -o xml2text-xml2text.o `test -f 'xml2text.c' || echo '$(srcdir)/'`xml2text.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/xml2text-xml2text.Tpo $(DEPDIR)/xml2text-xml2text.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xml2text.c' object='xml2text-xml2text.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xml2text-xml2text.o `test -f 'xml2text.c' || echo '$(srcdir)/'`xml2text.c + +xml2text-xml2text.obj: xml2text.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xml2text-xml2text.obj -MD -MP -MF $(DEPDIR)/xml2text-xml2text.Tpo -c -o xml2text-xml2text.obj `if test -f 'xml2text.c'; then $(CYGPATH_W) 'xml2text.c'; else $(CYGPATH_W) '$(srcdir)/xml2text.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/xml2text-xml2text.Tpo $(DEPDIR)/xml2text-xml2text.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xml2text.c' object='xml2text-xml2text.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xml2text-xml2text.obj `if test -f 'xml2text.c'; then $(CYGPATH_W) 'xml2text.c'; else $(CYGPATH_W) '$(srcdir)/xml2text.c'; fi` + +xml2text-fts-parser-html.o: fts-parser-html.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xml2text-fts-parser-html.o -MD -MP -MF $(DEPDIR)/xml2text-fts-parser-html.Tpo -c -o xml2text-fts-parser-html.o `test -f 'fts-parser-html.c' || echo '$(srcdir)/'`fts-parser-html.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/xml2text-fts-parser-html.Tpo $(DEPDIR)/xml2text-fts-parser-html.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fts-parser-html.c' object='xml2text-fts-parser-html.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xml2text-fts-parser-html.o `test -f 'fts-parser-html.c' || echo '$(srcdir)/'`fts-parser-html.c + +xml2text-fts-parser-html.obj: fts-parser-html.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xml2text-fts-parser-html.obj -MD -MP -MF $(DEPDIR)/xml2text-fts-parser-html.Tpo -c -o xml2text-fts-parser-html.obj `if test -f 'fts-parser-html.c'; then $(CYGPATH_W) 'fts-parser-html.c'; else $(CYGPATH_W) '$(srcdir)/fts-parser-html.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/xml2text-fts-parser-html.Tpo $(DEPDIR)/xml2text-fts-parser-html.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fts-parser-html.c' object='xml2text-fts-parser-html.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xml2text_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xml2text-fts-parser-html.obj `if test -f 'fts-parser-html.c'; then $(CYGPATH_W) 'fts-parser-html.c'; else $(CYGPATH_W) '$(srcdir)/fts-parser-html.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(SCRIPTS) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-pkglibexecPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/doveadm-dump-fts-expunge-log.Plo + -rm -f ./$(DEPDIR)/doveadm-fts.Plo + -rm -f ./$(DEPDIR)/fts-api.Plo + -rm -f ./$(DEPDIR)/fts-build-mail.Plo + -rm -f ./$(DEPDIR)/fts-expunge-log.Plo + -rm -f ./$(DEPDIR)/fts-indexer.Plo + -rm -f ./$(DEPDIR)/fts-parser-html.Plo + -rm -f ./$(DEPDIR)/fts-parser-script.Plo + -rm -f ./$(DEPDIR)/fts-parser-tika.Plo + -rm -f ./$(DEPDIR)/fts-parser.Plo + -rm -f ./$(DEPDIR)/fts-plugin.Plo + -rm -f ./$(DEPDIR)/fts-search-args.Plo + -rm -f ./$(DEPDIR)/fts-search-serialize.Plo + -rm -f ./$(DEPDIR)/fts-search.Plo + -rm -f ./$(DEPDIR)/fts-storage.Plo + -rm -f ./$(DEPDIR)/fts-user.Plo + -rm -f ./$(DEPDIR)/xml2text-fts-parser-html.Po + -rm -f ./$(DEPDIR)/xml2text-xml2text.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-doveadm_moduleLTLIBRARIES \ + install-moduleLTLIBRARIES install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-pkglibexecPROGRAMS install-pkglibexecSCRIPTS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/doveadm-dump-fts-expunge-log.Plo + -rm -f ./$(DEPDIR)/doveadm-fts.Plo + -rm -f ./$(DEPDIR)/fts-api.Plo + -rm -f ./$(DEPDIR)/fts-build-mail.Plo + -rm -f ./$(DEPDIR)/fts-expunge-log.Plo + -rm -f ./$(DEPDIR)/fts-indexer.Plo + -rm -f ./$(DEPDIR)/fts-parser-html.Plo + -rm -f ./$(DEPDIR)/fts-parser-script.Plo + -rm -f ./$(DEPDIR)/fts-parser-tika.Plo + -rm -f ./$(DEPDIR)/fts-parser.Plo + -rm -f ./$(DEPDIR)/fts-plugin.Plo + -rm -f ./$(DEPDIR)/fts-search-args.Plo + -rm -f ./$(DEPDIR)/fts-search-serialize.Plo + -rm -f ./$(DEPDIR)/fts-search.Plo + -rm -f ./$(DEPDIR)/fts-storage.Plo + -rm -f ./$(DEPDIR)/fts-user.Plo + -rm -f ./$(DEPDIR)/xml2text-fts-parser-html.Po + -rm -f ./$(DEPDIR)/xml2text-xml2text.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \ + uninstall-pkglibexecPROGRAMS uninstall-pkglibexecSCRIPTS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-pkglibexecPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-doveadm_moduleLTLIBRARIES install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-pkglibexecPROGRAMS \ + install-pkglibexecSCRIPTS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \ + uninstall-pkglibexecPROGRAMS uninstall-pkglibexecSCRIPTS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/fts/decode2text.sh b/src/plugins/fts/decode2text.sh new file mode 100755 index 0000000..1c881ff --- /dev/null +++ b/src/plugins/fts/decode2text.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +# Example attachment decoder script. The attachment comes from stdin, and +# the script is expected to output UTF-8 data to stdout. (If the output isn't +# UTF-8, everything except valid UTF-8 sequences are dropped from it.) + +# The attachment decoding is enabled by setting: +# +# plugin { +# fts_decoder = decode2text +# } +# service decode2text { +# executable = script /usr/local/libexec/dovecot/decode2text.sh +# user = dovecot +# unix_listener decode2text { +# mode = 0666 +# } +# } + +libexec_dir=`dirname $0` +content_type=$1 + +# The second parameter is the format's filename extension, which is used when +# found from a filename of application/octet-stream. You can also add more +# extensions by giving more parameters. +formats='application/pdf pdf +application/x-pdf pdf +application/msword doc +application/mspowerpoint ppt +application/vnd.ms-powerpoint ppt +application/ms-excel xls +application/x-msexcel xls +application/vnd.ms-excel xls +application/vnd.openxmlformats-officedocument.wordprocessingml.document docx +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx +application/vnd.openxmlformats-officedocument.presentationml.presentation pptx +application/vnd.oasis.opendocument.text odt +application/vnd.oasis.opendocument.spreadsheet ods +application/vnd.oasis.opendocument.presentation odp +' + +if [ "$content_type" = "" ]; then + echo "$formats" + exit 0 +fi + +fmt=`echo "$formats" | grep -w "^$content_type" | cut -d ' ' -f 2` +if [ "$fmt" = "" ]; then + echo "Content-Type: $content_type not supported" >&2 + exit 1 +fi + +# most decoders can't handle stdin directly, so write the attachment +# to a temp file +path=`mktemp` +trap "rm -f $path" 0 1 2 3 14 15 +cat > $path + +xmlunzip() { + name=$1 + + tempdir=`mktemp -d` + if [ "$tempdir" = "" ]; then + exit 1 + fi + trap "rm -rf $path $tempdir" 0 1 2 3 14 15 + cd $tempdir || exit 1 + unzip -q "$path" 2>/dev/null || exit 0 + find . -name "$name" -print0 | xargs -0 cat | + $libexec_dir/xml2text +} + +wait_timeout() { + childpid=$! + trap "kill -9 $childpid; rm -f $path" 1 2 3 14 15 + wait $childpid +} + +LANG=en_US.UTF-8 +export LANG +if [ $fmt = "pdf" ]; then + /usr/bin/pdftotext $path - 2>/dev/null& + wait_timeout 2>/dev/null +elif [ $fmt = "doc" ]; then + (/usr/bin/catdoc $path; true) 2>/dev/null& + wait_timeout 2>/dev/null +elif [ $fmt = "ppt" ]; then + (/usr/bin/catppt $path; true) 2>/dev/null& + wait_timeout 2>/dev/null +elif [ $fmt = "xls" ]; then + (/usr/bin/xls2csv $path; true) 2>/dev/null& + wait_timeout 2>/dev/null +elif [ $fmt = "odt" -o $fmt = "ods" -o $fmt = "odp" ]; then + xmlunzip "content.xml" +elif [ $fmt = "docx" ]; then + xmlunzip "document.xml" +elif [ $fmt = "xlsx" ]; then + xmlunzip "sharedStrings.xml" +elif [ $fmt = "pptx" ]; then + xmlunzip "slide*.xml" +else + echo "Buggy decoder script: $fmt not handled" >&2 + exit 1 +fi +exit 0 diff --git a/src/plugins/fts/doveadm-dump-fts-expunge-log.c b/src/plugins/fts/doveadm-dump-fts-expunge-log.c new file mode 100644 index 0000000..7438bca --- /dev/null +++ b/src/plugins/fts/doveadm-dump-fts-expunge-log.c @@ -0,0 +1,116 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "hex-binary.h" +#include "guid.h" +#include "doveadm-dump.h" +#include "doveadm-fts.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +struct fts_expunge_log_record { + uint32_t checksum; + uint32_t record_size; + guid_128_t guid; +}; + +static int dump_record(int fd, buffer_t *buf) +{ + struct fts_expunge_log_record rec; + off_t offset; + void *data; + const uint32_t *expunges, *uids; + ssize_t ret; + size_t data_size; + unsigned int i, uids_count; + + offset = lseek(fd, 0, SEEK_CUR); + + ret = read(fd, &rec, sizeof(rec)); + if (ret == 0) + return 0; + + if (ret != sizeof(rec)) + i_fatal("rec read() %d != %d", (int)ret, (int)sizeof(rec)); + + if (rec.record_size < sizeof(rec) + sizeof(uint32_t) || + rec.record_size > INT_MAX) { + i_fatal("Invalid record_size=%u at offset %"PRIuUOFF_T, + rec.record_size, offset); + } + data_size = rec.record_size - sizeof(rec); + buffer_set_used_size(buf, 0); + data = buffer_append_space_unsafe(buf, data_size); + ret = read(fd, data, data_size); + if (ret != (ssize_t)data_size) + i_fatal("rec read() %d != %d", (int)ret, (int)data_size); + + printf("#%"PRIuUOFF_T":\n", offset); + printf(" checksum = %8x\n", rec.checksum); + printf(" size .... = %u\n", rec.record_size); + printf(" mailbox . = %s\n", guid_128_to_string(rec.guid)); + + expunges = CONST_PTR_OFFSET(data, data_size - sizeof(uint32_t)); + printf(" expunges = %u\n", *expunges); + + printf(" uids .... = "); + + uids = data; + uids_count = (rec.record_size - sizeof(rec) - sizeof(uint32_t)) / + sizeof(uint32_t); + for (i = 0; i < uids_count; i += 2) { + if (i != 0) + printf(","); + if (uids[i] == uids[i+1]) + printf("%u", uids[i]); + else + printf("%u-%u", uids[i], uids[i+1]); + } + printf("\n"); + return 1; +} + +static void +cmd_dump_fts_expunge_log(const char *path, const char *const *args ATTR_UNUSED) +{ + buffer_t *buf; + int fd, ret; + + fd = open(path, O_RDONLY); + if (fd < 0) + i_fatal("open(%s) failed: %m", path); + + buf = buffer_create_dynamic(default_pool, 1024); + do { + T_BEGIN { + ret = dump_record(fd, buf); + } T_END; + } while (ret > 0); + buffer_free(&buf); + i_close_fd(&fd); +} + +static bool test_dump_fts_expunge_log(const char *path) +{ + const char *p; + + if ((p = strrchr(path, '/')) != NULL) + p++; + else + p = path; + return strcmp(p, "dovecot-expunges.log") == 0; +} + +static const struct doveadm_cmd_dump doveadm_cmd_dump_fts_expunge_log = { + "fts-expunge-log", + test_dump_fts_expunge_log, + cmd_dump_fts_expunge_log +}; + +void doveadm_dump_fts_expunge_log_init(void) +{ + doveadm_dump_register(&doveadm_cmd_dump_fts_expunge_log); +} diff --git a/src/plugins/fts/doveadm-fts.c b/src/plugins/fts/doveadm-fts.c new file mode 100644 index 0000000..1b902a1 --- /dev/null +++ b/src/plugins/fts/doveadm-fts.c @@ -0,0 +1,470 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "imap-util.h" +#include "mail-namespace.h" +#include "mail-search.h" +#include "mailbox-list-iter.h" +#include "fts-tokenizer.h" +#include "fts-filter.h" +#include "fts-language.h" +#include "fts-storage.h" +#include "fts-search-args.h" +#include "fts-user.h" +#include "doveadm-print.h" +#include "doveadm-mail.h" +#include "doveadm-mailbox-list-iter.h" +#include "doveadm-fts.h" + +const char *doveadm_fts_plugin_version = DOVECOT_ABI_VERSION; + +struct fts_tokenize_cmd_context { + struct doveadm_mail_cmd_context ctx; + const char *language; + const char *tokens; +}; + +static int +cmd_search_box(struct doveadm_mail_cmd_context *ctx, + const struct mailbox_info *info) +{ + struct mailbox *box; + struct fts_backend *backend; + struct fts_result result; + int ret = 0; + + backend = fts_list_backend(info->ns->list); + if (backend == NULL) { + i_error("fts not enabled for %s", info->vname); + ctx->exit_code = EX_CONFIG; + return -1; + } + + i_zero(&result); + i_array_init(&result.definite_uids, 16); + i_array_init(&result.maybe_uids, 16); + i_array_init(&result.scores, 16); + + box = mailbox_alloc(info->ns->list, info->vname, 0); + if (fts_backend_lookup(backend, box, ctx->search_args->args, + FTS_LOOKUP_FLAG_AND_ARGS, &result) < 0) { + i_error("fts lookup failed"); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + ret = -1; + } else { + printf("%s: ", info->vname); + if (array_count(&result.definite_uids) == 0) + printf("no results\n"); + else T_BEGIN { + string_t *str = t_str_new(128); + imap_write_seq_range(str, &result.definite_uids); + printf("%s\n", str_c(str)); + } T_END; + if (array_count(&result.maybe_uids) > 0) T_BEGIN { + string_t *str = t_str_new(128); + imap_write_seq_range(str, &result.maybe_uids); + printf(" - maybe: %s\n", str_c(str)); + } T_END; + fts_backend_lookup_done(backend); + } + mailbox_free(&box); + array_free(&result.definite_uids); + array_free(&result.maybe_uids); + array_free(&result.scores); + return ret; +} + +static int +cmd_fts_lookup_run(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user) +{ + const enum mailbox_list_iter_flags iter_flags = + MAILBOX_LIST_ITER_NO_AUTO_BOXES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS; + struct doveadm_mailbox_list_iter *iter; + const struct mailbox_info *info; + int ret = 0; + + iter = doveadm_mailbox_list_iter_init(ctx, user, ctx->search_args, + iter_flags); + while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { + if (cmd_search_box(ctx, info) < 0) + ret = -1; + } T_END; + if (doveadm_mailbox_list_iter_deinit(&iter) < 0) + ret = -1; + return ret; +} + +static void +cmd_fts_lookup_init(struct doveadm_mail_cmd_context *ctx, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("fts lookup"); + + ctx->search_args = doveadm_mail_build_search_args(args); +} + +static struct doveadm_mail_cmd_context * +cmd_fts_lookup_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_fts_lookup_run; + ctx->v.init = cmd_fts_lookup_init; + return ctx; +} + +static int +cmd_fts_expand_run(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box; + struct fts_backend *backend; + string_t *str = t_str_new(128); + + backend = fts_list_backend(ns->list); + if (backend == NULL) { + i_error("fts not enabled for INBOX"); + ctx->exit_code = EX_CONFIG; + return -1; + } + + box = mailbox_alloc(ns->list, "INBOX", 0); + mail_search_args_init(ctx->search_args, box, FALSE, NULL); + + if (fts_search_args_expand(backend, ctx->search_args) < 0) + i_fatal("Couldn't expand search args"); + mail_search_args_to_cmdline(str, ctx->search_args->args); + printf("%s\n", str_c(str)); + mailbox_free(&box); + return 0; +} + +static void +cmd_fts_expand_init(struct doveadm_mail_cmd_context *ctx, + const char *const args[]) +{ + if (args[0] == NULL) + doveadm_mail_help_name("fts expand"); + + ctx->search_args = doveadm_mail_build_search_args(args); +} + +static struct doveadm_mail_cmd_context * +cmd_fts_expand_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_fts_expand_run; + ctx->v.init = cmd_fts_expand_init; + return ctx; +} + +static int +cmd_fts_tokenize_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct fts_tokenize_cmd_context *ctx = + (struct fts_tokenize_cmd_context *)_ctx; + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct fts_backend *backend; + struct fts_user_language *user_lang; + const struct fts_language *lang = NULL; + int ret, ret2; + bool final = FALSE; + + backend = fts_list_backend(ns->list); + if (backend == NULL) { + i_error("fts not enabled for INBOX"); + _ctx->exit_code = EX_CONFIG; + return -1; + } + + if (ctx->language == NULL) { + struct fts_language_list *lang_list = + fts_user_get_language_list(user); + enum fts_language_result result; + const char *error; + + result = fts_language_detect(lang_list, + (const unsigned char *)ctx->tokens, strlen(ctx->tokens), + &lang, &error); + if (lang == NULL) + lang = fts_language_list_get_first(lang_list); + switch (result) { + case FTS_LANGUAGE_RESULT_SHORT: + i_warning("Text too short, can't detect its language - assuming %s", lang->name); + break; + case FTS_LANGUAGE_RESULT_UNKNOWN: + i_warning("Can't detect its language - assuming %s", lang->name); + break; + case FTS_LANGUAGE_RESULT_OK: + break; + case FTS_LANGUAGE_RESULT_ERROR: + i_error("Language detection library initialization failed: %s", error); + _ctx->exit_code = EX_CONFIG; + return -1; + default: + i_unreached(); + } + } else { + lang = fts_language_find(ctx->language); + if (lang == NULL) { + i_error("Unknown language: %s", ctx->language); + _ctx->exit_code = EX_USAGE; + return -1; + } + } + user_lang = fts_user_language_find(user, lang); + if (user_lang == NULL) { + i_error("Language not enabled for user: %s", ctx->language); + _ctx->exit_code = EX_USAGE; + return -1; + } + + fts_tokenizer_reset(user_lang->index_tokenizer); + for (;;) { + const char *token, *error; + + if (!final) { + ret = fts_tokenizer_next(user_lang->index_tokenizer, + (const unsigned char *)ctx->tokens, strlen(ctx->tokens), + &token, &error); + } else { + ret = fts_tokenizer_final(user_lang->index_tokenizer, + &token, &error); + } + if (ret < 0) + break; + if (ret > 0 && user_lang->filter != NULL) { + ret2 = fts_filter_filter(user_lang->filter, &token, &error); + if (ret2 > 0) + doveadm_print(token); + else if (ret2 < 0) + i_error("Couldn't create indexable tokens: %s", error); + } + if (ret == 0) { + if (final) + break; + final = TRUE; + } + } + return 0; +} + +static void +cmd_fts_tokenize_init(struct doveadm_mail_cmd_context *_ctx, + const char *const args[]) +{ + struct fts_tokenize_cmd_context *ctx = + (struct fts_tokenize_cmd_context *)_ctx; + + if (args[0] == NULL) + doveadm_mail_help_name("fts tokenize"); + + ctx->tokens = p_strdup(_ctx->pool, t_strarray_join(args, " ")); + + doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); + doveadm_print_header("token", "token", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); +} + +static bool +cmd_fts_tokenize_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct fts_tokenize_cmd_context *ctx = + (struct fts_tokenize_cmd_context *)_ctx; + + switch (c) { + case 'l': + ctx->language = p_strdup(_ctx->pool, optarg); + break; + default: + return FALSE; + } + return TRUE; +} + +static struct doveadm_mail_cmd_context * +cmd_fts_tokenize_alloc(void) +{ + struct fts_tokenize_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct fts_tokenize_cmd_context); + ctx->ctx.v.run = cmd_fts_tokenize_run; + ctx->ctx.v.init = cmd_fts_tokenize_init; + ctx->ctx.v.parse_arg = cmd_fts_tokenize_parse_arg; + ctx->ctx.getopt_args = "l"; + return &ctx->ctx; +} + +static int +fts_namespace_find(struct mail_user *user, const char *ns_prefix, + struct mail_namespace **ns_r) +{ + struct mail_namespace *ns; + + if (ns_prefix == NULL) + ns = mail_namespace_find_inbox(user->namespaces); + else { + ns = mail_namespace_find_prefix(user->namespaces, ns_prefix); + if (ns == NULL) { + i_error("Namespace prefix not found: %s", ns_prefix); + return -1; + } + } + + if (fts_list_backend(ns->list) == NULL) { + i_error("fts not enabled for user's namespace %s", + ns_prefix != NULL ? ns_prefix : "INBOX"); + return -1; + } + *ns_r = ns; + return 0; +} + +static int +cmd_fts_optimize_run(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user) +{ + const char *ns_prefix = ctx->args[0]; + struct mail_namespace *ns; + struct fts_backend *backend; + + if (fts_namespace_find(user, ns_prefix, &ns) < 0) { + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + backend = fts_list_backend(ns->list); + if (fts_backend_optimize(backend) < 0) { + i_error("fts optimize failed"); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + return -1; + } + return 0; +} + +static void +cmd_fts_optimize_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (str_array_length(args) > 1) + doveadm_mail_help_name("fts optimize"); +} + +static struct doveadm_mail_cmd_context * +cmd_fts_optimize_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_fts_optimize_run; + ctx->v.init = cmd_fts_optimize_init; + return ctx; +} + +static int +cmd_fts_rescan_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) +{ + const char *ns_prefix = ctx->args[0]; + struct mail_namespace *ns; + struct fts_backend *backend; + + if (fts_namespace_find(user, ns_prefix, &ns) < 0) { + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + backend = fts_list_backend(ns->list); + if (fts_backend_rescan(backend) < 0) { + i_error("fts rescan failed"); + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + return -1; + } + return 0; +} + +static void +cmd_fts_rescan_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[]) +{ + if (str_array_length(args) > 1) + doveadm_mail_help_name("fts rescan"); +} + +static struct doveadm_mail_cmd_context * +cmd_fts_rescan_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_fts_rescan_run; + ctx->v.init = cmd_fts_rescan_init; + return ctx; +} + +static struct doveadm_cmd_ver2 fts_commands[] = { +{ + .name = "fts lookup", + .mail_cmd = cmd_fts_lookup_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<search query>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "fts expand", + .mail_cmd = cmd_fts_expand_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<search query>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "fts tokenize", + .mail_cmd = cmd_fts_tokenize_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<text>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('l', "language", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', "text", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "fts optimize", + .mail_cmd = cmd_fts_optimize_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[<namespace>]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "namespace", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +{ + .name = "fts rescan", + .mail_cmd = cmd_fts_rescan_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[<namespace>]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('\0', "namespace", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}, +}; + +void doveadm_fts_plugin_init(struct module *module ATTR_UNUSED) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(fts_commands); i++) + doveadm_cmd_register_ver2(&fts_commands[i]); + doveadm_dump_fts_expunge_log_init(); +} + +void doveadm_fts_plugin_deinit(void) +{ +} diff --git a/src/plugins/fts/doveadm-fts.h b/src/plugins/fts/doveadm-fts.h new file mode 100644 index 0000000..d4307fe --- /dev/null +++ b/src/plugins/fts/doveadm-fts.h @@ -0,0 +1,11 @@ +#ifndef DOVEADM_FTS_H +#define DOVEADM_FTS_H + +struct module; + +void doveadm_dump_fts_expunge_log_init(void); + +void doveadm_fts_plugin_init(struct module *module); +void doveadm_fts_plugin_deinit(void); + +#endif diff --git a/src/plugins/fts/fts-api-private.h b/src/plugins/fts/fts-api-private.h new file mode 100644 index 0000000..a070564 --- /dev/null +++ b/src/plugins/fts/fts-api-private.h @@ -0,0 +1,139 @@ +#ifndef FTS_API_PRIVATE_H +#define FTS_API_PRIVATE_H + +#include "unichar.h" +#include "fts-api.h" + +struct mail_user; +struct mailbox_list; + +#define MAILBOX_GUID_HEX_LENGTH (GUID_128_SIZE*2) + +struct fts_backend_vfuncs { + struct fts_backend *(*alloc)(void); + int (*init)(struct fts_backend *backend, const char **error_r); + void (*deinit)(struct fts_backend *backend); + + int (*get_last_uid)(struct fts_backend *backend, struct mailbox *box, + uint32_t *last_uid_r); + + struct fts_backend_update_context * + (*update_init)(struct fts_backend *backend); + int (*update_deinit)(struct fts_backend_update_context *ctx); + + void (*update_set_mailbox)(struct fts_backend_update_context *ctx, + struct mailbox *box); + void (*update_expunge)(struct fts_backend_update_context *ctx, + uint32_t uid); + + /* Start a build for specified key */ + bool (*update_set_build_key)(struct fts_backend_update_context *ctx, + const struct fts_backend_build_key *key); + /* Finish a build for specified key - guaranteed to be called */ + void (*update_unset_build_key)(struct fts_backend_update_context *ctx); + /* Add data for current build key */ + int (*update_build_more)(struct fts_backend_update_context *ctx, + const unsigned char *data, size_t size); + + int (*refresh)(struct fts_backend *backend); + int (*rescan)(struct fts_backend *backend); + int (*optimize)(struct fts_backend *backend); + + bool (*can_lookup)(struct fts_backend *backend, + const struct mail_search_arg *args); + int (*lookup)(struct fts_backend *backend, struct mailbox *box, + struct mail_search_arg *args, enum fts_lookup_flags flags, + struct fts_result *result); + int (*lookup_multi)(struct fts_backend *backend, + struct mailbox *const boxes[], + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_multi_result *result); + void (*lookup_done)(struct fts_backend *backend); +}; + +enum fts_backend_flags { + /* Backend supports indexing binary MIME parts */ + FTS_BACKEND_FLAG_BINARY_MIME_PARTS = 0x01, + /* Send built text to backend normalized rather than + preserving original case */ + FTS_BACKEND_FLAG_NORMALIZE_INPUT = 0x02, + /* Send only fully indexable words rather than randomly sized blocks */ + FTS_BACKEND_FLAG_BUILD_FULL_WORDS = 0x04, + /* Fuzzy search works */ + FTS_BACKEND_FLAG_FUZZY_SEARCH = 0x08, + /* Tokenize all the input. update_build_more() will be called a single + directly indexable token at a time. Searching will modify the search + args so that lookup() sees only tokens that can be directly + searched. */ + FTS_BACKEND_FLAG_TOKENIZED_INPUT = 0x10 +}; + +struct fts_header_filters { + pool_t pool; + ARRAY_TYPE(const_string) includes; + ARRAY_TYPE(const_string) excludes; + bool loaded:1; + bool exclude_is_default:1; +}; + +struct fts_backend { + const char *name; + enum fts_backend_flags flags; + + struct fts_backend_vfuncs v; + struct mail_namespace *ns; + struct fts_header_filters header_filters; + + bool updating:1; +}; + +struct fts_backend_update_context { + struct fts_backend *backend; + normalizer_func_t *normalizer; + + struct mailbox *cur_box, *backend_box; + + bool build_key_open:1; + bool failed:1; +}; + +struct fts_index_header { + uint32_t last_indexed_uid; + + /* Checksum of settings. If the settings change, the index should + be rebuilt. */ + uint32_t settings_checksum; + uint32_t unused; +}; + +void fts_backend_register(const struct fts_backend *backend); +void fts_backend_unregister(const char *name); + +bool fts_backend_default_can_lookup(struct fts_backend *backend, + const struct mail_search_arg *args); + +void fts_filter_uids(ARRAY_TYPE(seq_range) *definite_dest, + const ARRAY_TYPE(seq_range) *definite_filter, + ARRAY_TYPE(seq_range) *maybe_dest, + const ARRAY_TYPE(seq_range) *maybe_filter); + +/* Returns TRUE if ok, FALSE if no fts header */ +bool fts_index_get_header(struct mailbox *box, struct fts_index_header *hdr_r); +int fts_index_set_header(struct mailbox *box, + const struct fts_index_header *hdr); +int ATTR_NOWARN_UNUSED_RESULT +fts_index_set_last_uid(struct mailbox *box, uint32_t last_uid); +int fts_backend_reset_last_uids(struct fts_backend *backend); +int fts_index_have_compatible_settings(struct mailbox_list *list, + uint32_t checksum); + +/* Returns TRUE if FTS backend should index the header for optimizing + separate lookups */ +bool fts_header_want_indexed(const char *hdr_name); +/* Returns TRUE if header's values should be considered to have a language. */ +bool fts_header_has_language(const char *hdr_name); + +int fts_mailbox_get_guid(struct mailbox *box, const char **guid_r); + +#endif diff --git a/src/plugins/fts/fts-api.c b/src/plugins/fts/fts-api.c new file mode 100644 index 0000000..a6ea716 --- /dev/null +++ b/src/plugins/fts/fts-api.c @@ -0,0 +1,554 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hex-binary.h" +#include "mail-index.h" +#include "mail-namespace.h" +#include "mail-storage-private.h" +#include "mailbox-list-iter.h" +#include "mail-search.h" +#include "fts-api-private.h" + +struct event_category event_category_fts = { + .name = "fts", +}; + +static ARRAY(const struct fts_backend *) backends; + +void fts_backend_register(const struct fts_backend *backend) +{ + if (!array_is_created(&backends)) + i_array_init(&backends, 4); + array_push_back(&backends, &backend); +} + +void fts_backend_unregister(const char *name) +{ + const struct fts_backend *const *be; + unsigned int i, count; + + be = array_get(&backends, &count); + for (i = 0; i < count; i++) { + if (strcmp(be[i]->name, name) == 0) { + array_delete(&backends, i, 1); + break; + } + } + if (i == count) + i_panic("fts_backend_unregister(%s): unknown backend", name); + + if (count == 1) + array_free(&backends); +} + +static const struct fts_backend * +fts_backend_class_lookup(const char *backend_name) +{ + const struct fts_backend *const *be; + unsigned int i, count; + + if (array_is_created(&backends)) { + be = array_get(&backends, &count); + for (i = 0; i < count; i++) { + if (strcmp(be[i]->name, backend_name) == 0) + return be[i]; + } + } + return NULL; +} + +static void +fts_header_filters_init(struct fts_backend *backend) +{ + struct fts_header_filters *filters = &backend->header_filters; + pool_t pool = filters->pool = pool_alloconly_create( + MEMPOOL_GROWING"fts_header_filters", 256); + + p_array_init(&filters->includes, pool, 8); + p_array_init(&filters->excludes, pool, 8); +} + +static void +fts_header_filters_deinit(struct fts_backend *backend) +{ + pool_unref(&backend->header_filters.pool); +} + +int fts_backend_init(const char *backend_name, struct mail_namespace *ns, + const char **error_r, struct fts_backend **backend_r) +{ + const struct fts_backend *be; + struct fts_backend *backend; + + be = fts_backend_class_lookup(backend_name); + if (be == NULL) { + *error_r = "Unknown backend"; + return -1; + } + + backend = be->v.alloc(); + backend->ns = ns; + if (backend->v.init(backend, error_r) < 0) { + i_free(backend); + return -1; + } + + fts_header_filters_init(backend); + *backend_r = backend; + return 0; +} + +void fts_backend_deinit(struct fts_backend **_backend) +{ + struct fts_backend *backend = *_backend; + + fts_header_filters_deinit(backend); + *_backend = NULL; + backend->v.deinit(backend); +} + +int fts_backend_get_last_uid(struct fts_backend *backend, struct mailbox *box, + uint32_t *last_uid_r) +{ + struct fts_index_header hdr; + + if (box->virtual_vfuncs != NULL) { + /* virtual mailboxes themselves don't have any indexes, + so catch this call here */ + if (!fts_index_get_header(box, &hdr)) + *last_uid_r = 0; + else + *last_uid_r = hdr.last_indexed_uid; + return 0; + } + + return backend->v.get_last_uid(backend, box, last_uid_r); +} + +bool fts_backend_is_updating(struct fts_backend *backend) +{ + return backend->updating; +} + +struct fts_backend_update_context * +fts_backend_update_init(struct fts_backend *backend) +{ + struct fts_backend_update_context *ctx; + + i_assert(!backend->updating); + + backend->updating = TRUE; + ctx = backend->v.update_init(backend); + if ((backend->flags & FTS_BACKEND_FLAG_NORMALIZE_INPUT) != 0) + ctx->normalizer = backend->ns->user->default_normalizer; + return ctx; +} + +static void fts_backend_set_cur_mailbox(struct fts_backend_update_context *ctx) +{ + fts_backend_update_unset_build_key(ctx); + if (ctx->backend_box != ctx->cur_box) { + ctx->backend->v.update_set_mailbox(ctx, ctx->cur_box); + ctx->backend_box = ctx->cur_box; + } +} + +int fts_backend_update_deinit(struct fts_backend_update_context **_ctx) +{ + struct fts_backend_update_context *ctx = *_ctx; + struct fts_backend *backend = ctx->backend; + int ret; + + *_ctx = NULL; + + ctx->cur_box = NULL; + fts_backend_set_cur_mailbox(ctx); + + ret = backend->v.update_deinit(ctx); + backend->updating = FALSE; + return ret; +} + +void fts_backend_update_set_mailbox(struct fts_backend_update_context *ctx, + struct mailbox *box) +{ + if (ctx->backend_box != NULL && box != ctx->backend_box) { + /* make sure we don't reference the backend box anymore */ + ctx->backend->v.update_set_mailbox(ctx, NULL); + ctx->backend_box = NULL; + } + ctx->cur_box = box; +} + +void fts_backend_update_expunge(struct fts_backend_update_context *ctx, + uint32_t uid) +{ + fts_backend_set_cur_mailbox(ctx); + ctx->backend->v.update_expunge(ctx, uid); +} + +bool fts_backend_update_set_build_key(struct fts_backend_update_context *ctx, + const struct fts_backend_build_key *key) +{ + fts_backend_set_cur_mailbox(ctx); + + i_assert(ctx->cur_box != NULL); + + if (!ctx->backend->v.update_set_build_key(ctx, key)) + return FALSE; + ctx->build_key_open = TRUE; + return TRUE; +} + +void fts_backend_update_unset_build_key(struct fts_backend_update_context *ctx) +{ + if (ctx->build_key_open) { + ctx->backend->v.update_unset_build_key(ctx); + ctx->build_key_open = FALSE; + } +} + +int fts_backend_update_build_more(struct fts_backend_update_context *ctx, + const unsigned char *data, size_t size) +{ + i_assert(ctx->build_key_open); + + return ctx->backend->v.update_build_more(ctx, data, size); +} + +int fts_backend_refresh(struct fts_backend *backend) +{ + return backend->v.refresh(backend); +} + +int fts_backend_reset_last_uids(struct fts_backend *backend) +{ + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + struct mailbox *box; + int ret = 0; + + iter = mailbox_list_iter_init(backend->ns->list, "*", + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_NO_AUTO_BOXES); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + if ((info->flags & + (MAILBOX_NONEXISTENT | MAILBOX_NOSELECT)) != 0) + continue; + + box = mailbox_alloc(info->ns->list, info->vname, 0); + if (mailbox_open(box) == 0) { + if (fts_index_set_last_uid(box, 0) < 0) + ret = -1; + } + mailbox_free(&box); + } + if (mailbox_list_iter_deinit(&iter) < 0) + ret = -1; + return ret; +} + +int fts_backend_rescan(struct fts_backend *backend) +{ + struct mailbox *box; + bool virtual_storage; + + box = mailbox_alloc(backend->ns->list, "", 0); + virtual_storage = box->virtual_vfuncs != NULL; + mailbox_free(&box); + + if (virtual_storage) { + /* just reset the last-uids for a virtual storage. */ + return fts_backend_reset_last_uids(backend); + } + + return backend->v.rescan == NULL ? 0 : + backend->v.rescan(backend); +} + +int fts_backend_optimize(struct fts_backend *backend) +{ + return backend->v.optimize == NULL ? 0 : + backend->v.optimize(backend); +} + +static void +fts_merge_maybies(ARRAY_TYPE(seq_range) *dest_maybe, + const ARRAY_TYPE(seq_range) *dest_definite, + const ARRAY_TYPE(seq_range) *src_maybe, + const ARRAY_TYPE(seq_range) *src_definite) +{ + ARRAY_TYPE(seq_range) src_unwanted; + const struct seq_range *range; + struct seq_range new_range; + unsigned int i, count; + uint32_t seq; + + /* add/leave to dest_maybe if at least one list has maybe, + and no lists have none */ + + /* create unwanted sequences list from both sources */ + t_array_init(&src_unwanted, 128); + new_range.seq1 = 0; new_range.seq2 = (uint32_t)-1; + array_push_back(&src_unwanted, &new_range); + seq_range_array_remove_seq_range(&src_unwanted, src_maybe); + seq_range_array_remove_seq_range(&src_unwanted, src_definite); + + /* drop unwanted uids */ + seq_range_array_remove_seq_range(dest_maybe, &src_unwanted); + + /* add uids that are in dest_definite and src_maybe lists */ + range = array_get(dest_definite, &count); + for (i = 0; i < count; i++) { + for (seq = range[i].seq1; seq <= range[i].seq2; seq++) { + if (seq_range_exists(src_maybe, seq)) + seq_range_array_add(dest_maybe, seq); + } + } +} + +void fts_filter_uids(ARRAY_TYPE(seq_range) *definite_dest, + const ARRAY_TYPE(seq_range) *definite_filter, + ARRAY_TYPE(seq_range) *maybe_dest, + const ARRAY_TYPE(seq_range) *maybe_filter) +{ + T_BEGIN { + fts_merge_maybies(maybe_dest, definite_dest, + maybe_filter, definite_filter); + } T_END; + /* keep only what exists in both lists. the rest is in + maybies or not wanted */ + seq_range_array_intersect(definite_dest, definite_filter); +} + +bool fts_backend_default_can_lookup(struct fts_backend *backend, + const struct mail_search_arg *args) +{ + for (; args != NULL; args = args->next) { + switch (args->type) { + case SEARCH_OR: + case SEARCH_SUB: + case SEARCH_INTHREAD: + if (fts_backend_default_can_lookup(backend, + args->value.subargs)) + return TRUE; + break; + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + case SEARCH_BODY: + case SEARCH_TEXT: + if (!args->no_fts) + return TRUE; + break; + default: + break; + } + } + return FALSE; +} + +bool fts_backend_can_lookup(struct fts_backend *backend, + const struct mail_search_arg *args) +{ + return backend->v.can_lookup(backend, args); +} + +static int fts_score_map_sort(const struct fts_score_map *m1, + const struct fts_score_map *m2) +{ + if (m1->uid < m2->uid) + return -1; + if (m1->uid > m2->uid) + return 1; + return 0; +} + +int fts_backend_lookup(struct fts_backend *backend, struct mailbox *box, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_result *result) +{ + array_clear(&result->definite_uids); + array_clear(&result->maybe_uids); + array_clear(&result->scores); + + if (backend->v.lookup(backend, box, args, flags, result) < 0) + return -1; + + if (!result->scores_sorted && array_is_created(&result->scores)) { + array_sort(&result->scores, fts_score_map_sort); + result->scores_sorted = TRUE; + } + return 0; +} + +int fts_backend_lookup_multi(struct fts_backend *backend, + struct mailbox *const boxes[], + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_multi_result *result) +{ + unsigned int i; + + i_assert(boxes[0] != NULL); + + if (backend->v.lookup_multi != NULL) { + if (backend->v.lookup_multi(backend, boxes, args, + flags, result) < 0) + return -1; + if (result->box_results == NULL) { + result->box_results = p_new(result->pool, + struct fts_result, 1); + } + return 0; + } + + for (i = 0; boxes[i] != NULL; i++) ; + result->box_results = p_new(result->pool, struct fts_result, i+1); + + for (i = 0; boxes[i] != NULL; i++) { + struct fts_result *box_result = &result->box_results[i]; + + p_array_init(&box_result->definite_uids, result->pool, 32); + p_array_init(&box_result->maybe_uids, result->pool, 32); + p_array_init(&box_result->scores, result->pool, 32); + if (backend->v.lookup(backend, boxes[i], args, + flags, box_result) < 0) + return -1; + } + return 0; +} + +void fts_backend_lookup_done(struct fts_backend *backend) +{ + if (backend->v.lookup_done != NULL) + backend->v.lookup_done(backend); +} + +static uint32_t fts_index_get_ext_id(struct mailbox *box) +{ + return mail_index_ext_register(box->index, "fts", + sizeof(struct fts_index_header), + 0, 0); +} + +bool fts_index_get_header(struct mailbox *box, struct fts_index_header *hdr_r) +{ + struct mail_index_view *view; + const void *data; + size_t data_size; + bool ret; + + mail_index_refresh(box->index); + view = mail_index_view_open(box->index); + mail_index_get_header_ext(view, fts_index_get_ext_id(box), + &data, &data_size); + if (data_size < sizeof(*hdr_r)) { + i_zero(hdr_r); + ret = FALSE; + } else { + memcpy(hdr_r, data, sizeof(*hdr_r)); + ret = TRUE; + } + mail_index_view_close(&view); + return ret; +} + +int fts_index_set_header(struct mailbox *box, + const struct fts_index_header *hdr) +{ + struct mail_index_transaction *trans; + uint32_t ext_id = fts_index_get_ext_id(box); + + trans = mail_index_transaction_begin(box->view, 0); + mail_index_update_header_ext(trans, ext_id, 0, hdr, sizeof(*hdr)); + return mail_index_transaction_commit(&trans); +} + +int fts_index_set_last_uid(struct mailbox *box, uint32_t last_uid) +{ + struct fts_index_header hdr; + + (void)fts_index_get_header(box, &hdr); + hdr.last_indexed_uid = last_uid; + return fts_index_set_header(box, &hdr); +} + +int fts_index_have_compatible_settings(struct mailbox_list *list, + uint32_t checksum) +{ + struct mail_namespace *ns = mailbox_list_get_namespace(list); + struct mailbox *box; + struct fts_index_header hdr; + const char *vname; + size_t len; + int ret; + + if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) + vname = "INBOX"; + else { + len = strlen(ns->prefix); + if (len > 0 && ns->prefix[len-1] == mail_namespace_get_sep(ns)) + len--; + vname = t_strndup(ns->prefix, len); + } + + box = mailbox_alloc(list, vname, 0); + if (mailbox_sync(box, (enum mailbox_sync_flags)0) < 0) { + i_error("fts: Failed to sync mailbox %s: %s", vname, + mailbox_get_last_internal_error(box, NULL)); + ret = -1; + } else { + ret = fts_index_get_header(box, &hdr) && + hdr.settings_checksum == checksum ? 1 : 0; + } + mailbox_free(&box); + return ret; +} + +static const char *indexed_headers[] = { + "From", "To", "Cc", "Bcc", "Subject" +}; + +bool fts_header_want_indexed(const char *hdr_name) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(indexed_headers); i++) { + if (strcasecmp(hdr_name, indexed_headers[i]) == 0) + return TRUE; + } + return FALSE; +} + +bool fts_header_has_language(const char *hdr_name) +{ + /* FIXME: should email address headers be detected as different + languages? That mainly contains people's names.. */ + /*if (message_header_is_address(hdr_name)) + return TRUE;*/ + + /* Subject definitely contains language-specific data that can be + detected. Comment and Keywords headers also could contain, although + just about nobody uses those headers. + + For now we assume that other headers contain non-language specific + data that we don't want to filter in special ways. For example + it is good to be able to search for Message-IDs. */ + return strcasecmp(hdr_name, "Subject") == 0 || + strcasecmp(hdr_name, "Comments") == 0 || + strcasecmp(hdr_name, "Keywords") == 0; +} + +int fts_mailbox_get_guid(struct mailbox *box, const char **guid_r) +{ + struct mailbox_metadata metadata; + + if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) + return -1; + + *guid_r = guid_128_to_string(metadata.guid); + return 0; +} diff --git a/src/plugins/fts/fts-api.h b/src/plugins/fts/fts-api.h new file mode 100644 index 0000000..11a331f --- /dev/null +++ b/src/plugins/fts/fts-api.h @@ -0,0 +1,173 @@ +#ifndef FTS_API_H +#define FTS_API_H + +struct mail; +struct mailbox; +struct mail_namespace; +struct mail_search_arg; + +struct fts_backend; + +#include "seq-range-array.h" + +enum fts_lookup_flags { + /* Specifies if the args should be ANDed or ORed together. */ + FTS_LOOKUP_FLAG_AND_ARGS = 0x01, + /* Require exact matching for non-fuzzy search args by returning all + such matches as maybe_uids instead of definite_uids */ + FTS_LOOKUP_FLAG_NO_AUTO_FUZZY = 0x02 +}; + +enum fts_backend_build_key_type { + /* Header */ + FTS_BACKEND_BUILD_KEY_HDR, + /* MIME part header */ + FTS_BACKEND_BUILD_KEY_MIME_HDR, + /* MIME body part */ + FTS_BACKEND_BUILD_KEY_BODY_PART, + /* Binary MIME body part, if backend supports binary data */ + FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY +}; + +struct fts_backend_build_key { + uint32_t uid; + enum fts_backend_build_key_type type; + struct message_part *part; + + /* for _KEY_HDR: */ + const char *hdr_name; + + /* for _KEY_BODY_PART and _KEY_BODY_PART_BINARY: */ + + /* Contains a valid parsed "type/subtype" string. For messages without + (valid) Content-Type: header, it's set to "text/plain". */ + const char *body_content_type; + /* Content-Disposition: header without parsing/validation if it exists, + otherwise NULL. */ + const char *body_content_disposition; +}; + +struct fts_score_map { + uint32_t uid; + float score; +}; +ARRAY_DEFINE_TYPE(fts_score_map, struct fts_score_map); + +/* the structure is meant to be implemented by plugins that want to carry + some state over from a call to next ones within an fts_search_context + session. + + The pointer to this structure is initially granted to be NULL and it + remains such unless the plugin itself activates it. + + Any memory management for the pointer and its contents is expected to + be performed by the plugin itself, possibly but not necessarily using + the result pool propagated to plugin call by struct fts_result.pool and + struct fts_multi_result.pool. */ + +struct fts_search_state; + +struct fts_result { + pool_t pool; + struct fts_search_state *search_state; + + struct mailbox *box; + + ARRAY_TYPE(seq_range) definite_uids; + /* The maybe_uids is useful with backends that can only filter out + messages, but can't definitively say if the search matched a + message. */ + ARRAY_TYPE(seq_range) maybe_uids; + ARRAY_TYPE(fts_score_map) scores; + bool scores_sorted; +}; + +struct fts_multi_result { + pool_t pool; + struct fts_search_state *search_state; + + /* box=NULL-terminated array of mailboxes and matching UIDs, + all allocated from the given pool. */ + struct fts_result *box_results; +}; + +extern struct event_category event_category_fts; + +int fts_backend_init(const char *backend_name, struct mail_namespace *ns, + const char **error_r, struct fts_backend **backend_r); +void fts_backend_deinit(struct fts_backend **backend); + +/* Get the last_uid for the mailbox. */ +int fts_backend_get_last_uid(struct fts_backend *backend, struct mailbox *box, + uint32_t *last_uid_r); + +/* Returns TRUE if there exists an update context. */ +bool fts_backend_is_updating(struct fts_backend *backend); + +/* Start an index update. */ +struct fts_backend_update_context * +fts_backend_update_init(struct fts_backend *backend); +/* Finish an index update. Returns 0 if ok, -1 if some updates failed. + If updates failed, the index is in unspecified state. */ +int fts_backend_update_deinit(struct fts_backend_update_context **ctx); + +/* Switch to updating the specified mailbox. box may also be set to NULL to + make sure the previous mailbox won't tried to be accessed anymore. */ +void fts_backend_update_set_mailbox(struct fts_backend_update_context *ctx, + struct mailbox *box); +/* Expunge the specified mail. */ +void fts_backend_update_expunge(struct fts_backend_update_context *ctx, + uint32_t uid); + +/* Switch to building index for specified key. If backend doesn't want to + index this key, it can return FALSE and caller will skip to next key. */ +bool fts_backend_update_set_build_key(struct fts_backend_update_context *ctx, + const struct fts_backend_build_key *key); +/* Make sure that if _build_more() is called, we'll assert-crash. */ +void fts_backend_update_unset_build_key(struct fts_backend_update_context *ctx); +/* Add more content to the index for the currently specified build key. + Non-BODY_PART_BINARY data must contain only full valid UTF-8 characters, + but it doesn't need to be NUL-terminated. size contains the data size in + bytes, not characters. This function may be called many times and the data + block sizes may be small. Backend returns 0 if ok, -1 if build should be + aborted. */ +int fts_backend_update_build_more(struct fts_backend_update_context *ctx, + const unsigned char *data, size_t size); + +/* Refresh index to make sure we see latest changes from lookups. + Returns 0 if ok, -1 if error. */ +int fts_backend_refresh(struct fts_backend *backend); +/* Go through the entire index and make sure all mails are indexed, + and delete any extra mails in the index. */ +int fts_backend_rescan(struct fts_backend *backend); +/* Optimize the index. This can be a somewhat heavy operation. */ +int fts_backend_optimize(struct fts_backend *backend); + +/* Returns TRUE if fts_backend_lookup() should even be tried for the + given args. */ +bool fts_backend_can_lookup(struct fts_backend *backend, + const struct mail_search_arg *args); +/* Do a FTS lookup for the given search args. Backends can support different + kinds of search arguments, so match_always=TRUE must be set to all search + args that were actually used to produce the search results. The other args + are handled by the regular search code. The backends MUST ignore all args + that have subargs (SEARCH_OR, SEARCH_SUB), since they are looked up + separately. + + The arrays in result must be initialized by caller. */ +int fts_backend_lookup(struct fts_backend *backend, struct mailbox *box, + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_result *result); + +/* Search from multiple mailboxes. result->pool must be initialized. */ +int fts_backend_lookup_multi(struct fts_backend *backend, + struct mailbox *const boxes[], + struct mail_search_arg *args, + enum fts_lookup_flags flags, + struct fts_multi_result *result); +/* Called after the lookups are done. The next lookup will be preceded by a + refresh. */ +void fts_backend_lookup_done(struct fts_backend *backend); + +#endif diff --git a/src/plugins/fts/fts-build-mail.c b/src/plugins/fts/fts-build-mail.c new file mode 100644 index 0000000..73d4f4b --- /dev/null +++ b/src/plugins/fts/fts-build-mail.c @@ -0,0 +1,719 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "buffer.h" +#include "str.h" +#include "rfc822-parser.h" +#include "message-address.h" +#include "message-parser.h" +#include "message-decoder.h" +#include "mail-storage.h" +#include "index-mail.h" +#include "fts-parser.h" +#include "fts-user.h" +#include "fts-language.h" +#include "fts-tokenizer.h" +#include "fts-filter.h" +#include "fts-api-private.h" +#include "fts-build-mail.h" + +/* there are other characters as well, but this doesn't have to be exact */ +#define IS_WORD_WHITESPACE(c) \ + ((c) == ' ' || (c) == '\t' || (c) == '\n') +/* if we see a word larger than this, just go ahead and split it from + wherever */ +#define MAX_WORD_SIZE 1024 + +struct fts_mail_build_context { + struct mail *mail; + struct fts_backend_update_context *update_ctx; + + char *content_type, *content_disposition; + struct fts_parser *body_parser; + + buffer_t *word_buf, *pending_input; + struct fts_user_language *cur_user_lang; +}; + +static int fts_build_data(struct fts_mail_build_context *ctx, + const unsigned char *data, size_t size, bool last); + +static void fts_build_parse_content_type(struct fts_mail_build_context *ctx, + const struct message_header_line *hdr) +{ + struct rfc822_parser_context parser; + string_t *content_type; + + if (ctx->content_type != NULL) + return; + + rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL); + rfc822_skip_lwsp(&parser); + + T_BEGIN { + content_type = t_str_new(64); + (void)rfc822_parse_content_type(&parser, content_type); + ctx->content_type = str_lcase(i_strdup(str_c(content_type))); + } T_END; + rfc822_parser_deinit(&parser); +} + +static void +fts_build_parse_content_disposition(struct fts_mail_build_context *ctx, + const struct message_header_line *hdr) +{ + /* just pass it as-is to backend. */ + i_free(ctx->content_disposition); + ctx->content_disposition = + i_strndup(hdr->full_value, hdr->full_value_len); +} + +static void fts_parse_mail_header(struct fts_mail_build_context *ctx, + const struct message_block *raw_block) +{ + const struct message_header_line *hdr = raw_block->hdr; + + if (strcasecmp(hdr->name, "Content-Type") == 0) + fts_build_parse_content_type(ctx, hdr); + else if (strcasecmp(hdr->name, "Content-Disposition") == 0) + fts_build_parse_content_disposition(ctx, hdr); +} + +static int +fts_build_unstructured_header(struct fts_mail_build_context *ctx, + const struct message_header_line *hdr) +{ + const unsigned char *data = hdr->full_value; + unsigned char *buf = NULL; + unsigned int i; + int ret; + + /* @UNSAFE: if there are any NULs, replace them with spaces */ + for (i = 0; i < hdr->full_value_len; i++) { + if (hdr->full_value[i] == '\0') { + if (buf == NULL) { + buf = i_memdup(hdr->full_value, + hdr->full_value_len); + data = buf; + } + buf[i] = ' '; + } + } + ret = fts_build_data(ctx, data, hdr->full_value_len, TRUE); + i_free(buf); + return ret; +} + +static void fts_mail_build_ctx_set_lang(struct fts_mail_build_context *ctx, + struct fts_user_language *user_lang) +{ + i_assert(user_lang != NULL); + + ctx->cur_user_lang = user_lang; + /* reset tokenizer between fields - just to be sure no state + leaks between fields (especially if previous indexing had + failed) */ + fts_tokenizer_reset(user_lang->index_tokenizer); +} + +static void +fts_build_tokenized_hdr_update_lang(struct fts_mail_build_context *ctx, + const struct message_header_line *hdr) +{ + /* Headers that don't contain any human language will only be + translated to lowercase - no stemming or other filtering. There's + unfortunately no pefect way of detecting which headers contain + human languages, so we check with fts_header_has_language if the + header is something that's supposed to containing human text. */ + if (fts_header_has_language(hdr->name)) + ctx->cur_user_lang = NULL; + else { + fts_mail_build_ctx_set_lang(ctx, + fts_user_get_data_lang(ctx->update_ctx->backend->ns->user)); + } +} + +static int fts_build_mail_header(struct fts_mail_build_context *ctx, + const struct message_block *block) +{ + const struct message_header_line *hdr = block->hdr; + struct fts_backend_build_key key; + int ret; + + if (hdr->eoh) + return 0; + + /* hdr->full_value is always set because we get the block from + message_decoder */ + i_zero(&key); + key.uid = ctx->mail->uid; + key.type = block->part->physical_pos == 0 ? + FTS_BACKEND_BUILD_KEY_HDR : FTS_BACKEND_BUILD_KEY_MIME_HDR; + key.part = block->part; + key.hdr_name = hdr->name; + + if ((ctx->update_ctx->backend->flags & + FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0) + fts_build_tokenized_hdr_update_lang(ctx, hdr); + + if (!fts_backend_update_set_build_key(ctx->update_ctx, &key)) + return 0; + + if (!message_header_is_address(hdr->name)) { + /* regular unstructured header */ + ret = fts_build_unstructured_header(ctx, hdr); + } else T_BEGIN { + /* message address. normalize it to give better + search results. */ + struct message_address *addr; + string_t *str; + + addr = message_address_parse(pool_datastack_create(), + hdr->full_value, + hdr->full_value_len, + UINT_MAX, 0); + str = t_str_new(hdr->full_value_len); + message_address_write(str, addr); + + ret = fts_build_data(ctx, str_data(str), str_len(str), TRUE); + } T_END; + + if ((ctx->update_ctx->backend->flags & + FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0) { + /* index the header name itself using data-language. */ + struct fts_user_language *prev_lang = ctx->cur_user_lang; + + fts_mail_build_ctx_set_lang(ctx, + fts_user_get_data_lang(ctx->update_ctx->backend->ns->user)); + key.hdr_name = ""; + if (fts_backend_update_set_build_key(ctx->update_ctx, &key)) { + if (fts_build_data(ctx, (const void *)hdr->name, + strlen(hdr->name), TRUE) < 0) + ret = -1; + } + fts_mail_build_ctx_set_lang(ctx, prev_lang); + } + return ret; +} + +static bool +fts_build_body_begin(struct fts_mail_build_context *ctx, + struct message_part *part, bool *binary_body_r) +{ + struct mail_storage *storage; + struct fts_parser_context parser_context; + struct fts_backend_build_key key; + + i_assert(ctx->body_parser == NULL); + + *binary_body_r = FALSE; + i_zero(&key); + key.uid = ctx->mail->uid; + key.part = part; + + i_zero(&parser_context); + parser_context.content_type = ctx->content_type != NULL ? + ctx->content_type : "text/plain"; + if (str_begins(parser_context.content_type, "multipart/")) { + /* multiparts are never indexed, only their contents */ + return FALSE; + } + storage = mailbox_get_storage(ctx->mail->box); + parser_context.user = mail_storage_get_user(storage); + parser_context.content_disposition = ctx->content_disposition; + + if (fts_parser_init(&parser_context, &ctx->body_parser)) { + /* extract text using the the returned parser */ + *binary_body_r = TRUE; + key.type = FTS_BACKEND_BUILD_KEY_BODY_PART; + } else if (str_begins(parser_context.content_type, "text/") || + str_begins(parser_context.content_type, "message/")) { + /* text body parts */ + key.type = FTS_BACKEND_BUILD_KEY_BODY_PART; + ctx->body_parser = fts_parser_text_init(); + } else { + /* possibly binary */ + if ((ctx->update_ctx->backend->flags & + FTS_BACKEND_FLAG_BINARY_MIME_PARTS) == 0) + return FALSE; + *binary_body_r = TRUE; + key.type = FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY; + } + key.body_content_type = parser_context.content_type; + key.body_content_disposition = ctx->content_disposition; + ctx->cur_user_lang = NULL; + if (!fts_backend_update_set_build_key(ctx->update_ctx, &key)) { + if (ctx->body_parser != NULL) + (void)fts_parser_deinit(&ctx->body_parser, NULL); + return FALSE; + } + return TRUE; +} + +static int +fts_build_add_tokens_with_filter(struct fts_mail_build_context *ctx, + const unsigned char *data, size_t size) +{ + struct fts_tokenizer *tokenizer = ctx->cur_user_lang->index_tokenizer; + struct fts_filter *filter = ctx->cur_user_lang->filter; + const char *token, *error; + int ret = 1, ret2; + + while (ret > 0) T_BEGIN { + ret = ret2 = fts_tokenizer_next(tokenizer, data, size, &token, &error); + if (ret2 > 0 && filter != NULL) + ret2 = fts_filter_filter(filter, &token, &error); + if (ret2 < 0) { + mail_set_critical(ctx->mail, + "fts: Couldn't create indexable tokens: %s", + error); + } + if (ret2 > 0) { + if (fts_backend_update_build_more(ctx->update_ctx, + (const void *)token, + strlen(token)) < 0) { + mail_storage_set_internal_error(ctx->mail->box->storage); + ret = -1; + } + } + } T_END; + return ret; +} + +static int +fts_detect_language(struct fts_mail_build_context *ctx, + const unsigned char *data, size_t size, bool last, + const struct fts_language **lang_r) +{ + struct mail_user *user = ctx->update_ctx->backend->ns->user; + struct fts_language_list *lang_list = fts_user_get_language_list(user); + const struct fts_language *lang; + const char *error; + + switch (fts_language_detect(lang_list, data, size, &lang, &error)) { + case FTS_LANGUAGE_RESULT_SHORT: + /* save the input so far and try again later */ + buffer_append(ctx->pending_input, data, size); + if (last) { + /* we've run out of data. use the default language. */ + *lang_r = fts_language_list_get_first(lang_list); + return 1; + } + return 0; + case FTS_LANGUAGE_RESULT_UNKNOWN: + /* use the default language */ + *lang_r = fts_language_list_get_first(lang_list); + return 1; + case FTS_LANGUAGE_RESULT_OK: + *lang_r = lang; + return 1; + case FTS_LANGUAGE_RESULT_ERROR: + /* internal language detection library failure + (e.g. invalid config). don't index anything. */ + mail_set_critical(ctx->mail, + "Language detection library initialization failed: %s", + error); + return -1; + default: + i_unreached(); + } +} + +static int +fts_build_tokenized(struct fts_mail_build_context *ctx, + const unsigned char *data, size_t size, bool last) +{ + struct mail_user *user = ctx->update_ctx->backend->ns->user; + const struct fts_language *lang; + int ret; + + if (ctx->cur_user_lang != NULL) { + /* we already have a language */ + } else if ((ret = fts_detect_language(ctx, data, size, last, &lang)) < 0) { + return -1; + } else if (ret == 0) { + /* wait for more data */ + return 0; + } else { + fts_mail_build_ctx_set_lang(ctx, fts_user_language_find(user, lang)); + + if (ctx->pending_input->used > 0) { + if (fts_build_add_tokens_with_filter(ctx, + ctx->pending_input->data, + ctx->pending_input->used) < 0) + return -1; + buffer_set_used_size(ctx->pending_input, 0); + } + } + if (fts_build_add_tokens_with_filter(ctx, data, size) < 0) + return -1; + if (last) { + if (fts_build_add_tokens_with_filter(ctx, NULL, 0) < 0) + return -1; + } + return 0; +} + +static int +fts_build_full_words(struct fts_mail_build_context *ctx, + const unsigned char *data, size_t size, bool last) +{ + size_t i; + + /* we'll need to send only full words to the backend */ + + if (ctx->word_buf != NULL && ctx->word_buf->used > 0) { + /* continuing previous word */ + for (i = 0; i < size; i++) { + if (IS_WORD_WHITESPACE(data[i])) + break; + } + buffer_append(ctx->word_buf, data, i); + data += i; + size -= i; + if (size == 0 && ctx->word_buf->used < MAX_WORD_SIZE && !last) { + /* word is still not finished */ + return 0; + } + /* we have a full word, index it */ + if (fts_backend_update_build_more(ctx->update_ctx, + ctx->word_buf->data, + ctx->word_buf->used) < 0) { + mail_storage_set_internal_error(ctx->mail->box->storage); + return -1; + } + buffer_set_used_size(ctx->word_buf, 0); + } + + /* find the boundary for last word */ + if (last) + i = size; + else { + for (i = size; i > 0; i--) { + if (IS_WORD_WHITESPACE(data[i-1])) + break; + } + } + + if (fts_backend_update_build_more(ctx->update_ctx, data, i) < 0) { + mail_storage_set_internal_error(ctx->mail->box->storage); + return -1; + } + + if (i < size) { + if (ctx->word_buf == NULL) { + ctx->word_buf = + buffer_create_dynamic(default_pool, 128); + } + buffer_append(ctx->word_buf, data + i, size - i); + } + return 0; +} + +static int fts_build_data(struct fts_mail_build_context *ctx, + const unsigned char *data, size_t size, bool last) +{ + if ((ctx->update_ctx->backend->flags & + FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0) { + return fts_build_tokenized(ctx, data, size, last); + } else if ((ctx->update_ctx->backend->flags & + FTS_BACKEND_FLAG_BUILD_FULL_WORDS) != 0) { + return fts_build_full_words(ctx, data, size, last); + } else { + if (fts_backend_update_build_more(ctx->update_ctx, data, size) < 0) { + mail_storage_set_internal_error(ctx->mail->box->storage); + return -1; + } + return 0; + } +} + +static int fts_build_body_block(struct fts_mail_build_context *ctx, + const struct message_block *block, bool last) +{ + i_assert(block->hdr == NULL); + + return fts_build_data(ctx, block->data, block->size, last); +} + +static int fts_body_parser_finish(struct fts_mail_build_context *ctx, + const char **retriable_err_msg_r, + bool *may_need_retry_r) +{ + struct message_block block; + const char *retriable_error; + int ret = 0; + int deinit_ret; + *may_need_retry_r = FALSE; + + do { + i_zero(&block); + fts_parser_more(ctx->body_parser, &block); + if (fts_build_body_block(ctx, &block, FALSE) < 0) { + ret = -1; + break; + } + } while (block.size > 0); + + deinit_ret = fts_parser_deinit(&ctx->body_parser, &retriable_error); + if (ret < 0) { + /* indexing already failed - we don't want to retry + in any case */ + return -1; + } + + if (deinit_ret == 0) { + /* retry the parsing */ + *may_need_retry_r = TRUE; + *retriable_err_msg_r = retriable_error; + return -1; + } + if (deinit_ret < 0) { + mail_storage_set_internal_error(ctx->mail->box->storage); + return -1; + } + return 0; +} + +static void +load_header_filter(const char *key, struct fts_backend *backend, + ARRAY_TYPE(const_string) list, bool *matches_all_r) +{ + const char *str = mail_user_plugin_getenv(backend->ns->user, key); + + *matches_all_r = FALSE; + if (str == NULL || *str == '\0') + return; + + char **entries = p_strsplit_spaces(backend->header_filters.pool, str, " "); + for (char **entry = entries; *entry != NULL; ++entry) { + const char *value = str_lcase(*entry); + array_push_back(&list, &value); + if (*value == '*') { + *matches_all_r = TRUE; + break; + } + } + array_sort(&list, i_strcmp_p); +} + +static struct fts_header_filters * +load_header_filters(struct fts_backend *backend) +{ + struct fts_header_filters *filters = &backend->header_filters; + if (!filters->loaded) { + bool match_all; + + /* match_all return ignored in includes */ + load_header_filter("fts_header_includes", backend, + filters->includes, &match_all); + + load_header_filter("fts_header_excludes", backend, + filters->excludes, &match_all); + filters->loaded = TRUE; + filters->exclude_is_default = match_all; + } + return filters; +} + +/* This performs comparison between two strings, where the second one can end + * with the wildcard '*'. When the match reaches a '*' on the pitem side, zero + * (match) is returned regardles of the remaining characters. + * + * The function obeys the same lexicographic order as i_strcmp_p() and + * strcmp(), which is the reason for the casts to unsigned before comparing. + */ +static int ATTR_PURE +header_prefix_cmp(const char *const *pkey, const char *const *pitem) +{ + const char *key = *pkey; + const char *item = *pitem; + + while (*key == *item && *key != '\0') key++, item++; + return item[0] == '*' && item[1] == '\0' ? 0 : + (unsigned char)*key - (unsigned char)*item; +} + +static bool +is_header_indexable(const char *header_name, struct fts_backend *backend) +{ + bool indexable; + T_BEGIN { + struct fts_header_filters *filters = load_header_filters(backend); + const char *hdr = t_str_lcase(header_name); + + if (array_bsearch(&filters->includes, &hdr, header_prefix_cmp) != NULL) + indexable = TRUE; + else if (filters->exclude_is_default || + array_bsearch(&filters->excludes, &hdr, header_prefix_cmp) != NULL) + indexable = FALSE; + else + indexable = TRUE; + } T_END; + return indexable; +} + +static int +fts_build_mail_real(struct fts_backend_update_context *update_ctx, + struct mail *mail, + const char **retriable_err_msg_r, + bool *may_need_retry_r) +{ + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, + }; + struct fts_mail_build_context ctx; + struct istream *input; + struct message_parser_ctx *parser; + struct message_decoder_context *decoder; + struct message_block raw_block, block; + struct message_part *prev_part, *parts; + bool skip_body = FALSE, body_part = FALSE, body_added = FALSE; + bool binary_body; + const char *error; + int ret; + + *may_need_retry_r = FALSE; + if (mail_get_stream_because(mail, NULL, NULL, "fts indexing", &input) < 0) { + if (mail->expunged) + return 0; + mail_set_critical(mail, "Failed to read stream: %s", + mailbox_get_last_internal_error(mail->box, NULL)); + return -1; + } + + i_zero(&ctx); + ctx.update_ctx = update_ctx; + ctx.mail = mail; + if ((update_ctx->backend->flags & FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0) + ctx.pending_input = buffer_create_dynamic(default_pool, 128); + + prev_part = NULL; + parser = message_parser_init(pool_datastack_create(), input, &parser_set); + + decoder = message_decoder_init(update_ctx->normalizer, 0); + for (;;) { + ret = message_parser_parse_next_block(parser, &raw_block); + i_assert(ret != 0); + if (ret < 0) { + if (input->stream_errno == 0) + ret = 0; + else { + mail_set_critical(mail, "read(%s) failed: %s", + i_stream_get_name(input), + i_stream_get_error(input)); + } + break; + } + + if (raw_block.part != prev_part) { + /* body part changed. we're now parsing the end of + boundary, possibly followed by message epilogue */ + if (ctx.body_parser != NULL) { + if (fts_body_parser_finish(&ctx, retriable_err_msg_r, + may_need_retry_r) < 0) { + ret = -1; + break; + } + } + message_decoder_set_return_binary(decoder, FALSE); + fts_backend_update_unset_build_key(update_ctx); + prev_part = raw_block.part; + i_free_and_null(ctx.content_type); + i_free_and_null(ctx.content_disposition); + + if (raw_block.size != 0) { + /* multipart. skip until beginning of next + part's headers */ + skip_body = TRUE; + } + } + + if (raw_block.hdr != NULL) { + /* always handle headers */ + } else if (raw_block.size == 0) { + /* end of headers */ + skip_body = !fts_build_body_begin(&ctx, raw_block.part, + &binary_body); + if (binary_body) + message_decoder_set_return_binary(decoder, TRUE); + body_part = TRUE; + } else { + if (skip_body) + continue; + } + + if (!message_decoder_decode_next_block(decoder, &raw_block, + &block)) + continue; + + if (block.hdr != NULL) { + fts_parse_mail_header(&ctx, &raw_block); + if (is_header_indexable(block.hdr->name, update_ctx->backend) && + fts_build_mail_header(&ctx, &block) < 0) { + ret = -1; + break; + } + } else if (block.size == 0) { + /* end of headers */ + } else { + i_assert(body_part); + if (ctx.body_parser != NULL) + fts_parser_more(ctx.body_parser, &block); + if (fts_build_body_block(&ctx, &block, FALSE) < 0) { + ret = -1; + break; + } + body_added = TRUE; + } + } + if (ctx.body_parser != NULL) { + if (ret == 0) + ret = fts_body_parser_finish(&ctx, retriable_err_msg_r, + may_need_retry_r); + else + (void)fts_parser_deinit(&ctx.body_parser, NULL); + } + if (ret == 0 && body_part && !skip_body && !body_added) { + /* make sure body is added even when it doesn't exist */ + block.data = NULL; block.size = 0; + ret = fts_build_body_block(&ctx, &block, TRUE); + } + if (message_parser_deinit_from_parts(&parser, &parts, &error) < 0) + index_mail_set_message_parts_corrupted(mail, error); + message_decoder_deinit(&decoder); + i_free(ctx.content_type); + i_free(ctx.content_disposition); + buffer_free(&ctx.word_buf); + buffer_free(&ctx.pending_input); + return ret < 0 ? -1 : 1; +} + +int fts_build_mail(struct fts_backend_update_context *update_ctx, + struct mail *mail) +{ + int ret; + /* Number of attempts to be taken if retry is needed */ + unsigned int attempts = 2; + const char *retriable_err_msg; + bool may_need_retry; + + T_BEGIN { + while ((ret = fts_build_mail_real(update_ctx, mail, + &retriable_err_msg, + &may_need_retry)) < 0 && + may_need_retry) { + if (--attempts == 0) { + /* Log this as info instead of as error, + because e.g. Tika doesn't differentiate + between temporary errors and invalid + document input. */ + i_info("%s - ignoring", retriable_err_msg); + ret = 0; + break; + } + } + } T_END; + return ret; +} diff --git a/src/plugins/fts/fts-build-mail.h b/src/plugins/fts/fts-build-mail.h new file mode 100644 index 0000000..aed4413 --- /dev/null +++ b/src/plugins/fts/fts-build-mail.h @@ -0,0 +1,9 @@ +#ifndef FTS_BUILD_MAIL_H +#define FTS_BUILD_MAIL_H + +/* Build indexes for the given mail. Returns 0 on success, -1 on error. + The error is set to mail's storage. */ +int fts_build_mail(struct fts_backend_update_context *update_ctx, + struct mail *mail); + +#endif diff --git a/src/plugins/fts/fts-expunge-log.c b/src/plugins/fts/fts-expunge-log.c new file mode 100644 index 0000000..d39ceea --- /dev/null +++ b/src/plugins/fts/fts-expunge-log.c @@ -0,0 +1,617 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "crc32.h" +#include "hash.h" +#include "istream.h" +#include "write-full.h" +#include "seq-range-array.h" +#include "mail-storage.h" +#include "fts-expunge-log.h" + +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +struct fts_expunge_log_record { + /* CRC32 of this entire record (except this checksum) */ + uint32_t checksum; + /* Size of this entire record */ + uint32_t record_size; + + /* Mailbox GUID */ + guid_128_t guid; + /* { uid1, uid2 } pairs */ + /* uint32_t expunge_uid_ranges[]; */ + + /* Total number of messages expunged so far in this log */ + /* uint32_t expunge_count; */ +}; + +struct fts_expunge_log { + char *path; + + int fd; + struct stat st; +}; + +struct fts_expunge_log_mailbox { + guid_128_t guid; + ARRAY_TYPE(seq_range) uids; + unsigned uids_count; +}; + +struct fts_expunge_log_append_ctx { + struct fts_expunge_log *log; + pool_t pool; + + HASH_TABLE(uint8_t *, struct fts_expunge_log_mailbox *) mailboxes; + struct fts_expunge_log_mailbox *prev_mailbox; + + bool failed; +}; + +struct fts_expunge_log_read_ctx { + struct fts_expunge_log *log; + + struct istream *input; + buffer_t buffer; + struct fts_expunge_log_read_record read_rec; + + bool failed; + bool corrupted; + bool unlink; +}; + +struct fts_expunge_log *fts_expunge_log_init(const char *path) +{ + struct fts_expunge_log *log; + + log = i_new(struct fts_expunge_log, 1); + log->path = i_strdup(path); + log->fd = -1; + return log; +} + +void fts_expunge_log_deinit(struct fts_expunge_log **_log) +{ + struct fts_expunge_log *log = *_log; + + *_log = NULL; + i_close_fd(&log->fd); + i_free(log->path); + i_free(log); +} + +static int fts_expunge_log_open(struct fts_expunge_log *log, bool create) +{ + int fd; + + i_assert(log->fd == -1); + + /* FIXME: use proper permissions */ + fd = open(log->path, O_RDWR | O_APPEND | (create ? O_CREAT : 0), 0600); + if (fd == -1) { + if (errno == ENOENT && !create) + return 0; + + i_error("open(%s) failed: %m", log->path); + return -1; + } + if (fstat(fd, &log->st) < 0) { + i_error("fstat(%s) failed: %m", log->path); + i_close_fd(&fd); + return -1; + } + log->fd = fd; + return 1; +} + +static int +fts_expunge_log_reopen_if_needed(struct fts_expunge_log *log, bool create) +{ + struct stat st; + + if (log->fd == -1) + return fts_expunge_log_open(log, create); + + if (stat(log->path, &st) == 0) { + if (st.st_ino == log->st.st_ino && + CMP_DEV_T(st.st_dev, log->st.st_dev)) { + /* same file */ + return 0; + } + /* file changed */ + } else if (errno == ENOENT) { + /* recreate the file */ + } else { + i_error("stat(%s) failed: %m", log->path); + return -1; + } + if (close(log->fd) < 0) + i_error("close(%s) failed: %m", log->path); + log->fd = -1; + return fts_expunge_log_open(log, create); +} + +static int +fts_expunge_log_read_expunge_count(struct fts_expunge_log *log, + uint32_t *expunge_count_r) +{ + ssize_t ret; + + i_assert(log->fd != -1); + + if (fstat(log->fd, &log->st) < 0) { + i_error("fstat(%s) failed: %m", log->path); + return -1; + } + if ((uoff_t)log->st.st_size < sizeof(*expunge_count_r)) { + *expunge_count_r = 0; + return 0; + } + /* we'll assume that write()s atomically grow the file size, as + O_APPEND almost guarantees. even if not, having a race condition + isn't the end of the world. the expunge count is simply read wrong + and fts optimize is performed earlier or later than intended. */ + ret = pread(log->fd, expunge_count_r, sizeof(*expunge_count_r), + log->st.st_size - 4); + if (ret < 0) { + i_error("pread(%s) failed: %m", log->path); + return -1; + } + if (ret != sizeof(*expunge_count_r)) { + i_error("pread(%s) read only %d of %d bytes", log->path, + (int)ret, (int)sizeof(*expunge_count_r)); + return -1; + } + return 0; +} + +struct fts_expunge_log_append_ctx * +fts_expunge_log_append_begin(struct fts_expunge_log *log) +{ + struct fts_expunge_log_append_ctx *ctx; + pool_t pool; + + pool = pool_alloconly_create("fts expunge log append", 1024); + ctx = p_new(pool, struct fts_expunge_log_append_ctx, 1); + ctx->log = log; + ctx->pool = pool; + hash_table_create(&ctx->mailboxes, pool, 0, guid_128_hash, guid_128_cmp); + + if (log != NULL && fts_expunge_log_reopen_if_needed(log, TRUE) < 0) + ctx->failed = TRUE; + return ctx; +} + +static struct fts_expunge_log_mailbox * +fts_expunge_log_mailbox_alloc(struct fts_expunge_log_append_ctx *ctx, + const guid_128_t mailbox_guid) +{ + uint8_t *guid_p; + struct fts_expunge_log_mailbox *mailbox; + + mailbox = p_new(ctx->pool, struct fts_expunge_log_mailbox, 1); + guid_128_copy(mailbox->guid, mailbox_guid); + p_array_init(&mailbox->uids, ctx->pool, 16); + + guid_p = mailbox->guid; + hash_table_insert(ctx->mailboxes, guid_p, mailbox); + return mailbox; +} + +static struct fts_expunge_log_mailbox * +fts_expunge_log_append_mailbox(struct fts_expunge_log_append_ctx *ctx, + const guid_128_t mailbox_guid) +{ + const uint8_t *guid_p = mailbox_guid; + struct fts_expunge_log_mailbox *mailbox; + + if (ctx->prev_mailbox != NULL && + guid_128_equals(mailbox_guid, ctx->prev_mailbox->guid)) + mailbox = ctx->prev_mailbox; + else { + mailbox = hash_table_lookup(ctx->mailboxes, guid_p); + if (mailbox == NULL) + mailbox = fts_expunge_log_mailbox_alloc(ctx, mailbox_guid); + ctx->prev_mailbox = mailbox; + } + return mailbox; +} +void fts_expunge_log_append_next(struct fts_expunge_log_append_ctx *ctx, + const guid_128_t mailbox_guid, + uint32_t uid) +{ + struct fts_expunge_log_mailbox *mailbox; + + mailbox = fts_expunge_log_append_mailbox(ctx, mailbox_guid); + if (!seq_range_array_add(&mailbox->uids, uid)) + mailbox->uids_count++; +} +void fts_expunge_log_append_range(struct fts_expunge_log_append_ctx *ctx, + const guid_128_t mailbox_guid, + const struct seq_range *uids) +{ + struct fts_expunge_log_mailbox *mailbox; + + mailbox = fts_expunge_log_append_mailbox(ctx, mailbox_guid); + mailbox->uids_count += seq_range_array_add_range_count(&mailbox->uids, + uids->seq1, uids->seq2); + /* To be honest, an unbacked log doesn't need to maintain the uids_count, + but we don't know here if we're supporting an unbacked log or not, so we + have to maintain the value, just in case. + At the moment, the only caller of this function is for unbacked logs. */ +} +void fts_expunge_log_append_record(struct fts_expunge_log_append_ctx *ctx, + const struct fts_expunge_log_read_record *record) +{ + const struct seq_range *range; + /* FIXME: Optimise with a merge */ + array_foreach(&record->uids, range) + fts_expunge_log_append_range(ctx, record->mailbox_guid, range); +} +static void fts_expunge_log_append_mailbox_record(struct fts_expunge_log_append_ctx *ctx, + struct fts_expunge_log_mailbox *mailbox) +{ + const struct seq_range *range; + /* FIXME: Optimise with a merge */ + array_foreach(&mailbox->uids, range) + fts_expunge_log_append_range(ctx, mailbox->guid, range); +} + +static void +fts_expunge_log_export(struct fts_expunge_log_append_ctx *ctx, + uint32_t expunge_count, buffer_t *output) +{ + struct hash_iterate_context *iter; + uint8_t *guid_p; + struct fts_expunge_log_mailbox *mailbox; + struct fts_expunge_log_record *rec; + size_t rec_offset; + + iter = hash_table_iterate_init(ctx->mailboxes); + while (hash_table_iterate(iter, ctx->mailboxes, &guid_p, &mailbox)) { + rec_offset = output->used; + rec = buffer_append_space_unsafe(output, sizeof(*rec)); + memcpy(rec->guid, mailbox->guid, sizeof(rec->guid)); + + /* uint32_t expunge_uid_ranges[]; */ + buffer_append(output, array_front(&mailbox->uids), + array_count(&mailbox->uids) * + sizeof(struct seq_range)); + /* uint32_t expunge_count; */ + expunge_count += mailbox->uids_count; + buffer_append(output, &expunge_count, sizeof(expunge_count)); + + /* update the header now that we know the record contents */ + rec = buffer_get_space_unsafe(output, rec_offset, + output->used - rec_offset); + rec->record_size = output->used - rec_offset; + rec->checksum = crc32_data(&rec->record_size, + rec->record_size - + sizeof(rec->checksum)); + } + hash_table_iterate_deinit(&iter); +} + +static int +fts_expunge_log_write(struct fts_expunge_log_append_ctx *ctx) +{ + struct fts_expunge_log *log = ctx->log; + buffer_t *buf; + uint32_t expunge_count, *e; + int ret; + + /* Unbacked expunge logs cannot be written, by definition */ + i_assert(log != NULL); + + /* try to append to the latest file */ + if (fts_expunge_log_reopen_if_needed(log, TRUE) < 0) + return -1; + + if (fts_expunge_log_read_expunge_count(log, &expunge_count) < 0) + return -1; + + buf = buffer_create_dynamic(default_pool, 1024); + fts_expunge_log_export(ctx, expunge_count, buf); + /* the file was opened with O_APPEND, so this write() should be + appended atomically without any need for locking. */ + for (;;) { + if (write_full(log->fd, buf->data, buf->used) < 0) { + i_error("write(%s) failed: %m", log->path); + if (ftruncate(log->fd, log->st.st_size) < 0) + i_error("ftruncate(%s) failed: %m", log->path); + } + if ((ret = fts_expunge_log_reopen_if_needed(log, TRUE)) <= 0) + break; + /* the log was unlinked, so we'll need to write again to + the new file. the expunge_count needs to be reset to zero + from here. */ + e = buffer_get_space_unsafe(buf, buf->used - sizeof(uint32_t), + sizeof(uint32_t)); + i_assert(*e > expunge_count); + *e -= expunge_count; + expunge_count = 0; + } + buffer_free(&buf); + + if (ret == 0) { + /* finish by closing the log. this forces NFS to flush the + changes to disk without our having to explicitly play with + fsync() */ + if (close(log->fd) < 0) { + /* FIXME: we should ftruncate() in case there + were partial writes.. */ + i_error("close(%s) failed: %m", log->path); + ret = -1; + } + log->fd = -1; + } + return ret; +} + +static int fts_expunge_log_append_finalize(struct fts_expunge_log_append_ctx **_ctx, + bool commit) +{ + struct fts_expunge_log_append_ctx *ctx = *_ctx; + int ret = ctx->failed ? -1 : 0; + + *_ctx = NULL; + if (commit && ret == 0) + ret = fts_expunge_log_write(ctx); + + hash_table_destroy(&ctx->mailboxes); + pool_unref(&ctx->pool); + return ret; +} + +int fts_expunge_log_uid_count(struct fts_expunge_log *log, + unsigned int *expunges_r) +{ + int ret; + + if ((ret = fts_expunge_log_reopen_if_needed(log, FALSE)) <= 0) { + *expunges_r = 0; + return ret; + } + + return fts_expunge_log_read_expunge_count(log, expunges_r); +} + +int fts_expunge_log_append_commit(struct fts_expunge_log_append_ctx **_ctx) +{ + return fts_expunge_log_append_finalize(_ctx, TRUE); +} + +int fts_expunge_log_append_abort(struct fts_expunge_log_append_ctx **_ctx) +{ + return fts_expunge_log_append_finalize(_ctx, FALSE); +} + +struct fts_expunge_log_read_ctx * +fts_expunge_log_read_begin(struct fts_expunge_log *log) +{ + struct fts_expunge_log_read_ctx *ctx; + + ctx = i_new(struct fts_expunge_log_read_ctx, 1); + ctx->log = log; + if (fts_expunge_log_reopen_if_needed(log, FALSE) < 0) + ctx->failed = TRUE; + else if (log->fd != -1) + ctx->input = i_stream_create_fd(log->fd, SIZE_MAX); + ctx->unlink = TRUE; + return ctx; +} + +static bool +fts_expunge_log_record_size_is_valid(const struct fts_expunge_log_record *rec, + unsigned int *uids_size_r) +{ + if (rec->record_size < sizeof(*rec) + sizeof(uint32_t)*3) + return FALSE; + *uids_size_r = rec->record_size - sizeof(*rec) - sizeof(uint32_t); + return *uids_size_r % sizeof(uint32_t)*2 == 0; +} + +static void +fts_expunge_log_read_failure(struct fts_expunge_log_read_ctx *ctx, + unsigned int wanted_size) +{ + size_t size; + + if (ctx->input->stream_errno != 0) { + ctx->failed = TRUE; + i_error("read(%s) failed: %s", ctx->log->path, + i_stream_get_error(ctx->input)); + } else { + size = i_stream_get_data_size(ctx->input); + ctx->corrupted = TRUE; + i_error("Corrupted fts expunge log %s: " + "Unexpected EOF (read %zu / %u bytes)", + ctx->log->path, size, wanted_size); + } +} + +const struct fts_expunge_log_read_record * +fts_expunge_log_read_next(struct fts_expunge_log_read_ctx *ctx) +{ + const unsigned char *data; + const struct fts_expunge_log_record *rec; + unsigned int uids_size; + size_t size; + uint32_t checksum; + + if (ctx->input == NULL) + return NULL; + + /* initial read to try to get the record */ + (void)i_stream_read_bytes(ctx->input, &data, &size, IO_BLOCK_SIZE); + if (size == 0 && ctx->input->stream_errno == 0) { + /* expected EOF - mark the file as read by unlinking it */ + if (ctx->unlink) + i_unlink_if_exists(ctx->log->path); + + /* try reading again, in case something new was written */ + i_stream_sync(ctx->input); + (void)i_stream_read_bytes(ctx->input, &data, &size, + IO_BLOCK_SIZE); + } + if (size < sizeof(*rec)) { + if (size == 0 && ctx->input->stream_errno == 0) { + /* expected EOF */ + return NULL; + } + fts_expunge_log_read_failure(ctx, sizeof(*rec)); + return NULL; + } + rec = (const void *)data; + + if (!fts_expunge_log_record_size_is_valid(rec, &uids_size)) { + ctx->corrupted = TRUE; + i_error("Corrupted fts expunge log %s: " + "Invalid record size: %u", + ctx->log->path, rec->record_size); + return NULL; + } + + /* read the entire record */ + while (size < rec->record_size) { + if (i_stream_read_bytes(ctx->input, &data, &size, rec->record_size) < 0) { + fts_expunge_log_read_failure(ctx, rec->record_size); + return NULL; + } + rec = (const void *)data; + } + + /* verify that the record checksum is valid */ + checksum = crc32_data(&rec->record_size, + rec->record_size - sizeof(rec->checksum)); + if (checksum != rec->checksum) { + ctx->corrupted = TRUE; + i_error("Corrupted fts expunge log %s: " + "Record checksum mismatch: %u != %u", + ctx->log->path, checksum, rec->checksum); + return NULL; + } + + memcpy(ctx->read_rec.mailbox_guid, rec->guid, + sizeof(ctx->read_rec.mailbox_guid)); + /* create the UIDs array by pointing it directly into input + stream's buffer */ + buffer_create_from_const_data(&ctx->buffer, rec + 1, uids_size); + array_create_from_buffer(&ctx->read_rec.uids, &ctx->buffer, + sizeof(struct seq_range)); + + i_stream_skip(ctx->input, rec->record_size); + return &ctx->read_rec; +} + +int fts_expunge_log_read_end(struct fts_expunge_log_read_ctx **_ctx) +{ + struct fts_expunge_log_read_ctx *ctx = *_ctx; + int ret = ctx->failed ? -1 : (ctx->corrupted ? 0 : 1); + + *_ctx = NULL; + + if (ctx->corrupted) { + if (ctx->unlink) + i_unlink_if_exists(ctx->log->path); + } + + i_stream_unref(&ctx->input); + i_free(ctx); + return ret; +} + +int fts_expunge_log_flatten(const char *path, + struct fts_expunge_log_append_ctx **flattened_r) +{ + struct fts_expunge_log *read; + struct fts_expunge_log_read_ctx *read_ctx; + const struct fts_expunge_log_read_record *record; + struct fts_expunge_log_append_ctx *append; + int ret; + + i_assert(path != NULL && flattened_r != NULL); + read = fts_expunge_log_init(path); + + read_ctx = fts_expunge_log_read_begin(read); + read_ctx->unlink = FALSE; + + append = fts_expunge_log_append_begin(NULL); + while((record = fts_expunge_log_read_next(read_ctx)) != NULL) { + fts_expunge_log_append_record(append, record); + } + + if ((ret = fts_expunge_log_read_end(&read_ctx)) > 0) + *flattened_r = append; + fts_expunge_log_deinit(&read); + + return ret; +} +bool fts_expunge_log_contains(const struct fts_expunge_log_append_ctx *ctx, + const guid_128_t mailbox_guid, uint32_t uid) +{ + const struct fts_expunge_log_mailbox *mailbox; + const uint8_t *guid_p = mailbox_guid; + + mailbox = hash_table_lookup(ctx->mailboxes, guid_p); + if (mailbox == NULL) + return FALSE; + return seq_range_exists(&mailbox->uids, uid); +} +int fts_expunge_log_append_remove(struct fts_expunge_log_append_ctx *from, + const struct fts_expunge_log_read_record *record) +{ + const uint8_t *guid_p = record->mailbox_guid; + struct fts_expunge_log_mailbox *mailbox = hash_table_lookup(from->mailboxes, guid_p); + if (mailbox == NULL) + return 0; /* may only remove things that exist */ + + mailbox->uids_count -= seq_range_array_remove_seq_range(&mailbox->uids, &record->uids); + return 1; +} +int fts_expunge_log_subtract(struct fts_expunge_log_append_ctx *from, + struct fts_expunge_log *subtract) +{ + unsigned int failures = 0; + struct fts_expunge_log_read_ctx *read_ctx = fts_expunge_log_read_begin(subtract); + read_ctx->unlink = FALSE; + + const struct fts_expunge_log_read_record *record; + while ((record = fts_expunge_log_read_next(read_ctx)) != NULL) { + if (fts_expunge_log_append_remove(from, record) <= 0) + failures++; + } + if (failures > 0) + i_warning("fts: Expunge log subtract ignored %u nonexistent mailbox GUIDs", + failures); + return fts_expunge_log_read_end(&read_ctx); +} +/* It could be argued that somehow adding a log (file) to the append context + and then calling the _write() helper would be easier. But then there's the + _commit() vs. _abort() cleanup that would need to be addressed. Just creating + a copy is simpler. */ +int fts_expunge_log_flat_write(const struct fts_expunge_log_append_ctx *read_log, + const char *path) +{ + int ret; + struct fts_expunge_log *nlog = fts_expunge_log_init(path); + struct fts_expunge_log_append_ctx *nappend = fts_expunge_log_append_begin(nlog); + + struct hash_iterate_context *iter; + uint8_t *guid_p; + struct fts_expunge_log_mailbox *mailbox; + + iter = hash_table_iterate_init(read_log->mailboxes); + while (hash_table_iterate(iter, read_log->mailboxes, &guid_p, &mailbox)) + fts_expunge_log_append_mailbox_record(nappend, mailbox); + + hash_table_iterate_deinit(&iter); + ret = fts_expunge_log_append_commit(&nappend); + fts_expunge_log_deinit(&nlog); + + return ret; +} diff --git a/src/plugins/fts/fts-expunge-log.h b/src/plugins/fts/fts-expunge-log.h new file mode 100644 index 0000000..cc15f29 --- /dev/null +++ b/src/plugins/fts/fts-expunge-log.h @@ -0,0 +1,58 @@ +#ifndef FTS_EXPUNGE_LOG +#define FTS_EXPUNGE_LOG + +#include "seq-range-array.h" +#include "guid.h" + +struct fts_expunge_log_read_record { + guid_128_t mailbox_guid; + ARRAY_TYPE(seq_range) uids; +}; + +struct fts_expunge_log *fts_expunge_log_init(const char *path); +void fts_expunge_log_deinit(struct fts_expunge_log **log); + +struct fts_expunge_log_append_ctx * +fts_expunge_log_append_begin(struct fts_expunge_log *log); +void fts_expunge_log_append_next(struct fts_expunge_log_append_ctx *ctx, + const guid_128_t mailbox_guid, + uint32_t uid); +void fts_expunge_log_append_range(struct fts_expunge_log_append_ctx *ctx, + const guid_128_t mailbox_guid, + const struct seq_range *uids); +void fts_expunge_log_append_record(struct fts_expunge_log_append_ctx *ctx, + const struct fts_expunge_log_read_record *record); +/* In-memory flattened structures may have records removed from them, + file-backed ones may not. Non-existence of UIDs is not an error, + non-existence of mailbox GUID causes an error return of 0. */ +int fts_expunge_log_append_remove(struct fts_expunge_log_append_ctx *ctx, + const struct fts_expunge_log_read_record *record); +int fts_expunge_log_append_commit(struct fts_expunge_log_append_ctx **ctx); +/* Do not commit non-backed structures, abort them after use. */ +int fts_expunge_log_append_abort(struct fts_expunge_log_append_ctx **ctx); + +int fts_expunge_log_uid_count(struct fts_expunge_log *log, + unsigned int *expunges_r); + +struct fts_expunge_log_read_ctx * +fts_expunge_log_read_begin(struct fts_expunge_log *log); +const struct fts_expunge_log_read_record * +fts_expunge_log_read_next(struct fts_expunge_log_read_ctx *ctx); +/* Returns 1 if all ok, 0 if there was corruption, -1 if I/O error. + If end() is called before reading all records, the log isn't unlinked. */ +int fts_expunge_log_read_end(struct fts_expunge_log_read_ctx **ctx); + +/* Read an entire log file, and flatten it into one hash of arrays. + The struct it returns cannot be written, as it has no backing store */ +int fts_expunge_log_flatten(const char *path, + struct fts_expunge_log_append_ctx **flattened_r); +bool fts_expunge_log_contains(const struct fts_expunge_log_append_ctx *ctx, + const guid_128_t mailbox_guid, uint32_t uid); +/* Modify in-place a flattened log. If non-existent mailbox GUIDs are + encountered, a warning will be logged. */ +int fts_expunge_log_subtract(struct fts_expunge_log_append_ctx *from, + struct fts_expunge_log *subtract); +/* Write a modified flattened log as a new file. */ +int fts_expunge_log_flat_write(const struct fts_expunge_log_append_ctx *flattened, + const char *path); +#endif diff --git a/src/plugins/fts/fts-indexer.c b/src/plugins/fts/fts-indexer.c new file mode 100644 index 0000000..aca23c9 --- /dev/null +++ b/src/plugins/fts/fts-indexer.c @@ -0,0 +1,300 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "connection.h" +#include "write-full.h" +#include "istream.h" +#include "ostream.h" +#include "strescape.h" +#include "time-util.h" +#include "settings-parser.h" +#include "mail-user.h" +#include "mail-storage-private.h" +#include "fts-api.h" +#include "fts-indexer.h" + +#define INDEXER_NOTIFY_INTERVAL_SECS 10 +#define INDEXER_SOCKET_NAME "indexer" +#define INDEXER_WAIT_MSECS 250 + +struct fts_indexer_context { + struct connection conn; + + struct mailbox *box; + struct ioloop *ioloop; + + struct timeval search_start_time, last_notify; + unsigned int percentage; + struct connection_list *connection_list; + + bool notified:1; + bool failed:1; + bool completed:1; +}; + +static void fts_indexer_notify(struct fts_indexer_context *ctx) +{ + unsigned long long elapsed_msecs, est_total_msecs; + unsigned int eta_secs; + + if (ioloop_time - ctx->last_notify.tv_sec < INDEXER_NOTIFY_INTERVAL_SECS) + return; + ctx->last_notify = ioloop_timeval; + + if (ctx->box->storage->callbacks.notify_ok == NULL || + ctx->percentage == 0) + return; + + elapsed_msecs = timeval_diff_msecs(&ioloop_timeval, + &ctx->search_start_time); + est_total_msecs = elapsed_msecs * 100 / ctx->percentage; + eta_secs = (est_total_msecs - elapsed_msecs) / 1000; + + T_BEGIN { + const char *text; + + text = t_strdup_printf("Indexed %d%% of the mailbox, " + "ETA %d:%02d", ctx->percentage, + eta_secs/60, eta_secs%60); + ctx->box->storage->callbacks. + notify_ok(ctx->box, text, + ctx->box->storage->callback_context); + ctx->notified = TRUE; + } T_END; +} + +static int fts_indexer_more_int(struct fts_indexer_context *ctx) +{ + struct ioloop *prev_ioloop = current_ioloop; + struct timeout *to; + + if (ctx->failed) + return -1; + if (ctx->completed) + return 1; + + /* wait for a while for the reply. FIXME: once search API supports + asynchronous waits, get rid of this wait and use the mail IO loop */ + io_loop_set_current(ctx->ioloop); + to = timeout_add_short(INDEXER_WAIT_MSECS, io_loop_stop, ctx->ioloop); + io_loop_run(ctx->ioloop); + timeout_remove(&to); + io_loop_set_current(prev_ioloop); + + if (ctx->failed) + return -1; + if (ctx->completed) + return 1; + return 0; +} + +int fts_indexer_more(struct fts_indexer_context *ctx) +{ + int ret; + + if ((ret = fts_indexer_more_int(ctx)) < 0) { + /* If failed is already set, the code has had a chance to + * set an internal error already, i.e. MAIL_ERROR_INUSE. */ + if (!ctx->failed) + mail_storage_set_internal_error(ctx->box->storage); + ctx->failed = TRUE; + return -1; + } + + if (ret == 0) + fts_indexer_notify(ctx); + + return ret; +} + +static void fts_indexer_destroy(struct connection *conn) +{ + struct fts_indexer_context *ctx = + container_of(conn, struct fts_indexer_context, conn); + connection_deinit(conn); + if (!ctx->completed) + ctx->failed = TRUE; + ctx->completed = TRUE; +} + +int fts_indexer_deinit(struct fts_indexer_context **_ctx) +{ + struct fts_indexer_context *ctx = *_ctx; + i_assert(ctx != NULL); + *_ctx = NULL; + if (!ctx->completed) + ctx->failed = TRUE; + int ret = ctx->failed ? -1 : 0; + if (ctx->notified) { + /* we notified at least once */ + ctx->box->storage->callbacks. + notify_ok(ctx->box, "Mailbox indexing finished", + ctx->box->storage->callback_context); + } + connection_list_deinit(&ctx->connection_list); + io_loop_set_current(ctx->ioloop); + io_loop_destroy(&ctx->ioloop); + i_free(ctx); + return ret; +} + +static int +fts_indexer_input_args(struct connection *conn, const char *const *args) +{ + struct fts_indexer_context *ctx = + container_of(conn, struct fts_indexer_context, conn); + int percentage; + if (args[1] == NULL) { + e_error(conn->event, "indexer sent invalid reply"); + return -1; + } + if (strcmp(args[0], "1") != 0) { + e_error(conn->event, "indexer sent invalid reply"); + return -1; + } + if (strcmp(args[1], "OK") == 0) + return 1; + if (str_to_int(args[1], &percentage) < 0) { + e_error(conn->event, "indexer sent invalid progress: %s", args[1]); + ctx->failed = TRUE; + return -1; + } + if (percentage < 0) { + e_error(ctx->box->event, "indexer failed to index mailbox"); + ctx->failed = TRUE; + return -1; + } + ctx->percentage = percentage; + if (ctx->percentage == 100) + ctx->completed = TRUE; + return 1; +} + +static void fts_indexer_client_connected(struct connection *conn, bool success) +{ + struct fts_indexer_context *ctx = + container_of(conn, struct fts_indexer_context, conn); + if (!success) { + ctx->completed = TRUE; + ctx->failed = TRUE; + return; + } + ctx->failed = ctx->completed = FALSE; + const char *cmd = t_strdup_printf("PREPEND\t1\t%s\t%s\t0\t%s\n", + str_tabescape(ctx->box->storage->user->username), + str_tabescape(ctx->box->vname), + str_tabescape(ctx->box->storage->user->session_id)); + o_stream_nsend_str(conn->output, cmd); +} + +static void fts_indexer_idle_timeout(struct connection *conn) +{ + struct fts_indexer_context *ctx = + container_of(conn, struct fts_indexer_context, conn); + mail_storage_set_error(ctx->box->storage, MAIL_ERROR_INUSE, + "Timeout while waiting for indexing to finish"); + ctx->failed = TRUE; + connection_disconnect(conn); +} + +static const struct connection_settings indexer_client_set = +{ + .service_name_in = "indexer", + .service_name_out = "indexer", + .major_version = 1, + .minor_version = 0, + .client_connect_timeout_msecs = 2000, + .input_max_size = SIZE_MAX, + .output_max_size = IO_BLOCK_SIZE, + .client = TRUE, +}; + +static const struct connection_vfuncs indexer_client_vfuncs = +{ + .destroy = fts_indexer_destroy, + .client_connected = fts_indexer_client_connected, + .input_args = fts_indexer_input_args, + .idle_timeout = fts_indexer_idle_timeout, +}; + +int fts_indexer_init(struct fts_backend *backend, struct mailbox *box, + struct fts_indexer_context **ctx_r) +{ + struct ioloop *prev_ioloop = current_ioloop; + struct fts_indexer_context *ctx; + struct mailbox_status status; + uint32_t last_uid, seq1, seq2; + const char *path, *value, *error; + unsigned int timeout_secs = 0; + int ret; + + value = mail_user_plugin_getenv(box->storage->user, "fts_index_timeout"); + if (value != NULL) { + if (settings_get_time(value, &timeout_secs, &error) < 0) { + e_error(box->storage->user->event, + "Invalid fts_index_timeout setting: %s", + error); + return -1; + } + } + + if (fts_backend_get_last_uid(backend, box, &last_uid) < 0) + return -1; + + mailbox_get_open_status(box, STATUS_UIDNEXT, &status); + if (status.uidnext == last_uid+1) { + /* everything is already indexed */ + return 0; + } + + mailbox_get_seq_range(box, last_uid+1, (uint32_t)-1, &seq1, &seq2); + if (seq1 == 0) { + /* no new messages (last messages in mailbox were expunged) */ + return 0; + } + + path = t_strconcat(box->storage->user->set->base_dir, + "/"INDEXER_SOCKET_NAME, NULL); + + ctx = i_new(struct fts_indexer_context, 1); + ctx->box = box; + ctx->search_start_time = ioloop_timeval; + ctx->conn.event_parent = box->event; + ctx->ioloop = io_loop_create(); + ctx->connection_list = connection_list_init(&indexer_client_set, + &indexer_client_vfuncs); + ctx->conn.input_idle_timeout_secs = timeout_secs; + connection_init_client_unix(ctx->connection_list, &ctx->conn, + path); + ret = connection_client_connect(&ctx->conn); + io_loop_set_current(prev_ioloop); + *ctx_r = ctx; + return ctx->failed || ret < 0 ? -1 : 1; +} + +#define INDEXER_HANDSHAKE "1\t0\tindexer\tindexer\n" + +int fts_indexer_cmd(struct mail_user *user, const char *cmd, + const char **path_r) +{ + const char *path; + int fd; + + path = t_strconcat(user->set->base_dir, + "/"INDEXER_SOCKET_NAME, NULL); + fd = net_connect_unix_with_retries(path, 1000); + if (fd == -1) { + i_error("net_connect_unix(%s) failed: %m", path); + return -1; + } + + cmd = t_strconcat(INDEXER_HANDSHAKE, cmd, NULL); + if (write_full(fd, cmd, strlen(cmd)) < 0) { + i_error("write(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + *path_r = path; + return fd; +} diff --git a/src/plugins/fts/fts-indexer.h b/src/plugins/fts/fts-indexer.h new file mode 100644 index 0000000..7ccbc7e --- /dev/null +++ b/src/plugins/fts/fts-indexer.h @@ -0,0 +1,22 @@ +#ifndef FTS_BUILD_H +#define FTS_BUILD_H + +struct fts_backend; +struct fts_indexer_context; + +/* Initialize indexing the given mailbox via indexer service. Returns 1 if + indexing started, 0 if there was no need to index or -1 if error. */ +int fts_indexer_init(struct fts_backend *backend, struct mailbox *box, + struct fts_indexer_context **ctx_r); +/* Returns 0 if ok, -1 if error. */ +int fts_indexer_deinit(struct fts_indexer_context **ctx); + +/* Build more. Returns 1 if finished, 0 if this function needs to be called + again, -1 if error. */ +int fts_indexer_more(struct fts_indexer_context *ctx); + +/* Returns fd, which you can either read from or close. */ +int fts_indexer_cmd(struct mail_user *user, const char *cmd, + const char **path_r); + +#endif diff --git a/src/plugins/fts/fts-parser-html.c b/src/plugins/fts/fts-parser-html.c new file mode 100644 index 0000000..aa2078d --- /dev/null +++ b/src/plugins/fts/fts-parser-html.c @@ -0,0 +1,64 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "message-parser.h" +#include "mail-html2text.h" +#include "fts-parser.h" + +struct html_fts_parser { + struct fts_parser parser; + struct mail_html2text *html2text; + buffer_t *output; +}; + +static struct fts_parser * +fts_parser_html_try_init(struct fts_parser_context *parser_context) +{ + struct html_fts_parser *parser; + + if (!mail_html2text_content_type_match(parser_context->content_type)) + return NULL; + + parser = i_new(struct html_fts_parser, 1); + parser->parser.v = fts_parser_html; + parser->html2text = mail_html2text_init(0); + parser->output = buffer_create_dynamic(default_pool, 4096); + return &parser->parser; +} + +static void fts_parser_html_more(struct fts_parser *_parser, + struct message_block *block) +{ + struct html_fts_parser *parser = (struct html_fts_parser *)_parser; + + if (block->size == 0) { + /* finished */ + return; + } + + buffer_set_used_size(parser->output, 0); + mail_html2text_more(parser->html2text, block->data, block->size, + parser->output); + + block->data = parser->output->data; + block->size = parser->output->used; +} + +static int fts_parser_html_deinit(struct fts_parser *_parser, + const char **retriable_err_msg_r ATTR_UNUSED) +{ + struct html_fts_parser *parser = (struct html_fts_parser *)_parser; + + mail_html2text_deinit(&parser->html2text); + buffer_free(&parser->output); + i_free(parser); + return 1; +} + +struct fts_parser_vfuncs fts_parser_html = { + fts_parser_html_try_init, + fts_parser_html_more, + fts_parser_html_deinit, + NULL +}; diff --git a/src/plugins/fts/fts-parser-script.c b/src/plugins/fts/fts-parser-script.c new file mode 100644 index 0000000..eefbe07 --- /dev/null +++ b/src/plugins/fts/fts-parser-script.c @@ -0,0 +1,277 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "net.h" +#include "istream.h" +#include "write-full.h" +#include "module-context.h" +#include "rfc822-parser.h" +#include "rfc2231-parser.h" +#include "message-parser.h" +#include "mail-user.h" +#include "fts-parser.h" + +#define SCRIPT_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, fts_parser_script_user_module) + +#define SCRIPT_HANDSHAKE "VERSION\tscript\t4\t0\nalarm=10\nnoreply\n" + +struct content { + const char *content_type; + const char *const *extensions; +}; + +struct fts_parser_script_user { + union mail_user_module_context module_ctx; + + ARRAY(struct content) content; +}; + +struct script_fts_parser { + struct fts_parser parser; + + int fd; + char *path; + + unsigned char outbuf[IO_BLOCK_SIZE]; + bool failed; + bool shutdown; +}; + +static MODULE_CONTEXT_DEFINE_INIT(fts_parser_script_user_module, + &mail_user_module_register); + +static int script_connect(struct mail_user *user, const char **path_r) +{ + const char *path; + int fd; + + path = mail_user_plugin_getenv(user, "fts_decoder"); + if (path == NULL) + return -1; + + if (*path != '/') + path = t_strconcat(user->set->base_dir, "/", path, NULL); + fd = net_connect_unix_with_retries(path, 1000); + if (fd == -1) + i_error("net_connect_unix(%s) failed: %m", path); + else + net_set_nonblock(fd, FALSE); + *path_r = path; + return fd; +} + +static int script_contents_read(struct mail_user *user) +{ + struct fts_parser_script_user *suser = SCRIPT_USER_CONTEXT(user); + const char *path, *cmd, *line; + char **args; + struct istream *input; + struct content *content; + bool eof_seen = FALSE; + int fd, ret = 0; + i_assert(suser != NULL); + + fd = script_connect(user, &path); + if (fd == -1) + return -1; + + cmd = t_strdup_printf(SCRIPT_HANDSHAKE"\n"); + if (write_full(fd, cmd, strlen(cmd)) < 0) { + i_error("write(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + input = i_stream_create_fd_autoclose(&fd, 1024); + while ((line = i_stream_read_next_line(input)) != NULL) { + /* <content-type> <extension> [<extension> ...] */ + args = p_strsplit_spaces(user->pool, line, " "); + if (args[0] == NULL) { + eof_seen = TRUE; + break; + } + if (args[0][0] == '\0' || args[1] == NULL) { + i_error("parser script sent invalid input: %s", line); + continue; + } + + content = array_append_space(&suser->content); + content->content_type = str_lcase(args[0]); + content->extensions = (const void *)(args+1); + } + if (input->stream_errno != 0) { + i_error("parser script read(%s) failed: %s", path, + i_stream_get_error(input)); + ret = -1; + } else if (!eof_seen) { + if (input->v_offset == 0) + i_error("parser script didn't send any data"); + else + i_error("parser script didn't send empty EOF line"); + } + i_stream_destroy(&input); + return ret; +} + +static bool script_support_content(struct mail_user *user, + const char **content_type, + const char *filename) +{ + struct fts_parser_script_user *suser = SCRIPT_USER_CONTEXT(user); + const struct content *content; + const char *extension; + + if (suser == NULL) { + suser = p_new(user->pool, struct fts_parser_script_user, 1); + p_array_init(&suser->content, user->pool, 32); + MODULE_CONTEXT_SET(user, fts_parser_script_user_module, suser); + } + if (array_count(&suser->content) == 0) { + if (script_contents_read(user) < 0) + return FALSE; + } + + if (strcmp(*content_type, "application/octet-stream") == 0) { + if (filename == NULL) + return FALSE; + extension = strrchr(filename, '.'); + if (extension == NULL) + return FALSE; + extension = extension + 1; + + array_foreach(&suser->content, content) { + if (content->extensions != NULL && + str_array_icase_find(content->extensions, extension)) { + *content_type = content->content_type; + return TRUE; + } + } + } else { + array_foreach(&suser->content, content) { + if (strcmp(content->content_type, *content_type) == 0) + return TRUE; + } + } + return FALSE; +} + +static void parse_content_disposition(const char *content_disposition, + const char **filename_r) +{ + struct rfc822_parser_context parser; + const char *const *results, *filename2; + string_t *str; + + *filename_r = NULL; + + if (content_disposition == NULL) + return; + + rfc822_parser_init(&parser, (const unsigned char *)content_disposition, + strlen(content_disposition), NULL); + rfc822_skip_lwsp(&parser); + + /* type; param; param; .. */ + str = t_str_new(32); + if (rfc822_parse_mime_token(&parser, str) < 0) { + rfc822_parser_deinit(&parser); + return; + } + + rfc2231_parse(&parser, &results); + filename2 = NULL; + for (; *results != NULL; results += 2) { + if (strcasecmp(results[0], "filename") == 0) { + *filename_r = results[1]; + break; + } + if (strcasecmp(results[0], "filename*") == 0) + filename2 = results[1]; + } + if (*filename_r == NULL) { + /* RFC 2231 style non-ascii filename. we don't really care + much about the filename actually, just about its extension */ + *filename_r = filename2; + } + rfc822_parser_deinit(&parser); +} + +static struct fts_parser * +fts_parser_script_try_init(struct fts_parser_context *parser_context) +{ + struct script_fts_parser *parser; + const char *filename, *path, *cmd; + int fd; + + parse_content_disposition(parser_context->content_disposition, &filename); + if (!script_support_content(parser_context->user, &parser_context->content_type, filename)) + return NULL; + + fd = script_connect(parser_context->user, &path); + if (fd == -1) + return NULL; + cmd = t_strdup_printf(SCRIPT_HANDSHAKE"%s\n\n", parser_context->content_type); + if (write_full(fd, cmd, strlen(cmd)) < 0) { + i_error("write(%s) failed: %m", path); + i_close_fd(&fd); + return NULL; + } + + parser = i_new(struct script_fts_parser, 1); + parser->parser.v = fts_parser_script; + parser->path = i_strdup(path); + parser->fd = fd; + return &parser->parser; +} + +static void fts_parser_script_more(struct fts_parser *_parser, + struct message_block *block) +{ + struct script_fts_parser *parser = (struct script_fts_parser *)_parser; + ssize_t ret; + + if (block->size > 0) { + /* first we'll send everything to the script */ + if (!parser->failed && + write_full(parser->fd, block->data, block->size) < 0) { + i_error("write(%s) failed: %m", parser->path); + parser->failed = TRUE; + } + block->size = 0; + } else { + if (!parser->shutdown) { + if (shutdown(parser->fd, SHUT_WR) < 0) + i_error("shutdown(%s) failed: %m", parser->path); + parser->shutdown = TRUE; + } + /* read the result from the script */ + ret = read(parser->fd, parser->outbuf, sizeof(parser->outbuf)); + if (ret < 0) + i_error("read(%s) failed: %m", parser->path); + else { + block->data = parser->outbuf; + block->size = ret; + } + } +} + +static int fts_parser_script_deinit(struct fts_parser *_parser, + const char **retriable_err_msg_r ATTR_UNUSED) +{ + struct script_fts_parser *parser = (struct script_fts_parser *)_parser; + int ret = parser->failed ? -1 : 1; + + if (close(parser->fd) < 0) + i_error("close(%s) failed: %m", parser->path); + i_free(parser->path); + i_free(parser); + return ret; +} + +struct fts_parser_vfuncs fts_parser_script = { + fts_parser_script_try_init, + fts_parser_script_more, + fts_parser_script_deinit, + NULL +}; diff --git a/src/plugins/fts/fts-parser-tika.c b/src/plugins/fts/fts-parser-tika.c new file mode 100644 index 0000000..bb6379c --- /dev/null +++ b/src/plugins/fts/fts-parser-tika.c @@ -0,0 +1,278 @@ +/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "istream.h" +#include "module-context.h" +#include "iostream-ssl.h" +#include "http-url.h" +#include "http-client.h" +#include "message-parser.h" +#include "mail-user.h" +#include "fts-parser.h" + +#define TIKA_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, fts_parser_tika_user_module) + +struct fts_parser_tika_user { + union mail_user_module_context module_ctx; + struct http_url *http_url; +}; + +struct tika_fts_parser { + struct fts_parser parser; + struct mail_user *user; + struct http_client_request *http_req; + + struct ioloop *ioloop; + struct io *io; + struct istream *payload; + + bool failed; +}; + +static struct http_client *tika_http_client = NULL; +static MODULE_CONTEXT_DEFINE_INIT(fts_parser_tika_user_module, + &mail_user_module_register); + +static int +tika_get_http_client_url(struct mail_user *user, struct http_url **http_url_r) +{ + struct fts_parser_tika_user *tuser = TIKA_USER_CONTEXT(user); + struct http_client_settings http_set; + struct ssl_iostream_settings ssl_set; + const char *url, *error; + + url = mail_user_plugin_getenv(user, "fts_tika"); + if (url == NULL) { + /* fts_tika disabled */ + return -1; + } + + if (tuser != NULL) { + *http_url_r = tuser->http_url; + return *http_url_r == NULL ? -1 : 0; + } + + tuser = p_new(user->pool, struct fts_parser_tika_user, 1); + MODULE_CONTEXT_SET(user, fts_parser_tika_user_module, tuser); + + if (http_url_parse(url, NULL, 0, user->pool, + &tuser->http_url, &error) < 0) { + i_error("fts_tika: Failed to parse HTTP url %s: %s", url, error); + return -1; + } + + if (tika_http_client == NULL) { + mail_user_init_ssl_client_settings(user, &ssl_set); + + i_zero(&http_set); + http_set.max_idle_time_msecs = 100; + http_set.max_parallel_connections = 1; + http_set.max_pipelined_requests = 1; + http_set.max_redirects = 1; + http_set.max_attempts = 3; + http_set.connect_timeout_msecs = 5*1000; + http_set.request_timeout_msecs = 60*1000; + http_set.ssl = &ssl_set; + http_set.debug = user->mail_debug; + http_set.event_parent = user->event; + + /* FIXME: We should initialize a shared client instead. However, + this is currently not possible due to an obscure bug + in the blocking HTTP payload API, which causes + conflicts with other HTTP applications like FTS Solr. + Using a private client will provide a quick fix for + now. */ + tika_http_client = http_client_init_private(&http_set); + } + *http_url_r = tuser->http_url; + return 0; +} + +static void +fts_tika_parser_response(const struct http_response *response, + struct tika_fts_parser *parser) +{ + i_assert(parser->payload == NULL); + + switch (response->status) { + case 200: + /* read response */ + if (response->payload == NULL) + parser->payload = i_stream_create_from_data("", 0); + else { + i_stream_ref(response->payload); + parser->payload = response->payload; + } + break; + case 204: /* empty response */ + case 415: /* Unsupported Media Type */ + case 422: /* Unprocessable Entity */ + e_debug(parser->user->event, "fts_tika: PUT %s failed: %s", + mail_user_plugin_getenv(parser->user, "fts_tika"), + http_response_get_message(response)); + parser->payload = i_stream_create_from_data("", 0); + break; + default: + if (response->status / 100 == 5) { + /* Server Error - the problem could be anything (in Tika or + HTTP server or proxy) and might be retriable, but Tika has + trouble processing some documents and throws up this error + every time for those documents. */ + parser->parser.may_need_retry = TRUE; + i_free(parser->parser.retriable_error_msg); + parser->parser.retriable_error_msg = + i_strdup_printf("fts_tika: PUT %s failed: %s", + mail_user_plugin_getenv(parser->user, "fts_tika"), + http_response_get_message(response)); + parser->payload = i_stream_create_from_data("", 0); + } else { + i_error("fts_tika: PUT %s failed: %s", + mail_user_plugin_getenv(parser->user, "fts_tika"), + http_response_get_message(response)); + parser->failed = TRUE; + } + break; + } + parser->http_req = NULL; + io_loop_stop(current_ioloop); +} + +static struct fts_parser * +fts_parser_tika_try_init(struct fts_parser_context *parser_context) +{ + struct tika_fts_parser *parser; + struct http_url *http_url; + struct http_client_request *http_req; + + if (tika_get_http_client_url(parser_context->user, &http_url) < 0) + return NULL; + if (http_url->path == NULL) + http_url->path = "/"; + + parser = i_new(struct tika_fts_parser, 1); + parser->parser.v = fts_parser_tika; + parser->user = parser_context->user; + + http_req = http_client_request(tika_http_client, "PUT", + http_url->host.name, + t_strconcat(http_url->path, http_url->enc_query, NULL), + fts_tika_parser_response, parser); + http_client_request_set_port(http_req, http_url->port); + http_client_request_set_ssl(http_req, http_url->have_ssl); + if (parser_context->content_type != NULL) + http_client_request_add_header(http_req, "Content-Type", + parser_context->content_type); + if (parser_context->content_disposition != NULL) + http_client_request_add_header(http_req, "Content-Disposition", + parser_context->content_disposition); + http_client_request_add_header(http_req, "Accept", "text/plain"); + + parser->http_req = http_req; + return &parser->parser; +} + +static void fts_parser_tika_more(struct fts_parser *_parser, + struct message_block *block) +{ + struct tika_fts_parser *parser = (struct tika_fts_parser *)_parser; + struct ioloop *prev_ioloop = current_ioloop; + const unsigned char *data; + size_t size; + ssize_t ret; + + if (block->size > 0) { + /* first we'll send everything to Tika */ + if (!parser->failed && + http_client_request_send_payload(&parser->http_req, + block->data, + block->size) < 0) + parser->failed = TRUE; + block->size = 0; + return; + } + + if (parser->payload == NULL) { + /* read the result from Tika */ + if (!parser->failed && + http_client_request_finish_payload(&parser->http_req) < 0) + parser->failed = TRUE; + if (!parser->failed && parser->payload == NULL) + http_client_wait(tika_http_client); + if (parser->failed) + return; + i_assert(parser->payload != NULL); + } + /* continue returning data from Tika. we'll create a new ioloop just + for reading this one payload. */ + while ((ret = i_stream_read_more(parser->payload, &data, &size)) == 0) { + if (parser->failed) + break; + /* wait for more input from Tika */ + if (parser->ioloop == NULL) { + parser->ioloop = io_loop_create(); + parser->io = io_add_istream(parser->payload, io_loop_stop, + current_ioloop); + } else { + io_loop_set_current(parser->ioloop); + } + io_loop_run(current_ioloop); + } + /* switch back to original ioloop. */ + io_loop_set_current(prev_ioloop); + + if (parser->failed) + ; + else if (size > 0) { + i_assert(ret > 0); + block->data = data; + block->size = size; + i_stream_skip(parser->payload, size); + } else { + /* finished */ + i_assert(ret == -1); + if (parser->payload->stream_errno != 0) { + i_error("read(%s) failed: %s", + i_stream_get_name(parser->payload), + i_stream_get_error(parser->payload)); + parser->failed = TRUE; + } + } +} + +static int fts_parser_tika_deinit(struct fts_parser *_parser, const char **retriable_err_msg_r) +{ + struct tika_fts_parser *parser = (struct tika_fts_parser *)_parser; + int ret = _parser->may_need_retry ? 0: (parser->failed ? -1 : 1); + + i_assert(ret != 0 || _parser->retriable_error_msg != NULL); + if (retriable_err_msg_r != NULL) + *retriable_err_msg_r = t_strdup(_parser->retriable_error_msg); + i_free(_parser->retriable_error_msg); + + /* remove io before unrefing payload - otherwise lib-http adds another + timeout to ioloop unnecessarily */ + i_stream_unref(&parser->payload); + io_remove(&parser->io); + http_client_request_abort(&parser->http_req); + if (parser->ioloop != NULL) { + io_loop_set_current(parser->ioloop); + io_loop_destroy(&parser->ioloop); + } + i_free(parser); + return ret; +} + +static void fts_parser_tika_unload(void) +{ + if (tika_http_client != NULL) + http_client_deinit(&tika_http_client); +} + +struct fts_parser_vfuncs fts_parser_tika = { + fts_parser_tika_try_init, + fts_parser_tika_more, + fts_parser_tika_deinit, + fts_parser_tika_unload +}; diff --git a/src/plugins/fts/fts-parser.c b/src/plugins/fts/fts-parser.c new file mode 100644 index 0000000..c0eac80 --- /dev/null +++ b/src/plugins/fts/fts-parser.c @@ -0,0 +1,127 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "unichar.h" +#include "message-parser.h" +#include "fts-parser.h" + +static const struct fts_parser_vfuncs *parsers[] = { + &fts_parser_html, + &fts_parser_script, + &fts_parser_tika +}; + +static const char *plaintext_content_types[] = { + "text/plain", + "message/delivery-status", + "message/disposition-notification", + "application/pgp-signature", + NULL +}; + +bool fts_parser_init(struct fts_parser_context *parser_context, + struct fts_parser **parser_r) +{ + unsigned int i; + i_assert(parser_context->user != NULL); + i_assert(parser_context->content_type != NULL); + + if (str_array_find(plaintext_content_types, parser_context->content_type)) { + /* we probably don't want/need to allow parsers to handle + plaintext? */ + return FALSE; + } + + for (i = 0; i < N_ELEMENTS(parsers); i++) { + *parser_r = parsers[i]->try_init(parser_context); + if (*parser_r != NULL) + return TRUE; + } + return FALSE; +} + +struct fts_parser *fts_parser_text_init(void) +{ + return i_new(struct fts_parser, 1); +} + +static bool data_has_nuls(const unsigned char *data, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + if (data[i] == '\0') + return TRUE; + } + return FALSE; +} + +static void replace_nul_bytes(buffer_t *buf) +{ + unsigned char *data; + size_t i, size; + + data = buffer_get_modifiable_data(buf, &size); + for (i = 0; i < size; i++) { + if (data[i] == '\0') + data[i] = ' '; + } +} + +void fts_parser_more(struct fts_parser *parser, struct message_block *block) +{ + if (parser->v.more != NULL) + parser->v.more(parser, block); + + if (!uni_utf8_data_is_valid(block->data, block->size) || + data_has_nuls(block->data, block->size)) { + /* output isn't valid UTF-8. make it. */ + if (parser->utf8_output == NULL) { + parser->utf8_output = + buffer_create_dynamic(default_pool, 4096); + } else { + buffer_set_used_size(parser->utf8_output, 0); + } + if (uni_utf8_get_valid_data(block->data, block->size, + parser->utf8_output)) { + /* valid UTF-8, but there were NULs */ + buffer_append(parser->utf8_output, block->data, + block->size); + } + replace_nul_bytes(parser->utf8_output); + block->data = parser->utf8_output->data; + block->size = parser->utf8_output->used; + } +} + +int fts_parser_deinit(struct fts_parser **_parser, const char **retriable_err_msg_r) +{ + struct fts_parser *parser = *_parser; + int ret = 1; + + *_parser = NULL; + + buffer_free(&parser->utf8_output); + if (parser->v.deinit != NULL) { + const char *error = NULL; + ret = parser->v.deinit(parser, &error); + if (ret == 0) { + i_assert(error != NULL); + if (retriable_err_msg_r != NULL) + *retriable_err_msg_r = error; + } + } else + i_free(parser); + return ret; +} + +void fts_parsers_unload(void) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(parsers); i++) { + if (parsers[i]->unload != NULL) + parsers[i]->unload(); + } +} diff --git a/src/plugins/fts/fts-parser.h b/src/plugins/fts/fts-parser.h new file mode 100644 index 0000000..0eb716e --- /dev/null +++ b/src/plugins/fts/fts-parser.h @@ -0,0 +1,48 @@ +#ifndef FTS_PARSER_H +#define FTS_PARSER_H + +struct message_block; +struct mail_user; + +struct fts_parser_context { + /* Can't be NULL */ + struct mail_user *user; + /* Can't be NULL */ + const char *content_type; + const char *content_disposition; +}; + +struct fts_parser_vfuncs { + struct fts_parser *(*try_init)(struct fts_parser_context *parser_context); + void (*more)(struct fts_parser *parser, struct message_block *block); + int (*deinit)(struct fts_parser *parser, const char **retriable_err_msg_r); + void (*unload)(void); +}; + +struct fts_parser { + struct fts_parser_vfuncs v; + buffer_t *utf8_output; + bool may_need_retry; + char *retriable_error_msg; +}; + +extern struct fts_parser_vfuncs fts_parser_html; +extern struct fts_parser_vfuncs fts_parser_script; +extern struct fts_parser_vfuncs fts_parser_tika; + +bool fts_parser_init(struct fts_parser_context *parser_context, + struct fts_parser **parser_r); +struct fts_parser *fts_parser_text_init(void); + +/* The parser is initially called with message body blocks. Once message is + finished, it's still called with incoming size=0 while the parser increases + it to non-zero. */ +void fts_parser_more(struct fts_parser *parser, struct message_block *block); +/* Returns 1 if ok, 0 if the parsing should be retried, -1 if error. + If 0 is returned, the retriable_err_msg_r is set, which should be logged + as error if no retrying is performed. */ +int fts_parser_deinit(struct fts_parser **parser, const char **retriable_err_msg_r); + +void fts_parsers_unload(void); + +#endif diff --git a/src/plugins/fts/fts-plugin.c b/src/plugins/fts/fts-plugin.c new file mode 100644 index 0000000..1902cb6 --- /dev/null +++ b/src/plugins/fts/fts-plugin.c @@ -0,0 +1,33 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage-hooks.h" +#include "fts-filter.h" +#include "fts-tokenizer.h" +#include "fts-parser.h" +#include "fts-storage.h" +#include "fts-user.h" +#include "fts-plugin.h" +#include "fts-library.h" + +const char *fts_plugin_version = DOVECOT_ABI_VERSION; + +static struct mail_storage_hooks fts_mail_storage_hooks = { + .mail_namespaces_added = fts_mail_namespaces_added, + .mailbox_list_created = fts_mailbox_list_created, + .mailbox_allocated = fts_mailbox_allocated, + .mail_allocated = fts_mail_allocated +}; + +void fts_plugin_init(struct module *module) +{ + fts_library_init(); + mail_storage_hooks_add(module, &fts_mail_storage_hooks); +} + +void fts_plugin_deinit(void) +{ + fts_library_deinit(); + fts_parsers_unload(); + mail_storage_hooks_remove(&fts_mail_storage_hooks); +} diff --git a/src/plugins/fts/fts-plugin.h b/src/plugins/fts/fts-plugin.h new file mode 100644 index 0000000..aeec68c --- /dev/null +++ b/src/plugins/fts/fts-plugin.h @@ -0,0 +1,7 @@ +#ifndef FTS_PLUGIN_H +#define FTS_PLUGIN_H + +void fts_plugin_init(struct module *module); +void fts_plugin_deinit(void); + +#endif diff --git a/src/plugins/fts/fts-search-args.c b/src/plugins/fts/fts-search-args.c new file mode 100644 index 0000000..b58b238 --- /dev/null +++ b/src/plugins/fts/fts-search-args.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "mail-namespace.h" +#include "mail-search.h" +#include "fts-api-private.h" +#include "fts-tokenizer.h" +#include "fts-filter.h" +#include "fts-user.h" +#include "fts-search-args.h" + +static void strings_deduplicate(ARRAY_TYPE(const_string) *arr) +{ + const char *const *strings; + unsigned int i, count; + + strings = array_get(arr, &count); + for (i = 1; i < count; ) { + if (strcmp(strings[i-1], strings[i]) == 0) { + array_delete(arr, i, 1); + strings = array_get(arr, &count); + } else { + i++; + } + } +} + +static struct mail_search_arg * +fts_search_arg_create_or(const struct mail_search_arg *orig_arg, pool_t pool, + const ARRAY_TYPE(const_string) *tokens) +{ + struct mail_search_arg *arg, *or_arg, **argp; + const char *token; + + /* create the OR arg first as the parent */ + or_arg = p_new(pool, struct mail_search_arg, 1); + or_arg->type = SEARCH_OR; + + /* now create all the child args for the OR */ + argp = &or_arg->value.subargs; + array_foreach_elem(tokens, token) { + arg = p_new(pool, struct mail_search_arg, 1); + *arg = *orig_arg; + arg->match_not = FALSE; /* we copied this to the root OR */ + arg->next = NULL; + arg->value.str = p_strdup(pool, token); + + *argp = arg; + argp = &arg->next; + } + return or_arg; +} + +static int +fts_backend_dovecot_expand_tokens(struct fts_filter *filter, + pool_t pool, + struct mail_search_arg *parent_arg, + const struct mail_search_arg *orig_arg, + const char *orig_token, const char *token, + const char **error_r) +{ + struct mail_search_arg *arg; + ARRAY_TYPE(const_string) tokens; + const char *token2, *error; + int ret; + + t_array_init(&tokens, 4); + /* first add the word exactly as it without any tokenization */ + array_push_back(&tokens, &orig_token); + /* then add it tokenized, but without filtering */ + array_push_back(&tokens, &token); + + /* add the word filtered */ + if (filter != NULL) { + token2 = t_strdup(token); + ret = fts_filter_filter(filter, &token2, &error); + if (ret > 0) { + token2 = t_strdup(token2); + array_push_back(&tokens, &token2); + } else if (ret < 0) { + *error_r = t_strdup_printf("Couldn't filter search token: %s", error); + return -1; + } else { + /* The filter dropped the token, which means it was + never even indexed. Ignore this word entirely in the + search query. */ + return 0; + } + } + array_sort(&tokens, i_strcmp_p); + strings_deduplicate(&tokens); + + arg = fts_search_arg_create_or(orig_arg, pool, &tokens); + arg->next = parent_arg->value.subargs; + parent_arg->value.subargs = arg; + return 0; +} + +static int +fts_backend_dovecot_tokenize_lang(struct fts_user_language *user_lang, + pool_t pool, struct mail_search_arg *or_arg, + struct mail_search_arg *orig_arg, + const char *orig_token, const char **error_r) +{ + size_t orig_token_len = strlen(orig_token); + struct mail_search_arg *and_arg, *orig_or_args = or_arg->value.subargs; + const char *token, *error; + int ret; + + /* we want all the tokens found from the string to be found, so create + a parent AND and place all the filtered token alternatives under + it */ + and_arg = p_new(pool, struct mail_search_arg, 1); + and_arg->type = SEARCH_SUB; + and_arg->next = orig_or_args; + or_arg->value.subargs = and_arg; + + /* reset tokenizer between search args in case there's any state left + from some previous failure */ + fts_tokenizer_reset(user_lang->search_tokenizer); + while ((ret = fts_tokenizer_next(user_lang->search_tokenizer, + (const void *)orig_token, + orig_token_len, &token, &error)) > 0) { + if (fts_backend_dovecot_expand_tokens(user_lang->filter, pool, + and_arg, orig_arg, orig_token, + token, error_r) < 0) + return -1; + } + while (ret >= 0 && + (ret = fts_tokenizer_final(user_lang->search_tokenizer, &token, &error)) > 0) { + if (fts_backend_dovecot_expand_tokens(user_lang->filter, pool, + and_arg, orig_arg, orig_token, + token, error_r) < 0) + return -1; + } + if (ret < 0) { + *error_r = t_strdup_printf("Couldn't tokenize search args: %s", error); + return -1; + } + if (and_arg->value.subargs == NULL) { + /* nothing was actually expanded, remove the empty and_arg */ + or_arg->value.subargs = orig_or_args; + } + return 0; +} + +static int fts_search_arg_expand(struct fts_backend *backend, pool_t pool, + struct mail_search_arg **argp) +{ + const ARRAY_TYPE(fts_user_language) *languages; + struct fts_user_language *lang; + struct mail_search_arg *or_arg, *orig_arg = *argp; + const char *error, *orig_token = orig_arg->value.str; + + if (((*argp)->type == SEARCH_HEADER || + (*argp)->type == SEARCH_HEADER_ADDRESS || + (*argp)->type == SEARCH_HEADER_COMPRESS_LWSP) && + !fts_header_has_language((*argp)->hdr_field_name)) { + /* use only the data-language */ + languages = fts_user_get_data_languages(backend->ns->user); + } else { + languages = fts_user_get_all_languages(backend->ns->user); + } + + /* OR together all the different expansions for different languages. + it's enough for one of them to match. */ + or_arg = p_new(pool, struct mail_search_arg, 1); + or_arg->type = SEARCH_OR; + or_arg->match_not = orig_arg->match_not; + or_arg->next = orig_arg->next; + + array_foreach_elem(languages, lang) { + if (fts_backend_dovecot_tokenize_lang(lang, pool, or_arg, + orig_arg, orig_token, &error) < 0) { + i_error("fts: %s", error); + return -1; + } + } + + if (or_arg->value.subargs == NULL) { + /* we couldn't parse any tokens from the input */ + or_arg->type = SEARCH_ALL; + or_arg->match_not = !or_arg->match_not; + } + *argp = or_arg; + return 0; +} + +static int +fts_search_args_expand_tree(struct fts_backend *backend, pool_t pool, + struct mail_search_arg **argp) +{ + int ret; + + for (; *argp != NULL; argp = &(*argp)->next) { + switch ((*argp)->type) { + case SEARCH_OR: + case SEARCH_SUB: + case SEARCH_INTHREAD: + if (fts_search_args_expand_tree(backend, pool, + &(*argp)->value.subargs) < 0) + return -1; + break; + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + if ((*argp)->value.str[0] == '\0') { + /* we're testing for the existence of + the header */ + break; + } + /* fall through */ + case SEARCH_BODY: + case SEARCH_TEXT: + T_BEGIN { + ret = fts_search_arg_expand(backend, pool, argp); + } T_END; + if (ret < 0) + return -1; + break; + default: + break; + } + } + return 0; +} + +int fts_search_args_expand(struct fts_backend *backend, + struct mail_search_args *args) +{ + struct mail_search_arg *args_dup, *orig_args = args->args; + + /* don't keep re-expanding every time the search args are used. + this is especially important to avoid an assert-crash in + index_search_result_update_flags(). */ + if (args->fts_expanded) + return 0; + args->fts_expanded = TRUE; + + /* duplicate the args, so if expansion fails we haven't changed + anything */ + args_dup = mail_search_arg_dup(args->pool, args->args); + + if (fts_search_args_expand_tree(backend, args->pool, &args_dup) < 0) + return -1; + + /* we'll need to re-simplify the args if we changed anything */ + args->simplified = FALSE; + args->args = args_dup; + mail_search_args_simplify(args); + + /* duplicated args aren't initialized */ + i_assert(args->init_refcount > 0); + mail_search_arg_init(args, args_dup); + mail_search_arg_deinit(orig_args); + return 0; +} diff --git a/src/plugins/fts/fts-search-args.h b/src/plugins/fts/fts-search-args.h new file mode 100644 index 0000000..9fb8923 --- /dev/null +++ b/src/plugins/fts/fts-search-args.h @@ -0,0 +1,7 @@ +#ifndef FTS_SEARCH_ARGS_H +#define FTS_SEARCH_ARGS_H + +int fts_search_args_expand(struct fts_backend *backend, + struct mail_search_args *args); + +#endif diff --git a/src/plugins/fts/fts-search-serialize.c b/src/plugins/fts/fts-search-serialize.c new file mode 100644 index 0000000..e30d4ce --- /dev/null +++ b/src/plugins/fts/fts-search-serialize.c @@ -0,0 +1,99 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "mail-search.h" +#include "fts-search-serialize.h" + +#define HAVE_SUBARGS(arg) \ + ((arg)->type == SEARCH_SUB || (arg)->type == SEARCH_OR) + +void fts_search_serialize(buffer_t *buf, const struct mail_search_arg *args) +{ + char chr; + + for (; args != NULL; args = args->next) { + chr = (args->match_always ? 1 : 0) | + (args->nonmatch_always ? 2 : 0); + buffer_append_c(buf, chr); + + if (HAVE_SUBARGS(args)) + fts_search_serialize(buf, args->value.subargs); + } +} + +static void fts_search_deserialize_idx(struct mail_search_arg *args, + const buffer_t *buf, unsigned int *idx) +{ + const char *data = buf->data; + + for (; args != NULL; args = args->next) { + i_assert(*idx < buf->used); + + args->match_always = (data[*idx] & 1) != 0; + args->nonmatch_always = (data[*idx] & 2) != 0; + args->result = args->match_always ? 1 : + (args->nonmatch_always ? 0 : -1); + *idx += 1; + + if (HAVE_SUBARGS(args)) { + fts_search_deserialize_idx(args->value.subargs, + buf, idx); + } + } +} + +void fts_search_deserialize(struct mail_search_arg *args, + const buffer_t *buf) +{ + unsigned int idx = 0; + + fts_search_deserialize_idx(args, buf, &idx); + i_assert(idx == buf->used); +} + +static void +fts_search_deserialize_add_idx(struct mail_search_arg *args, + const buffer_t *buf, unsigned int *idx, + bool matches) +{ + const char *data = buf->data; + + for (; args != NULL; args = args->next) { + i_assert(*idx < buf->used); + + if (data[*idx] != 0) { + if (matches) { + args->match_always = TRUE; + args->result = 1; + } else { + args->nonmatch_always = TRUE; + args->result = 0; + } + } + *idx += 1; + + if (HAVE_SUBARGS(args)) { + fts_search_deserialize_add_idx(args->value.subargs, + buf, idx, matches); + } + } +} + +void fts_search_deserialize_add_matches(struct mail_search_arg *args, + const buffer_t *buf) +{ + unsigned int idx = 0; + + fts_search_deserialize_add_idx(args, buf, &idx, TRUE); + i_assert(idx == buf->used); +} + +void fts_search_deserialize_add_nonmatches(struct mail_search_arg *args, + const buffer_t *buf) +{ + unsigned int idx = 0; + + fts_search_deserialize_add_idx(args, buf, &idx, FALSE); + i_assert(idx == buf->used); +} diff --git a/src/plugins/fts/fts-search-serialize.h b/src/plugins/fts/fts-search-serialize.h new file mode 100644 index 0000000..c1a7d88 --- /dev/null +++ b/src/plugins/fts/fts-search-serialize.h @@ -0,0 +1,16 @@ +#ifndef FTS_SEARCH_SERIALIZE_H +#define FTS_SEARCH_SERIALIZE_H + +/* serialize [non]match_always fields (clearing buffer) */ +void fts_search_serialize(buffer_t *buf, const struct mail_search_arg *args); +/* add/remove [non]match_always fields in search args */ +void fts_search_deserialize(struct mail_search_arg *args, + const buffer_t *buf); +/* add match_always=TRUE fields to search args */ +void fts_search_deserialize_add_matches(struct mail_search_arg *args, + const buffer_t *buf); +/* add nonmatch_always=TRUE fields to search args */ +void fts_search_deserialize_add_nonmatches(struct mail_search_arg *args, + const buffer_t *buf); + +#endif diff --git a/src/plugins/fts/fts-search.c b/src/plugins/fts/fts-search.c new file mode 100644 index 0000000..895ea59 --- /dev/null +++ b/src/plugins/fts/fts-search.c @@ -0,0 +1,385 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "seq-range-array.h" +#include "mail-search.h" +#include "fts-api-private.h" +#include "fts-search-args.h" +#include "fts-search-serialize.h" +#include "fts-storage.h" +#include "hash.h" + +static void +uid_range_to_seqs(struct fts_search_context *fctx, + const ARRAY_TYPE(seq_range) *uid_range, + ARRAY_TYPE(seq_range) *seq_range) +{ + const struct seq_range *range; + unsigned int i, count; + uint32_t seq1, seq2; + + range = array_get(uid_range, &count); + if (!array_is_created(seq_range)) + p_array_init(seq_range, fctx->result_pool, count); + for (i = 0; i < count; i++) { + if (range[i].seq1 > range[i].seq2) + continue; + mailbox_get_seq_range(fctx->box, range[i].seq1, range[i].seq2, + &seq1, &seq2); + if (seq1 != 0) + seq_range_array_add_range(seq_range, seq1, seq2); + } +} + +static int fts_search_lookup_level_single(struct fts_search_context *fctx, + struct mail_search_arg *args, + bool and_args) +{ + enum fts_lookup_flags flags = fctx->flags | + (and_args ? FTS_LOOKUP_FLAG_AND_ARGS : 0); + struct fts_search_level *level; + struct fts_result result; + + i_zero(&result); + result.search_state = fctx->search_state; + result.pool = fctx->result_pool; + p_array_init(&result.definite_uids, fctx->result_pool, 32); + p_array_init(&result.maybe_uids, fctx->result_pool, 32); + p_array_init(&result.scores, fctx->result_pool, 32); + + mail_search_args_reset(args, TRUE); + if (fts_backend_lookup(fctx->backend, fctx->box, args, flags, + &result) < 0) + return -1; + + fctx->search_state = result.search_state; + level = array_append_space(&fctx->levels); + level->args_matches = buffer_create_dynamic(fctx->result_pool, 16); + fts_search_serialize(level->args_matches, args); + + uid_range_to_seqs(fctx, &result.definite_uids, &level->definite_seqs); + uid_range_to_seqs(fctx, &result.maybe_uids, &level->maybe_seqs); + level->score_map = result.scores; + return 0; +} + +static void +level_scores_add_vuids(struct mailbox *box, + struct fts_search_level *level, struct fts_result *br) +{ + const struct fts_score_map *scores; + unsigned int i, count; + ARRAY_TYPE(seq_range) backend_uids; + ARRAY_TYPE(uint32_t) vuids_arr; + const uint32_t *vuids; + struct fts_score_map *score; + + scores = array_get(&br->scores, &count); + t_array_init(&vuids_arr, count); + t_array_init(&backend_uids, 64); + for (i = 0; i < count; i++) + seq_range_array_add(&backend_uids, scores[i].uid); + box->virtual_vfuncs->get_virtual_uid_map(box, br->box, + &backend_uids, &vuids_arr); + + i_assert(array_count(&vuids_arr) == array_count(&br->scores)); + vuids = array_get(&vuids_arr, &count); + for (i = 0; i < count; i++) { + score = array_append_space(&level->score_map); + score->uid = vuids[i]; + score->score = scores[i].score; + } +} + +static int +mailbox_cmp_fts_backend(struct mailbox *const *m1, struct mailbox *const *m2) +{ + struct fts_backend *b1, *b2; + + b1 = fts_mailbox_backend(*m1); + b2 = fts_mailbox_backend(*m2); + if (b1 < b2) + return -1; + if (b1 > b2) + return 1; + return 0; +} + +static int +multi_add_lookup_result(struct fts_search_context *fctx, + struct fts_search_level *level, + struct mail_search_arg *args, + struct fts_multi_result *result) +{ + ARRAY_TYPE(seq_range) vuids; + size_t orig_size; + unsigned int i; + + orig_size = level->args_matches->used; + fts_search_serialize(level->args_matches, args); + if (orig_size > 0) { + if (level->args_matches->used != orig_size * 2 || + memcmp(level->args_matches->data, + CONST_PTR_OFFSET(level->args_matches->data, + orig_size), orig_size) != 0) + i_panic("incompatible fts backends for namespaces"); + buffer_set_used_size(level->args_matches, orig_size); + } + + t_array_init(&vuids, 64); + for (i = 0; result->box_results[i].box != NULL; i++) { + struct fts_result *br = &result->box_results[i]; + + array_clear(&vuids); + if (array_is_created(&br->definite_uids)) { + fctx->box->virtual_vfuncs->get_virtual_uids(fctx->box, + br->box, &br->definite_uids, &vuids); + } + uid_range_to_seqs(fctx, &vuids, &level->definite_seqs); + + array_clear(&vuids); + if (array_is_created(&br->maybe_uids)) { + fctx->box->virtual_vfuncs->get_virtual_uids(fctx->box, + br->box, &br->maybe_uids, &vuids); + } + uid_range_to_seqs(fctx, &vuids, &level->maybe_seqs); + + if (array_is_created(&br->scores)) + level_scores_add_vuids(fctx->box, level, br); + } + return 0; +} + +static int fts_search_lookup_level_multi(struct fts_search_context *fctx, + struct mail_search_arg *args, + bool and_args) +{ + enum fts_lookup_flags flags = fctx->flags | + (and_args ? FTS_LOOKUP_FLAG_AND_ARGS : 0); + ARRAY_TYPE(mailboxes) mailboxes_arr, tmp_mailboxes; + struct mailbox *const *mailboxes; + struct fts_backend *backend; + struct fts_search_level *level; + struct fts_multi_result result; + unsigned int i, j, mailbox_count; + + p_array_init(&mailboxes_arr, fctx->result_pool, 8); + fctx->box->virtual_vfuncs->get_virtual_backend_boxes(fctx->box, + &mailboxes_arr, TRUE); + array_sort(&mailboxes_arr, mailbox_cmp_fts_backend); + + i_zero(&result); + result.search_state = fctx->search_state; + result.pool = fctx->result_pool; + + level = array_append_space(&fctx->levels); + level->args_matches = buffer_create_dynamic(fctx->result_pool, 16); + p_array_init(&level->score_map, fctx->result_pool, 1); + + mailboxes = array_get(&mailboxes_arr, &mailbox_count); + t_array_init(&tmp_mailboxes, mailbox_count); + for (i = 0; i < mailbox_count; i = j) { + array_clear(&tmp_mailboxes); + array_push_back(&tmp_mailboxes, &mailboxes[i]); + + backend = fts_mailbox_backend(mailboxes[i]); + for (j = i + 1; j < mailbox_count; j++) { + if (fts_mailbox_backend(mailboxes[j]) != backend) + break; + array_push_back(&tmp_mailboxes, &mailboxes[j]); + } + array_append_zero(&tmp_mailboxes); + + mail_search_args_reset(args, TRUE); + if (fts_backend_lookup_multi(backend, + array_front(&tmp_mailboxes), + args, flags, &result) < 0) + return -1; + + if (multi_add_lookup_result(fctx, level, args, &result) < 0) + return -1; + } + fctx->search_state = result.search_state; + return 0; +} + +static int fts_search_lookup_level(struct fts_search_context *fctx, + struct mail_search_arg *args, + bool and_args) +{ + int ret; + + T_BEGIN { + ret = !fctx->virtual_mailbox ? + fts_search_lookup_level_single(fctx, args, and_args) : + fts_search_lookup_level_multi(fctx, args, and_args); + } T_END; + if (ret < 0) + return -1; + + for (; args != NULL; args = args->next) { + if (args->type != SEARCH_OR && args->type != SEARCH_SUB) + continue; + + if (fts_search_lookup_level(fctx, args->value.subargs, + args->type == SEARCH_SUB) < 0) + return -1; + } + return 0; +} + +static void +fts_search_merge_scores_and(ARRAY_TYPE(fts_score_map) *dest, + const ARRAY_TYPE(fts_score_map) *src) +{ + struct fts_score_map *dest_map; + const struct fts_score_map *src_map; + unsigned int desti, srci, dest_count, src_count; + + dest_map = array_get_modifiable(dest, &dest_count); + src_map = array_get(src, &src_count); + + /* arg_scores are summed to current scores. we could drop UIDs that + don't exist in both, but that's just extra work so don't bother */ + for (desti = srci = 0; desti < dest_count && srci < src_count;) { + if (dest_map[desti].uid < src_map[srci].uid) + desti++; + else if (dest_map[desti].uid > src_map[srci].uid) + srci++; + else { + if (dest_map[desti].score < src_map[srci].score) + dest_map[desti].score = src_map[srci].score; + desti++; srci++; + } + } +} + +static void +fts_search_merge_scores_or(ARRAY_TYPE(fts_score_map) *dest, + const ARRAY_TYPE(fts_score_map) *src) +{ + ARRAY_TYPE(fts_score_map) src2; + const struct fts_score_map *src_map, *src2_map; + unsigned int srci, src2i, src_count, src2_count; + + t_array_init(&src2, array_count(dest)); + array_append_array(&src2, dest); + array_clear(dest); + + src_map = array_get(src, &src_count); + src2_map = array_get(&src2, &src2_count); + + /* add any missing UIDs to current scores. if any existing UIDs have + lower scores than in arg_scores, increase them. */ + for (srci = src2i = 0; srci < src_count || src2i < src2_count;) { + if (src2i == src2_count || + src_map[srci].uid < src2_map[src2i].uid) { + array_push_back(dest, &src_map[srci]); + srci++; + } else if (srci == src_count || + src_map[srci].uid > src2_map[src2i].uid) { + array_push_back(dest, &src2_map[src2i]); + src2i++; + } else { + i_assert(src_map[srci].uid == src2_map[src2i].uid); + if (src_map[srci].score > src2_map[src2i].score) + array_push_back(dest, &src_map[srci]); + else + array_push_back(dest, &src2_map[src2i]); + srci++; src2i++; + } + } +} + +static void +fts_search_merge_scores_level(struct fts_search_context *fctx, + struct mail_search_arg *args, unsigned int *idx, + bool and_args, ARRAY_TYPE(fts_score_map) *scores) +{ + const struct fts_search_level *level; + ARRAY_TYPE(fts_score_map) arg_scores; + + i_assert(array_count(scores) == 0); + + /* + The (simplified) args can look like: + + A and B and (C or D) and (E or F) and ... + A or B or (C and D) or (E and F) or ... + + The A op B part's scores are in level->scores. The child args' + scores are in the sub levels' scores. + */ + + level = array_idx(&fctx->levels, *idx); + array_append_array(scores, &level->score_map); + + t_array_init(&arg_scores, 64); + for (; args != NULL; args = args->next) { + if (args->type != SEARCH_OR && args->type != SEARCH_SUB) + continue; + + *idx += 1; + array_clear(&arg_scores); + fts_search_merge_scores_level(fctx, args->value.subargs, idx, + args->type == SEARCH_OR, + &arg_scores); + + if (and_args) + fts_search_merge_scores_and(scores, &arg_scores); + else + fts_search_merge_scores_or(scores, &arg_scores); + } +} + +static void fts_search_merge_scores(struct fts_search_context *fctx) +{ + unsigned int idx = 0; + + fts_search_merge_scores_level(fctx, fctx->args->args, &idx, + TRUE, &fctx->scores->score_map); +} + +static void fts_search_try_lookup(struct fts_search_context *fctx) +{ + uint32_t last_uid, seq1, seq2; + + i_assert(array_count(&fctx->levels) == 0); + i_assert(fctx->args->simplified); + + if (fts_backend_refresh(fctx->backend) < 0) + return; + if (fts_backend_get_last_uid(fctx->backend, fctx->box, &last_uid) < 0) + return; + mailbox_get_seq_range(fctx->box, last_uid+1, (uint32_t)-1, + &seq1, &seq2); + fctx->first_unindexed_seq = seq1 != 0 ? seq1 : (uint32_t)-1; + + if (fctx->virtual_mailbox) { + hash_table_clear(fctx->last_indexed_virtual_uids, TRUE); + fctx->next_unindexed_seq = fctx->first_unindexed_seq; + } + + if ((fctx->backend->flags & FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0) { + if (fts_search_args_expand(fctx->backend, fctx->args) < 0) + return; + } + fts_search_serialize(fctx->orig_matches, fctx->args->args); + + if (fts_search_lookup_level(fctx, fctx->args->args, TRUE) == 0) { + fctx->fts_lookup_success = TRUE; + fts_search_merge_scores(fctx); + } + + fts_search_deserialize(fctx->args->args, fctx->orig_matches); + fts_backend_lookup_done(fctx->backend); +} + +void fts_search_lookup(struct fts_search_context *fctx) +{ + struct event_reason *reason = event_reason_begin("fts:lookup"); + fts_search_try_lookup(fctx); + event_reason_end(&reason); +} diff --git a/src/plugins/fts/fts-storage.c b/src/plugins/fts/fts-storage.c new file mode 100644 index 0000000..101d52a --- /dev/null +++ b/src/plugins/fts/fts-storage.c @@ -0,0 +1,981 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "str.h" +#include "strescape.h" +#include "write-full.h" +#include "mail-search-build.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "fts-api-private.h" +#include "fts-tokenizer.h" +#include "fts-indexer.h" +#include "fts-build-mail.h" +#include "fts-search-serialize.h" +#include "fts-plugin.h" +#include "fts-user.h" +#include "fts-storage.h" +#include "hash.h" + + +#define FTS_CONTEXT(obj) \ + MODULE_CONTEXT(obj, fts_storage_module) +#define FTS_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, fts_storage_module) +#define FTS_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, fts_mail_module) +#define FTS_LIST_CONTEXT(obj) \ + MODULE_CONTEXT(obj, fts_mailbox_list_module) +#define FTS_LIST_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, fts_mailbox_list_module) + +#define INDEXER_SOCKET_NAME "indexer" +#define INDEXER_HANDSHAKE "VERSION\tindexer\t1\t0\n" + +struct fts_mailbox_list { + union mailbox_list_module_context module_ctx; + struct fts_backend *backend; + + const char *backend_name; + struct fts_backend_update_context *update_ctx; + unsigned int update_ctx_refcount; + + bool failed:1; +}; + +struct fts_mailbox { + union mailbox_module_context module_ctx; + struct fts_backend_update_context *sync_update_ctx; + bool fts_mailbox_excluded; +}; + +struct fts_transaction_context { + union mailbox_transaction_module_context module_ctx; + + struct fts_scores *scores; + uint32_t next_index_seq; + uint32_t highest_virtual_uid; + unsigned int precache_extra_count; + + bool indexing:1; + bool precached:1; + bool mails_saved:1; + const char *failure_reason; +}; + +struct fts_mail { + union mail_module_context module_ctx; + char score[30]; + + bool virtual_mail:1; +}; + +static MODULE_CONTEXT_DEFINE_INIT(fts_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(fts_mail_module, &mail_module_register); +static MODULE_CONTEXT_DEFINE_INIT(fts_mailbox_list_module, + &mailbox_list_module_register); + +static int fts_mailbox_get_last_cached_seq(struct mailbox *box, uint32_t *seq_r) +{ + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(box->list); + uint32_t seq1, seq2, last_uid; + + if (fts_backend_get_last_uid(flist->backend, box, &last_uid) < 0) { + mail_storage_set_internal_error(box->storage); + return -1; + } + + if (last_uid == 0) + *seq_r = 0; + else { + mailbox_get_seq_range(box, 1, last_uid, &seq1, &seq2); + *seq_r = seq2; + } + return 0; +} + +static int +fts_mailbox_get_status(struct mailbox *box, enum mailbox_status_items items, + struct mailbox_status *status_r) +{ + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(box); + uint32_t seq; + + if (fbox->module_ctx.super.get_status(box, items, status_r) < 0) + return -1; + + if ((items & STATUS_LAST_CACHED_SEQ) != 0) { + if (fts_mailbox_get_last_cached_seq(box, &seq) < 0) + return -1; + + /* Always use the FTS's last_cached_seq. This is because we + don't want to reindex all mails to FTS if .cache file is + deleted. */ + status_r->last_cached_seq = seq; + } + return 0; +} + + +static void fts_scores_unref(struct fts_scores **_scores) +{ + struct fts_scores *scores = *_scores; + + *_scores = NULL; + if (--scores->refcount == 0) { + array_free(&scores->score_map); + i_free(scores); + } +} + +static void fts_try_build_init(struct mail_search_context *ctx, + struct fts_search_context *fctx) +{ + int ret; + + i_assert(!fts_backend_is_updating(fctx->backend)); + + ret = fts_indexer_init(fctx->backend, ctx->transaction->box, + &fctx->indexer_ctx); + if (ret < 0) + return; + + if (ret == 0) { + /* the index was up to date */ + fts_search_lookup(fctx); + } else { + /* hide "searching" notifications while building index */ + ctx->progress_hidden = TRUE; + } +} + +static bool fts_want_build_args(const struct mail_search_arg *args) +{ + /* we want to update index only when searching from message body. + it's not worth the wait for searching only from headers, which + could be in cache file already */ + for (; args != NULL; args = args->next) { + switch (args->type) { + case SEARCH_OR: + case SEARCH_SUB: + case SEARCH_INTHREAD: + if (fts_want_build_args(args->value.subargs)) + return TRUE; + break; + case SEARCH_BODY: + case SEARCH_TEXT: + if (!args->no_fts) + return TRUE; + break; + default: + break; + } + } + return FALSE; +} + +static bool fts_args_have_fuzzy(const struct mail_search_arg *args) +{ + for (; args != NULL; args = args->next) { + if (args->fuzzy) + return TRUE; + switch (args->type) { + case SEARCH_OR: + case SEARCH_SUB: + case SEARCH_INTHREAD: + if (fts_args_have_fuzzy(args->value.subargs)) + return TRUE; + break; + default: + break; + } + } + return FALSE; +} + +static enum fts_enforced fts_enforced_parse(const char *str) +{ + if (str == NULL || strcmp(str, "no") == 0) + return FTS_ENFORCED_NO; + else if (strcmp(str, "body") == 0) + return FTS_ENFORCED_BODY; + else + return FTS_ENFORCED_YES; +} + +static struct mail_search_context * +fts_mailbox_search_init(struct mailbox_transaction_context *t, + struct mail_search_args *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(t); + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(t->box); + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(t->box->list); + struct mail_search_context *ctx; + struct fts_search_context *fctx; + + ctx = fbox->module_ctx.super.search_init(t, args, sort_program, + wanted_fields, wanted_headers); + + if (!fts_backend_can_lookup(flist->backend, args->args)) + return ctx; + + fctx = i_new(struct fts_search_context, 1); + fctx->box = t->box; + fctx->backend = flist->backend; + fctx->t = t; + fctx->args = args; + fctx->result_pool = pool_alloconly_create("fts results", 1024*64); + fctx->orig_matches = buffer_create_dynamic(default_pool, 64); + fctx->virtual_mailbox = t->box->virtual_vfuncs != NULL; + if (fctx->virtual_mailbox) { + hash_table_create(&fctx->last_indexed_virtual_uids, + default_pool, 0, str_hash, strcmp); + } + fctx->enforced = fts_enforced_parse( + mail_user_plugin_getenv(t->box->storage->user, "fts_enforced")); + i_array_init(&fctx->levels, 8); + fctx->scores = i_new(struct fts_scores, 1); + fctx->scores->refcount = 1; + i_array_init(&fctx->scores->score_map, 64); + MODULE_CONTEXT_SET(ctx, fts_storage_module, fctx); + + /* FIXME: we'll assume that all the args are fuzzy. not good, + but would require much more work to fix it. */ + if (!fts_args_have_fuzzy(args->args) && + mail_user_plugin_getenv_bool(t->box->storage->user, + "fts_no_autofuzzy")) + fctx->flags |= FTS_LOOKUP_FLAG_NO_AUTO_FUZZY; + /* transaction contains the last search's scores. they can be + queried later with mail_get_special() */ + if (ft->scores != NULL) + fts_scores_unref(&ft->scores); + ft->scores = fctx->scores; + ft->scores->refcount++; + + if (fctx->enforced == FTS_ENFORCED_YES || + fts_want_build_args(args->args)) + fts_try_build_init(ctx, fctx); + else + fts_search_lookup(fctx); + return ctx; +} + +static bool fts_mailbox_build_continue(struct mail_search_context *ctx) +{ + struct fts_search_context *fctx = FTS_CONTEXT_REQUIRE(ctx); + int ret; + + ret = fts_indexer_more(fctx->indexer_ctx); + if (ret == 0) + return FALSE; + + /* indexing finished */ + ctx->progress_hidden = FALSE; + if (fts_indexer_deinit(&fctx->indexer_ctx) < 0) + ret = -1; + if (ret > 0) + fts_search_lookup(fctx); + if (ret < 0) { + /* if indexing timed out, it probably means that + the mailbox is still being indexed, but it's a large + mailbox and it takes a while. in this situation + we'll simply abort the search. + + if indexing failed for any other reason, just + fallback to searching the slow way. */ + fctx->indexing_timed_out = + mailbox_get_last_mail_error(fctx->box) == MAIL_ERROR_INUSE; + } + return TRUE; +} + +static bool +fts_mailbox_search_next_nonblock(struct mail_search_context *ctx, + struct mail **mail_r, bool *tryagain_r) +{ + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box); + struct fts_search_context *fctx = FTS_CONTEXT(ctx); + + if (fctx != NULL && fctx->indexer_ctx != NULL) { + /* this command is still building the indexes */ + if (!fts_mailbox_build_continue(ctx)) { + *tryagain_r = TRUE; + return FALSE; + } + if (fctx->indexing_timed_out) { + *tryagain_r = FALSE; + return FALSE; + } + } + if (fctx != NULL && !fctx->fts_lookup_success && + fctx->enforced != FTS_ENFORCED_NO) + return FALSE; + + return fbox->module_ctx.super. + search_next_nonblock(ctx, mail_r, tryagain_r); +} + +static void +fts_search_apply_results_level(struct mail_search_context *ctx, + struct mail_search_arg *args, unsigned int *idx) +{ + struct fts_search_context *fctx = FTS_CONTEXT_REQUIRE(ctx); + const struct fts_search_level *level; + + level = array_idx(&fctx->levels, *idx); + + if (array_is_created(&level->definite_seqs) && + seq_range_exists(&level->definite_seqs, ctx->seq)) + fts_search_deserialize_add_matches(args, level->args_matches); + else if (!array_is_created(&level->maybe_seqs) || + !seq_range_exists(&level->maybe_seqs, ctx->seq)) + fts_search_deserialize_add_nonmatches(args, level->args_matches); + + for (; args != NULL; args = args->next) { + if (args->type != SEARCH_OR && args->type != SEARCH_SUB) + continue; + + *idx += 1; + fts_search_apply_results_level(ctx, args->value.subargs, idx); + } +} + +static bool fts_mailbox_search_next_update_seq(struct mail_search_context *ctx) +{ + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box); + struct fts_search_context *fctx = FTS_CONTEXT(ctx); + unsigned int idx; + + if (fctx == NULL || !fctx->fts_lookup_success) { + /* fts lookup not done for this search */ + if (fctx != NULL && fctx->indexing_timed_out) + return FALSE; + return fbox->module_ctx.super.search_next_update_seq(ctx); + } + + /* restore original [non]matches */ + fts_search_deserialize(ctx->args->args, fctx->orig_matches); + + if (!fbox->module_ctx.super.search_next_update_seq(ctx)) + return FALSE; + + if (ctx->seq >= fctx->first_unindexed_seq) { + /* we've not indexed this far */ + return TRUE; + } + + /* apply [non]matches based on the FTS lookup results */ + idx = 0; + fts_search_apply_results_level(ctx, ctx->args->args, &idx); + return TRUE; +} + +static int fts_mailbox_search_deinit(struct mail_search_context *ctx) +{ + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box); + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(ctx->transaction); + struct fts_search_context *fctx = FTS_CONTEXT(ctx); + int ret = 0; + + if (fctx != NULL) { + if (fctx->virtual_mailbox) + hash_table_destroy(&fctx->last_indexed_virtual_uids); + if (fctx->indexer_ctx != NULL) { + if (fts_indexer_deinit(&fctx->indexer_ctx) < 0) + ft->failure_reason = "FTS indexing failed"; + } + if (fctx->indexing_timed_out) + ret = -1; + else if (!fctx->fts_lookup_success && + fctx->enforced != FTS_ENFORCED_NO) { + /* FTS lookup failed and we didn't want to fallback to + opening all the mails and searching manually */ + mail_storage_set_internal_error(ctx->transaction->box->storage); + ret = -1; + } + + buffer_free(&fctx->orig_matches); + array_free(&fctx->levels); + pool_unref(&fctx->result_pool); + fts_scores_unref(&fctx->scores); + i_free(fctx); + } + if (fbox->module_ctx.super.search_deinit(ctx) < 0) + ret = -1; + return ret; +} + +static int fts_score_cmp(const uint32_t *uid, const struct fts_score_map *score) +{ + return *uid < score->uid ? -1 : + (*uid > score->uid ? 1 : 0); +} + +static int fts_mail_get_special(struct mail *_mail, enum mail_fetch_field field, + const char **value_r) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct fts_mail *fmail = FTS_MAIL_CONTEXT(mail); + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(_mail->transaction); + const struct fts_score_map *scores; + + if (field != MAIL_FETCH_SEARCH_RELEVANCY || ft->scores == NULL) + scores = NULL; + else { + scores = array_bsearch(&ft->scores->score_map, &_mail->uid, + fts_score_cmp); + } + if (scores != NULL) { + i_assert(scores->uid == _mail->uid); + (void)i_snprintf(fmail->score, sizeof(fmail->score), + "%f", scores->score); + + *value_r = fmail->score; + return 0; + } + + return fmail->module_ctx.super.get_special(_mail, field, value_r); +} + +static int +fts_mail_precache_range(struct mailbox_transaction_context *trans, + struct fts_backend_update_context *update_ctx, + uint32_t seq1, uint32_t seq2, unsigned int *extra_count) +{ + struct mail_search_args *search_args; + struct mail_search_context *ctx; + struct mail *mail; + int ret = 0; + + search_args = mail_search_build_init(); + mail_search_build_add_seqset(search_args, seq1, seq2); + ctx = mailbox_search_init(trans, search_args, NULL, + MAIL_FETCH_STREAM_HEADER | + MAIL_FETCH_STREAM_BODY, NULL); + mail_search_args_unref(&search_args); + + while (mailbox_search_next(ctx, &mail)) { + if (fts_build_mail(update_ctx, mail) < 0) { + ret = -1; + break; + } + if (mail_precache(mail) < 0) { + ret = -1; + break; + } + *extra_count += 1; + } + if (mailbox_search_deinit(&ctx) < 0) + ret = -1; + return ret; +} + +static int fts_mail_precache_init(struct mail *_mail) +{ + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(_mail->transaction); + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(_mail->box->list); + uint32_t last_seq; + + if (fts_mailbox_get_last_cached_seq(_mail->box, &last_seq) < 0) { + ft->failure_reason = "Failed to lookup last indexed FTS mail"; + return -1; + } + + ft->precached = TRUE; + ft->next_index_seq = last_seq + 1; + if (flist->update_ctx == NULL) + flist->update_ctx = fts_backend_update_init(flist->backend); + flist->update_ctx_refcount++; + return 0; +} + +static int fts_mail_index(struct mail *_mail) +{ + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(_mail->transaction); + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(_mail->box->list); + struct mail_private *pmail = (struct mail_private *)_mail; + + if (ft->failure_reason != NULL) + return -1; + + if (!ft->precached) { + if (fts_mail_precache_init(_mail) < 0) + return -1; + } + if (pmail->vmail != NULL) { + /* Indexing via virtual mailbox: Index all the mails in this + same real mailbox. */ + uint32_t msgs_count = + mail_index_view_get_messages_count(_mail->box->view); + + fts_backend_update_set_mailbox(flist->update_ctx, _mail->box); + if (ft->next_index_seq > msgs_count) { + /* everything indexed already */ + return 0; + } else if (fts_mail_precache_range(_mail->transaction, + flist->update_ctx, + ft->next_index_seq, + msgs_count, + &ft->precache_extra_count) < 0) { + return -1; + } else { + ft->next_index_seq = msgs_count+1; + return 0; + } + } + + if (ft->next_index_seq < _mail->seq) { + /* we'll first need to index all the missing mails up to the + current one. */ + fts_backend_update_set_mailbox(flist->update_ctx, _mail->box); + if (fts_mail_precache_range(_mail->transaction, + flist->update_ctx, + ft->next_index_seq, + _mail->seq-1, + &ft->precache_extra_count) < 0) + return -1; + ft->next_index_seq = _mail->seq; + } + + if (ft->next_index_seq == _mail->seq) { + fts_backend_update_set_mailbox(flist->update_ctx, _mail->box); + if (fts_build_mail(flist->update_ctx, _mail) < 0) + return -1; + ft->next_index_seq = _mail->seq + 1; + } + return 0; +} + +static int fts_mail_precache(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct fts_mail *fmail = FTS_MAIL_CONTEXT(mail); + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(_mail->transaction); + int ret = 0; + + fmail->module_ctx.super.precache(_mail); + if (fmail->virtual_mail) { + if (ft->highest_virtual_uid < _mail->uid) + ft->highest_virtual_uid = _mail->uid; + } else if (!ft->indexing) T_BEGIN { + /* avoid recursing here from fts_mail_precache_range() */ + struct event_reason *reason = + event_reason_begin("fts:index"); + ft->indexing = TRUE; + ret = fts_mail_index(_mail); + i_assert(ft->indexing); + ft->indexing = FALSE; + event_reason_end(&reason); + } T_END; + return ret; +} + +void fts_mail_allocated(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + struct fts_mailbox *fbox = FTS_CONTEXT(_mail->box); + struct fts_mail *fmail; + + if (fbox == NULL) + return; + + fmail = p_new(mail->pool, struct fts_mail, 1); + fmail->module_ctx.super = *v; + mail->vlast = &fmail->module_ctx.super; + fmail->virtual_mail = _mail->box->virtual_vfuncs != NULL; + + v->get_special = fts_mail_get_special; + v->precache = fts_mail_precache; + MODULE_CONTEXT_SET(mail, fts_mail_module, fmail); +} + +static struct mailbox_transaction_context * +fts_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(box); + struct mailbox_transaction_context *t; + struct fts_transaction_context *ft; + + ft = i_new(struct fts_transaction_context, 1); + + t = fbox->module_ctx.super.transaction_begin(box, flags, reason); + MODULE_CONTEXT_SET(t, fts_storage_module, ft); + return t; +} + +static int fts_transaction_end(struct mailbox_transaction_context *t, const char **error_r) +{ + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(t); + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(t->box->list); + int ret = 0; + + if (ft->failure_reason != NULL) { + *error_r = t_strdup(ft->failure_reason); + ret = -1; + } + + struct event_reason *reason = event_reason_begin("fts:index"); + if (ft->precached) { + i_assert(flist->update_ctx_refcount > 0); + if (--flist->update_ctx_refcount == 0) { + if (fts_backend_update_deinit(&flist->update_ctx) < 0) { + ret = -1; + *error_r = "backend deinit"; + } + } + } else if (ft->highest_virtual_uid > 0) { + if (fts_index_set_last_uid(t->box, ft->highest_virtual_uid) < 0) { + ret = -1; + *error_r = "index last uid setting"; + } + } + if (ft->scores != NULL) + fts_scores_unref(&ft->scores); + if (ft->precache_extra_count > 0) { + if (ret < 0) { + i_error("fts: Failed after indexing %u extra mails internally in %s: %s", + ft->precache_extra_count, t->box->vname, *error_r); + } else { + i_info("fts: Indexed %u extra mails internally in %s", + ft->precache_extra_count, t->box->vname); + } + } + event_reason_end(&reason); + i_free(ft); + return ret; +} + +static void fts_transaction_rollback(struct mailbox_transaction_context *t) +{ + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(t->box); + const char *error; + + (void)fts_transaction_end(t, &error); + fbox->module_ctx.super.transaction_rollback(t); +} + +static void fts_queue_index(struct mailbox *box) +{ + struct mail_user *user = box->storage->user; + string_t *str = t_str_new(256); + const char *path, *value; + unsigned int max_recent_msgs; + int fd; + + path = t_strconcat(user->set->base_dir, "/"INDEXER_SOCKET_NAME, NULL); + fd = net_connect_unix(path); + if (fd == -1) { + i_error("net_connect_unix(%s) failed: %m", path); + return; + } + + value = mail_user_plugin_getenv(user, "fts_autoindex_max_recent_msgs"); + if (value == NULL || str_to_uint(value, &max_recent_msgs) < 0) + max_recent_msgs = 0; + + str_append(str, INDEXER_HANDSHAKE); + str_append(str, "APPEND\t0\t"); + str_append_tabescaped(str, user->username); + str_append_c(str, '\t'); + str_append_tabescaped(str, box->vname); + str_printfa(str, "\t%u", max_recent_msgs); + str_append_c(str, '\t'); + str_append_tabescaped(str, box->storage->user->session_id); + str_append_c(str, '\n'); + if (write_full(fd, str_data(str), str_len(str)) < 0) + i_error("write(%s) failed: %m", path); + i_close_fd(&fd); +} + +static int +fts_transaction_commit(struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes_r) +{ + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(t); + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(t->box); + struct mailbox *box = t->box; + bool autoindex; + int ret = 0; + const char *error; + + autoindex = ft->mails_saved && !fbox->fts_mailbox_excluded && + mail_user_plugin_getenv_bool(box->storage->user, + "fts_autoindex"); + + if (fts_transaction_end(t, &error) < 0) { + mail_storage_set_error(t->box->storage, MAIL_ERROR_TEMP, + t_strdup_printf("FTS transaction commit failed: %s", + error)); + ret = -1; + } + if (fbox->module_ctx.super.transaction_commit(t, changes_r) < 0) + ret = -1; + if (ret < 0) + return -1; + + if (autoindex) + fts_queue_index(box); + return 0; +} + +static void fts_mailbox_sync_notify(struct mailbox *box, uint32_t uid, + enum mailbox_sync_type sync_type) +{ + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(box->list); + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(box); + + if (fbox->module_ctx.super.sync_notify != NULL) + fbox->module_ctx.super.sync_notify(box, uid, sync_type); + + if (sync_type != MAILBOX_SYNC_TYPE_EXPUNGE) { + if (uid == 0 && fbox->sync_update_ctx != NULL) { + /* this sync is finished */ + (void)fts_backend_update_deinit(&fbox->sync_update_ctx); + } + return; + } + + if (fbox->sync_update_ctx == NULL) { + if (fts_backend_is_updating(flist->backend)) { + /* FIXME: maildir workaround - we could get here + because we're building an index, which doesn't find + some mail and starts syncing the mailbox.. */ + return; + } + fbox->sync_update_ctx = fts_backend_update_init(flist->backend); + fts_backend_update_set_mailbox(fbox->sync_update_ctx, box); + } + fts_backend_update_expunge(fbox->sync_update_ctx, uid); +} + +static int fts_sync_deinit(struct mailbox_sync_context *ctx, + struct mailbox_sync_status *status_r) +{ + struct mailbox *box = ctx->box; + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(box); + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT(box->list); + bool optimize; + int ret = 0; + + optimize = (ctx->flags & (MAILBOX_SYNC_FLAG_FORCE_RESYNC | + MAILBOX_SYNC_FLAG_OPTIMIZE)) != 0; + if (fbox->module_ctx.super.sync_deinit(ctx, status_r) < 0) + return -1; + ctx = NULL; + + if (optimize) { + i_assert(flist != NULL); + if (fts_backend_optimize(flist->backend) < 0) { + mailbox_set_critical(box, "FTS optimize failed"); + ret = -1; + } + } + return ret; +} + +static int fts_save_finish(struct mail_save_context *ctx) +{ + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(ctx->transaction); + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box); + + if (fbox->module_ctx.super.save_finish(ctx) < 0) + return -1; + ft->mails_saved = TRUE; + return 0; +} + +static int fts_copy(struct mail_save_context *ctx, struct mail *mail) +{ + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(ctx->transaction); + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box); + + if (fbox->module_ctx.super.copy(ctx, mail) < 0) + return -1; + ft->mails_saved = TRUE; + return 0; +} + +static void fts_mailbox_virtual_match_mail(struct mail_search_context *ctx, + struct mail *mail) +{ + struct fts_search_context *fctx = FTS_CONTEXT(ctx); + unsigned int idx, be_last_uid; + + if (fctx == NULL || !fctx->fts_lookup_success || !fctx->virtual_mailbox || + ctx->seq < fctx->first_unindexed_seq) + return; + /* Table of last indexed UID per backend mailbox */ + HASH_TABLE_TYPE(virtual_last_indexed) hash_tbl = + fctx->last_indexed_virtual_uids; + + struct mail *backend_mail; + if (mail->box->mail_vfuncs->get_backend_mail(mail, &backend_mail) < 0) + return; + const char *box_name = backend_mail->box->vname; + /* Get the last indexed UID in the backend mailbox */ + void *uid_value = + hash_table_lookup(fctx->last_indexed_virtual_uids, box_name); + if (uid_value == NULL) { + /* This backend's last indexed uid is not yet inserted to the table */ + struct fts_mailbox_list *flist = + FTS_LIST_CONTEXT(backend_mail->box->list); + if (flist == NULL || flist->failed || + mailbox_open(backend_mail->box) < 0 || + fts_backend_get_last_uid(flist->backend, backend_mail->box, + &be_last_uid) < 0) { + be_last_uid = 0; + } else { + const char *vname_copy = + p_strdup(fctx->result_pool, backend_mail->box->vname); + hash_table_insert(hash_tbl, vname_copy, + POINTER_CAST(be_last_uid + 1)); + } + } else { + be_last_uid = POINTER_CAST_TO(uid_value, uint32_t) - 1; + } + if (backend_mail->uid <= be_last_uid) { + /* Mail was already indexed in the backend mailbox. + Apply [non]matches based on the FTS lookup results */ + struct fts_transaction_context *ft = FTS_CONTEXT_REQUIRE(ctx->transaction); + + if (fctx->next_unindexed_seq == mail->seq) { + fctx->next_unindexed_seq++; + ft->highest_virtual_uid = mail->uid; + } + idx = 0; + fts_search_apply_results_level(ctx, ctx->args->args, &idx); + } else { + fctx->virtual_seen_unindexed_gaps = TRUE; + } +} + +static int fts_mailbox_search_next_match_mail(struct mail_search_context *ctx, + struct mail *mail) +{ + struct fts_mailbox *fbox = FTS_CONTEXT_REQUIRE(ctx->transaction->box); + + fts_mailbox_virtual_match_mail(ctx, mail); + return fbox->module_ctx.super.search_next_match_mail(ctx, mail); +} + +void fts_mailbox_allocated(struct mailbox *box) +{ + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT(box->list); + struct mailbox_vfuncs *v = box->vlast; + struct fts_mailbox *fbox; + + if (flist == NULL || flist->failed) + return; + + fbox = p_new(box->pool, struct fts_mailbox, 1); + fbox->module_ctx.super = *v; + box->vlast = &fbox->module_ctx.super; + fbox->fts_mailbox_excluded = fts_user_autoindex_exclude(box); + + v->get_status = fts_mailbox_get_status; + v->search_init = fts_mailbox_search_init; + v->search_next_nonblock = fts_mailbox_search_next_nonblock; + v->search_next_update_seq = fts_mailbox_search_next_update_seq; + v->search_deinit = fts_mailbox_search_deinit; + v->transaction_begin = fts_transaction_begin; + v->transaction_rollback = fts_transaction_rollback; + v->transaction_commit = fts_transaction_commit; + v->sync_notify = fts_mailbox_sync_notify; + v->sync_deinit = fts_sync_deinit; + v->save_finish = fts_save_finish; + v->copy = fts_copy; + v->search_next_match_mail = fts_mailbox_search_next_match_mail; + + MODULE_CONTEXT_SET(box, fts_storage_module, fbox); +} + +static void fts_mailbox_list_deinit(struct mailbox_list *list) +{ + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(list); + + if (flist->backend != NULL) + fts_backend_deinit(&flist->backend); + flist->module_ctx.super.deinit(list); +} + +static int +fts_init_namespace(struct fts_mailbox_list *flist, struct mail_namespace *ns, + const char **error_r) +{ + struct fts_backend *backend; + if (fts_backend_init(flist->backend_name, ns, error_r, &backend) < 0) { + flist->failed = TRUE; + return -1; + } + flist->backend = backend; + if ((flist->backend->flags & FTS_BACKEND_FLAG_FUZZY_SEARCH) != 0) + ns->user->fuzzy_search = TRUE; + return 0; +} + +void fts_mail_namespaces_added(struct mail_namespace *ns) +{ + while(ns != NULL) { + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT(ns->list); + const char *error; + + if (flist != NULL && !flist->failed && flist->backend == NULL && + fts_init_namespace(flist, ns, &error) < 0) { + i_error("fts: Failed to initialize backend '%s': %s", + flist->backend_name, error); + } + ns = ns->next; + } +} + +void +fts_mailbox_list_created(struct mailbox_list *list) +{ + const char *name = mail_user_plugin_getenv(list->ns->user, "fts"); + const char *path; + + if (name == NULL || name[0] == '\0') { + e_debug(list->ns->user->event, + "fts: No fts setting - plugin disabled"); + return; + } + + if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_INDEX, &path)) { + e_debug(list->ns->user->event, + "fts: Indexes disabled for namespace '%s'", + list->ns->prefix); + return; + } + + struct fts_mailbox_list *flist; + struct mailbox_list_vfuncs *v = list->vlast; + + flist = p_new(list->pool, struct fts_mailbox_list, 1); + flist->module_ctx.super = *v; + flist->backend_name = name; + list->vlast = &flist->module_ctx.super; + v->deinit = fts_mailbox_list_deinit; + MODULE_CONTEXT_SET(list, fts_mailbox_list_module, flist); +} + +struct fts_backend *fts_mailbox_backend(struct mailbox *box) +{ + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT_REQUIRE(box->list); + + return flist->backend; +} + +struct fts_backend *fts_list_backend(struct mailbox_list *list) +{ + struct fts_mailbox_list *flist = FTS_LIST_CONTEXT(list); + + return flist == NULL ? NULL : flist->backend; +} diff --git a/src/plugins/fts/fts-storage.h b/src/plugins/fts/fts-storage.h new file mode 100644 index 0000000..ea28ed2 --- /dev/null +++ b/src/plugins/fts/fts-storage.h @@ -0,0 +1,70 @@ +#ifndef FTS_STORAGE_H +#define FTS_STORAGE_H + +#include "mail-storage-private.h" +#include "fts-api.h" + +enum fts_enforced { + FTS_ENFORCED_NO, + FTS_ENFORCED_YES, + FTS_ENFORCED_BODY, +}; + +struct fts_scores { + int refcount; + ARRAY_TYPE(fts_score_map) score_map; +}; + +struct fts_search_level { + ARRAY_TYPE(seq_range) definite_seqs, maybe_seqs; + buffer_t *args_matches; + ARRAY_TYPE(fts_score_map) score_map; +}; + +HASH_TABLE_DEFINE_TYPE(virtual_last_indexed, const char *, void *); + +struct fts_search_context { + union mail_search_module_context module_ctx; + + struct fts_backend *backend; + struct mailbox *box; + struct mailbox_transaction_context *t; + struct mail_search_args *args; + enum fts_lookup_flags flags; + enum fts_enforced enforced; + + pool_t result_pool; + ARRAY(struct fts_search_level) levels; + buffer_t *orig_matches; + + uint32_t first_unindexed_seq; + uint32_t next_unindexed_seq; + HASH_TABLE_TYPE(virtual_last_indexed) last_indexed_virtual_uids; + + /* final scores, combined from all levels */ + struct fts_scores *scores; + + struct fts_indexer_context *indexer_ctx; + struct fts_search_state *search_state; + + bool virtual_mailbox:1; + bool fts_lookup_success:1; + bool indexing_timed_out:1; + bool virtual_seen_unindexed_gaps:1; +}; + +/* Figure out if we want to use full text search indexes and update + backends in fctx accordingly. */ +void fts_search_analyze(struct fts_search_context *fctx); +/* Perform the actual index lookup and update definite_uids and maybe_uids. */ +void fts_search_lookup(struct fts_search_context *fctx); +/* Returns FTS backend for the given mailbox (assumes it has one). */ +struct fts_backend *fts_mailbox_backend(struct mailbox *box); +/* Returns FTS backend for the given mailbox list, or NULL if it has none. */ +struct fts_backend *fts_list_backend(struct mailbox_list *list); + +void fts_mail_allocated(struct mail *mail); +void fts_mail_namespaces_added(struct mail_namespace *ns); +void fts_mailbox_allocated(struct mailbox *box); +void fts_mailbox_list_created(struct mailbox_list *list); +#endif diff --git a/src/plugins/fts/fts-user.c b/src/plugins/fts/fts-user.c new file mode 100644 index 0000000..3c813cd --- /dev/null +++ b/src/plugins/fts/fts-user.c @@ -0,0 +1,423 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "module-context.h" +#include "mail-user.h" +#include "mail-storage-private.h" +#include "mailbox-match-plugin.h" +#include "fts-language.h" +#include "fts-filter.h" +#include "fts-tokenizer.h" +#include "fts-user.h" + +#define FTS_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, fts_user_module) + +struct fts_user { + union mail_user_module_context module_ctx; + int refcount; + + struct fts_language_list *lang_list; + struct fts_user_language *data_lang; + ARRAY_TYPE(fts_user_language) languages, data_languages; + + struct mailbox_match_plugin *autoindex_exclude; +}; + +static MODULE_CONTEXT_DEFINE_INIT(fts_user_module, + &mail_user_module_register); + +static const char *const *str_keyvalues_to_array(const char *str) +{ + const char *key, *value, *const *keyvalues; + ARRAY_TYPE(const_string) arr; + unsigned int i; + + if (str == NULL) + return NULL; + + t_array_init(&arr, 8); + keyvalues = t_strsplit_spaces(str, " "); + for (i = 0; keyvalues[i] != NULL; i++) { + value = strchr(keyvalues[i], '='); + if (value != NULL) + key = t_strdup_until(keyvalues[i], value++); + else { + key = keyvalues[i]; + value = ""; + } + array_push_back(&arr, &key); + array_push_back(&arr, &value); + } + array_append_zero(&arr); + return array_front(&arr); +} + +static int +fts_user_init_languages(struct mail_user *user, struct fts_user *fuser, + const char **error_r) +{ + const char *languages, *unknown; + const char *lang_config[3] = {NULL, NULL, NULL}; + + languages = mail_user_plugin_getenv(user, "fts_languages"); + if (languages == NULL) { + *error_r = "fts_languages setting is missing"; + return -1; + } + + lang_config[1] = mail_user_plugin_getenv(user, "fts_language_config"); + if (lang_config[1] != NULL) + lang_config[0] = "fts_language_config"; + if (fts_language_list_init(lang_config, &fuser->lang_list, error_r) < 0) + return -1; + + if (!fts_language_list_add_names(fuser->lang_list, languages, &unknown)) { + *error_r = t_strdup_printf( + "fts_languages: Unknown language '%s'", unknown); + return -1; + } + if (array_count(fts_language_list_get_all(fuser->lang_list)) == 0) { + *error_r = "fts_languages setting is empty"; + return -1; + } + return 0; +} + +static int +fts_user_create_filters(struct mail_user *user, const struct fts_language *lang, + struct fts_filter **filter_r, const char **error_r) +{ + const struct fts_filter *filter_class; + struct fts_filter *filter = NULL, *parent = NULL; + const char *filters_key, *const *filters, *filter_set_name; + const char *str, *error, *set_key; + unsigned int i; + int ret = 0; + + /* try to get the language-specific filters first */ + filters_key = t_strconcat("fts_filters_", lang->name, NULL); + str = mail_user_plugin_getenv(user, filters_key); + if (str == NULL) { + /* fallback to global filters */ + filters_key = "fts_filters"; + str = mail_user_plugin_getenv(user, filters_key); + if (str == NULL) { + /* No filters */ + *filter_r = NULL; + return 0; + } + } + + filters = t_strsplit_spaces(str, " "); + for (i = 0; filters[i] != NULL; i++) { + filter_class = fts_filter_find(filters[i]); + if (filter_class == NULL) { + *error_r = t_strdup_printf("%s: Unknown filter '%s'", + filters_key, filters[i]); + ret = -1; + break; + } + + /* try the language-specific setting first */ + filter_set_name = t_str_replace(filters[i], '-', '_'); + set_key = t_strdup_printf("fts_filter_%s_%s", + lang->name, filter_set_name); + str = mail_user_plugin_getenv(user, set_key); + if (str == NULL) { + set_key = t_strdup_printf("fts_filter_%s", filter_set_name); + str = mail_user_plugin_getenv(user, set_key); + } + + if (fts_filter_create(filter_class, parent, lang, + str_keyvalues_to_array(str), + &filter, &error) < 0) { + *error_r = t_strdup_printf("%s: %s", set_key, error); + ret = -1; + break; + } + if (parent != NULL) + fts_filter_unref(&parent); + parent = filter; + } + if (ret < 0) { + if (parent != NULL) + fts_filter_unref(&parent); + return -1; + } + *filter_r = filter; + return 0; +} + +static int +fts_user_create_tokenizer(struct mail_user *user, + const struct fts_language *lang, + struct fts_tokenizer **tokenizer_r, bool search, + const char **error_r) +{ + const struct fts_tokenizer *tokenizer_class; + struct fts_tokenizer *tokenizer = NULL, *parent = NULL; + const char *tokenizers_key, *const *tokenizers, *tokenizer_set_name; + const char *str, *error, *set_key; + unsigned int i; + int ret = 0; + + tokenizers_key = t_strconcat("fts_tokenizers_", lang->name, NULL); + str = mail_user_plugin_getenv(user, tokenizers_key); + if (str == NULL) { + str = mail_user_plugin_getenv(user, "fts_tokenizers"); + if (str == NULL) { + *error_r = t_strdup_printf("%s or fts_tokenizers setting must exist", tokenizers_key); + return -1; + } + tokenizers_key = "fts_tokenizers"; + } + + tokenizers = t_strsplit_spaces(str, " "); + + for (i = 0; tokenizers[i] != NULL; i++) { + tokenizer_class = fts_tokenizer_find(tokenizers[i]); + if (tokenizer_class == NULL) { + *error_r = t_strdup_printf("%s: Unknown tokenizer '%s'", + tokenizers_key, tokenizers[i]); + ret = -1; + break; + } + + tokenizer_set_name = t_str_replace(tokenizers[i], '-', '_'); + set_key = t_strdup_printf("fts_tokenizer_%s_%s", tokenizer_set_name, lang->name); + str = mail_user_plugin_getenv(user, set_key); + if (str == NULL) { + set_key = t_strdup_printf("fts_tokenizer_%s", tokenizer_set_name); + str = mail_user_plugin_getenv(user, set_key); + } + + /* tell the tokenizers that we're tokenizing a search string + (instead of tokenizing indexed data) */ + if (search) + str = t_strconcat("search=yes ", str, NULL); + + if (fts_tokenizer_create(tokenizer_class, parent, + str_keyvalues_to_array(str), + &tokenizer, &error) < 0) { + *error_r = t_strdup_printf("%s: %s", set_key, error); + ret = -1; + break; + } + if (parent != NULL) + fts_tokenizer_unref(&parent); + parent = tokenizer; + } + if (ret < 0) { + if (parent != NULL) + fts_tokenizer_unref(&parent); + return -1; + } + *tokenizer_r = tokenizer; + return 0; +} + +static int +fts_user_language_init_tokenizers(struct mail_user *user, + struct fts_user_language *user_lang, + const char **error_r) +{ + if (fts_user_create_tokenizer(user, user_lang->lang, + &user_lang->index_tokenizer, FALSE, + error_r) < 0) + return -1; + + if (fts_user_create_tokenizer(user, user_lang->lang, + &user_lang->search_tokenizer, TRUE, + error_r) < 0) + return -1; + return 0; +} + +struct fts_user_language * +fts_user_language_find(struct mail_user *user, + const struct fts_language *lang) +{ + struct fts_user_language *user_lang; + struct fts_user *fuser = FTS_USER_CONTEXT(user); + + i_assert(fuser != NULL); + array_foreach_elem(&fuser->languages, user_lang) { + if (strcmp(user_lang->lang->name, lang->name) == 0) + return user_lang; + } + return NULL; +} + +static int fts_user_language_create(struct mail_user *user, + struct fts_user *fuser, + const struct fts_language *lang, + const char **error_r) +{ + struct fts_user_language *user_lang; + + user_lang = p_new(user->pool, struct fts_user_language, 1); + user_lang->lang = lang; + array_push_back(&fuser->languages, &user_lang); + + if (fts_user_language_init_tokenizers(user, user_lang, error_r) < 0) + return -1; + if (fts_user_create_filters(user, lang, &user_lang->filter, error_r) < 0) + return -1; + return 0; +} + +static int fts_user_languages_fill_all(struct mail_user *user, + struct fts_user *fuser, + const char **error_r) +{ + const struct fts_language *lang; + + array_foreach_elem(fts_language_list_get_all(fuser->lang_list), lang) { + if (fts_user_language_create(user, fuser, lang, error_r) < 0) + return -1; + } + return 0; +} + +static int +fts_user_init_data_language(struct mail_user *user, struct fts_user *fuser, + const char **error_r) +{ + struct fts_user_language *user_lang; + const char *error; + + user_lang = p_new(user->pool, struct fts_user_language, 1); + user_lang->lang = &fts_language_data; + + if (fts_user_language_init_tokenizers(user, user_lang, error_r) < 0) + return -1; + + if (fts_filter_create(fts_filter_lowercase, NULL, user_lang->lang, NULL, + &user_lang->filter, &error) < 0) + i_unreached(); + i_assert(user_lang->filter != NULL); + + p_array_init(&fuser->data_languages, user->pool, 1); + array_push_back(&fuser->data_languages, &user_lang); + array_push_back(&fuser->languages, &user_lang); + + fuser->data_lang = user_lang; + return 0; +} + +struct fts_language_list *fts_user_get_language_list(struct mail_user *user) +{ + struct fts_user *fuser = FTS_USER_CONTEXT(user); + + i_assert(fuser != NULL); + return fuser->lang_list; +} + +const ARRAY_TYPE(fts_user_language) * +fts_user_get_all_languages(struct mail_user *user) +{ + struct fts_user *fuser = FTS_USER_CONTEXT(user); + + i_assert(fuser != NULL); + return &fuser->languages; +} + +const ARRAY_TYPE(fts_user_language) * +fts_user_get_data_languages(struct mail_user *user) +{ + struct fts_user *fuser = FTS_USER_CONTEXT(user); + + i_assert(fuser != NULL); + return &fuser->data_languages; +} + +struct fts_user_language *fts_user_get_data_lang(struct mail_user *user) +{ + struct fts_user *fuser = FTS_USER_CONTEXT(user); + + i_assert(fuser != NULL); + return fuser->data_lang; +} + +bool fts_user_autoindex_exclude(struct mailbox *box) +{ + struct fts_user *fuser = FTS_USER_CONTEXT(box->storage->user); + + return mailbox_match_plugin_exclude(fuser->autoindex_exclude, box); +} + +static void fts_user_language_free(struct fts_user_language *user_lang) +{ + if (user_lang->filter != NULL) + fts_filter_unref(&user_lang->filter); + if (user_lang->index_tokenizer != NULL) + fts_tokenizer_unref(&user_lang->index_tokenizer); + if (user_lang->search_tokenizer != NULL) + fts_tokenizer_unref(&user_lang->search_tokenizer); +} + +static void fts_user_free(struct fts_user *fuser) +{ + struct fts_user_language *user_lang; + + if (fuser->lang_list != NULL) + fts_language_list_deinit(&fuser->lang_list); + + if (array_is_created(&fuser->languages)) { + array_foreach_elem(&fuser->languages, user_lang) + fts_user_language_free(user_lang); + } + mailbox_match_plugin_deinit(&fuser->autoindex_exclude); +} + +static int +fts_mail_user_init_libfts(struct mail_user *user, struct fts_user *fuser, + const char **error_r) +{ + p_array_init(&fuser->languages, user->pool, 4); + + if (fts_user_init_languages(user, fuser, error_r) < 0 || + fts_user_init_data_language(user, fuser, error_r) < 0) + return -1; + if (fts_user_languages_fill_all(user, fuser, error_r) < 0) + return -1; + return 0; +} + +int fts_mail_user_init(struct mail_user *user, bool initialize_libfts, + const char **error_r) +{ + struct fts_user *fuser = FTS_USER_CONTEXT(user); + + if (fuser != NULL) { + /* multiple fts plugins are loaded */ + fuser->refcount++; + return 0; + } + + fuser = p_new(user->pool, struct fts_user, 1); + fuser->refcount = 1; + if (initialize_libfts) { + if (fts_mail_user_init_libfts(user, fuser, error_r) < 0) { + fts_user_free(fuser); + return -1; + } + } + fuser->autoindex_exclude = + mailbox_match_plugin_init(user, "fts_autoindex_exclude"); + + MODULE_CONTEXT_SET(user, fts_user_module, fuser); + return 0; +} + +void fts_mail_user_deinit(struct mail_user *user) +{ + struct fts_user *fuser = FTS_USER_CONTEXT(user); + + if (fuser != NULL) { + i_assert(fuser->refcount > 0); + if (--fuser->refcount == 0) + fts_user_free(fuser); + } +} diff --git a/src/plugins/fts/fts-user.h b/src/plugins/fts/fts-user.h new file mode 100644 index 0000000..043f4e1 --- /dev/null +++ b/src/plugins/fts/fts-user.h @@ -0,0 +1,27 @@ +#ifndef FTS_USER_H +#define FTS_USER_H + +struct fts_user_language { + const struct fts_language *lang; + struct fts_filter *filter; + struct fts_tokenizer *index_tokenizer, *search_tokenizer; +}; +ARRAY_DEFINE_TYPE(fts_user_language, struct fts_user_language *); + +struct fts_user_language * +fts_user_language_find(struct mail_user *user, + const struct fts_language *lang); +struct fts_language_list *fts_user_get_language_list(struct mail_user *user); +const ARRAY_TYPE(fts_user_language) * +fts_user_get_all_languages(struct mail_user *user); +struct fts_user_language *fts_user_get_data_lang(struct mail_user *user); +const ARRAY_TYPE(fts_user_language) * +fts_user_get_data_languages(struct mail_user *user); + +bool fts_user_autoindex_exclude(struct mailbox *box); + +int fts_mail_user_init(struct mail_user *user, bool initialize_libfts, + const char **error_r); +void fts_mail_user_deinit(struct mail_user *user); + +#endif diff --git a/src/plugins/fts/xml2text.c b/src/plugins/fts/xml2text.c new file mode 100644 index 0000000..f3c573c --- /dev/null +++ b/src/plugins/fts/xml2text.c @@ -0,0 +1,44 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "message-parser.h" +#include "fts-parser.h" + +#include <unistd.h> + +int main(void) +{ + struct fts_parser *parser; + unsigned char buf[IO_BLOCK_SIZE]; + struct message_block block; + ssize_t ret; + struct fts_parser_context parser_context = {.content_type = "text/html"}; + + lib_init(); + + parser = fts_parser_html.try_init(&parser_context); + i_assert(parser != NULL); + + i_zero(&block); + while ((ret = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { + block.data = buf; + block.size = ret; + parser->v.more(parser, &block); + if (write(STDOUT_FILENO, block.data, block.size) < 0) + i_fatal("write(stdout) failed: %m"); + } + if (ret < 0) + i_fatal("read(stdin) failed: %m"); + + for (;;) { + block.size = 0; + parser->v.more(parser, &block); + if (block.size == 0) + break; + if (write(STDOUT_FILENO, block.data, block.size) < 0) + i_fatal("write(stdout) failed: %m"); + } + + lib_deinit(); + return 0; +} diff --git a/src/plugins/imap-acl/Makefile.am b/src/plugins/imap-acl/Makefile.am new file mode 100644 index 0000000..c683974 --- /dev/null +++ b/src/plugins/imap-acl/Makefile.am @@ -0,0 +1,31 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/imapc \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/acl \ + -I$(top_srcdir)/src/lib-imap-client \ + -I$(top_srcdir)/src/lib-ssl-iostream + +imap_moduledir = $(moduledir) + +NOPLUGIN_LDFLAGS = +lib02_imap_acl_plugin_la_LDFLAGS = -module -avoid-version + +imap_module_LTLIBRARIES = \ + lib02_imap_acl_plugin.la + +if DOVECOT_PLUGIN_DEPS +lib02_imap_acl_plugin_la_LIBADD = \ + ../acl/lib01_acl_plugin.la +endif + +lib02_imap_acl_plugin_la_SOURCES = \ + imap-acl-plugin.c + +noinst_HEADERS = \ + imap-acl-plugin.h diff --git a/src/plugins/imap-acl/Makefile.in b/src/plugins/imap-acl/Makefile.in new file mode 100644 index 0000000..9f284af --- /dev/null +++ b/src/plugins/imap-acl/Makefile.in @@ -0,0 +1,831 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/imap-acl +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(imap_moduledir)" +LTLIBRARIES = $(imap_module_LTLIBRARIES) +@DOVECOT_PLUGIN_DEPS_TRUE@lib02_imap_acl_plugin_la_DEPENDENCIES = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../acl/lib01_acl_plugin.la +am_lib02_imap_acl_plugin_la_OBJECTS = imap-acl-plugin.lo +lib02_imap_acl_plugin_la_OBJECTS = \ + $(am_lib02_imap_acl_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib02_imap_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib02_imap_acl_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/imap-acl-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib02_imap_acl_plugin_la_SOURCES) +DIST_SOURCES = $(lib02_imap_acl_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/imapc \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/acl \ + -I$(top_srcdir)/src/lib-imap-client \ + -I$(top_srcdir)/src/lib-ssl-iostream + +imap_moduledir = $(moduledir) +lib02_imap_acl_plugin_la_LDFLAGS = -module -avoid-version +imap_module_LTLIBRARIES = \ + lib02_imap_acl_plugin.la + +@DOVECOT_PLUGIN_DEPS_TRUE@lib02_imap_acl_plugin_la_LIBADD = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../acl/lib01_acl_plugin.la + +lib02_imap_acl_plugin_la_SOURCES = \ + imap-acl-plugin.c + +noinst_HEADERS = \ + imap-acl-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/imap-acl/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/imap-acl/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-imap_moduleLTLIBRARIES: $(imap_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(imap_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(imap_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(imap_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(imap_moduledir)"; \ + } + +uninstall-imap_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(imap_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(imap_moduledir)/$$f"; \ + done + +clean-imap_moduleLTLIBRARIES: + -test -z "$(imap_module_LTLIBRARIES)" || rm -f $(imap_module_LTLIBRARIES) + @list='$(imap_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib02_imap_acl_plugin.la: $(lib02_imap_acl_plugin_la_OBJECTS) $(lib02_imap_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib02_imap_acl_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib02_imap_acl_plugin_la_LINK) -rpath $(imap_moduledir) $(lib02_imap_acl_plugin_la_OBJECTS) $(lib02_imap_acl_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-acl-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(imap_moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/imap-acl-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-imap_moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/imap-acl-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-imap_moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-imap_moduleLTLIBRARIES install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-imap_moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/imap-acl/imap-acl-plugin.c b/src/plugins/imap-acl/imap-acl-plugin.c new file mode 100644 index 0000000..5d1c3be --- /dev/null +++ b/src/plugins/imap-acl/imap-acl-plugin.c @@ -0,0 +1,1128 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "imap-quote.h" +#include "imap-resp-code.h" +#include "imap-commands.h" +#include "imapc-client.h" +#include "imapc-client-private.h" +#include "imapc-settings.h" +#include "imapc-storage.h" +#include "mail-storage.h" +#include "mail-namespace.h" +#include "mail-storage-private.h" +#include "module-context.h" +#include "acl-api.h" +#include "acl-storage.h" +#include "acl-plugin.h" +#include "imap-acl-plugin.h" + + +#define ERROR_NOT_ADMIN "["IMAP_RESP_CODE_NOPERM"] " \ + "You lack administrator privileges on this mailbox." + +#define IMAP_ACL_ANYONE "anyone" +#define IMAP_ACL_AUTHENTICATED "authenticated" +#define IMAP_ACL_OWNER "owner" +#define IMAP_ACL_GROUP_PREFIX "$" +#define IMAP_ACL_GROUP_OVERRIDE_PREFIX "!$" +#define IMAP_ACL_GLOBAL_PREFIX "#" + +#define IMAP_ACL_CONTEXT(obj) \ + MODULE_CONTEXT(obj, imap_acl_storage_module) +#define IMAP_ACL_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, imap_acl_storage_module) + +struct imap_acl_letter_map { + char letter; + const char *name; +}; + +static const struct imap_acl_letter_map imap_acl_letter_map[] = { + { 'l', MAIL_ACL_LOOKUP }, + { 'r', MAIL_ACL_READ }, + { 'w', MAIL_ACL_WRITE }, + { 's', MAIL_ACL_WRITE_SEEN }, + { 't', MAIL_ACL_WRITE_DELETED }, + { 'i', MAIL_ACL_INSERT }, + { 'p', MAIL_ACL_POST }, + { 'e', MAIL_ACL_EXPUNGE }, + { 'k', MAIL_ACL_CREATE }, + { 'x', MAIL_ACL_DELETE }, + { 'a', MAIL_ACL_ADMIN }, + { '\0', NULL } +}; + +struct imap_acl_storage { + union mail_storage_module_context module_ctx; + struct imapc_acl_context *iacl_ctx; +}; + +struct imap_acl_storage_module imap_acl_storage_module = + MODULE_CONTEXT_INIT(&mail_storage_module_register); + +const char *imap_acl_plugin_version = DOVECOT_ABI_VERSION; + +static struct module *imap_acl_module; +static imap_client_created_func_t *next_hook_client_created; + +enum imap_acl_cmd { + IMAP_ACL_CMD_MYRIGHTS = 0, + IMAP_ACL_CMD_GETACL, + IMAP_ACL_CMD_SETACL, + IMAP_ACL_CMD_DELETEACL, +}; + +const char *imapc_acl_cmd_names[] = { + "MYRIGHTS", "GETACL", "SETACL", "DELETEACL" +}; + +struct imapc_acl_context { + struct imapc_client *client; + enum imap_acl_cmd proxy_cmd; + struct mail_storage *storage; + struct imapc_mailbox *expected_box; + string_t *reply; +}; + +static int +acl_mailbox_open_as_admin(struct client_command_context *cmd, + struct mailbox *box, const char *name) +{ + enum mailbox_existence existence = MAILBOX_EXISTENCE_NONE; + int ret; + + if (ACL_USER_CONTEXT(cmd->client->user) == NULL) { + client_send_command_error(cmd, "ACLs disabled."); + return 0; + } + + if (mailbox_exists(box, TRUE, &existence) == 0 && + existence == MAILBOX_EXISTENCE_SELECT) { + ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN); + if (ret > 0) + return ret; + } + + /* mailbox doesn't exist / not an administrator. */ + if (existence != MAILBOX_EXISTENCE_SELECT || + acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP) <= 0) { + client_send_tagline(cmd, t_strdup_printf( + "NO ["IMAP_RESP_CODE_NONEXISTENT"] " + MAIL_ERRSTR_MAILBOX_NOT_FOUND, name)); + } else { + client_send_tagline(cmd, "NO "ERROR_NOT_ADMIN); + } + return 0; +} + +static const struct imap_acl_letter_map * +imap_acl_letter_map_find(const char *name) +{ + unsigned int i; + + for (i = 0; imap_acl_letter_map[i].name != NULL; i++) { + if (strcmp(imap_acl_letter_map[i].name, name) == 0) + return &imap_acl_letter_map[i]; + } + return NULL; +} + +static void +imap_acl_write_rights_list(string_t *dest, const char *const *rights) +{ + const struct imap_acl_letter_map *map; + unsigned int i; + size_t orig_len = str_len(dest); + bool append_c = FALSE, append_d = FALSE; + + for (i = 0; rights[i] != NULL; i++) { + /* write only letters */ + map = imap_acl_letter_map_find(rights[i]); + if (map != NULL) { + str_append_c(dest, map->letter); + if (map->letter == 'k' || map->letter == 'x') + append_c = TRUE; + if (map->letter == 't' || map->letter == 'e') + append_d = TRUE; + } + } + if (append_c) + str_append_c(dest, 'c'); + if (append_d) + str_append_c(dest, 'd'); + if (orig_len == str_len(dest)) + str_append(dest, "\"\""); +} + +static void +imap_acl_write_right(string_t *dest, string_t *tmp, + const struct acl_rights *right, bool neg) +{ + const char *const *rights = neg ? right->neg_rights : right->rights; + + str_truncate(tmp, 0); + if (neg) str_append_c(tmp,'-'); + if (right->global) + str_append(tmp, IMAP_ACL_GLOBAL_PREFIX); + switch (right->id_type) { + case ACL_ID_ANYONE: + str_append(tmp, IMAP_ACL_ANYONE); + break; + case ACL_ID_AUTHENTICATED: + str_append(tmp, IMAP_ACL_AUTHENTICATED); + break; + case ACL_ID_OWNER: + str_append(tmp, IMAP_ACL_OWNER); + break; + case ACL_ID_USER: + str_append(tmp, right->identifier); + break; + case ACL_ID_GROUP: + str_append(tmp, IMAP_ACL_GROUP_PREFIX); + str_append(tmp, right->identifier); + break; + case ACL_ID_GROUP_OVERRIDE: + str_append(tmp, IMAP_ACL_GROUP_OVERRIDE_PREFIX); + str_append(tmp, right->identifier); + break; + case ACL_ID_TYPE_COUNT: + i_unreached(); + } + + imap_append_astring(dest, str_c(tmp)); + str_append_c(dest, ' '); + imap_acl_write_rights_list(dest, rights); +} + +static bool +acl_rights_is_owner(struct acl_backend *backend, + const struct acl_rights *rights) +{ + switch (rights->id_type) { + case ACL_ID_OWNER: + return TRUE; + case ACL_ID_USER: + return acl_backend_user_name_equals(backend, + rights->identifier); + default: + return FALSE; + } +} + +static bool have_positive_owner_rights(struct acl_backend *backend, + struct acl_object *aclobj) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + bool ret = FALSE; + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (acl_rights_is_owner(backend, &rights)) { + if (rights.rights != NULL) { + ret = TRUE; + break; + } + } + } + (void)acl_object_list_deinit(&iter); + return ret; +} + +static int +imap_acl_write_aclobj(string_t *dest, struct acl_backend *backend, + struct acl_object *aclobj, bool convert_owner, + bool add_default) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + string_t *tmp; + const char *username; + size_t orig_len = str_len(dest); + bool seen_owner = FALSE, seen_positive_owner = FALSE; + int ret; + + username = acl_backend_get_acl_username(backend); + if (username == NULL) + convert_owner = FALSE; + + tmp = t_str_new(128); + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (acl_rights_is_owner(backend, &rights)) { + if (rights.id_type == ACL_ID_OWNER && convert_owner) { + rights.id_type = ACL_ID_USER; + rights.identifier = username; + } + if (seen_owner && convert_owner) { + /* oops, we have both owner and user=myself. + can't do the conversion, so try again. */ + str_truncate(dest, orig_len); + return imap_acl_write_aclobj(dest, backend, + aclobj, FALSE, + add_default); + } + seen_owner = TRUE; + if (rights.rights != NULL) + seen_positive_owner = TRUE; + } + + if (rights.rights != NULL) { + str_append_c(dest, ' '); + imap_acl_write_right(dest, tmp, &rights, FALSE); + } + if (rights.neg_rights != NULL) { + str_append_c(dest, ' '); + imap_acl_write_right(dest, tmp, &rights, TRUE); + } + } + ret = acl_object_list_deinit(&iter); + + if (!seen_positive_owner && username != NULL && add_default) { + /* no positive owner rights returned, write default ACLs */ + i_zero(&rights); + if (!convert_owner) { + rights.id_type = ACL_ID_OWNER; + } else { + rights.id_type = ACL_ID_USER; + rights.identifier = username; + } + rights.rights = acl_object_get_default_rights(aclobj); + if (rights.rights != NULL) { + str_append_c(dest, ' '); + imap_acl_write_right(dest, tmp, &rights, FALSE); + } + } + return ret; +} + +static const char * +imapc_acl_get_mailbox_error(struct imapc_mailbox *mbox) +{ + enum mail_error err; + const char *error = mailbox_get_last_error(&mbox->box, &err); + const char *resp_code; + string_t *str = t_str_new(128); + + if (imapc_mail_error_to_resp_text_code(err, &resp_code)) + str_printfa(str, "[%s] ", resp_code); + str_append(str, error); + + return str_c(str); +} + +static void +imapc_acl_myrights_untagged_cb(const struct imapc_untagged_reply *reply, + struct imapc_storage_client *client) +{ + struct imap_acl_storage *iacl_storage = + IMAP_ACL_CONTEXT_REQUIRE(&client->_storage->storage); + struct imapc_acl_context *ctx = iacl_storage->iacl_ctx; + const char *value; + + if (!imap_arg_get_astring(&reply->args[0], &value) || + ctx->expected_box == NULL) + return; + + /* Untagged reply was not meant for this mailbox */ + if (!imapc_mailbox_name_equals(ctx->expected_box, value)) + return; + + /* copy rights from reply to string + <args[0](mailbox)> <args[1](rights)> */ + if (imap_arg_get_astring(&reply->args[1], &value)) { + str_append(ctx->reply, value); + } else { + /* Rights could not been parsed mark this + failed and clear the prepared reply. */ + str_truncate(ctx->reply, 0); + } + /* Just handle one untagged reply. */ + ctx->expected_box = NULL; +} + +static void +imapc_acl_getacl_untagged_cb(const struct imapc_untagged_reply *reply, + struct imapc_storage_client *client) +{ + struct imap_acl_storage *iacl_storage = + IMAP_ACL_CONTEXT_REQUIRE(&client->_storage->storage); + struct imapc_acl_context *ctx = iacl_storage->iacl_ctx; + const char *key, *value; + unsigned int i; + + if (!imap_arg_get_astring(&reply->args[0], &value) || + ctx->expected_box == NULL) + return; + + /* Untagged reply was not meant for this mailbox */ + if (!imapc_mailbox_name_equals(ctx->expected_box, value)) + return; + + /* Parse key:value pairs of user:right and append them + to the prepared reply. */ + for (i = 1; reply->args[i].type != IMAP_ARG_EOL; i += 2) { + if (imap_arg_get_astring(&reply->args[i], &key) && + imap_arg_get_astring(&reply->args[i+1], &value)) { + str_append(iacl_storage->iacl_ctx->reply, key); + str_append_c(iacl_storage->iacl_ctx->reply, ' '); + str_append(iacl_storage->iacl_ctx->reply, value); + str_append_c(iacl_storage->iacl_ctx->reply, ' '); + } else { + /* Rights could not been parsed clear prepared reply. */ + str_truncate(ctx->reply, 0); + break; + } + } + /* Just handle one untagged reply. */ + ctx->expected_box = NULL; +} + +static struct imapc_acl_context * +imap_acl_cmd_context_alloc(struct imapc_mailbox *mbox) +{ + struct imapc_acl_context *iacl_ctx = + p_new(mbox->box.storage->pool, struct imapc_acl_context, 1); + iacl_ctx->reply = str_new(mbox->box.storage->pool, 128); + return iacl_ctx; +} + +static void imap_acl_cmd_context_init(struct imapc_acl_context *iacl_ctx, + struct imapc_mailbox *mbox, + enum imap_acl_cmd proxy_cmd) +{ + iacl_ctx->client = mbox->storage->client->client; + iacl_ctx->proxy_cmd = proxy_cmd; + iacl_ctx->expected_box = mbox; + str_truncate(iacl_ctx->reply, 0); +} + +static struct imapc_acl_context * +imap_acl_cmd_context_register(struct imapc_mailbox *mbox, enum imap_acl_cmd proxy_cmd) +{ + struct mailbox *box = &mbox->box; + struct imap_acl_storage *iacl_storage = IMAP_ACL_CONTEXT(box->storage); + + if (iacl_storage == NULL) { + iacl_storage = p_new(box->storage->pool, struct imap_acl_storage, 1); + MODULE_CONTEXT_SET(box->storage, imap_acl_storage_module, iacl_storage); + iacl_storage->iacl_ctx = imap_acl_cmd_context_alloc(mbox); + } + + imap_acl_cmd_context_init(iacl_storage->iacl_ctx, mbox, proxy_cmd); + + return iacl_storage->iacl_ctx; +} + +static const char *imap_acl_get_mailbox_name(const struct mail_namespace *ns, + const char *mailbox) +{ + /* Strip namespace prefix from mailbox name or append "INBOX" if + mailbox is "" and mailbox is in shared namespace. */ + + if (ns->prefix_len == 0) + return mailbox; + + i_assert(ns->prefix_len >= 1); + + if ((mailbox[ns->prefix_len-1] == '\0' || + mailbox[ns->prefix_len] == '\0') && + strncmp(mailbox, ns->prefix, ns->prefix_len-1) == 0 && + ns->type == MAIL_NAMESPACE_TYPE_SHARED) { + /* Given mailbox name does not contain an actual mailbox name + but just the namespace prefix so default to "INBOX". */ + return "INBOX"; + } + + if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 && + strcasecmp(mailbox, "INBOX") == 0) { + /* For user INBOX always use INBOX and ignore namespace + prefix. */ + return "INBOX"; + } + + i_assert(strncmp(mailbox, ns->prefix, ns->prefix_len-1) == 0); + return mailbox+ns->prefix_len; +} + +static const char * +imapc_acl_prepare_cmd(string_t *reply_r, const char *mailbox, + const struct mail_namespace *ns, const char *cmd_args, + const enum imap_acl_cmd proxy_cmd) +{ + string_t *proxy_cmd_str = t_str_new(128); + /* Prepare proxy_cmd and untagged replies */ + switch (proxy_cmd) { + case IMAP_ACL_CMD_MYRIGHTS: + /* Prepare client untagged reply. */ + str_append(reply_r, "* MYRIGHTS "); + imap_append_astring(reply_r, mailbox); + str_append_c(reply_r, ' '); + + str_append(proxy_cmd_str, "MYRIGHTS "); + /* Strip namespace prefix. */ + imap_append_astring(proxy_cmd_str, + imap_acl_get_mailbox_name(ns, mailbox)); + break; + case IMAP_ACL_CMD_GETACL: + /* Prepare client untagged reply. */ + str_append(reply_r, "* ACL "); + imap_append_astring(reply_r, mailbox); + str_append_c(reply_r, ' '); + + str_append(proxy_cmd_str, "GETACL "); + imap_append_astring(proxy_cmd_str, + imap_acl_get_mailbox_name(ns, mailbox)); + break; + case IMAP_ACL_CMD_SETACL: + /* No contents in untagged replies for SETACL */ + str_append(proxy_cmd_str, "SETACL "); + imap_append_astring(proxy_cmd_str, + imap_acl_get_mailbox_name(ns, mailbox)); + + str_append_c(proxy_cmd_str, ' '); + str_append(proxy_cmd_str, cmd_args); + break; + case IMAP_ACL_CMD_DELETEACL: + /* No contents in untagged replies for DELETEACL */ + str_append(proxy_cmd_str, "DELETEACL "); + imap_append_astring(proxy_cmd_str, + imap_acl_get_mailbox_name(ns, mailbox)); + + str_append_c(proxy_cmd_str, ' '); + str_append(proxy_cmd_str, cmd_args); + break; + default: + i_unreached(); + } + return str_c(proxy_cmd_str); +} + +static struct imapc_command * +imapc_acl_simple_context_init(struct imapc_simple_context *ctx, + struct imapc_mailbox *mbox) +{ + imapc_simple_context_init(ctx, mbox->storage->client); + return imapc_client_cmd(mbox->storage->client->client, + imapc_simple_callback, ctx); +} + +static void imapc_acl_send_client_reply(struct imapc_acl_context *iacl_ctx, + struct client_command_context *orig_cmd, + const char *success_tagged_reply) +{ + if (str_len(iacl_ctx->reply) == 0) + client_send_tagline(orig_cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG); + else { + client_send_line(orig_cmd->client, str_c(iacl_ctx->reply)); + client_send_tagline(orig_cmd, success_tagged_reply); + } +} + +static bool imap_acl_proxy_cmd(struct mailbox *box, + const char *mailbox, + const char *cmd_args, + const struct mail_namespace *ns, + struct client_command_context *orig_cmd, + const enum imap_acl_cmd proxy_cmd) +{ + struct imapc_acl_context *iacl_ctx; + struct imapc_simple_context ctx; + struct imapc_command *imapc_cmd; + const char *proxy_cmd_str; + + if (strcmp(box->storage->name, "imapc") != 0) { + /* Storage is not "imapc". */ + return FALSE; + } + + struct imapc_mailbox *mbox = IMAPC_MAILBOX(box); + if (!IMAPC_HAS_FEATURE(mbox->storage, IMAPC_FEATURE_ACL)) { + /* Storage is "imapc" but no proxying of ACL commands should + be done. */ + return FALSE; + } + + iacl_ctx = imap_acl_cmd_context_register(mbox, proxy_cmd); + + /* Register callbacks for untagged replies */ + imapc_storage_client_register_untagged(mbox->storage->client, "ACL", + imapc_acl_getacl_untagged_cb); + imapc_storage_client_register_untagged(mbox->storage->client, "MYRIGHTS", + imapc_acl_myrights_untagged_cb); + + imapc_cmd = imapc_acl_simple_context_init(&ctx, mbox); + + /* Prepare untagged replies and return proxy_cmd */ + proxy_cmd_str = imapc_acl_prepare_cmd(iacl_ctx->reply, mailbox, + ns, cmd_args, proxy_cmd); + + imapc_command_send(imapc_cmd, proxy_cmd_str); + imapc_simple_run(&ctx, &imapc_cmd); + + if (ctx.ret != 0) { + /* If the remote replied BAD or NO send NO. */ + client_send_tagline(orig_cmd, + t_strdup_printf("NO %s", imapc_acl_get_mailbox_error(mbox))); + } else { + /* Command was OK on remote backend, send untagged reply from + ctx.str and tagged reply. */ + switch (iacl_ctx->proxy_cmd) { + case IMAP_ACL_CMD_DELETEACL: + client_send_tagline(orig_cmd, "OK Deleteacl complete."); + break; + case IMAP_ACL_CMD_GETACL: + imapc_acl_send_client_reply(iacl_ctx, + orig_cmd, + "OK Getacl complete."); + break; + case IMAP_ACL_CMD_MYRIGHTS: + imapc_acl_send_client_reply(iacl_ctx, + orig_cmd, + "OK Myrights complete."); + break; + case IMAP_ACL_CMD_SETACL: + client_send_tagline(orig_cmd, "OK Setacl complete."); + break; + default: + i_unreached(); + } + } + + /* Unregister callbacks for untagged replies */ + imapc_storage_client_unregister_untagged(mbox->storage->client, "MYRIGHTS"); + imapc_storage_client_unregister_untagged(mbox->storage->client, "ACL"); + return TRUE; +} + +static void imap_acl_cmd_getacl(struct mailbox *box, struct mail_namespace *ns, + const char *mailbox, + struct client_command_context *cmd) +{ + struct acl_backend *backend; + string_t *str; + int ret; + + if (acl_mailbox_open_as_admin(cmd, box, mailbox) <= 0) + return; + + backend = acl_mailbox_list_get_backend(ns->list); + + str = t_str_new(128); + str_append(str, "* ACL "); + imap_append_astring(str, mailbox); + + ret = imap_acl_write_aclobj(str, backend, + acl_mailbox_get_aclobj(box), TRUE, + ns->type == MAIL_NAMESPACE_TYPE_PRIVATE); + if (ret > -1) { + client_send_line(cmd->client, str_c(str)); + client_send_tagline(cmd, "OK Getacl completed."); + } else { + client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG); + } +} + +static bool cmd_getacl(struct client_command_context *cmd) +{ + struct mail_namespace *ns; + struct mailbox *box; + const char *mailbox, *orig_mailbox; + + if (!client_read_string_args(cmd, 1, &mailbox)) + return FALSE; + orig_mailbox = mailbox; + + ns = client_find_namespace(cmd, &mailbox); + if (ns == NULL) + return TRUE; + + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + /* If the location is remote and imapc_feature acl is enabled, proxy the + command to the configured imapc location. */ + if (!imap_acl_proxy_cmd(box, orig_mailbox, NULL, ns, cmd, IMAP_ACL_CMD_GETACL)) + imap_acl_cmd_getacl(box, ns, orig_mailbox, cmd); + mailbox_free(&box); + return TRUE; +} + +static void imap_acl_cmd_myrights(struct mailbox *box, const char *mailbox, + struct client_command_context *cmd) +{ + const char *const *rights; + string_t *str = t_str_new(128); + + if (acl_object_get_my_rights(acl_mailbox_get_aclobj(box), + pool_datastack_create(), &rights) < 0) { + client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG); + return; + } + + /* Post right alone doesn't give permissions to see if the mailbox + exists or not. Only mail deliveries care about that. */ + if (*rights == NULL || + (strcmp(*rights, MAIL_ACL_POST) == 0 && rights[1] == NULL)) { + client_send_tagline(cmd, t_strdup_printf( + "NO ["IMAP_RESP_CODE_NONEXISTENT"] " + MAIL_ERRSTR_MAILBOX_NOT_FOUND, mailbox)); + return; + } + + str_append(str, "* MYRIGHTS "); + imap_append_astring(str, mailbox); + str_append_c(str, ' '); + imap_acl_write_rights_list(str, rights); + + client_send_line(cmd->client, str_c(str)); + client_send_tagline(cmd, "OK Myrights completed."); +} + +static bool cmd_myrights(struct client_command_context *cmd) +{ + struct mail_namespace *ns; + struct mailbox *box; + const char *mailbox, *orig_mailbox; + + if (!client_read_string_args(cmd, 1, &mailbox)) + return FALSE; + orig_mailbox = mailbox; + + if (ACL_USER_CONTEXT(cmd->client->user) == NULL) { + client_send_command_error(cmd, "ACLs disabled."); + return TRUE; + } + + ns = client_find_namespace(cmd, &mailbox); + if (ns == NULL) + return TRUE; + + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + + /* If the location is remote and imapc_feature acl is enabled, proxy the + command to the configured imapc location. */ + if (!imap_acl_proxy_cmd(box, orig_mailbox, NULL, ns, + cmd, IMAP_ACL_CMD_MYRIGHTS)) + imap_acl_cmd_myrights(box, orig_mailbox, cmd); + mailbox_free(&box); + return TRUE; +} + +static bool cmd_listrights(struct client_command_context *cmd) +{ + struct mailbox *box; + struct mail_namespace *ns; + const char *mailbox, *orig_mailbox, *identifier; + string_t *str; + + if (!client_read_string_args(cmd, 2, &mailbox, &identifier)) + return FALSE; + orig_mailbox = mailbox; + + ns = client_find_namespace(cmd, &mailbox); + if (ns == NULL) + return TRUE; + + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + + str = t_str_new(128); + str_append(str, "* LISTRIGHTS "); + imap_append_astring(str, orig_mailbox); + str_append_c(str, ' '); + imap_append_astring(str, identifier); + str_append_c(str, ' '); + str_append(str, "\"\" l r w s t p i e k x a c d"); + + client_send_line(cmd->client, str_c(str)); + client_send_tagline(cmd, "OK Listrights completed."); + mailbox_free(&box); + return TRUE; +} + +static int +imap_acl_letters_parse(const char *letters, const char *const **rights_r, + const char **client_error_r) +{ + static const char *acl_k = MAIL_ACL_CREATE; + static const char *acl_x = MAIL_ACL_DELETE; + static const char *acl_e = MAIL_ACL_EXPUNGE; + static const char *acl_t = MAIL_ACL_WRITE_DELETED; + ARRAY_TYPE(const_string) rights; + unsigned int i; + + t_array_init(&rights, 64); + for (; *letters != '\0'; letters++) { + for (i = 0; imap_acl_letter_map[i].name != NULL; i++) { + if (imap_acl_letter_map[i].letter == *letters) { + array_push_back(&rights, + &imap_acl_letter_map[i].name); + break; + } + } + if (imap_acl_letter_map[i].name == NULL) { + /* Handling of obsolete rights as virtual + rights according to RFC 4314 */ + switch (*letters) { + case 'c': + array_push_back(&rights, &acl_k); + array_push_back(&rights, &acl_x); + break; + case 'd': + array_push_back(&rights, &acl_e); + array_push_back(&rights, &acl_t); + break; + default: + *client_error_r = t_strdup_printf( + "Invalid ACL right: %c", *letters); + return -1; + } + } + } + array_append_zero(&rights); + *rights_r = array_front(&rights); + return 0; +} + +static bool acl_anyone_allow(struct mail_user *user) +{ + const char *env; + + env = mail_user_plugin_getenv(user, "acl_anyone"); + return env != NULL && strcmp(env, "allow") == 0; +} + +static int +imap_acl_identifier_parse(struct client_command_context *cmd, + const char *id, struct acl_rights *rights, + bool check_anyone, const char **client_error_r) +{ + struct mail_user *user = cmd->client->user; + + if (str_begins(id, IMAP_ACL_GLOBAL_PREFIX)) { + *client_error_r = t_strdup_printf("Global ACLs can't be modified: %s", + id); + return -1; + } + + if (strcmp(id, IMAP_ACL_ANYONE) == 0) { + if (check_anyone && !acl_anyone_allow(user)) { + *client_error_r = "'anyone' identifier is disallowed"; + return -1; + } + rights->id_type = ACL_ID_ANYONE; + } else if (strcmp(id, IMAP_ACL_AUTHENTICATED) == 0) { + if (check_anyone && !acl_anyone_allow(user)) { + *client_error_r = "'authenticated' identifier is disallowed"; + return -1; + } + rights->id_type = ACL_ID_AUTHENTICATED; + } else if (strcmp(id, IMAP_ACL_OWNER) == 0) + rights->id_type = ACL_ID_OWNER; + else if (str_begins(id, IMAP_ACL_GROUP_PREFIX)) { + rights->id_type = ACL_ID_GROUP; + rights->identifier = id + strlen(IMAP_ACL_GROUP_PREFIX); + } else if (str_begins(id, IMAP_ACL_GROUP_OVERRIDE_PREFIX)) { + rights->id_type = ACL_ID_GROUP_OVERRIDE; + rights->identifier = id + + strlen(IMAP_ACL_GROUP_OVERRIDE_PREFIX); + } else { + rights->id_type = ACL_ID_USER; + rights->identifier = id; + } + return 0; +} + +static void imap_acl_update_ensure_keep_admins(struct acl_backend *backend, + struct acl_object *aclobj, + struct acl_rights_update *update) +{ + static const char *acl_admin = MAIL_ACL_ADMIN; + const char *const *rights = update->rights.rights; + const char *const *default_rights; + ARRAY_TYPE(const_string) new_rights; + unsigned int i; + + t_array_init(&new_rights, 64); + for (i = 0; rights[i] != NULL; i++) { + if (strcmp(rights[i], MAIL_ACL_ADMIN) == 0) + break; + array_push_back(&new_rights, &rights[i]); + } + + switch (update->modify_mode) { + case ACL_MODIFY_MODE_ADD: + if (have_positive_owner_rights(backend, aclobj)) + return; + + /* adding initial rights for a user. we need to add + the defaults also. don't worry about duplicates. */ + for (; rights[i] != NULL; i++) + array_push_back(&new_rights, &rights[i]); + default_rights = acl_object_get_default_rights(aclobj); + for (i = 0; default_rights[i] != NULL; i++) + array_push_back(&new_rights, &default_rights[i]); + break; + case ACL_MODIFY_MODE_REMOVE: + if (rights[i] == NULL) + return; + + /* skip over the ADMIN removal and add the rest */ + for (i++; rights[i] != NULL; i++) + array_push_back(&new_rights, &rights[i]); + break; + case ACL_MODIFY_MODE_REPLACE: + if (rights[i] != NULL) + return; + + /* add the missing ADMIN right */ + array_push_back(&new_rights, &acl_admin); + break; + default: + return; + } + array_append_zero(&new_rights); + update->rights.rights = array_front(&new_rights); +} + +static int +cmd_acl_mailbox_update(struct mailbox *box, + const struct acl_rights_update *update, + const char **client_error_r) +{ + struct mailbox_transaction_context *t; + int ret; + + if (mailbox_open(box) < 0) { + *client_error_r = mailbox_get_last_error(box, NULL); + return -1; + } + + t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, + __func__); + ret = acl_mailbox_update_acl(t, update); + if (mailbox_transaction_commit(&t) < 0) + ret = -1; + *client_error_r = MAIL_ERRSTR_CRITICAL_MSG; + return ret; +} + +static void imap_acl_cmd_setacl(struct mailbox *box, struct mail_namespace *ns, + const char *mailbox, const char *identifier, + const char *rights, + struct client_command_context *cmd) +{ + struct acl_backend *backend; + struct acl_object *aclobj; + struct acl_rights_update update; + struct acl_rights *r; + const char *client_error; + bool negative = FALSE; + + i_zero(&update); + if (*identifier == '-') { + negative = TRUE; + identifier++; + } + + switch (*rights) { + case '-': + update.modify_mode = ACL_MODIFY_MODE_REMOVE; + rights++; + break; + case '+': + update.modify_mode = ACL_MODIFY_MODE_ADD; + rights++; + break; + default: + update.modify_mode = ACL_MODIFY_MODE_REPLACE; + break; + } + + if (imap_acl_identifier_parse(cmd, identifier, &update.rights, + TRUE, &client_error) < 0) { + client_send_command_error(cmd, client_error); + return; + } + if (imap_acl_letters_parse(rights, &update.rights.rights, &client_error) < 0) { + client_send_command_error(cmd, client_error); + return; + } + r = &update.rights; + + if (acl_mailbox_open_as_admin(cmd, box, mailbox) <= 0) + return; + + backend = acl_mailbox_list_get_backend(ns->list); + if (ns->type == MAIL_NAMESPACE_TYPE_PUBLIC && + r->id_type == ACL_ID_OWNER) { + client_send_tagline(cmd, "NO Public namespaces have no owner"); + return; + } + + aclobj = acl_mailbox_get_aclobj(box); + if (negative) { + update.neg_modify_mode = update.modify_mode; + update.modify_mode = ACL_MODIFY_MODE_REMOVE; + update.rights.neg_rights = update.rights.rights; + update.rights.rights = NULL; + } else if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE && + r->rights != NULL && + ((r->id_type == ACL_ID_USER && + acl_backend_user_name_equals(backend, r->identifier)) || + (r->id_type == ACL_ID_OWNER && + strcmp(acl_backend_get_acl_username(backend), + ns->user->username) == 0))) { + /* make sure client doesn't (accidentally) remove admin + privileges from its own mailboxes */ + imap_acl_update_ensure_keep_admins(backend, aclobj, &update); + } + + if (cmd_acl_mailbox_update(box, &update, &client_error) < 0) + client_send_tagline(cmd, t_strdup_printf("NO %s", client_error)); + else + client_send_tagline(cmd, "OK Setacl complete."); +} + +static bool cmd_setacl(struct client_command_context *cmd) +{ + struct mail_namespace *ns; + struct mailbox *box; + const char *mailbox, *orig_mailbox, *identifier, *rights; + string_t *proxy_cmd_args = t_str_new(64); + + if (!client_read_string_args(cmd, 3, &mailbox, &identifier, &rights)) + return FALSE; + orig_mailbox = mailbox; + + if (*identifier == '\0') { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + /* Keep original identifer for proxy_cmd_args */ + imap_append_astring(proxy_cmd_args, identifier); + str_append_c(proxy_cmd_args, ' '); + /* Append original rights for proxy_cmd_args */ + imap_append_astring(proxy_cmd_args, rights); + + ns = client_find_namespace(cmd, &mailbox); + if (ns == NULL) + return TRUE; + + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + /* If the location is remote and imapc_feature acl is enabled, proxy the + command to the configured imapc location. */ + if (!imap_acl_proxy_cmd(box, orig_mailbox, str_c(proxy_cmd_args), + ns, cmd, IMAP_ACL_CMD_SETACL)) + imap_acl_cmd_setacl(box, ns, orig_mailbox, identifier, rights, cmd); + mailbox_free(&box); + return TRUE; +} + +static void imap_acl_cmd_deleteacl(struct mailbox *box, const char *mailbox, + const char *identifier, + struct client_command_context *cmd) +{ + struct acl_rights_update update; + const char *client_error; + + i_zero(&update); + if (*identifier != '-') + update.modify_mode = ACL_MODIFY_MODE_CLEAR; + else { + update.neg_modify_mode = ACL_MODIFY_MODE_CLEAR; + identifier++; + } + + if (imap_acl_identifier_parse(cmd, identifier, &update.rights, + FALSE, &client_error) < 0) { + client_send_command_error(cmd, client_error); + return; + } + + if (acl_mailbox_open_as_admin(cmd, box, mailbox) <= 0) + return; + + if (cmd_acl_mailbox_update(box, &update, &client_error) < 0) + client_send_tagline(cmd, t_strdup_printf("NO %s", client_error)); + else + client_send_tagline(cmd, "OK Deleteacl complete."); +} + +static bool cmd_deleteacl(struct client_command_context *cmd) +{ + struct mailbox *box; + struct mail_namespace *ns; + const char *mailbox, *orig_mailbox, *identifier; + string_t *proxy_cmd_args = t_str_new(64); + + if (!client_read_string_args(cmd, 2, &mailbox, &identifier)) + return FALSE; + orig_mailbox = mailbox; + + if (*identifier == '\0') { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + ns = client_find_namespace(cmd, &mailbox); + if (ns == NULL) + return TRUE; + + /* Escaped identifer for proxy_cmd_args */ + imap_append_astring(proxy_cmd_args, identifier); + + box = mailbox_alloc(ns->list, mailbox, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS); + + /* If the location is remote and imapc_feature acl is enabled, proxy the + command to the configured imapc location. */ + if (!imap_acl_proxy_cmd(box, orig_mailbox, str_c(proxy_cmd_args), + ns, cmd, IMAP_ACL_CMD_DELETEACL)) + imap_acl_cmd_deleteacl(box, orig_mailbox, identifier, cmd); + mailbox_free(&box); + return TRUE; +} + +static void imap_acl_client_created(struct client **client) +{ + if (mail_user_is_plugin_loaded((*client)->user, imap_acl_module)) { + client_add_capability(*client, "ACL"); + client_add_capability(*client, "RIGHTS=texk"); + } + + if (next_hook_client_created != NULL) + next_hook_client_created(client); +} + +void imap_acl_plugin_init(struct module *module) +{ + command_register("LISTRIGHTS", cmd_listrights, 0); + command_register("GETACL", cmd_getacl, 0); + command_register("MYRIGHTS", cmd_myrights, 0); + command_register("SETACL", cmd_setacl, 0); + command_register("DELETEACL", cmd_deleteacl, 0); + + imap_acl_module = module; + next_hook_client_created = + imap_client_created_hook_set(imap_acl_client_created); +} + +void imap_acl_plugin_deinit(void) +{ + command_unregister("GETACL"); + command_unregister("MYRIGHTS"); + command_unregister("SETACL"); + command_unregister("DELETEACL"); + command_unregister("LISTRIGHTS"); + + imap_client_created_hook_set(next_hook_client_created); +} + +const char *imap_acl_plugin_dependencies[] = { "acl", NULL }; +const char imap_acl_plugin_binary_dependency[] = "imap"; diff --git a/src/plugins/imap-acl/imap-acl-plugin.h b/src/plugins/imap-acl/imap-acl-plugin.h new file mode 100644 index 0000000..117e8fb --- /dev/null +++ b/src/plugins/imap-acl/imap-acl-plugin.h @@ -0,0 +1,12 @@ +#ifndef IMAP_ACL_PLUGIN_H +#define IMAP_ACL_PLUGIN_H + +extern const char *imap_acl_plugin_dependencies[]; +extern const char imap_acl_plugin_binary_dependency[]; + +extern MODULE_CONTEXT_DEFINE(imap_acl_storage_module, &mail_storage_module_register); + +void imap_acl_plugin_init(struct module *module); +void imap_acl_plugin_deinit(void); + +#endif diff --git a/src/plugins/imap-old-stats/Makefile.am b/src/plugins/imap-old-stats/Makefile.am new file mode 100644 index 0000000..ed07ed5 --- /dev/null +++ b/src/plugins/imap-old-stats/Makefile.am @@ -0,0 +1,28 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-old-stats \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/old-stats + +imap_moduledir = $(moduledir) + +NOPLUGIN_LDFLAGS = +lib95_imap_old_stats_plugin_la_LDFLAGS = -module -avoid-version + +imap_module_LTLIBRARIES = \ + lib95_imap_old_stats_plugin.la + +if DOVECOT_PLUGIN_DEPS +lib95_imap_old_stats_plugin_la_LIBADD = \ + ../old-stats/lib90_old_stats_plugin.la +endif + +lib95_imap_old_stats_plugin_la_SOURCES = \ + imap-stats-plugin.c + +noinst_HEADERS = \ + imap-stats-plugin.h diff --git a/src/plugins/imap-old-stats/Makefile.in b/src/plugins/imap-old-stats/Makefile.in new file mode 100644 index 0000000..458b4a8 --- /dev/null +++ b/src/plugins/imap-old-stats/Makefile.in @@ -0,0 +1,827 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/imap-old-stats +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(imap_moduledir)" +LTLIBRARIES = $(imap_module_LTLIBRARIES) +@DOVECOT_PLUGIN_DEPS_TRUE@lib95_imap_old_stats_plugin_la_DEPENDENCIES = ../old-stats/lib90_old_stats_plugin.la +am_lib95_imap_old_stats_plugin_la_OBJECTS = imap-stats-plugin.lo +lib95_imap_old_stats_plugin_la_OBJECTS = \ + $(am_lib95_imap_old_stats_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib95_imap_old_stats_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(lib95_imap_old_stats_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/imap-stats-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib95_imap_old_stats_plugin_la_SOURCES) +DIST_SOURCES = $(lib95_imap_old_stats_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-old-stats \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/old-stats + +imap_moduledir = $(moduledir) +lib95_imap_old_stats_plugin_la_LDFLAGS = -module -avoid-version +imap_module_LTLIBRARIES = \ + lib95_imap_old_stats_plugin.la + +@DOVECOT_PLUGIN_DEPS_TRUE@lib95_imap_old_stats_plugin_la_LIBADD = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../old-stats/lib90_old_stats_plugin.la + +lib95_imap_old_stats_plugin_la_SOURCES = \ + imap-stats-plugin.c + +noinst_HEADERS = \ + imap-stats-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/imap-old-stats/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/imap-old-stats/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-imap_moduleLTLIBRARIES: $(imap_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(imap_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(imap_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(imap_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(imap_moduledir)"; \ + } + +uninstall-imap_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(imap_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(imap_moduledir)/$$f"; \ + done + +clean-imap_moduleLTLIBRARIES: + -test -z "$(imap_module_LTLIBRARIES)" || rm -f $(imap_module_LTLIBRARIES) + @list='$(imap_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib95_imap_old_stats_plugin.la: $(lib95_imap_old_stats_plugin_la_OBJECTS) $(lib95_imap_old_stats_plugin_la_DEPENDENCIES) $(EXTRA_lib95_imap_old_stats_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib95_imap_old_stats_plugin_la_LINK) -rpath $(imap_moduledir) $(lib95_imap_old_stats_plugin_la_OBJECTS) $(lib95_imap_old_stats_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-stats-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(imap_moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/imap-stats-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-imap_moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/imap-stats-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-imap_moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-imap_moduleLTLIBRARIES install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-imap_moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/imap-old-stats/imap-stats-plugin.c b/src/plugins/imap-old-stats/imap-stats-plugin.c new file mode 100644 index 0000000..6c91aa7 --- /dev/null +++ b/src/plugins/imap-old-stats/imap-stats-plugin.c @@ -0,0 +1,128 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "base64.h" +#include "str.h" +#include "imap-commands.h" +#include "stats.h" +#include "stats-plugin.h" +#include "stats-connection.h" +#include "imap-stats-plugin.h" + +#define IMAP_STATS_IMAP_CONTEXT(obj) \ + MODULE_CONTEXT(obj, imap_stats_imap_module) + +struct stats_client_command { + union imap_module_context module_ctx; + + unsigned int id; + bool continued; + struct stats *stats, *pre_stats; +}; + +static MODULE_CONTEXT_DEFINE_INIT(imap_stats_imap_module, + &imap_module_register); + +const char *imap_stats_plugin_version = DOVECOT_ABI_VERSION; + +static void stats_command_pre(struct client_command_context *cmd) +{ + struct stats_user *suser = STATS_USER_CONTEXT(cmd->client->user); + struct stats_client_command *scmd; + static unsigned int stats_cmd_id_counter = 0; + + if (suser == NULL || !suser->track_commands) + return; + + if (strcasecmp(cmd->name, "IDLE") == 0) { + /* IDLE can run forever and waste stats process's memory while + waiting for it to timeout. don't send them. */ + return; + } + + scmd = IMAP_STATS_IMAP_CONTEXT(cmd); + if (scmd == NULL) { + scmd = p_new(cmd->pool, struct stats_client_command, 1); + scmd->id = ++stats_cmd_id_counter; + scmd->stats = stats_alloc(cmd->pool); + scmd->pre_stats = stats_alloc(cmd->pool); + MODULE_CONTEXT_SET(cmd, imap_stats_imap_module, scmd); + } + + mail_user_stats_fill(cmd->client->user, scmd->pre_stats); +} + +static void stats_command_post(struct client_command_context *cmd) +{ + struct stats_user *suser = STATS_USER_CONTEXT(cmd->client->user); + struct stats_client_command *scmd = IMAP_STATS_IMAP_CONTEXT(cmd); + struct stats *new_stats, *diff_stats; + const char *error; + size_t args_pos = 0, args_len = 0; + string_t *str; + buffer_t *buf; + + if (suser == NULL || scmd == NULL) + return; + + new_stats = stats_alloc(pool_datastack_create()); + diff_stats = stats_alloc(pool_datastack_create()); + + mail_user_stats_fill(cmd->client->user, new_stats); + if (!stats_diff(scmd->pre_stats, new_stats, diff_stats, &error)) + i_error("stats: command stats shrank: %s", error); + stats_add(scmd->stats, diff_stats); + + str = t_str_new(128); + str_append(str, "UPDATE-CMD\t"); + str_append(str, suser->stats_session_id); + + str_printfa(str, "\t%u\t", scmd->id); + if (cmd->state == CLIENT_COMMAND_STATE_DONE) + str_append_c(str, 'd'); + if (scmd->continued) + str_append_c(str, 'c'); + else { + str_append_c(str, '\t'); + str_append(str, cmd->name); + str_append_c(str, '\t'); + args_pos = str_len(str); + if (cmd->args != NULL) + str_append(str, cmd->args); + args_len = str_len(str) - args_pos; + scmd->continued = TRUE; + } + + buf = t_buffer_create(128); + stats_export(buf, scmd->stats); + str_append_c(str, '\t'); + base64_encode(buf->data, buf->used, str); + + str_append_c(str, '\n'); + + if (str_len(str) > PIPE_BUF) { + /* truncate the args so it fits */ + size_t delete_count = str_len(str) - PIPE_BUF; + + i_assert(args_pos != 0); + if (delete_count > args_len) + delete_count = args_len; + str_delete(str, args_pos + args_len - delete_count, + delete_count); + } + + stats_connection_send(suser->stats_conn, str); +} + +void imap_old_stats_plugin_init(struct module *module ATTR_UNUSED) +{ + command_hook_register(stats_command_pre, stats_command_post); +} + +void imap_old_stats_plugin_deinit(void) +{ + command_hook_unregister(stats_command_pre, stats_command_post); +} + +const char *imap_old_stats_plugin_dependencies[] = { "old_stats", NULL }; +const char imap_old_stats_plugin_binary_dependency[] = "imap"; diff --git a/src/plugins/imap-old-stats/imap-stats-plugin.h b/src/plugins/imap-old-stats/imap-stats-plugin.h new file mode 100644 index 0000000..2a95b53 --- /dev/null +++ b/src/plugins/imap-old-stats/imap-stats-plugin.h @@ -0,0 +1,12 @@ +#ifndef IMAP_STATS_PLUGIN_H +#define IMAP_STATS_PLUGIN_H + +struct module; + +extern const char *imap_stats_plugin_dependencies[]; +extern const char imap_stats_plugin_binary_dependency[]; + +void imap_old_stats_plugin_init(struct module *module); +void imap_old_stats_plugin_deinit(void); + +#endif diff --git a/src/plugins/imap-quota/Makefile.am b/src/plugins/imap-quota/Makefile.am new file mode 100644 index 0000000..7c19c02 --- /dev/null +++ b/src/plugins/imap-quota/Makefile.am @@ -0,0 +1,27 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/quota + +imap_moduledir = $(moduledir) + +NOPLUGIN_LDFLAGS = +lib11_imap_quota_plugin_la_LDFLAGS = -module -avoid-version + +imap_module_LTLIBRARIES = \ + lib11_imap_quota_plugin.la + +if DOVECOT_PLUGIN_DEPS +lib11_imap_quota_plugin_la_LIBADD = \ + ../quota/lib10_quota_plugin.la +endif + +lib11_imap_quota_plugin_la_SOURCES = \ + imap-quota-plugin.c + +noinst_HEADERS = \ + imap-quota-plugin.h diff --git a/src/plugins/imap-quota/Makefile.in b/src/plugins/imap-quota/Makefile.in new file mode 100644 index 0000000..122d1df --- /dev/null +++ b/src/plugins/imap-quota/Makefile.in @@ -0,0 +1,827 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/imap-quota +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(imap_moduledir)" +LTLIBRARIES = $(imap_module_LTLIBRARIES) +@DOVECOT_PLUGIN_DEPS_TRUE@lib11_imap_quota_plugin_la_DEPENDENCIES = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../quota/lib10_quota_plugin.la +am_lib11_imap_quota_plugin_la_OBJECTS = imap-quota-plugin.lo +lib11_imap_quota_plugin_la_OBJECTS = \ + $(am_lib11_imap_quota_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib11_imap_quota_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib11_imap_quota_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/imap-quota-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib11_imap_quota_plugin_la_SOURCES) +DIST_SOURCES = $(lib11_imap_quota_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/quota + +imap_moduledir = $(moduledir) +lib11_imap_quota_plugin_la_LDFLAGS = -module -avoid-version +imap_module_LTLIBRARIES = \ + lib11_imap_quota_plugin.la + +@DOVECOT_PLUGIN_DEPS_TRUE@lib11_imap_quota_plugin_la_LIBADD = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../quota/lib10_quota_plugin.la + +lib11_imap_quota_plugin_la_SOURCES = \ + imap-quota-plugin.c + +noinst_HEADERS = \ + imap-quota-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/imap-quota/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/imap-quota/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-imap_moduleLTLIBRARIES: $(imap_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(imap_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(imap_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(imap_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(imap_moduledir)"; \ + } + +uninstall-imap_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(imap_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(imap_moduledir)/$$f"; \ + done + +clean-imap_moduleLTLIBRARIES: + -test -z "$(imap_module_LTLIBRARIES)" || rm -f $(imap_module_LTLIBRARIES) + @list='$(imap_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib11_imap_quota_plugin.la: $(lib11_imap_quota_plugin_la_OBJECTS) $(lib11_imap_quota_plugin_la_DEPENDENCIES) $(EXTRA_lib11_imap_quota_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib11_imap_quota_plugin_la_LINK) -rpath $(imap_moduledir) $(lib11_imap_quota_plugin_la_OBJECTS) $(lib11_imap_quota_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-quota-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(imap_moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/imap-quota-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-imap_moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/imap-quota-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-imap_moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-imap_moduleLTLIBRARIES install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-imap_moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/imap-quota/imap-quota-plugin.c b/src/plugins/imap-quota/imap-quota-plugin.c new file mode 100644 index 0000000..7863419 --- /dev/null +++ b/src/plugins/imap-quota/imap-quota-plugin.c @@ -0,0 +1,266 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "ostream.h" +#include "imap-quote.h" +#include "mail-namespace.h" +#include "imap-commands.h" +#include "quota.h" +#include "quota-plugin.h" +#include "imap-quota-plugin.h" + + +#define QUOTA_USER_SEPARATOR ':' + +const char *imap_quota_plugin_version = DOVECOT_ABI_VERSION; + +static struct module *imap_quota_module; +static imap_client_created_func_t *next_hook_client_created; + +static const char * +imap_quota_root_get_name(struct mail_user *user, struct mail_user *owner, + struct quota_root *root) +{ + const char *name; + + name = quota_root_get_name(root); + if (user == owner || owner == NULL) + return name; + return t_strdup_printf("%s%c%s", owner->username, + QUOTA_USER_SEPARATOR, name); +} + +static int +quota_reply_write(string_t *str, struct mail_user *user, + struct mail_user *owner, struct quota_root *root) +{ + const char *name, *const *list, *error; + unsigned int i; + uint64_t value, limit; + size_t prefix_len, orig_len = str_len(str); + enum quota_get_result ret = QUOTA_GET_RESULT_UNLIMITED; + + str_append(str, "* QUOTA "); + name = imap_quota_root_get_name(user, owner, root); + imap_append_astring(str, name); + + str_append(str, " ("); + prefix_len = str_len(str); + list = quota_root_get_resources(root); + for (i = 0; *list != NULL; list++) { + ret = quota_get_resource(root, "", *list, &value, &limit, &error); + if (ret == QUOTA_GET_RESULT_INTERNAL_ERROR) { + i_error("Failed to get quota resource %s: %s", + *list, error); + break; + } + if (ret == QUOTA_GET_RESULT_LIMITED) { + if (i > 0) + str_append_c(str, ' '); + str_printfa(str, "%s %"PRIu64" %"PRIu64, *list, + value, limit); + i++; + } + } + if (str_len(str) == prefix_len) { + /* this quota root doesn't have any quota actually enabled. */ + str_truncate(str, orig_len); + } else { + str_append(str, ")\r\n"); + } + return ret == QUOTA_GET_RESULT_INTERNAL_ERROR ? -1 : 0; +} + +static bool cmd_getquotaroot(struct client_command_context *cmd) +{ + struct client *client = cmd->client; + struct quota_user *quser = QUOTA_USER_CONTEXT(client->user); + struct mail_namespace *ns; + struct mailbox *box; + struct quota_root_iter *iter; + struct quota_root *root; + const char *mailbox, *orig_mailbox, *name; + string_t *quotaroot_reply, *quota_reply; + int ret; + + /* <mailbox> */ + if (!client_read_string_args(cmd, 1, &mailbox)) + return FALSE; + orig_mailbox = mailbox; + + ns = client_find_namespace(cmd, &mailbox); + if (ns == NULL) + return TRUE; + + if (quser == NULL) { + client_send_tagline(cmd, "OK No quota."); + return TRUE; + } + if (ns->owner != NULL && ns->owner != client->user) { + client_send_tagline(cmd, "NO Not showing other users' quota."); + return TRUE; + } + + box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY); + + /* build QUOTAROOT reply and QUOTA reply for all quota roots */ + quotaroot_reply = t_str_new(128); + quota_reply = t_str_new(256); + str_append(quotaroot_reply, "* QUOTAROOT "); + imap_append_astring(quotaroot_reply, orig_mailbox); + + ret = 0; + iter = quota_root_iter_init(box); + while ((root = quota_root_iter_next(iter)) != NULL) { + if (quota_root_is_hidden(root)) + continue; + str_append_c(quotaroot_reply, ' '); + name = imap_quota_root_get_name(client->user, ns->owner, root); + imap_append_astring(quotaroot_reply, name); + + if (quota_reply_write(quota_reply, client->user, ns->owner, root) < 0) + ret = -1; + } + quota_root_iter_deinit(&iter); + mailbox_free(&box); + + /* send replies */ + if (ret < 0) + client_send_tagline(cmd, "NO Internal quota calculation error."); + else if (str_len(quota_reply) == 0) + client_send_tagline(cmd, "OK No quota."); + else { + client_send_line(client, str_c(quotaroot_reply)); + o_stream_nsend(client->output, str_data(quota_reply), + str_len(quota_reply)); + client_send_tagline(cmd, "OK Getquotaroot completed."); + } + return TRUE; +} + +static bool +parse_quota_root(struct mail_user *user, const char *root_name, + struct mail_user **owner_r, struct quota_root **root_r) +{ + const char *p; + + *owner_r = user; + *root_r = quota_root_lookup(user, root_name); + if (*root_r != NULL || !user->admin) + return *root_r != NULL; + + /* we're an admin. see if there's a quota root for another user. */ + p = strchr(root_name, QUOTA_USER_SEPARATOR); + if (p != NULL) { + *owner_r = mail_user_find(user, t_strdup_until(root_name, p)); + *root_r = *owner_r == NULL ? NULL : + quota_root_lookup(*owner_r, p + 1); + } + return *root_r != NULL; +} + +static bool cmd_getquota(struct client_command_context *cmd) +{ + struct mail_user *owner; + struct quota_root *root; + const char *root_name; + string_t *quota_reply; + + /* <quota root> */ + if (!client_read_string_args(cmd, 1, &root_name)) + return FALSE; + + if (!parse_quota_root(cmd->client->user, root_name, &owner, &root)) { + client_send_tagline(cmd, "NO Quota root doesn't exist."); + return TRUE; + } + + quota_reply = t_str_new(128); + if (quota_reply_write(quota_reply, cmd->client->user, owner, root) < 0) + client_send_tagline(cmd, "NO Internal quota calculation error."); + else { + o_stream_nsend(cmd->client->output, str_data(quota_reply), + str_len(quota_reply)); + client_send_tagline(cmd, "OK Getquota completed."); + } + return TRUE; +} + +static bool cmd_setquota(struct client_command_context *cmd) +{ + struct quota_root *root; + struct mail_user *owner; + const struct imap_arg *args, *list_args; + const char *root_name, *name, *value_str, *client_error; + uint64_t value; + + /* <quota root> <resource limits> */ + if (!client_read_args(cmd, 2, 0, &args)) + return FALSE; + + if (!imap_arg_get_astring(&args[0], &root_name) || + !imap_arg_get_list(&args[1], &list_args)) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + if (!cmd->client->user->admin) { + client_send_tagline(cmd, "NO Quota can be changed only by admin."); + return TRUE; + } + + if (!parse_quota_root(cmd->client->user, root_name, &owner, &root)) { + client_send_tagline(cmd, "NO Quota root doesn't exist."); + return TRUE; + } + + for (; !IMAP_ARG_IS_EOL(list_args); list_args += 2) { + if (!imap_arg_get_atom(&list_args[0], &name) || + !imap_arg_get_atom(&list_args[1], &value_str) || + str_to_uint64(value_str, &value) < 0) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + if (quota_set_resource(root, name, value, &client_error) < 0) { + client_send_command_error(cmd, client_error); + return TRUE; + } + } + + client_send_tagline(cmd, "OK Setquota completed."); + return TRUE; +} + +static void imap_quota_client_created(struct client **client) +{ + if (mail_user_is_plugin_loaded((*client)->user, imap_quota_module)) + client_add_capability(*client, "QUOTA"); + + if (next_hook_client_created != NULL) + next_hook_client_created(client); +} + +void imap_quota_plugin_init(struct module *module) +{ + command_register("GETQUOTAROOT", cmd_getquotaroot, 0); + command_register("GETQUOTA", cmd_getquota, 0); + command_register("SETQUOTA", cmd_setquota, 0); + + imap_quota_module = module; + next_hook_client_created = + imap_client_created_hook_set(imap_quota_client_created); +} + +void imap_quota_plugin_deinit(void) +{ + command_unregister("GETQUOTAROOT"); + command_unregister("GETQUOTA"); + command_unregister("SETQUOTA"); + + imap_client_created_hook_set(next_hook_client_created); +} + +const char *imap_quota_plugin_dependencies[] = { "quota", NULL }; +const char imap_quota_plugin_binary_dependency[] = "imap"; diff --git a/src/plugins/imap-quota/imap-quota-plugin.h b/src/plugins/imap-quota/imap-quota-plugin.h new file mode 100644 index 0000000..1e55d10 --- /dev/null +++ b/src/plugins/imap-quota/imap-quota-plugin.h @@ -0,0 +1,12 @@ +#ifndef IMAP_QUOTA_PLUGIN_H +#define IMAP_QUOTA_PLUGIN_H + +struct module; + +extern const char *imap_quota_plugin_dependencies[]; +extern const char imap_quota_plugin_binary_dependency[]; + +void imap_quota_plugin_init(struct module *module); +void imap_quota_plugin_deinit(void); + +#endif diff --git a/src/plugins/imap-zlib/Makefile.am b/src/plugins/imap-zlib/Makefile.am new file mode 100644 index 0000000..fa4f2f1 --- /dev/null +++ b/src/plugins/imap-zlib/Makefile.am @@ -0,0 +1,25 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-compression \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/imap + +imap_moduledir = $(moduledir) + +NOPLUGIN_LDFLAGS = +lib30_imap_zlib_plugin_la_LDFLAGS = -module -avoid-version + +imap_module_LTLIBRARIES = \ + lib30_imap_zlib_plugin.la + +lib30_imap_zlib_plugin_la_LIBADD = \ + ../../lib-compression/libcompression.la + +lib30_imap_zlib_plugin_la_SOURCES = \ + imap-zlib-plugin.c + +noinst_HEADERS = \ + imap-zlib-plugin.h diff --git a/src/plugins/imap-zlib/Makefile.in b/src/plugins/imap-zlib/Makefile.in new file mode 100644 index 0000000..2a91e20 --- /dev/null +++ b/src/plugins/imap-zlib/Makefile.in @@ -0,0 +1,827 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/imap-zlib +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(imap_moduledir)" +LTLIBRARIES = $(imap_module_LTLIBRARIES) +lib30_imap_zlib_plugin_la_DEPENDENCIES = \ + ../../lib-compression/libcompression.la +am_lib30_imap_zlib_plugin_la_OBJECTS = imap-zlib-plugin.lo +lib30_imap_zlib_plugin_la_OBJECTS = \ + $(am_lib30_imap_zlib_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib30_imap_zlib_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib30_imap_zlib_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/imap-zlib-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib30_imap_zlib_plugin_la_SOURCES) +DIST_SOURCES = $(lib30_imap_zlib_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-compression \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/imap + +imap_moduledir = $(moduledir) +lib30_imap_zlib_plugin_la_LDFLAGS = -module -avoid-version +imap_module_LTLIBRARIES = \ + lib30_imap_zlib_plugin.la + +lib30_imap_zlib_plugin_la_LIBADD = \ + ../../lib-compression/libcompression.la + +lib30_imap_zlib_plugin_la_SOURCES = \ + imap-zlib-plugin.c + +noinst_HEADERS = \ + imap-zlib-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/imap-zlib/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/imap-zlib/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-imap_moduleLTLIBRARIES: $(imap_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(imap_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(imap_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(imap_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(imap_moduledir)"; \ + } + +uninstall-imap_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(imap_module_LTLIBRARIES)'; test -n "$(imap_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(imap_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(imap_moduledir)/$$f"; \ + done + +clean-imap_moduleLTLIBRARIES: + -test -z "$(imap_module_LTLIBRARIES)" || rm -f $(imap_module_LTLIBRARIES) + @list='$(imap_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib30_imap_zlib_plugin.la: $(lib30_imap_zlib_plugin_la_OBJECTS) $(lib30_imap_zlib_plugin_la_DEPENDENCIES) $(EXTRA_lib30_imap_zlib_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib30_imap_zlib_plugin_la_LINK) -rpath $(imap_moduledir) $(lib30_imap_zlib_plugin_la_OBJECTS) $(lib30_imap_zlib_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-zlib-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(imap_moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/imap-zlib-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-imap_moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/imap-zlib-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-imap_moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-imap_moduleLTLIBRARIES clean-libtool \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-imap_moduleLTLIBRARIES install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-imap_moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/imap-zlib/imap-zlib-plugin.c b/src/plugins/imap-zlib/imap-zlib-plugin.c new file mode 100644 index 0000000..df5508b --- /dev/null +++ b/src/plugins/imap-zlib/imap-zlib-plugin.c @@ -0,0 +1,184 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "module-context.h" +#include "imap-commands.h" +#include "compression.h" +#include "imap-zlib-plugin.h" + + +#define IMAP_COMPRESS_DEFAULT_LEVEL 6 + +#define IMAP_ZLIB_IMAP_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, imap_zlib_imap_module) + +struct zlib_client { + union imap_module_context module_ctx; + + int (*next_state_export)(struct client *client, bool internal, + buffer_t *dest, const char **error_r); + const struct compression_handler *handler; +}; + +const char *imap_zlib_plugin_version = DOVECOT_ABI_VERSION; + +static struct module *imap_zlib_module; +static imap_client_created_func_t *next_hook_client_created; + +static MODULE_CONTEXT_DEFINE_INIT(imap_zlib_imap_module, + &imap_module_register); + +static void client_skip_line(struct client *client) +{ + const unsigned char *data; + size_t data_size; + + data = i_stream_get_data(client->input, &data_size); + i_assert(data_size > 0); + if (data[0] == '\n') + i_stream_skip(client->input, 1); + else if (data[0] == '\r' && data_size > 1 && data[1] == '\n') + i_stream_skip(client->input, 2); + else + i_unreached(); + client->input_skip_line = FALSE; +} + +static void client_update_imap_parser_streams(struct client *client) +{ + struct client_command_context *cmd; + + if (client->free_parser != NULL) { + imap_parser_set_streams(client->free_parser, + client->input, client->output); + } + + for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) { + imap_parser_set_streams(cmd->parser, + client->input, client->output); + } +} + +static bool cmd_compress(struct client_command_context *cmd) +{ + struct client *client = cmd->client; + struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client); + const struct compression_handler *handler; + const struct imap_arg *args; + struct istream *old_input; + struct ostream *old_output; + const char *mechanism, *value; + int level; + int ret; + + /* <mechanism> */ + if (!client_read_args(cmd, 0, 0, &args)) + return FALSE; + + if (!imap_arg_get_atom(args, &mechanism) || + !IMAP_ARG_IS_EOL(&args[1])) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + if (zclient->handler != NULL) { + client_send_tagline(cmd, t_strdup_printf( + "NO [COMPRESSIONACTIVE] COMPRESSION=%s already enabled.", + t_str_ucase(zclient->handler->name))); + return TRUE; + } + + ret = compression_lookup_handler(t_str_lcase(mechanism), &handler); + if (ret <= 0) { + const char * tagline = + t_strdup_printf("NO %s compression mechanism", + ret == 0 ? "Unsupported" : "Unknown"); + client_send_tagline(cmd, tagline); + return TRUE; + } + + client_skip_line(client); + client_send_tagline(cmd, "OK Begin compression."); + + const char *setting = t_strdup_printf("imap_compress_%s_level", + handler->name); + value = mail_user_plugin_getenv(client->user, setting); + if (value == NULL) { + level = handler->get_default_level(); + } else if (str_to_int(value, &level) < 0 || + level < handler->get_min_level() || + level > handler->get_max_level()) { + i_error("%s: Level must be between %d..%d", + setting, + handler->get_min_level(), + handler->get_max_level()); + level = handler->get_default_level(); + } + old_input = client->input; + old_output = client->output; + client->input = handler->create_istream(old_input); + client->output = handler->create_ostream(old_output, level); + /* preserve output offset so that the bytes out counter in logout + message doesn't get reset here */ + client->output->offset = old_output->offset; + i_stream_unref(&old_input); + o_stream_unref(&old_output); + + client_update_imap_parser_streams(client); + zclient->handler = handler; + return TRUE; +} + +static int +imap_zlib_state_export(struct client *client, bool internal, + buffer_t *dest, const char **error_r) +{ + struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client); + + if (zclient->handler != NULL && internal) { + *error_r = "COMPRESS enabled"; + return 0; + } + return zclient->next_state_export(client, internal, dest, error_r); +} + +static void imap_zlib_client_created(struct client **clientp) +{ + struct client *client = *clientp; + struct zlib_client *zclient; + const struct compression_handler *handler; + + if (mail_user_is_plugin_loaded(client->user, imap_zlib_module) && + compression_lookup_handler("deflate", &handler) > 0) { + zclient = p_new(client->pool, struct zlib_client, 1); + MODULE_CONTEXT_SET(client, imap_zlib_imap_module, zclient); + + zclient->next_state_export = (*clientp)->v.state_export; + (*clientp)->v.state_export = imap_zlib_state_export; + + client_add_capability(*clientp, "COMPRESS=DEFLATE"); + } + + if (next_hook_client_created != NULL) + next_hook_client_created(clientp); +} + +void imap_zlib_plugin_init(struct module *module) +{ + command_register("COMPRESS", cmd_compress, 0); + + imap_zlib_module = module; + next_hook_client_created = + imap_client_created_hook_set(imap_zlib_client_created); +} + +void imap_zlib_plugin_deinit(void) +{ + command_unregister("COMPRESS"); + + imap_client_created_hook_set(next_hook_client_created); +} + +const char imap_zlib_plugin_binary_dependency[] = "imap"; diff --git a/src/plugins/imap-zlib/imap-zlib-plugin.h b/src/plugins/imap-zlib/imap-zlib-plugin.h new file mode 100644 index 0000000..251bb23 --- /dev/null +++ b/src/plugins/imap-zlib/imap-zlib-plugin.h @@ -0,0 +1,12 @@ +#ifndef IMAP_ZLIB_PLUGIN_H +#define IMAP_ZLIB_PLUGIN_H + +struct module; + +extern const char *imap_zlib_plugin_dependencies[]; +extern const char imap_zlib_plugin_binary_dependency[]; + +void imap_zlib_plugin_init(struct module *module); +void imap_zlib_plugin_deinit(void); + +#endif diff --git a/src/plugins/last-login/Makefile.am b/src/plugins/last-login/Makefile.am new file mode 100644 index 0000000..cdedf43 --- /dev/null +++ b/src/plugins/last-login/Makefile.am @@ -0,0 +1,19 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib10_last_login_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib10_last_login_plugin.la + +lib10_last_login_plugin_la_SOURCES = \ + last-login-plugin.c + +noinst_HEADERS = \ + last-login-plugin.h diff --git a/src/plugins/last-login/Makefile.in b/src/plugins/last-login/Makefile.in new file mode 100644 index 0000000..2e20c77 --- /dev/null +++ b/src/plugins/last-login/Makefile.in @@ -0,0 +1,822 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/last-login +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib10_last_login_plugin_la_LIBADD = +am_lib10_last_login_plugin_la_OBJECTS = last-login-plugin.lo +lib10_last_login_plugin_la_OBJECTS = \ + $(am_lib10_last_login_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib10_last_login_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib10_last_login_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/last-login-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib10_last_login_plugin_la_SOURCES) +DIST_SOURCES = $(lib10_last_login_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +lib10_last_login_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib10_last_login_plugin.la + +lib10_last_login_plugin_la_SOURCES = \ + last-login-plugin.c + +noinst_HEADERS = \ + last-login-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/last-login/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/last-login/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib10_last_login_plugin.la: $(lib10_last_login_plugin_la_OBJECTS) $(lib10_last_login_plugin_la_DEPENDENCIES) $(EXTRA_lib10_last_login_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib10_last_login_plugin_la_LINK) -rpath $(moduledir) $(lib10_last_login_plugin_la_OBJECTS) $(lib10_last_login_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/last-login-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/last-login-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/last-login-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/last-login/last-login-plugin.c b/src/plugins/last-login/last-login-plugin.c new file mode 100644 index 0000000..1f580b9 --- /dev/null +++ b/src/plugins/last-login/last-login-plugin.c @@ -0,0 +1,157 @@ +/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "dict.h" +#include "mail-user.h" +#include "mail-namespace.h" +#include "mail-storage-private.h" +#include "mail-storage-hooks.h" +#include "last-login-plugin.h" + +#define LAST_LOGIN_USER_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, last_login_user_module) + +#define LAST_LOGIN_DEFAULT_KEY_PREFIX "last-login/" + +struct last_login_user { + union mail_user_module_context module_ctx; + struct dict *dict; + struct timeout *to; +}; + +const char *last_login_plugin_version = DOVECOT_ABI_VERSION; + +static MODULE_CONTEXT_DEFINE_INIT(last_login_user_module, + &mail_user_module_register); + +static void last_login_dict_deinit(struct mail_user *user) +{ + struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user); + + if (luser->dict != NULL) { + dict_wait(luser->dict); + dict_deinit(&luser->dict); + } + /* remove timeout after dict_wait(), which may trigger + last_login_dict_commit() */ + timeout_remove(&luser->to); +} + +static void last_login_user_deinit(struct mail_user *user) +{ + struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user); + + last_login_dict_deinit(user); + luser->module_ctx.super.deinit(user); +} + +static void +last_login_dict_commit(const struct dict_commit_result *result, + struct mail_user *user) +{ + struct last_login_user *luser = LAST_LOGIN_USER_CONTEXT(user); + + switch(result->ret) { + case DICT_COMMIT_RET_OK: + case DICT_COMMIT_RET_NOTFOUND: + break; + case DICT_COMMIT_RET_FAILED: + i_error("last_login_dict: Failed to write value: %s", + result->error); + break; + case DICT_COMMIT_RET_WRITE_UNCERTAIN: + i_error("last_login_dict: Write was unconfirmed (timeout or disconnect): %s", + result->error); + break; + } + + /* don't deinit the dict immediately here, lib-dict will just crash */ + luser->to = timeout_add(0, last_login_dict_deinit, user); +} + +static void last_login_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct last_login_user *luser; + struct dict *dict; + struct dict_settings set; + struct dict_transaction_context *trans; + const char *dict_value, *key_name, *precision, *error; + + if (user->autocreated) { + /* we want to handle only logged in users, + not lda's raw user or accessed shared users */ + return; + } + if (user->session_restored) { + /* This is IMAP unhibernation, not a real login. */ + return; + } + + dict_value = mail_user_plugin_getenv(user, "last_login_dict"); + if (dict_value == NULL || dict_value[0] == '\0') + return; + + i_zero(&set); + set.base_dir = user->set->base_dir; + set.event_parent = user->event; + if (dict_init(dict_value, &set, &dict, &error) < 0) { + i_error("last_login_dict: dict_init(%s) failed: %s", + dict_value, error); + return; + } + + luser = p_new(user->pool, struct last_login_user, 1); + luser->module_ctx.super = *v; + user->vlast = &luser->module_ctx.super; + v->deinit = last_login_user_deinit; + + luser->dict = dict; + MODULE_CONTEXT_SET(user, last_login_user_module, luser); + + key_name = mail_user_plugin_getenv(user, "last_login_key"); + if (key_name == NULL) { + key_name = t_strdup_printf(LAST_LOGIN_DEFAULT_KEY_PREFIX"%s", + user->username); + } + key_name = t_strconcat(DICT_PATH_SHARED, key_name, NULL); + + precision = mail_user_plugin_getenv(user, "last_login_precision"); + + const struct dict_op_settings *dset = mail_user_get_dict_op_settings(user); + trans = dict_transaction_begin(dict, dset); + if (precision == NULL || strcmp(precision, "s") == 0) + dict_set(trans, key_name, dec2str(ioloop_time)); + else if (strcmp(precision, "ms") == 0) { + dict_set(trans, key_name, t_strdup_printf( + "%ld%03u", (long)ioloop_timeval.tv_sec, + (unsigned int)(ioloop_timeval.tv_usec/1000))); + } else if (strcmp(precision, "us") == 0) { + dict_set(trans, key_name, t_strdup_printf( + "%ld%06u", (long)ioloop_timeval.tv_sec, + (unsigned int)ioloop_timeval.tv_usec)); + } else if (strcmp(precision, "ns") == 0) { + dict_set(trans, key_name, t_strdup_printf( + "%ld%06u000", (long)ioloop_timeval.tv_sec, + (unsigned int)ioloop_timeval.tv_usec)); + } else { + i_error("last_login_dict: Invalid last_login_precision '%s'", precision); + } + dict_transaction_no_slowness_warning(trans); + dict_transaction_commit_async(&trans, last_login_dict_commit, user); +} + +static struct mail_storage_hooks last_login_mail_storage_hooks = { + .mail_user_created = last_login_mail_user_created +}; + +void last_login_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &last_login_mail_storage_hooks); +} + +void last_login_plugin_deinit(void) +{ + mail_storage_hooks_remove(&last_login_mail_storage_hooks); +} diff --git a/src/plugins/last-login/last-login-plugin.h b/src/plugins/last-login/last-login-plugin.h new file mode 100644 index 0000000..481a148 --- /dev/null +++ b/src/plugins/last-login/last-login-plugin.h @@ -0,0 +1,7 @@ +#ifndef LAST_LOGIN_PLUGIN_H +#define LAST_LOGIN_PLUGIN_H + +void last_login_plugin_init(struct module *module); +void last_login_plugin_deinit(void); + +#endif diff --git a/src/plugins/lazy-expunge/Makefile.am b/src/plugins/lazy-expunge/Makefile.am new file mode 100644 index 0000000..8147b64 --- /dev/null +++ b/src/plugins/lazy-expunge/Makefile.am @@ -0,0 +1,21 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/maildir \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/plugins/quota + +NOPLUGIN_LDFLAGS = +lib02_lazy_expunge_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib02_lazy_expunge_plugin.la + +lib02_lazy_expunge_plugin_la_SOURCES = \ + lazy-expunge-plugin.c + +noinst_HEADERS = \ + lazy-expunge-plugin.h diff --git a/src/plugins/lazy-expunge/Makefile.in b/src/plugins/lazy-expunge/Makefile.in new file mode 100644 index 0000000..b8c31b3 --- /dev/null +++ b/src/plugins/lazy-expunge/Makefile.in @@ -0,0 +1,824 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/lazy-expunge +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib02_lazy_expunge_plugin_la_LIBADD = +am_lib02_lazy_expunge_plugin_la_OBJECTS = lazy-expunge-plugin.lo +lib02_lazy_expunge_plugin_la_OBJECTS = \ + $(am_lib02_lazy_expunge_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib02_lazy_expunge_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib02_lazy_expunge_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/lazy-expunge-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib02_lazy_expunge_plugin_la_SOURCES) +DIST_SOURCES = $(lib02_lazy_expunge_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/maildir \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/plugins/quota + +lib02_lazy_expunge_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib02_lazy_expunge_plugin.la + +lib02_lazy_expunge_plugin_la_SOURCES = \ + lazy-expunge-plugin.c + +noinst_HEADERS = \ + lazy-expunge-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/lazy-expunge/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/lazy-expunge/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib02_lazy_expunge_plugin.la: $(lib02_lazy_expunge_plugin_la_OBJECTS) $(lib02_lazy_expunge_plugin_la_DEPENDENCIES) $(EXTRA_lib02_lazy_expunge_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib02_lazy_expunge_plugin_la_LINK) -rpath $(moduledir) $(lib02_lazy_expunge_plugin_la_OBJECTS) $(lib02_lazy_expunge_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lazy-expunge-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/lazy-expunge-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/lazy-expunge-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/lazy-expunge/lazy-expunge-plugin.c b/src/plugins/lazy-expunge/lazy-expunge-plugin.c new file mode 100644 index 0000000..ca10839 --- /dev/null +++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.c @@ -0,0 +1,654 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "str.h" +#include "hash.h" +#include "seq-range-array.h" +#include "mkdir-parents.h" +#include "mail-storage-private.h" +#include "mail-search-build.h" +#include "mailbox-list-private.h" +#include "mailbox-match-plugin.h" +#include "mail-namespace.h" +#include "lazy-expunge-plugin.h" + +#include <stdio.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> + +#define LAZY_EXPUNGE_CONTEXT(obj) \ + MODULE_CONTEXT(obj, lazy_expunge_mail_storage_module) +#define LAZY_EXPUNGE_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, lazy_expunge_mail_storage_module) +#define LAZY_EXPUNGE_LIST_CONTEXT(obj) \ + MODULE_CONTEXT(obj, lazy_expunge_mailbox_list_module) +#define LAZY_EXPUNGE_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, lazy_expunge_mail_user_module) +#define LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, lazy_expunge_mail_user_module) +#define LAZY_EXPUNGE_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT(obj, lazy_expunge_mail_module) +#define LAZY_EXPUNGE_MAIL_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, lazy_expunge_mail_module) + +struct lazy_expunge_mail { + union mail_module_context module_ctx; + bool moving; + bool recursing; +}; + +struct lazy_expunge_mail_user { + union mail_user_module_context module_ctx; + + struct mail_namespace *lazy_ns; + struct mailbox_match_plugin *excludes; + const char *lazy_mailbox_vname; + const char *env; + bool copy_only_last_instance; +}; + +struct lazy_expunge_mailbox_list { + union mailbox_list_module_context module_ctx; + + bool allow_rename:1; + bool internal_namespace:1; +}; + +struct lazy_expunge_transaction { + union mailbox_transaction_module_context module_ctx; + + struct mailbox *dest_box; + struct mailbox_transaction_context *dest_trans; + + pool_t pool; + HASH_TABLE(const char *, void *) guids; + + char *delayed_errstr; + char *delayed_internal_errstr; + enum mail_error delayed_error; + + bool copy_only_last_instance; +}; + +const char *lazy_expunge_plugin_version = DOVECOT_ABI_VERSION; + +static MODULE_CONTEXT_DEFINE_INIT(lazy_expunge_mail_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(lazy_expunge_mail_module, + &mail_module_register); +static MODULE_CONTEXT_DEFINE_INIT(lazy_expunge_mailbox_list_module, + &mailbox_list_module_register); +static MODULE_CONTEXT_DEFINE_INIT(lazy_expunge_mail_user_module, + &mail_user_module_register); + +static const char * +get_dest_vname(struct mailbox_list *list, struct mailbox *src_box) +{ + struct lazy_expunge_mail_user *luser = + LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(list->ns->user); + const char *name; + char src_sep, dest_sep; + + if (luser->lazy_mailbox_vname != NULL) + return luser->lazy_mailbox_vname; + + /* use the (canonical / unaliased) storage name */ + name = src_box->name; + /* replace hierarchy separators with destination virtual separator */ + src_sep = mailbox_list_get_hierarchy_sep(src_box->list); + dest_sep = mail_namespace_get_sep(list->ns); + if (src_sep != dest_sep) { + string_t *str = t_str_new(128); + unsigned int i; + + for (i = 0; name[i] != '\0'; i++) { + if (name[i] == src_sep) + str_append_c(str, dest_sep); + else + str_append_c(str, name[i]); + } + name = str_c(str); + } + /* add expunge namespace prefix. the name is now a proper vname */ + return t_strconcat(list->ns->prefix, name, NULL); +} + +static struct mailbox * +mailbox_open_or_create(struct mailbox_list *list, struct mailbox *src_box, + const char **error_r) +{ + struct mailbox *box; + enum mail_error error; + const char *name; + + name = get_dest_vname(list, src_box); + + box = mailbox_alloc(list, name, MAILBOX_FLAG_NO_INDEX_FILES | + MAILBOX_FLAG_SAVEONLY | MAILBOX_FLAG_IGNORE_ACLS); + if (mailbox_open(box) == 0) { + *error_r = NULL; + return box; + } + + *error_r = mailbox_get_last_internal_error(box, &error); + if (error != MAIL_ERROR_NOTFOUND) { + *error_r = t_strdup_printf("Failed to open mailbox %s: %s", + name, *error_r); + mailbox_free(&box); + return NULL; + } + + /* try creating and re-opening it. */ + if (mailbox_create(box, NULL, FALSE) < 0 && + mailbox_get_last_mail_error(box) != MAIL_ERROR_EXISTS) { + *error_r = t_strdup_printf("Failed to create mailbox %s: %s", name, + mailbox_get_last_internal_error(box, NULL)); + mailbox_free(&box); + return NULL; + } + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("Failed to open created mailbox %s: %s", name, + mailbox_get_last_internal_error(box, NULL)); + mailbox_free(&box); + return NULL; + } + return box; +} + +static unsigned int +lazy_expunge_count_in_transaction(struct lazy_expunge_transaction *lt, + const char *guid) +{ + void *refcountp; + unsigned int refcount; + + if (lt->pool == NULL) { + lt->pool = pool_alloconly_create("lazy expunge transaction", + 1024); + hash_table_create(<->guids, lt->pool, 0, str_hash, strcmp); + } + + refcountp = hash_table_lookup(lt->guids, guid); + refcount = POINTER_CAST_TO(refcountp, unsigned int) + 1; + refcountp = POINTER_CAST(refcount); + if (refcount == 1) { + guid = p_strdup(lt->pool, guid); + hash_table_insert(lt->guids, guid, refcountp); + } else { + hash_table_update(lt->guids, guid, refcountp); + } + return refcount-1; +} + +static int lazy_expunge_mail_is_last_instance(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct lazy_expunge_transaction *lt = + LAZY_EXPUNGE_CONTEXT_REQUIRE(_mail->transaction); + const char *value, *errstr; + unsigned long refcount; + enum mail_error error; + + /* mail is reused by the search query, so the next mail_prefetch() on + it will try to prefetch the refcount */ + mail->wanted_fields |= MAIL_FETCH_REFCOUNT; + + if (mail_get_special(_mail, MAIL_FETCH_REFCOUNT, &value) < 0) { + errstr = mailbox_get_last_internal_error(_mail->box, &error); + if (error == MAIL_ERROR_EXPUNGED) { + /* already expunged - just ignore it */ + return 0; + } + mail_set_critical(_mail, + "lazy_expunge: Couldn't lookup message's refcount: %s", + errstr); + return -1; + } + if (*value == '\0') { + /* refcounts not supported by backend. assume all mails are + the last instance. */ + return 1; + } + if (str_to_ulong(value, &refcount) < 0) + i_panic("Invalid mail refcount number: %s", value); + if (refcount > 1) { + /* this probably isn't the last instance of the mail, but + it's possible that the same mail was copied to this mailbox + multiple times and we're deleting more than one instance + within this transaction. in those cases each expunge will + see the same refcount, so we need to adjust the refcount + by tracking the expunged message's refcount IDs. */ + if (mail_get_special(_mail, MAIL_FETCH_REFCOUNT_ID, &value) < 0) { + errstr = mailbox_get_last_internal_error(_mail->box, &error); + if (error == MAIL_ERROR_EXPUNGED) { + /* already expunged - just ignore it */ + return 0; + } + mail_set_critical(_mail, + "lazy_expunge: Couldn't lookup message's refcount ID: %s", errstr); + return -1; + } + if (*value == '\0') { + /* refcount IDs not supported by backend, but refcounts + are? not with our current backends. */ + mail_set_critical(_mail, + "lazy_expunge: Message unexpectedly has no refcount ID"); + return -1; + } + refcount -= lazy_expunge_count_in_transaction(lt, value); + } + return refcount <= 1 ? 1 : 0; +} + +static bool lazy_expunge_is_internal_mailbox(struct mailbox *box) +{ + struct mail_namespace *ns = box->list->ns; + struct lazy_expunge_mail_user *luser = + LAZY_EXPUNGE_USER_CONTEXT(ns->user); + struct lazy_expunge_mailbox_list *llist = + LAZY_EXPUNGE_LIST_CONTEXT(box->list); + + if (luser == NULL || llist == NULL) { + /* lazy_expunge not enabled at all */ + return FALSE; + } + if (llist->internal_namespace) { + /* lazy-expunge namespace */ + return TRUE; + } + if (luser->lazy_mailbox_vname != NULL && + strcmp(luser->lazy_mailbox_vname, box->vname) == 0) { + /* lazy-expunge mailbox */ + return TRUE; + } + if (mailbox_match_plugin_exclude(luser->excludes, box)) { + /* Mailbox explicitly excluded by configuration */ + return TRUE; + } + return FALSE; +} + +static void lazy_expunge_set_error(struct lazy_expunge_transaction *lt, + struct mail_storage *storage) +{ + const char *errstr; + enum mail_error error; + + errstr = mail_storage_get_last_error(storage, &error); + if (error == MAIL_ERROR_EXPUNGED) { + /* expunging failed because the mail was already expunged. + we don't want to fail because of that. */ + return; + } + + if (lt->delayed_error != MAIL_ERROR_NONE) + return; + lt->delayed_error = error; + lt->delayed_errstr = i_strdup(errstr); + lt->delayed_internal_errstr = + i_strdup(mail_storage_get_last_internal_error(storage, NULL)); +} + +static void lazy_expunge_mail_expunge_move(struct mail *_mail) +{ + struct mail_namespace *ns = _mail->box->list->ns; + struct lazy_expunge_mail_user *luser = + LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(ns->user); + struct mail_private *mail = (struct mail_private *)_mail; + struct lazy_expunge_mail *mmail = + LAZY_EXPUNGE_MAIL_CONTEXT_REQUIRE(mail); + struct lazy_expunge_transaction *lt = + LAZY_EXPUNGE_CONTEXT_REQUIRE(_mail->transaction); + struct mail_save_context *save_ctx; + const char *error; + + if (lt->dest_box == NULL) { + lt->dest_box = mailbox_open_or_create(luser->lazy_ns->list, + _mail->box, &error); + if (lt->dest_box == NULL) { + mail_set_critical(_mail, + "lazy_expunge: Couldn't open expunge mailbox: " + "%s", error); + lazy_expunge_set_error(lt, _mail->box->storage); + return; + } + if (mailbox_sync(lt->dest_box, 0) < 0) { + mail_set_critical(_mail, + "lazy_expunge: Couldn't sync expunge mailbox"); + lazy_expunge_set_error(lt, lt->dest_box->storage); + mailbox_free(<->dest_box); + return; + } + + lt->dest_trans = mailbox_transaction_begin(lt->dest_box, + MAILBOX_TRANSACTION_FLAG_EXTERNAL, + __func__); + } + + save_ctx = mailbox_save_alloc(lt->dest_trans); + mailbox_save_copy_flags(save_ctx, _mail); + save_ctx->data.flags &= ENUM_NEGATE(MAIL_DELETED); + + mmail->recursing = TRUE; + if (mailbox_move(&save_ctx, _mail) < 0 && !_mail->expunged) + lazy_expunge_set_error(lt, lt->dest_box->storage); + mmail->recursing = FALSE; +} + +static void lazy_expunge_mail_expunge(struct mail *_mail) +{ + struct lazy_expunge_transaction *lt = + LAZY_EXPUNGE_CONTEXT_REQUIRE(_mail->transaction); + struct mail_private *mail = (struct mail_private *)_mail; + struct lazy_expunge_mail *mmail = + LAZY_EXPUNGE_MAIL_CONTEXT_REQUIRE(mail); + struct mail *real_mail; + bool moving = mmail->moving; + int ret; + + if (lt->delayed_error != MAIL_ERROR_NONE) + return; + if (mmail->recursing) { + mmail->module_ctx.super.expunge(_mail); + return; + } + + /* Clear this in case the mail is used for non-move later on. */ + mmail->moving = FALSE; + + /* don't copy the mail if we're expunging from lazy_expunge + namespace (even if it's via a virtual mailbox) */ + if (mail_get_backend_mail(_mail, &real_mail) < 0) { + lazy_expunge_set_error(lt, _mail->box->storage); + return; + } + if (lazy_expunge_is_internal_mailbox(real_mail->box)) { + mmail->module_ctx.super.expunge(_mail); + return; + } + + struct event_reason *reason = + event_reason_begin("lazy_expunge:expunge"); + if (!lt->copy_only_last_instance) + ret = 1; + else { + /* we want to copy only the last instance of the mail to + lazy_expunge namespace. other instances will be expunged + immediately. */ + if (moving) + ret = 0; + else { + ret = lazy_expunge_mail_is_last_instance(_mail); + if (ret < 0) + lazy_expunge_set_error(lt, _mail->box->storage); + } + } + if (ret > 0) + lazy_expunge_mail_expunge_move(_mail); + event_reason_end(&reason); + + if (ret == 0) { + /* Not the last instance of the mail - expunge it normally. + Since this is a normal expunge, do it without the + reason_code. */ + mmail->module_ctx.super.expunge(_mail); + } +} + +static int lazy_expunge_copy(struct mail_save_context *ctx, struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mailbox_module_context *mbox = + LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx->transaction->box); + struct lazy_expunge_mail *mmail = LAZY_EXPUNGE_MAIL_CONTEXT(mail); + + if (mmail != NULL) + mmail->moving = ctx->moving; + return mbox->super.copy(ctx, _mail); +} + +static struct mailbox_transaction_context * +lazy_expunge_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + struct lazy_expunge_mail_user *luser = + LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(box->list->ns->user); + union mailbox_module_context *mbox = LAZY_EXPUNGE_CONTEXT_REQUIRE(box); + struct mailbox_transaction_context *t; + struct lazy_expunge_transaction *lt; + + t = mbox->super.transaction_begin(box, flags, reason); + lt = i_new(struct lazy_expunge_transaction, 1); + lt->copy_only_last_instance = luser->copy_only_last_instance; + + MODULE_CONTEXT_SET(t, lazy_expunge_mail_storage_module, lt); + return t; +} + +static void lazy_expunge_transaction_free(struct lazy_expunge_transaction *lt) +{ + if (lt->dest_trans != NULL) + mailbox_transaction_rollback(<->dest_trans); + if (lt->dest_box != NULL) + mailbox_free(<->dest_box); + hash_table_destroy(<->guids); + pool_unref(<->pool); + i_free(lt->delayed_errstr); + i_free(lt->delayed_internal_errstr); + i_free(lt); +} + +static int +lazy_expunge_transaction_commit(struct mailbox_transaction_context *ctx, + struct mail_transaction_commit_changes *changes_r) +{ + union mailbox_module_context *mbox = LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx->box); + struct lazy_expunge_transaction *lt = LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx); + int ret; + + if (lt->dest_trans != NULL && lt->delayed_error == MAIL_ERROR_NONE) { + if (mailbox_transaction_commit(<->dest_trans) < 0) { + lazy_expunge_set_error(lt, ctx->box->storage); + } + } + + if (lt->delayed_error == MAIL_ERROR_NONE) + ret = mbox->super.transaction_commit(ctx, changes_r); + else if (lt->delayed_error != MAIL_ERROR_TEMP) { + mail_storage_set_error(ctx->box->storage, lt->delayed_error, + lt->delayed_errstr); + mbox->super.transaction_rollback(ctx); + ret = -1; + } else { + mailbox_set_critical(ctx->box, + "Lazy-expunge transaction failed: %s", + lt->delayed_internal_errstr); + mbox->super.transaction_rollback(ctx); + ret = -1; + } + lazy_expunge_transaction_free(lt); + return ret; +} + +static void +lazy_expunge_transaction_rollback(struct mailbox_transaction_context *ctx) +{ + union mailbox_module_context *mbox = LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx->box); + struct lazy_expunge_transaction *lt = LAZY_EXPUNGE_CONTEXT_REQUIRE(ctx); + + mbox->super.transaction_rollback(ctx); + lazy_expunge_transaction_free(lt); +} + +static void lazy_expunge_mail_allocated(struct mail *_mail) +{ + struct lazy_expunge_transaction *lt = + LAZY_EXPUNGE_CONTEXT(_mail->transaction); + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + struct lazy_expunge_mail *mmail; + + if (lt == NULL) + return; + + mmail = p_new(mail->pool, struct lazy_expunge_mail, 1); + mmail->module_ctx.super = *v; + mail->vlast = &mmail->module_ctx.super; + + v->expunge = lazy_expunge_mail_expunge; + MODULE_CONTEXT_SET(mail, lazy_expunge_mail_module, mmail); +} + +static int +lazy_expunge_mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + union mailbox_module_context *lbox = LAZY_EXPUNGE_CONTEXT_REQUIRE(src); + struct lazy_expunge_mailbox_list *src_llist = + LAZY_EXPUNGE_LIST_CONTEXT(src->list); + struct lazy_expunge_mailbox_list *dest_llist = + LAZY_EXPUNGE_LIST_CONTEXT(dest->list); + + i_assert(src_llist != NULL && dest_llist != NULL); + + if (!src_llist->allow_rename && + (src_llist->internal_namespace || + dest_llist->internal_namespace)) { + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename mailboxes to/from expunge namespace."); + return -1; + } + return lbox->super.rename_box(src, dest); +} + +static void lazy_expunge_mailbox_allocated(struct mailbox *box) +{ + struct lazy_expunge_mailbox_list *llist = + LAZY_EXPUNGE_LIST_CONTEXT(box->list); + union mailbox_module_context *mbox; + struct mailbox_vfuncs *v = box->vlast; + + if (llist == NULL || (box->flags & MAILBOX_FLAG_DELETE_UNSAFE) != 0) + return; + + mbox = p_new(box->pool, union mailbox_module_context, 1); + mbox->super = *v; + box->vlast = &mbox->super; + MODULE_CONTEXT_SET_SELF(box, lazy_expunge_mail_storage_module, mbox); + + if (!lazy_expunge_is_internal_mailbox(box)) { + v->copy = lazy_expunge_copy; + v->transaction_begin = lazy_expunge_transaction_begin; + v->transaction_commit = lazy_expunge_transaction_commit; + v->transaction_rollback = lazy_expunge_transaction_rollback; + v->rename_box = lazy_expunge_mailbox_rename; + } else if (llist->internal_namespace) { + v->rename_box = lazy_expunge_mailbox_rename; + } else { + /* internal mailbox in a non-internal namespace - + don't add any unnecessary restrictions to it. if it's not + wanted, just use the ACL plugin. */ + } +} + +static void lazy_expunge_mailbox_list_created(struct mailbox_list *list) +{ + struct lazy_expunge_mail_user *luser = + LAZY_EXPUNGE_USER_CONTEXT(list->ns->user); + struct lazy_expunge_mailbox_list *llist; + + if (luser == NULL) + return; + + /* if this is one of our internal namespaces, mark it as such before + quota plugin sees it */ + if (strcmp(list->ns->prefix, luser->env) == 0) + list->ns->flags |= NAMESPACE_FLAG_NOQUOTA; + + if (list->ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) { + llist = p_new(list->pool, struct lazy_expunge_mailbox_list, 1); + MODULE_CONTEXT_SET(list, lazy_expunge_mailbox_list_module, + llist); + } +} + +static void +lazy_expunge_mail_namespaces_created(struct mail_namespace *namespaces) +{ + struct lazy_expunge_mail_user *luser = + LAZY_EXPUNGE_USER_CONTEXT(namespaces->user); + struct lazy_expunge_mailbox_list *llist; + + if (luser == NULL) + return; + + luser->lazy_ns = mail_namespace_find_prefix(namespaces, luser->env); + if (luser->lazy_ns != NULL) { + /* we don't want to override this namespace's expunge operation. */ + llist = LAZY_EXPUNGE_LIST_CONTEXT(luser->lazy_ns->list); + i_assert(llist != NULL); + llist->internal_namespace = TRUE; + } else { + /* store the the expunged mails to the specified mailbox. */ + luser->lazy_ns = mail_namespace_find(namespaces, luser->env); + luser->lazy_mailbox_vname = luser->env; + } + mail_namespace_ref(luser->lazy_ns); +} + +static void lazy_expunge_user_deinit(struct mail_user *user) +{ + struct lazy_expunge_mail_user *luser = LAZY_EXPUNGE_USER_CONTEXT_REQUIRE(user); + + /* mail_namespaces_created hook isn't necessarily ever called */ + if (luser->lazy_ns != NULL) + mail_namespace_unref(&luser->lazy_ns); + mailbox_match_plugin_deinit(&luser->excludes); + + luser->module_ctx.super.deinit(user); +} + +static void lazy_expunge_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct lazy_expunge_mail_user *luser; + const char *env; + + env = mail_user_plugin_getenv(user, "lazy_expunge"); + if (env != NULL && env[0] != '\0') { + luser = p_new(user->pool, struct lazy_expunge_mail_user, 1); + luser->module_ctx.super = *v; + user->vlast = &luser->module_ctx.super; + v->deinit = lazy_expunge_user_deinit; + luser->env = env; + luser->copy_only_last_instance = + mail_user_plugin_getenv_bool(user, "lazy_expunge_only_last_instance"); + luser->excludes = mailbox_match_plugin_init(user, "lazy_expunge_exclude"); + + MODULE_CONTEXT_SET(user, lazy_expunge_mail_user_module, luser); + } else { + e_debug(user->event, "lazy_expunge: No lazy_expunge setting - " + "plugin disabled"); + } +} + +static struct mail_storage_hooks lazy_expunge_mail_storage_hooks = { + .mail_user_created = lazy_expunge_mail_user_created, + .mail_namespaces_created = lazy_expunge_mail_namespaces_created, + .mailbox_list_created = lazy_expunge_mailbox_list_created, + .mailbox_allocated = lazy_expunge_mailbox_allocated, + .mail_allocated = lazy_expunge_mail_allocated +}; + +void lazy_expunge_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &lazy_expunge_mail_storage_hooks); +} + +void lazy_expunge_plugin_deinit(void) +{ + mail_storage_hooks_remove(&lazy_expunge_mail_storage_hooks); +} diff --git a/src/plugins/lazy-expunge/lazy-expunge-plugin.h b/src/plugins/lazy-expunge/lazy-expunge-plugin.h new file mode 100644 index 0000000..88ee53d --- /dev/null +++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.h @@ -0,0 +1,7 @@ +#ifndef LAZY_EXPUNGE_PLUGIN_H +#define LAZY_EXPUNGE_PLUGIN_H + +void lazy_expunge_plugin_init(struct module *module); +void lazy_expunge_plugin_deinit(void); + +#endif diff --git a/src/plugins/listescape/Makefile.am b/src/plugins/listescape/Makefile.am new file mode 100644 index 0000000..fa614a3 --- /dev/null +++ b/src/plugins/listescape/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib20_listescape_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_listescape_plugin.la + +lib20_listescape_plugin_la_SOURCES = \ + listescape-plugin.c + +noinst_HEADERS = \ + listescape-plugin.h diff --git a/src/plugins/listescape/Makefile.in b/src/plugins/listescape/Makefile.in new file mode 100644 index 0000000..4e420e1 --- /dev/null +++ b/src/plugins/listescape/Makefile.in @@ -0,0 +1,821 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/listescape +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_listescape_plugin_la_LIBADD = +am_lib20_listescape_plugin_la_OBJECTS = listescape-plugin.lo +lib20_listescape_plugin_la_OBJECTS = \ + $(am_lib20_listescape_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_listescape_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_listescape_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/listescape-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_listescape_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_listescape_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +lib20_listescape_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_listescape_plugin.la + +lib20_listescape_plugin_la_SOURCES = \ + listescape-plugin.c + +noinst_HEADERS = \ + listescape-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/listescape/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/listescape/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_listescape_plugin.la: $(lib20_listescape_plugin_la_OBJECTS) $(lib20_listescape_plugin_la_DEPENDENCIES) $(EXTRA_lib20_listescape_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_listescape_plugin_la_LINK) -rpath $(moduledir) $(lib20_listescape_plugin_la_OBJECTS) $(lib20_listescape_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listescape-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/listescape-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/listescape-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/listescape/listescape-plugin.c b/src/plugins/listescape/listescape-plugin.c new file mode 100644 index 0000000..89fb1f9 --- /dev/null +++ b/src/plugins/listescape/listescape-plugin.c @@ -0,0 +1,36 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage-hooks.h" +#include "mailbox-list-private.h" +#include "listescape-plugin.h" + +#define DEFAULT_ESCAPE_CHAR '\\' + +const char *listescape_plugin_version = DOVECOT_ABI_VERSION; + +static void listescape_mailbox_list_created(struct mailbox_list *list) +{ + const char *env; + + if (list->set.storage_name_escape_char == '\0') { + env = mail_user_plugin_getenv(list->ns->user, "listescape_char"); + list->set.storage_name_escape_char = + env != NULL && *env != '\0' ? + env[0] : DEFAULT_ESCAPE_CHAR; + } +} + +static struct mail_storage_hooks listescape_mail_storage_hooks = { + .mailbox_list_created = listescape_mailbox_list_created +}; + +void listescape_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &listescape_mail_storage_hooks); +} + +void listescape_plugin_deinit(void) +{ + mail_storage_hooks_remove(&listescape_mail_storage_hooks); +} diff --git a/src/plugins/listescape/listescape-plugin.h b/src/plugins/listescape/listescape-plugin.h new file mode 100644 index 0000000..f8ff4fc --- /dev/null +++ b/src/plugins/listescape/listescape-plugin.h @@ -0,0 +1,7 @@ +#ifndef LISTESCAPE_PLUGIN_H +#define LISTESCAPE_PLUGIN_H + +void listescape_plugin_init(struct module *module); +void listescape_plugin_deinit(void); + +#endif diff --git a/src/plugins/mail-crypt/Makefile.am b/src/plugins/mail-crypt/Makefile.am new file mode 100644 index 0000000..942dc87 --- /dev/null +++ b/src/plugins/mail-crypt/Makefile.am @@ -0,0 +1,116 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-dcrypt \ + -I$(top_srcdir)/src/lib-fs \ + -I$(top_srcdir)/src/doveadm \ + -I$(top_srcdir)/src/plugins/acl + +if SSL_VERSION_GE_102 +test_options = +else !SSL_VERSION_GE_102 +test_options = NOUNDEF=1 +endif !SSL_VERSION_GE_102 + +doveadm_moduledir = $(moduledir)/doveadm + +NOPLUGIN_LDFLAGS = + +module_LTLIBRARIES = \ + lib10_mail_crypt_plugin.la \ + lib05_mail_crypt_acl_plugin.la \ + libfs_crypt.la \ + libfs_mail_crypt.la + +doveadm_module_LTLIBRARIES = \ + libdoveadm_mail_crypt_plugin.la + +lib10_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version +lib10_mail_crypt_plugin_la_LIBADD = \ + $(LIBDCRYPT_LIBS) \ + $(LIBDOVECOT) + +lib05_mail_crypt_acl_plugin_la_LDFLAGS = -module -avoid-version +if DOVECOT_PLUGIN_DEPS +lib05_mail_crypt_acl_plugin_la_LIBADD = \ + $(LIBDCRYPT_LIBS) \ + lib10_mail_crypt_plugin.la +endif + +lib10_mail_crypt_plugin_la_SOURCES = \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c \ + mail-crypt-key.c \ + mail-crypt-plugin.c + +lib05_mail_crypt_acl_plugin_la_SOURCES = \ + mail-crypt-acl-plugin.c + +libfs_crypt_la_SOURCES = fs-crypt.c \ + mail-crypt-global-key.c \ + mail-crypt-pluginenv.c \ + fs-crypt-settings.c + +libfs_crypt_la_LIBADD = $(LIBDOVECOT) +libfs_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libfs_crypt_la_LDFLAGS = -module -avoid-version + +libfs_mail_crypt_la_SOURCES = fs-mail-crypt.c \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c +libfs_mail_crypt_la_LIBADD = $(LIBDOVECOT) +libfs_mail_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libfs_mail_crypt_la_LDFLAGS = -module -avoid-version + +libdoveadm_mail_crypt_plugin_la_SOURCES = \ + doveadm-mail-crypt.c +libdoveadm_mail_crypt_plugin_la_LIBADD = $(LIBDOVECOT) +libdoveadm_mail_crypt_plugin_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libdoveadm_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version + +test_programs = \ + test-mail-global-key \ + test-mail-key + +test_mail_global_key_SOURCES = \ + test-mail-global-key.c \ + fs-crypt-settings.c \ + mail-crypt-global-key.c +test_mail_global_key_LDADD = $(LIBDOVECOT) +test_mail_global_key_DEPENDENCIES = $(LIBDOVECOT_DEPS) +test_mail_global_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS) +test_mail_global_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\" + +test_mail_key_SOURCES = \ + test-mail-key.c \ + mail-crypt-key.c \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c + +test_mail_key_LDADD = $(LIBDOVECOT_STORAGE) $(LIBDOVECOT) +test_mail_key_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(LIBDOVECOT_STORAGE_DEPS) +test_mail_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS) +test_mail_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\" + +EXTRA_DIST = fs-crypt-common.c + +noinst_HEADERS = \ + mail-crypt-plugin.h \ + mail-crypt-common.h \ + mail-crypt-global-key.h \ + mail-crypt-key.h \ + fs-crypt-settings.h + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +noinst_PROGRAMS = $(test_programs) diff --git a/src/plugins/mail-crypt/Makefile.in b/src/plugins/mail-crypt/Makefile.in new file mode 100644 index 0000000..c815616 --- /dev/null +++ b/src/plugins/mail-crypt/Makefile.in @@ -0,0 +1,1200 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/plugins/mail-crypt +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-mail-global-key$(EXEEXT) test-mail-key$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(doveadm_moduledir)" \ + "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES) +@DOVECOT_PLUGIN_DEPS_TRUE@lib05_mail_crypt_acl_plugin_la_DEPENDENCIES = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ lib10_mail_crypt_plugin.la +am_lib05_mail_crypt_acl_plugin_la_OBJECTS = mail-crypt-acl-plugin.lo +lib05_mail_crypt_acl_plugin_la_OBJECTS = \ + $(am_lib05_mail_crypt_acl_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib05_mail_crypt_acl_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(lib05_mail_crypt_acl_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +am__DEPENDENCIES_1 = +lib10_mail_crypt_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_lib10_mail_crypt_plugin_la_OBJECTS = mail-crypt-global-key.lo \ + mail-crypt-userenv.lo mail-crypt-key.lo mail-crypt-plugin.lo +lib10_mail_crypt_plugin_la_OBJECTS = \ + $(am_lib10_mail_crypt_plugin_la_OBJECTS) +lib10_mail_crypt_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib10_mail_crypt_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_libdoveadm_mail_crypt_plugin_la_OBJECTS = doveadm-mail-crypt.lo +libdoveadm_mail_crypt_plugin_la_OBJECTS = \ + $(am_libdoveadm_mail_crypt_plugin_la_OBJECTS) +libdoveadm_mail_crypt_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(libdoveadm_mail_crypt_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +am_libfs_crypt_la_OBJECTS = fs-crypt.lo mail-crypt-global-key.lo \ + mail-crypt-pluginenv.lo fs-crypt-settings.lo +libfs_crypt_la_OBJECTS = $(am_libfs_crypt_la_OBJECTS) +libfs_crypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libfs_crypt_la_LDFLAGS) $(LDFLAGS) -o \ + $@ +am_libfs_mail_crypt_la_OBJECTS = fs-mail-crypt.lo \ + mail-crypt-global-key.lo mail-crypt-userenv.lo +libfs_mail_crypt_la_OBJECTS = $(am_libfs_mail_crypt_la_OBJECTS) +libfs_mail_crypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libfs_mail_crypt_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_mail_global_key_OBJECTS = \ + test_mail_global_key-test-mail-global-key.$(OBJEXT) \ + test_mail_global_key-fs-crypt-settings.$(OBJEXT) \ + test_mail_global_key-mail-crypt-global-key.$(OBJEXT) +test_mail_global_key_OBJECTS = $(am_test_mail_global_key_OBJECTS) +test_mail_global_key_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(test_mail_global_key_CFLAGS) $(CFLAGS) \ + $(test_mail_global_key_LDFLAGS) $(LDFLAGS) -o $@ +am_test_mail_key_OBJECTS = test_mail_key-test-mail-key.$(OBJEXT) \ + test_mail_key-mail-crypt-key.$(OBJEXT) \ + test_mail_key-mail-crypt-global-key.$(OBJEXT) \ + test_mail_key-mail-crypt-userenv.$(OBJEXT) +test_mail_key_OBJECTS = $(am_test_mail_key_OBJECTS) +test_mail_key_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_mail_key_CFLAGS) \ + $(CFLAGS) $(test_mail_key_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/doveadm-mail-crypt.Plo \ + ./$(DEPDIR)/fs-crypt-settings.Plo ./$(DEPDIR)/fs-crypt.Plo \ + ./$(DEPDIR)/fs-mail-crypt.Plo \ + ./$(DEPDIR)/mail-crypt-acl-plugin.Plo \ + ./$(DEPDIR)/mail-crypt-global-key.Plo \ + ./$(DEPDIR)/mail-crypt-key.Plo \ + ./$(DEPDIR)/mail-crypt-plugin.Plo \ + ./$(DEPDIR)/mail-crypt-pluginenv.Plo \ + ./$(DEPDIR)/mail-crypt-userenv.Plo \ + ./$(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po \ + ./$(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po \ + ./$(DEPDIR)/test_mail_global_key-test-mail-global-key.Po \ + ./$(DEPDIR)/test_mail_key-mail-crypt-global-key.Po \ + ./$(DEPDIR)/test_mail_key-mail-crypt-key.Po \ + ./$(DEPDIR)/test_mail_key-mail-crypt-userenv.Po \ + ./$(DEPDIR)/test_mail_key-test-mail-key.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib05_mail_crypt_acl_plugin_la_SOURCES) \ + $(lib10_mail_crypt_plugin_la_SOURCES) \ + $(libdoveadm_mail_crypt_plugin_la_SOURCES) \ + $(libfs_crypt_la_SOURCES) $(libfs_mail_crypt_la_SOURCES) \ + $(test_mail_global_key_SOURCES) $(test_mail_key_SOURCES) +DIST_SOURCES = $(lib05_mail_crypt_acl_plugin_la_SOURCES) \ + $(lib10_mail_crypt_plugin_la_SOURCES) \ + $(libdoveadm_mail_crypt_plugin_la_SOURCES) \ + $(libfs_crypt_la_SOURCES) $(libfs_mail_crypt_la_SOURCES) \ + $(test_mail_global_key_SOURCES) $(test_mail_key_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-dcrypt \ + -I$(top_srcdir)/src/lib-fs \ + -I$(top_srcdir)/src/doveadm \ + -I$(top_srcdir)/src/plugins/acl + +@SSL_VERSION_GE_102_FALSE@test_options = NOUNDEF=1 +@SSL_VERSION_GE_102_TRUE@test_options = +doveadm_moduledir = $(moduledir)/doveadm +module_LTLIBRARIES = \ + lib10_mail_crypt_plugin.la \ + lib05_mail_crypt_acl_plugin.la \ + libfs_crypt.la \ + libfs_mail_crypt.la + +doveadm_module_LTLIBRARIES = \ + libdoveadm_mail_crypt_plugin.la + +lib10_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version +lib10_mail_crypt_plugin_la_LIBADD = \ + $(LIBDCRYPT_LIBS) \ + $(LIBDOVECOT) + +lib05_mail_crypt_acl_plugin_la_LDFLAGS = -module -avoid-version +@DOVECOT_PLUGIN_DEPS_TRUE@lib05_mail_crypt_acl_plugin_la_LIBADD = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ $(LIBDCRYPT_LIBS) \ +@DOVECOT_PLUGIN_DEPS_TRUE@ lib10_mail_crypt_plugin.la + +lib10_mail_crypt_plugin_la_SOURCES = \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c \ + mail-crypt-key.c \ + mail-crypt-plugin.c + +lib05_mail_crypt_acl_plugin_la_SOURCES = \ + mail-crypt-acl-plugin.c + +libfs_crypt_la_SOURCES = fs-crypt.c \ + mail-crypt-global-key.c \ + mail-crypt-pluginenv.c \ + fs-crypt-settings.c + +libfs_crypt_la_LIBADD = $(LIBDOVECOT) +libfs_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libfs_crypt_la_LDFLAGS = -module -avoid-version +libfs_mail_crypt_la_SOURCES = fs-mail-crypt.c \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c + +libfs_mail_crypt_la_LIBADD = $(LIBDOVECOT) +libfs_mail_crypt_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libfs_mail_crypt_la_LDFLAGS = -module -avoid-version +libdoveadm_mail_crypt_plugin_la_SOURCES = \ + doveadm-mail-crypt.c + +libdoveadm_mail_crypt_plugin_la_LIBADD = $(LIBDOVECOT) +libdoveadm_mail_crypt_plugin_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) +libdoveadm_mail_crypt_plugin_la_LDFLAGS = -module -avoid-version +test_programs = \ + test-mail-global-key \ + test-mail-key + +test_mail_global_key_SOURCES = \ + test-mail-global-key.c \ + fs-crypt-settings.c \ + mail-crypt-global-key.c + +test_mail_global_key_LDADD = $(LIBDOVECOT) +test_mail_global_key_DEPENDENCIES = $(LIBDOVECOT_DEPS) +test_mail_global_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS) +test_mail_global_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\" +test_mail_key_SOURCES = \ + test-mail-key.c \ + mail-crypt-key.c \ + mail-crypt-global-key.c \ + mail-crypt-userenv.c + +test_mail_key_LDADD = $(LIBDOVECOT_STORAGE) $(LIBDOVECOT) +test_mail_key_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(LIBDOVECOT_STORAGE_DEPS) +test_mail_key_LDFLAGS = $(DOVECOT_BINARY_LDFLAGS) +test_mail_key_CFLAGS = $(AM_CPPFLAGS) $(DOVECOT_BINARY_CFLAGS) -Dtop_builddir=\"$(top_builddir)\" +EXTRA_DIST = fs-crypt-common.c +noinst_HEADERS = \ + mail-crypt-plugin.h \ + mail-crypt-common.h \ + mail-crypt-global-key.h \ + mail-crypt-key.h \ + fs-crypt-settings.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/mail-crypt/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/mail-crypt/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \ + } + +uninstall-doveadm_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \ + done + +clean-doveadm_moduleLTLIBRARIES: + -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES) + @list='$(doveadm_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib05_mail_crypt_acl_plugin.la: $(lib05_mail_crypt_acl_plugin_la_OBJECTS) $(lib05_mail_crypt_acl_plugin_la_DEPENDENCIES) $(EXTRA_lib05_mail_crypt_acl_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib05_mail_crypt_acl_plugin_la_LINK) -rpath $(moduledir) $(lib05_mail_crypt_acl_plugin_la_OBJECTS) $(lib05_mail_crypt_acl_plugin_la_LIBADD) $(LIBS) + +lib10_mail_crypt_plugin.la: $(lib10_mail_crypt_plugin_la_OBJECTS) $(lib10_mail_crypt_plugin_la_DEPENDENCIES) $(EXTRA_lib10_mail_crypt_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib10_mail_crypt_plugin_la_LINK) -rpath $(moduledir) $(lib10_mail_crypt_plugin_la_OBJECTS) $(lib10_mail_crypt_plugin_la_LIBADD) $(LIBS) + +libdoveadm_mail_crypt_plugin.la: $(libdoveadm_mail_crypt_plugin_la_OBJECTS) $(libdoveadm_mail_crypt_plugin_la_DEPENDENCIES) $(EXTRA_libdoveadm_mail_crypt_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(libdoveadm_mail_crypt_plugin_la_LINK) -rpath $(doveadm_moduledir) $(libdoveadm_mail_crypt_plugin_la_OBJECTS) $(libdoveadm_mail_crypt_plugin_la_LIBADD) $(LIBS) + +libfs_crypt.la: $(libfs_crypt_la_OBJECTS) $(libfs_crypt_la_DEPENDENCIES) $(EXTRA_libfs_crypt_la_DEPENDENCIES) + $(AM_V_CCLD)$(libfs_crypt_la_LINK) -rpath $(moduledir) $(libfs_crypt_la_OBJECTS) $(libfs_crypt_la_LIBADD) $(LIBS) + +libfs_mail_crypt.la: $(libfs_mail_crypt_la_OBJECTS) $(libfs_mail_crypt_la_DEPENDENCIES) $(EXTRA_libfs_mail_crypt_la_DEPENDENCIES) + $(AM_V_CCLD)$(libfs_mail_crypt_la_LINK) -rpath $(moduledir) $(libfs_mail_crypt_la_OBJECTS) $(libfs_mail_crypt_la_LIBADD) $(LIBS) + +test-mail-global-key$(EXEEXT): $(test_mail_global_key_OBJECTS) $(test_mail_global_key_DEPENDENCIES) $(EXTRA_test_mail_global_key_DEPENDENCIES) + @rm -f test-mail-global-key$(EXEEXT) + $(AM_V_CCLD)$(test_mail_global_key_LINK) $(test_mail_global_key_OBJECTS) $(test_mail_global_key_LDADD) $(LIBS) + +test-mail-key$(EXEEXT): $(test_mail_key_OBJECTS) $(test_mail_key_DEPENDENCIES) $(EXTRA_test_mail_key_DEPENDENCIES) + @rm -f test-mail-key$(EXEEXT) + $(AM_V_CCLD)$(test_mail_key_LINK) $(test_mail_key_OBJECTS) $(test_mail_key_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-crypt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fs-crypt-settings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fs-crypt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fs-mail-crypt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-acl-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-global-key.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-key.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-pluginenv.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-crypt-userenv.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_global_key-test-mail-global-key.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_key-mail-crypt-global-key.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_key-mail-crypt-key.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_key-mail-crypt-userenv.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_mail_key-test-mail-key.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +test_mail_global_key-test-mail-global-key.o: test-mail-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-test-mail-global-key.o -MD -MP -MF $(DEPDIR)/test_mail_global_key-test-mail-global-key.Tpo -c -o test_mail_global_key-test-mail-global-key.o `test -f 'test-mail-global-key.c' || echo '$(srcdir)/'`test-mail-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-test-mail-global-key.Tpo $(DEPDIR)/test_mail_global_key-test-mail-global-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mail-global-key.c' object='test_mail_global_key-test-mail-global-key.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-test-mail-global-key.o `test -f 'test-mail-global-key.c' || echo '$(srcdir)/'`test-mail-global-key.c + +test_mail_global_key-test-mail-global-key.obj: test-mail-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-test-mail-global-key.obj -MD -MP -MF $(DEPDIR)/test_mail_global_key-test-mail-global-key.Tpo -c -o test_mail_global_key-test-mail-global-key.obj `if test -f 'test-mail-global-key.c'; then $(CYGPATH_W) 'test-mail-global-key.c'; else $(CYGPATH_W) '$(srcdir)/test-mail-global-key.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-test-mail-global-key.Tpo $(DEPDIR)/test_mail_global_key-test-mail-global-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mail-global-key.c' object='test_mail_global_key-test-mail-global-key.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-test-mail-global-key.obj `if test -f 'test-mail-global-key.c'; then $(CYGPATH_W) 'test-mail-global-key.c'; else $(CYGPATH_W) '$(srcdir)/test-mail-global-key.c'; fi` + +test_mail_global_key-fs-crypt-settings.o: fs-crypt-settings.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-fs-crypt-settings.o -MD -MP -MF $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Tpo -c -o test_mail_global_key-fs-crypt-settings.o `test -f 'fs-crypt-settings.c' || echo '$(srcdir)/'`fs-crypt-settings.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Tpo $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fs-crypt-settings.c' object='test_mail_global_key-fs-crypt-settings.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-fs-crypt-settings.o `test -f 'fs-crypt-settings.c' || echo '$(srcdir)/'`fs-crypt-settings.c + +test_mail_global_key-fs-crypt-settings.obj: fs-crypt-settings.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-fs-crypt-settings.obj -MD -MP -MF $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Tpo -c -o test_mail_global_key-fs-crypt-settings.obj `if test -f 'fs-crypt-settings.c'; then $(CYGPATH_W) 'fs-crypt-settings.c'; else $(CYGPATH_W) '$(srcdir)/fs-crypt-settings.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Tpo $(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fs-crypt-settings.c' object='test_mail_global_key-fs-crypt-settings.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-fs-crypt-settings.obj `if test -f 'fs-crypt-settings.c'; then $(CYGPATH_W) 'fs-crypt-settings.c'; else $(CYGPATH_W) '$(srcdir)/fs-crypt-settings.c'; fi` + +test_mail_global_key-mail-crypt-global-key.o: mail-crypt-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-mail-crypt-global-key.o -MD -MP -MF $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Tpo -c -o test_mail_global_key-mail-crypt-global-key.o `test -f 'mail-crypt-global-key.c' || echo '$(srcdir)/'`mail-crypt-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Tpo $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-global-key.c' object='test_mail_global_key-mail-crypt-global-key.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-mail-crypt-global-key.o `test -f 'mail-crypt-global-key.c' || echo '$(srcdir)/'`mail-crypt-global-key.c + +test_mail_global_key-mail-crypt-global-key.obj: mail-crypt-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -MT test_mail_global_key-mail-crypt-global-key.obj -MD -MP -MF $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Tpo -c -o test_mail_global_key-mail-crypt-global-key.obj `if test -f 'mail-crypt-global-key.c'; then $(CYGPATH_W) 'mail-crypt-global-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-global-key.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Tpo $(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-global-key.c' object='test_mail_global_key-mail-crypt-global-key.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_global_key_CFLAGS) $(CFLAGS) -c -o test_mail_global_key-mail-crypt-global-key.obj `if test -f 'mail-crypt-global-key.c'; then $(CYGPATH_W) 'mail-crypt-global-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-global-key.c'; fi` + +test_mail_key-test-mail-key.o: test-mail-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-test-mail-key.o -MD -MP -MF $(DEPDIR)/test_mail_key-test-mail-key.Tpo -c -o test_mail_key-test-mail-key.o `test -f 'test-mail-key.c' || echo '$(srcdir)/'`test-mail-key.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-test-mail-key.Tpo $(DEPDIR)/test_mail_key-test-mail-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mail-key.c' object='test_mail_key-test-mail-key.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-test-mail-key.o `test -f 'test-mail-key.c' || echo '$(srcdir)/'`test-mail-key.c + +test_mail_key-test-mail-key.obj: test-mail-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-test-mail-key.obj -MD -MP -MF $(DEPDIR)/test_mail_key-test-mail-key.Tpo -c -o test_mail_key-test-mail-key.obj `if test -f 'test-mail-key.c'; then $(CYGPATH_W) 'test-mail-key.c'; else $(CYGPATH_W) '$(srcdir)/test-mail-key.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-test-mail-key.Tpo $(DEPDIR)/test_mail_key-test-mail-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mail-key.c' object='test_mail_key-test-mail-key.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-test-mail-key.obj `if test -f 'test-mail-key.c'; then $(CYGPATH_W) 'test-mail-key.c'; else $(CYGPATH_W) '$(srcdir)/test-mail-key.c'; fi` + +test_mail_key-mail-crypt-key.o: mail-crypt-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-key.o -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-key.Tpo -c -o test_mail_key-mail-crypt-key.o `test -f 'mail-crypt-key.c' || echo '$(srcdir)/'`mail-crypt-key.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-key.Tpo $(DEPDIR)/test_mail_key-mail-crypt-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-key.c' object='test_mail_key-mail-crypt-key.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-key.o `test -f 'mail-crypt-key.c' || echo '$(srcdir)/'`mail-crypt-key.c + +test_mail_key-mail-crypt-key.obj: mail-crypt-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-key.obj -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-key.Tpo -c -o test_mail_key-mail-crypt-key.obj `if test -f 'mail-crypt-key.c'; then $(CYGPATH_W) 'mail-crypt-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-key.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-key.Tpo $(DEPDIR)/test_mail_key-mail-crypt-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-key.c' object='test_mail_key-mail-crypt-key.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-key.obj `if test -f 'mail-crypt-key.c'; then $(CYGPATH_W) 'mail-crypt-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-key.c'; fi` + +test_mail_key-mail-crypt-global-key.o: mail-crypt-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-global-key.o -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-global-key.Tpo -c -o test_mail_key-mail-crypt-global-key.o `test -f 'mail-crypt-global-key.c' || echo '$(srcdir)/'`mail-crypt-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-global-key.Tpo $(DEPDIR)/test_mail_key-mail-crypt-global-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-global-key.c' object='test_mail_key-mail-crypt-global-key.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-global-key.o `test -f 'mail-crypt-global-key.c' || echo '$(srcdir)/'`mail-crypt-global-key.c + +test_mail_key-mail-crypt-global-key.obj: mail-crypt-global-key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-global-key.obj -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-global-key.Tpo -c -o test_mail_key-mail-crypt-global-key.obj `if test -f 'mail-crypt-global-key.c'; then $(CYGPATH_W) 'mail-crypt-global-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-global-key.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-global-key.Tpo $(DEPDIR)/test_mail_key-mail-crypt-global-key.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-global-key.c' object='test_mail_key-mail-crypt-global-key.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-global-key.obj `if test -f 'mail-crypt-global-key.c'; then $(CYGPATH_W) 'mail-crypt-global-key.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-global-key.c'; fi` + +test_mail_key-mail-crypt-userenv.o: mail-crypt-userenv.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-userenv.o -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-userenv.Tpo -c -o test_mail_key-mail-crypt-userenv.o `test -f 'mail-crypt-userenv.c' || echo '$(srcdir)/'`mail-crypt-userenv.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-userenv.Tpo $(DEPDIR)/test_mail_key-mail-crypt-userenv.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-userenv.c' object='test_mail_key-mail-crypt-userenv.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-userenv.o `test -f 'mail-crypt-userenv.c' || echo '$(srcdir)/'`mail-crypt-userenv.c + +test_mail_key-mail-crypt-userenv.obj: mail-crypt-userenv.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -MT test_mail_key-mail-crypt-userenv.obj -MD -MP -MF $(DEPDIR)/test_mail_key-mail-crypt-userenv.Tpo -c -o test_mail_key-mail-crypt-userenv.obj `if test -f 'mail-crypt-userenv.c'; then $(CYGPATH_W) 'mail-crypt-userenv.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-userenv.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_mail_key-mail-crypt-userenv.Tpo $(DEPDIR)/test_mail_key-mail-crypt-userenv.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mail-crypt-userenv.c' object='test_mail_key-mail-crypt-userenv.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_mail_key_CFLAGS) $(CFLAGS) -c -o test_mail_key-mail-crypt-userenv.obj `if test -f 'mail-crypt-userenv.c'; then $(CYGPATH_W) 'mail-crypt-userenv.c'; else $(CYGPATH_W) '$(srcdir)/mail-crypt-userenv.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/doveadm-mail-crypt.Plo + -rm -f ./$(DEPDIR)/fs-crypt-settings.Plo + -rm -f ./$(DEPDIR)/fs-crypt.Plo + -rm -f ./$(DEPDIR)/fs-mail-crypt.Plo + -rm -f ./$(DEPDIR)/mail-crypt-acl-plugin.Plo + -rm -f ./$(DEPDIR)/mail-crypt-global-key.Plo + -rm -f ./$(DEPDIR)/mail-crypt-key.Plo + -rm -f ./$(DEPDIR)/mail-crypt-plugin.Plo + -rm -f ./$(DEPDIR)/mail-crypt-pluginenv.Plo + -rm -f ./$(DEPDIR)/mail-crypt-userenv.Plo + -rm -f ./$(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po + -rm -f ./$(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po + -rm -f ./$(DEPDIR)/test_mail_global_key-test-mail-global-key.Po + -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-global-key.Po + -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-key.Po + -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-userenv.Po + -rm -f ./$(DEPDIR)/test_mail_key-test-mail-key.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-doveadm_moduleLTLIBRARIES \ + install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/doveadm-mail-crypt.Plo + -rm -f ./$(DEPDIR)/fs-crypt-settings.Plo + -rm -f ./$(DEPDIR)/fs-crypt.Plo + -rm -f ./$(DEPDIR)/fs-mail-crypt.Plo + -rm -f ./$(DEPDIR)/mail-crypt-acl-plugin.Plo + -rm -f ./$(DEPDIR)/mail-crypt-global-key.Plo + -rm -f ./$(DEPDIR)/mail-crypt-key.Plo + -rm -f ./$(DEPDIR)/mail-crypt-plugin.Plo + -rm -f ./$(DEPDIR)/mail-crypt-pluginenv.Plo + -rm -f ./$(DEPDIR)/mail-crypt-userenv.Plo + -rm -f ./$(DEPDIR)/test_mail_global_key-fs-crypt-settings.Po + -rm -f ./$(DEPDIR)/test_mail_global_key-mail-crypt-global-key.Po + -rm -f ./$(DEPDIR)/test_mail_global_key-test-mail-global-key.Po + -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-global-key.Po + -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-key.Po + -rm -f ./$(DEPDIR)/test_mail_key-mail-crypt-userenv.Po + -rm -f ./$(DEPDIR)/test_mail_key-test-mail-key.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-doveadm_moduleLTLIBRARIES \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am \ + install-doveadm_moduleLTLIBRARIES install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/mail-crypt/doveadm-mail-crypt.c b/src/plugins/mail-crypt/doveadm-mail-crypt.c new file mode 100644 index 0000000..a4322ed --- /dev/null +++ b/src/plugins/mail-crypt/doveadm-mail-crypt.c @@ -0,0 +1,1048 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "askpass.h" +#include "doveadm-mail.h" +#include "getopt.h" +#include "array.h" +#include "str.h" +#include "buffer.h" +#include "ioloop.h" +#include "ioloop-private.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "mail-storage-settings.h" +#include "mailbox-attribute.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mailbox-list-iter.h" +#include "doveadm-print.h" +#include "hex-binary.h" + +#define DOVEADM_MCP_SUCCESS "\xE2\x9C\x93" /* emits a utf-8 CHECK MARK (U+2713) */ +#define DOVEADM_MCP_FAIL "x" +#define DOVEADM_MCP_USERKEY "<userkey>" + +struct generated_key { + const char *name; + const char *id; + const char *error; + struct mailbox *box; + bool success:1; + bool active:1; +}; + +ARRAY_DEFINE_TYPE(generated_keys, struct generated_key); + +struct mcp_cmd_context { + struct doveadm_mail_cmd_context ctx; + + const char *old_password; + const char *new_password; + + unsigned int matched_keys; + + bool userkey_only:1; + bool recrypt_box_keys:1; + bool force:1; + bool ask_old_password:1; + bool ask_new_password:1; + bool clear_password:1; +}; + +struct mcp_key_iter_ctx { + pool_t pool; + ARRAY_TYPE(generated_keys) keys; +}; + +void doveadm_mail_crypt_plugin_init(struct module *mod ATTR_UNUSED); +void doveadm_mail_crypt_plugin_deinit(void); + +static int +mcp_user_create(struct mail_user *user, const char *dest_username, + struct mail_user **dest_user_r, + struct mail_storage_service_user **dest_service_user_r, + const char **error_r) +{ + const struct mail_storage_service_input *old_input; + struct mail_storage_service_input input; + struct mail_storage_service_ctx *service_ctx; + struct ioloop_context *cur_ioloop_ctx; + + int ret; + + i_assert(user->_service_user != NULL); + service_ctx = mail_storage_service_user_get_service_ctx(user->_service_user); + old_input = mail_storage_service_user_get_input(user->_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + + i_zero(&input); + input.module = old_input->module; + input.service = old_input->service; + input.username = dest_username; + input.session_id_prefix = user->session_id; + input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS | + MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT; + + ret = mail_storage_service_lookup_next(service_ctx, &input, + dest_service_user_r, + dest_user_r, error_r); + + if (ret == 0) + *error_r = "User not found"; + + return ret; +} + +static int +mcp_update_shared_key(struct mailbox_transaction_context *t, + struct mail_user *user, const char *target_uid, + struct dcrypt_private_key *key, const char **error_r) +{ + const char *error; + struct mail_user *dest_user; + struct mail_storage_service_user *dest_service_user; + struct ioloop_context *cur_ioloop_ctx; + struct dcrypt_public_key *pkey; + const char *dest_username; + int ret = 0; + + bool disallow_insecure = mail_crypt_acl_secure_sharing_enabled(user); + + ret = mcp_user_create(user, target_uid, &dest_user, + &dest_service_user, &error); + + /* to make sure we get correct logging context */ + if (ret > 0) + mail_storage_service_io_deactivate_user(dest_service_user); + mail_storage_service_io_activate_user(user->_service_user); + + if (ret <= 0) { + i_error("Cannot initialize destination user %s: %s", + target_uid, error); + return ret; + } else { + i_assert(dest_user != NULL); + dest_username = dest_user->username; + + /* get public key from target user */ + if ((ret = mail_crypt_user_get_public_key(dest_user, + &pkey, error_r)) <= 0) { + if (ret == 0 && disallow_insecure) { + *error_r = t_strdup_printf("User %s has no active public key", + dest_user->username); + ret = -1; + } else if (ret == 0) { + /* perform insecure sharing */ + dest_username = NULL; + pkey = NULL; + ret = 1; + } + } + + if (ret == 1) { + ARRAY_TYPE(dcrypt_private_key) keys; + t_array_init(&keys, 1); + array_push_back(&keys, &key); + ret = mail_crypt_box_share_private_keys(t, pkey, + dest_username, + &keys, error_r); + } + + } + + /* logging context swap again */ + mail_storage_service_io_deactivate_user(user->_service_user); + mail_storage_service_io_activate_user(dest_service_user); + + mail_user_deinit(&dest_user); + mail_storage_service_user_unref(&dest_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + + mail_storage_service_io_activate_user(user->_service_user); + + return ret; +} + +static int mcp_update_shared_keys(struct doveadm_mail_cmd_context *ctx, + struct mailbox *box, struct mail_user *user, + const char *pubid, struct dcrypt_private_key *key) +{ + const char *error; + int ret; + + ARRAY_TYPE(const_string) ids; + t_array_init(&ids, 8); + + /* figure out who needs the key */ + if (mail_crypt_box_get_pvt_digests(box, pool_datastack_create(), + MAIL_ATTRIBUTE_TYPE_SHARED, + &ids, &error) < 0) { + i_error("mail_crypt_box_get_pvt_digests(%s, /shared) failed: %s", + mailbox_get_vname(box), + error); + return -1; + } + + const char *id; + bool found = FALSE; + string_t *uid = t_str_new(64); + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, ctx->transaction_flags, __func__); + + ret = 0; + + /* then perform sharing */ + array_foreach_elem(&ids, id) { + if (strchr(id, '/') != NULL) { + str_truncate(uid, 0); + const char *hexuid = t_strcut(id, '/'); + hex_to_binary(hexuid, uid); + if (mcp_update_shared_key(t, user, str_c(uid), key, + &error) < 0) { + i_error("mcp_update_shared_key(%s, %s) failed: %s", + mailbox_get_vname(box), + str_c(uid), + error); + ret = -1; + break; + } + } else if (!found) { + found = TRUE; + if (mail_crypt_box_set_shared_key(t, pubid, key, + NULL, NULL, + &error) < 0) { + i_error("mail_crypt_box_set_shared_key(%s) failed: %s", + mailbox_get_vname(box), + error); + ret = -1; + break; + } + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + i_error("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + error); + ret = -1; + } + + return ret; +} + +static int mcp_keypair_generate(struct mcp_cmd_context *ctx, + struct dcrypt_public_key *user_key, + struct mailbox *box, struct dcrypt_keypair *pair_r, + const char **pubid_r, const char **error_r) +{ + struct dcrypt_keypair pair = {NULL, NULL}; + + int ret; + + if ((ret = mail_crypt_box_get_public_key(box, &pair.pub, error_r)) < 0) { + ret = -1; + } else if (ret == 1 && !ctx->force) { + i_info("Folder key exists. Use -f to generate a new one"); + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + const char *error; + if (!dcrypt_key_id_public(pair.pub, + MAIL_CRYPT_KEY_ID_ALGORITHM, + key_id, &error)) { + i_error("dcrypt_key_id_public() failed: %s", + error); + return -1; + } + *pubid_r = p_strdup(ctx->ctx.pool, binary_to_hex(key_id->data, + key_id->used)); + *pair_r = pair; + return 1; + } else if (ret == 1 && ctx->recrypt_box_keys) { + /* do nothing, because force isn't being used *OR* + we are recrypting box keys and force refers to + user keypair. + + FIXME: this could be less confusing altogether */ + ret = 0; + } else { + if (mail_crypt_box_generate_keypair(box, &pair, user_key, + pubid_r, error_r) < 0) { + ret = -1; + } else { + *pubid_r = p_strdup(ctx->ctx.pool, *pubid_r); + *pair_r = pair; + return 1; + } + } + + if (pair.pub != NULL) + dcrypt_key_unref_public(&pair.pub); + if (pair.priv != NULL) + dcrypt_key_unref_private(&pair.priv); + + return ret; +} + +static int mcp_keypair_generate_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user, + ARRAY_TYPE(generated_keys) *result) +{ + const char *error; + int ret; + struct dcrypt_public_key *user_key; + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + const char *pubid; + bool user_key_generated = FALSE; + struct generated_key *res; + + if ((ret = mail_crypt_user_get_public_key(user, &user_key, + &error)) <= 0) { + struct dcrypt_keypair pair; + if (ret < 0) { + i_error("mail_crypt_user_get_public_key(%s) failed: %s", + user->username, + error); + } else if (mail_crypt_user_generate_keypair(user, &pair, + &pubid, &error) < 0) { + ret = -1; + i_error("mail_crypt_user_generate_keypair(%s) failed: %s", + user->username, + error); + res = array_append_space(result); + res->name = ""; + res->error = p_strdup(_ctx->pool, error); + res->success = FALSE; + } else { + res = array_append_space(result); + res->name = DOVEADM_MCP_USERKEY; + res->id = p_strdup(_ctx->pool, pubid); + res->success = TRUE; + /* don't do it again later on */ + user_key_generated = TRUE; + ret = 1; + user_key = pair.pub; + dcrypt_key_unref_private(&pair.priv); + } + if (ret < 0) return ret; + ctx->matched_keys++; + } + if (ret == 1 && ctx->userkey_only && !user_key_generated) { + if (!ctx->force) { + i_info("userkey exists. Use -f to generate a new one"); + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_public(user_key, + MAIL_CRYPT_KEY_ID_ALGORITHM, + key_id, &error)) { + i_error("dcrypt_key_id_public() failed: %s", + error); + dcrypt_key_unref_public(&user_key); + return -1; + } + const char *hash = binary_to_hex(key_id->data, + key_id->used); + res = array_append_space(result); + res->name = DOVEADM_MCP_USERKEY; + res->id = p_strdup(_ctx->pool, hash); + res->success = TRUE; + ctx->matched_keys++; + dcrypt_key_unref_public(&user_key); + return 1; + } + struct dcrypt_keypair pair; + dcrypt_key_unref_public(&user_key); + /* regen user key */ + res = array_append_space(result); + res->name = DOVEADM_MCP_USERKEY; + if (mail_crypt_user_generate_keypair(user, &pair, &pubid, + &error) < 0) { + res->success = FALSE; + res->error = p_strdup(_ctx->pool, error); + return -1; + } + res->success = TRUE; + res->id = p_strdup(_ctx->pool, pubid); + user_key = pair.pub; + dcrypt_key_unref_private(&pair.priv); + ctx->matched_keys++; + } + + if (ctx->userkey_only) { + dcrypt_key_unref_public(&user_key); + return 0; + } + + const char *const *patterns = (const char *const[]){ "*", NULL }; + + /* only re-encrypt all folder keys if wanted */ + if (!ctx->recrypt_box_keys) { + patterns = ctx->ctx.args; + } + + const struct mailbox_info *info; + struct mailbox_list_iterate_context *iter = + mailbox_list_iter_init_namespaces(user->namespaces, + patterns, + MAIL_NAMESPACE_TYPE_PRIVATE, + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_NO_AUTO_BOXES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while((info = mailbox_list_iter_next(iter)) != NULL) { + if ((info->flags & MAILBOX_NOSELECT) != 0 || + (info->flags & MAILBOX_NONEXISTENT) != 0) continue; + struct dcrypt_keypair pair; + + struct mailbox *box = + mailbox_alloc(info->ns->list, + info->vname, 0); + if (mailbox_open(box) < 0) { + res = array_append_space(result); + res->name = p_strdup(_ctx->pool, info->vname); + res->success = FALSE; + res->error = p_strdup(_ctx->pool, + mailbox_get_last_internal_error(box, NULL)); + } else if ((ret = mcp_keypair_generate(ctx, user_key, box, + &pair, &pubid, + &error)) < 0) { + res = array_append_space(result); + res->name = p_strdup(_ctx->pool, info->vname); + res->success = FALSE; + res->error = p_strdup(_ctx->pool, error); + } else if (ret == 0) { + /* nothing happened because key already existed and + force wasn't used, skip */ + } else if (ret > 0) { + res = array_append_space(result); + res->name = p_strdup(_ctx->pool, info->vname); + res->success = TRUE; + res->id = pubid; + T_BEGIN { + mcp_update_shared_keys(&ctx->ctx, box, user, + pubid, pair.priv); + } T_END; + if (pair.pub != NULL) + dcrypt_key_unref_public(&pair.pub); + if (pair.priv != NULL) + dcrypt_key_unref_private(&pair.priv); + ctx->matched_keys++; + } + mailbox_free(&box); + } + + (void)mailbox_list_iter_deinit(&iter); + + dcrypt_key_unref_public(&user_key); + return 0; +} + +static int cmd_mcp_keypair_generate_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + int ret = 0; + + ARRAY_TYPE(generated_keys) result; + p_array_init(&result, _ctx->pool, 8); + + if (mcp_keypair_generate_run(_ctx, user, &result) < 0) + _ctx->exit_code = EX_DATAERR; + + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + doveadm_print_header("success", " ", 0); + doveadm_print_header("box", "Folder", 0); + doveadm_print_header("pubid", "Public ID", 0); + + const struct generated_key *res; + + array_foreach(&result, res) { + if (res->success) + doveadm_print(DOVEADM_MCP_SUCCESS); + else { + _ctx->exit_code = EX_DATAERR; + ret = -1; + doveadm_print(DOVEADM_MCP_FAIL); + } + doveadm_print(res->name); + if (!res->success) + doveadm_print(t_strdup_printf("ERROR: %s", res->error)); + else + doveadm_print(res->id); + } + + if (ctx->matched_keys == 0) + i_warning("mailbox cryptokey generate: Nothing was matched. " + "Use -U or specify mask?"); + return ret; +} + +static void mcp_key_list(struct mcp_cmd_context *ctx, + struct mail_user *user, + void(*callback)(const struct generated_key *, void *), + void *context) +{ + const char *error; + + /* we need to use the mailbox attribute API here, as we + are not necessarily able to decrypt any of these keys + */ + + ARRAY_TYPE(const_string) ids; + t_array_init(&ids, 8); + + if (ctx->userkey_only) { + struct mailbox_attribute_iter *iter; + struct mail_namespace *ns = + mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = + mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY); + struct mail_attribute_value value; + i_zero(&value); + + if (mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED, + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value) < 0) { + i_error("mailbox_get_attribute(%s, %s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_internal_error(box, NULL)); + } + + iter = mailbox_attribute_iter_init(box, + MAIL_ATTRIBUTE_TYPE_PRIVATE, + USER_CRYPT_PREFIX + PRIVKEYS_PREFIX); + const char *key_id; + if (value.value == NULL) + value.value = "<NO ACTIVE KEY>"; + while ((key_id = mailbox_attribute_iter_next(iter)) != NULL) { + struct generated_key key; + key.id = key_id; + key.active = strcmp(value.value, key_id) == 0; + key.name = ""; + key.box = box; + callback(&key, context); + ctx->matched_keys++; + } + if (mailbox_attribute_iter_deinit(&iter) < 0) + i_error("mailbox_attribute_iter_deinit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + mailbox_free(&box); + return; + } + + const struct mailbox_info *info; + struct mailbox_list_iterate_context *iter = + mailbox_list_iter_init_namespaces(user->namespaces, + ctx->ctx.args, + MAIL_NAMESPACE_TYPE_PRIVATE, + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_NO_AUTO_BOXES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + + while((info = mailbox_list_iter_next(iter)) != NULL) { + if ((info->flags & MAILBOX_NOSELECT) != 0 || + (info->flags & MAILBOX_NONEXISTENT) != 0) continue; + + struct mailbox *box = + mailbox_alloc(info->ns->list, + info->vname, MAILBOX_FLAG_READONLY); + struct mail_attribute_value value; + i_zero(&value); + array_clear(&ids); + + /* get active ID */ + if (mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED, + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value) < 0) { + i_error("mailbox_get_attribute(%s, %s) failed: %s", + mailbox_get_vname(box), + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_internal_error(box, NULL)); + } else if (mail_crypt_box_get_pvt_digests(box, pool_datastack_create(), + MAIL_ATTRIBUTE_TYPE_PRIVATE, + &ids, &error) < 0) { + i_error("mail_crypt_box_get_pvt_digests(%s) failed: %s", + mailbox_get_vname(box), + error); + } else { + const char *id; + const char *boxname = mailbox_get_vname(box); + if (value.value == NULL) + value.value = "<NO ACTIVE KEY>"; + array_foreach_elem(&ids, id) { + struct generated_key key; + key.name = boxname; + key.id = id; + if (value.value != NULL) + key.active = strcmp(id, value.value) == 0; + else + key.active = FALSE; + key.box = box; + callback(&key, context); + ctx->matched_keys++; + } + } + mailbox_free(&box); + } + + (void)mailbox_list_iter_deinit(&iter); +} + +static void cmd_mcp_key_list_cb(const struct generated_key *_key, void *context) +{ + struct mcp_key_iter_ctx *ctx = context; + struct generated_key *key = array_append_space(&ctx->keys); + key->name = p_strdup(ctx->pool, _key->name); + key->id = p_strdup(ctx->pool, _key->id); + key->active = _key->active; +} + +static int cmd_mcp_key_list_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + struct mcp_key_iter_ctx iter_ctx; + i_zero(&iter_ctx); + iter_ctx.pool = _ctx->pool; + p_array_init(&iter_ctx.keys, _ctx->pool, 8); + + mcp_key_list(ctx, user, cmd_mcp_key_list_cb, &iter_ctx); + + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + doveadm_print_header("box", "Folder", 0); + doveadm_print_header("active", "Active", 0); + doveadm_print_header("pubid", "Public ID", 0); + + const struct generated_key *key; + array_foreach(&iter_ctx.keys, key) { + doveadm_print(key->name); + doveadm_print(key->active ? "yes" : "no"); + doveadm_print(key->id); + } + + if (ctx->matched_keys == 0) + i_warning("mailbox cryptokey list: Nothing was matched. " + "Use -U or specify mask?"); + + return 0; +} + +static void cmd_mcp_key_export_cb(const struct generated_key *key, + void *context ATTR_UNUSED) +{ + struct dcrypt_private_key *pkey; + bool user_key = FALSE; + const char *error = NULL; + int ret; + + if (*key->name == '\0') + user_key = TRUE; + + doveadm_print(key->name); + doveadm_print(key->id); + + if ((ret = mail_crypt_get_private_key(key->box, key->id, user_key, FALSE, + &pkey, &error)) <= 0) { + if (ret == 0) + error = "key not found"; + doveadm_print(t_strdup_printf("ERROR: %s", error)); + doveadm_print(""); + } else { + string_t *out = t_str_new(64); + if (!dcrypt_key_store_private(pkey, DCRYPT_FORMAT_PEM, NULL, out, + NULL, NULL, &error)) { + doveadm_print(t_strdup_printf("ERROR: %s", error)); + doveadm_print(""); + } else { + /* this is to make it more compatible with openssl cli + as it expects BEGIN on it's own line */ + doveadm_print(t_strdup_printf("\n%s", str_c(out))); + } + dcrypt_key_unref_private(&pkey); + } +} + +static int cmd_mcp_key_export_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER); + doveadm_print_header("box", "Folder", 0); + doveadm_print_header("name", "Public ID", 0); + doveadm_print_header("error", "Error", 0); + doveadm_print_header("key", "Key", 0); + + mcp_key_list(ctx, user, cmd_mcp_key_export_cb, NULL); + + return 0; +} + +static int cmd_mcp_key_password_run(struct doveadm_mail_cmd_context *_ctx, + struct mail_user *user) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + bool cli = (_ctx->cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); + + struct raw_key { + const char *attr; + const char *id; + const char *data; + }; + + ARRAY(struct raw_key) raw_keys; + + doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER); + + doveadm_print_header_simple("result"); + + if (ctx->ask_old_password) { + if (ctx->old_password != NULL) { + doveadm_print("old password specified, cannot ask for it"); + _ctx->exit_code = EX_USAGE; + return -1; + } + if (!cli) { + doveadm_print("No cli - cannot ask for password"); + _ctx->exit_code = EX_USAGE; + return -1; + } + ctx->old_password = + p_strdup(_ctx->pool, t_askpass("Old password: ")); + } + + if (ctx->ask_new_password) { + if (ctx->new_password != NULL) { + doveadm_print("new password specified, cannot ask for it"); + _ctx->exit_code = EX_USAGE; + return -1; + } + if (!cli) { + doveadm_print("No cli - cannot ask for password"); + _ctx->exit_code = EX_USAGE; + return -1; + } + const char *passw; + passw = t_askpass("New password: "); + if (strcmp(passw, t_askpass("Confirm new password: ")) != 0) { + doveadm_print("Passwords don't match, aborting"); + _ctx->exit_code = EX_USAGE; + return -1; + } + ctx->new_password = p_strdup(_ctx->pool, passw); + } + + if (ctx->clear_password && + (ctx->new_password != NULL || + mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD) != NULL)) { + doveadm_print("clear password and new password specified"); + _ctx->exit_code = EX_USAGE; + return -1; + } + + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", 0); + if (mailbox_open(box) < 0) { + doveadm_print(t_strdup_printf("mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL))); + _ctx->exit_code = EX_TEMPFAIL; + return -1; + } + + t_array_init(&raw_keys, 8); + + /* then get the current user keys, all of them */ + struct mailbox_attribute_iter *iter = + mailbox_attribute_iter_init(box, + MAIL_ATTRIBUTE_TYPE_PRIVATE, + USER_CRYPT_PREFIX + PRIVKEYS_PREFIX); + const char *error; + const char *key_id; + int ret = 1; + unsigned int count = 0; + + while ((key_id = mailbox_attribute_iter_next(iter)) != NULL) { + const char *attr = + t_strdup_printf(USER_CRYPT_PREFIX PRIVKEYS_PREFIX "%s", + key_id); + + struct mail_attribute_value value; + if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_PRIVATE, + attr, &value)) < 0) { + doveadm_print(t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s", + mailbox_get_vname(box), attr, + mailbox_get_last_internal_error(box, NULL))); + _ctx->exit_code = EX_TEMPFAIL; + break; + } else if (ret > 0) { + struct raw_key *raw_key = array_append_space(&raw_keys); + raw_key->attr = p_strdup(_ctx->pool, attr); + raw_key->id = p_strdup(_ctx->pool, key_id); + raw_key->data = p_strdup(_ctx->pool, value.value); + } + } + + if (ret == 1) { + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, _ctx->transaction_flags, + __func__); + struct dcrypt_private_key *key; + const struct raw_key *raw_key; + const char *algo = ctx->new_password != NULL ? + MAIL_CRYPT_PW_CIPHER : + NULL; + string_t *newkey = t_str_new(256); + + array_foreach(&raw_keys, raw_key) { + struct mail_attribute_value value; + + if (!dcrypt_key_load_private(&key, raw_key->data, + ctx->old_password, NULL, + &error)) { + doveadm_print(t_strdup_printf("dcrypt_key_load_private(%s) failed: %s", + raw_key->id, + error)); + _ctx->exit_code = EX_DATAERR; + ret = -1; + break; + } + + /* save it */ + str_truncate(newkey, 0); + + if (!dcrypt_key_store_private(key, DCRYPT_FORMAT_DOVECOT, + algo, newkey, + ctx->new_password, + NULL, &error)) { + doveadm_print(t_strdup_printf("dcrypt_key_store_private(%s) failed: %s", + raw_key->id, + error)); + _ctx->exit_code = EX_DATAERR; + ret = -1; + } + + dcrypt_key_unref_private(&key); + if (ret == -1) break; + + i_zero(&value); + value.value = str_c(newkey); + + /* and store it */ + if (mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_PRIVATE, + raw_key->attr, &value) < 0) { + doveadm_print(t_strdup_printf("mailbox_attribute_set(%s, %s) failed: %s", + mailbox_get_vname(box), + raw_key->attr, + mailbox_get_last_internal_error(box, NULL))); + _ctx->exit_code = EX_TEMPFAIL; + ret = -1; + break; + } + count++; + } + + if (ret < 1) { + mailbox_transaction_rollback(&t); + } else { + if (mailbox_transaction_commit(&t) < 0) { + doveadm_print(t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL))); + } else { + doveadm_print(t_strdup_printf("Changed password for %u key(s)", + count)); + } + } + } + + (void)mailbox_attribute_iter_deinit(&iter); + mailbox_free(&box); + + return ret; +} + + +static bool cmd_mcp_keypair_generate_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + switch (c) { + case 'U': + ctx->userkey_only = TRUE; + break; + case 'R': + ctx->recrypt_box_keys = TRUE; + break; + case 'f': + ctx->force = TRUE; + break; + default: + return FALSE; + } + return TRUE; + +} + +static bool cmd_mcp_key_password_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + switch (c) { + case 'N': + ctx->ask_new_password = TRUE; + break; + case 'O': + ctx->ask_old_password = TRUE; + break; + case 'C': + ctx->clear_password = TRUE; + break; + case 'o': + ctx->old_password = p_strdup(_ctx->pool, optarg); + break; + case 'n': + ctx->new_password = p_strdup(_ctx->pool, optarg); + break; + default: + return FALSE; + } + return TRUE; +} + +static bool cmd_mcp_key_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) +{ + struct mcp_cmd_context *ctx = + (struct mcp_cmd_context *)_ctx; + + switch (c) { + case 'U': + ctx->userkey_only = TRUE; + break; + default: + return FALSE; + } + return TRUE; + +} + +static struct doveadm_mail_cmd_context *cmd_mcp_keypair_generate_alloc(void) +{ + struct mcp_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context); + ctx->ctx.getopt_args = "URf"; + ctx->ctx.v.parse_arg = cmd_mcp_keypair_generate_parse_arg; + ctx->ctx.v.run = cmd_mcp_keypair_generate_run; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_mcp_key_list_alloc(void) +{ + struct mcp_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context); + ctx->ctx.getopt_args = "U"; + ctx->ctx.v.parse_arg = cmd_mcp_key_parse_arg; + ctx->ctx.v.run = cmd_mcp_key_list_run; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_mcp_key_export_alloc(void) +{ + struct mcp_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context); + ctx->ctx.getopt_args = "U"; + ctx->ctx.v.parse_arg = cmd_mcp_key_parse_arg; + ctx->ctx.v.run = cmd_mcp_key_export_run; + return &ctx->ctx; +} + +static struct doveadm_mail_cmd_context *cmd_mcp_key_password_alloc(void) +{ + struct mcp_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct mcp_cmd_context); + ctx->ctx.getopt_args = "NOCo:n:"; + ctx->ctx.v.parse_arg = cmd_mcp_key_password_parse_arg; + ctx->ctx.v.run = cmd_mcp_key_password_run; + return &ctx->ctx; +} + +struct doveadm_cmd_ver2 doveadm_cmd_mcp_keypair_generate = { + .name = "mailbox cryptokey generate", + .mail_cmd = cmd_mcp_keypair_generate_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-URf] mailbox [ mailbox .. ]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('U', "user-key-only", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('R', "re-encrypt-box-keys", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('f', "force", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; + +struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_list = { + .name = "mailbox cryptokey list", + .mail_cmd = cmd_mcp_key_list_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "-U | mailbox [ mailbox .. ]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('U', "user-key", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; + +struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_export = { + .name = "mailbox cryptokey export", + .mail_cmd = cmd_mcp_key_export_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "-U | mailbox [ mailbox .. ]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('U', "user-key", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; + +struct doveadm_cmd_ver2 doveadm_cmd_mcp_key_password = { + .name = "mailbox cryptokey password", + .mail_cmd = cmd_mcp_key_password_alloc, + .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-NOC] [-opassword] [-npassword]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAM('C', "clear-password", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('N', "ask-new-password", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('n', "new-password", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('O', "ask-old-password", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('o', "old-password", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAMS_END +}; + +void doveadm_mail_crypt_plugin_init(struct module *mod ATTR_UNUSED) +{ + doveadm_cmd_register_ver2(&doveadm_cmd_mcp_keypair_generate); + doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_list); + doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_export); + doveadm_cmd_register_ver2(&doveadm_cmd_mcp_key_password); +} + +void doveadm_mail_crypt_plugin_deinit(void) +{ +} diff --git a/src/plugins/mail-crypt/fs-crypt-common.c b/src/plugins/mail-crypt/fs-crypt-common.c new file mode 100644 index 0000000..7432fa6 --- /dev/null +++ b/src/plugins/mail-crypt/fs-crypt-common.c @@ -0,0 +1,368 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "randgen.h" +#include "istream.h" +#include "ostream.h" +#include "istream-decrypt.h" +#include "ostream-encrypt.h" +#include "iostream-temp.h" +#include "mailbox-list.h" +#include "mail-namespace.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "dcrypt-iostream.h" +#include "fs-api-private.h" + +struct crypt_fs { + struct fs fs; + struct mail_crypt_global_keys keys; + bool keys_loaded; + + char *enc_algo; + char *set_prefix; + char *public_key_path; + char *private_key_path; + char *password; +}; + +struct crypt_fs_file { + struct fs_file file; + struct crypt_fs *fs; + struct fs_file *super_read; + enum fs_open_mode open_mode; + struct istream *input; + + struct ostream *super_output; + struct ostream *temp_output; +}; + +#define CRYPT_FS(ptr) container_of((ptr), struct crypt_fs, fs) +#define CRYPT_FILE(ptr) container_of((ptr), struct crypt_fs_file, file) + +/* defined outside this file */ +extern const struct fs FS_CLASS_CRYPT; + +static +int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r); + +static struct fs *fs_crypt_alloc(void) +{ + struct crypt_fs *fs; + + fs = i_new(struct crypt_fs, 1); + fs->fs = FS_CLASS_CRYPT; + + return &fs->fs; +} + +static int +fs_crypt_init(struct fs *_fs, const char *args, const struct fs_settings *set, + const char **error_r) +{ + struct crypt_fs *fs = CRYPT_FS(_fs); + const char *enc_algo, *set_prefix; + const char *p, *arg, *value, *error, *parent_name, *parent_args; + const char *public_key_path = "", *private_key_path = "", *password = ""; + + if (!dcrypt_initialize("openssl", NULL, &error)) + i_fatal("dcrypt_initialize(): %s", error); + + /* [algo=<s>:][set_prefix=<n>:][public_key_path=<s>:] + [private_key_path=<s>:[password=<s>:]]<parent fs> */ + set_prefix = "mail_crypt_global"; + enc_algo = "aes-256-gcm-sha256"; + for (;;) { + p = strchr(args, ':'); + if (p == NULL) { + *error_r = "Missing parameters"; + return -1; + } + arg = t_strdup_until(args, p); + if ((value = strchr(arg, '=')) == NULL) + break; + arg = t_strdup_until(arg, value++); + args = p+1; + + if (strcmp(arg, "algo") == 0) + enc_algo = value; + else if (strcmp(arg, "set_prefix") == 0) + set_prefix = value; + else if (strcmp(arg, "public_key_path") == 0) + public_key_path = value; + else if (strcmp(arg, "private_key_path") == 0) + private_key_path = value; + else if (strcmp(arg, "password") == 0) + password = value; + else { + *error_r = t_strdup_printf( + "Invalid parameter '%s'", arg); + return -1; + } + } + + parent_args = strchr(args, ':'); + if (parent_args == NULL) { + parent_name = args; + parent_args = ""; + } else { + parent_name = t_strdup_until(args, parent_args); + parent_args++; + } + if (fs_init(parent_name, parent_args, set, &_fs->parent, error_r) < 0) + return -1; + fs->enc_algo = i_strdup(enc_algo); + fs->set_prefix = i_strdup(set_prefix); + fs->public_key_path = i_strdup_empty(public_key_path); + fs->private_key_path = i_strdup_empty(private_key_path); + fs->password = i_strdup_empty(password); + return 0; +} + +static void fs_crypt_free(struct fs *_fs) +{ + struct crypt_fs *fs = CRYPT_FS(_fs); + + mail_crypt_global_keys_free(&fs->keys); + i_free(fs->enc_algo); + i_free(fs->set_prefix); + i_free(fs->public_key_path); + i_free(fs->private_key_path); + i_free(fs->password); + i_free(fs); +} + +static struct fs_file *fs_crypt_file_alloc(void) +{ + struct crypt_fs_file *file = i_new(struct crypt_fs_file, 1); + return &file->file; +} + +static void +fs_crypt_file_init(struct fs_file *_file, const char *path, + enum fs_open_mode mode, enum fs_open_flags flags) +{ + struct crypt_fs *fs = CRYPT_FS(_file->fs); + struct crypt_fs_file *file = CRYPT_FILE(_file); + + file->file.path = i_strdup(path); + file->fs = fs; + file->open_mode = mode; + + /* avoid unnecessarily creating two seekable streams */ + flags &= ENUM_NEGATE(FS_OPEN_FLAG_SEEKABLE); + + file->file.parent = fs_file_init_parent(_file, path, mode, flags); + if (mode == FS_OPEN_MODE_READONLY && + (flags & FS_OPEN_FLAG_ASYNC) == 0) { + /* use async stream for super, so fs_read_stream() won't create + another seekable stream needlessly */ + file->super_read = fs_file_init_parent(_file, path, + mode, flags | FS_OPEN_FLAG_ASYNC | + FS_OPEN_FLAG_ASYNC_NOQUEUE); + } else { + file->super_read = file->file.parent; + } +} + +static void fs_crypt_file_deinit(struct fs_file *_file) +{ + struct crypt_fs_file *file = CRYPT_FILE(_file); + + if (file->super_read != _file->parent) + fs_file_deinit(&file->super_read); + fs_file_free(_file); + i_free(file->file.path); + i_free(file); +} + +static void fs_crypt_file_close(struct fs_file *_file) +{ + struct crypt_fs_file *file = CRYPT_FILE(_file); + + i_stream_unref(&file->input); + fs_file_close(file->super_read); + fs_file_close(_file->parent); +} + +static int fs_crypt_read_file(const char *set_name, const char *path, + char **key_data_r, const char **error_r) +{ + struct istream *input; + int ret; + + input = i_stream_create_file(path, SIZE_MAX); + while (i_stream_read(input) > 0) ; + if (input->stream_errno != 0) { + *error_r = t_strdup_printf("%s: read(%s) failed: %s", + set_name, path, i_stream_get_error(input)); + ret = -1; + } else { + size_t size; + const unsigned char *data = i_stream_get_data(input, &size); + *key_data_r = i_strndup(data, size); + ret = 0; + } + i_stream_unref(&input); + return ret; +} + +static int +fs_crypt_load_keys_from_path(struct crypt_fs *fs, const char **error_r) +{ + char *key_data; + + mail_crypt_global_keys_init(&fs->keys); + if (fs->public_key_path != NULL) { + if (fs_crypt_read_file("crypt:public_key_path", + fs->public_key_path, + &key_data, error_r) < 0) + return -1; + if (mail_crypt_load_global_public_key("crypt:public_key_path", + key_data, &fs->keys, + error_r) < 0) { + i_free(key_data); + return -1; + } + i_free(key_data); + } + if (fs->private_key_path != NULL) { + if (fs_crypt_read_file("crypt:private_key_path", + fs->private_key_path, + &key_data, error_r) < 0) + return -1; + if (mail_crypt_load_global_private_key("crypt:private_key_path", + key_data, "crypt:password", + fs->password, &fs->keys, + error_r) < 0) { + i_free(key_data); + return -1; + } + i_free(key_data); + } + return 0; +} + +static int +fs_crypt_istream_get_key(const char *pubkey_digest, + struct dcrypt_private_key **priv_key_r, + const char **error_r, void *context) +{ + struct crypt_fs_file *file = context; + + if (fs_crypt_load_keys(file->fs, error_r) < 0) + return -1; + + *priv_key_r = mail_crypt_global_key_find(&file->fs->keys, pubkey_digest); + if (*priv_key_r == NULL) + return 0; + dcrypt_key_ref_private(*priv_key_r); + return 1; +} + +static struct istream * +fs_crypt_read_stream(struct fs_file *_file, size_t max_buffer_size) +{ + struct crypt_fs_file *file = CRYPT_FILE(_file); + struct istream *input; + + if (file->input != NULL) { + i_stream_ref(file->input); + i_stream_seek(file->input, 0); + return file->input; + } + + input = fs_read_stream(file->super_read, max_buffer_size); + + file->input = i_stream_create_decrypt_callback(input, + fs_crypt_istream_get_key, file); + i_stream_unref(&input); + i_stream_ref(file->input); + return file->input; +} + +static void fs_crypt_write_stream(struct fs_file *_file) +{ + struct crypt_fs_file *file = CRYPT_FILE(_file); + const char *error; + + i_assert(_file->output == NULL); + + if (fs_crypt_load_keys(file->fs, &error) < 0) { + _file->output = o_stream_create_error_str(EIO, + "Couldn't read settings: %s", error); + return; + } + + if (file->fs->keys.public_key == NULL) { + if (_file->fs->set.debug) + i_debug("No public key provided, " + "NOT encrypting stream %s", + fs_file_path(_file)); + file->super_output = fs_write_stream(_file->parent); + _file->output = file->super_output; + return; + } + + enum io_stream_encrypt_flags flags; + if (strstr(file->fs->enc_algo, "gcm") != NULL || + strstr(file->fs->enc_algo, "ccm") != NULL) { + flags = IO_STREAM_ENC_INTEGRITY_AEAD; + } else { + flags = IO_STREAM_ENC_INTEGRITY_HMAC; + } + + file->temp_output = + iostream_temp_create_named(_file->fs->temp_path_prefix, + IOSTREAM_TEMP_FLAG_TRY_FD_DUP, + fs_file_path(_file)); + _file->output = o_stream_create_encrypt(file->temp_output, + file->fs->enc_algo, file->fs->keys.public_key, + flags); +} + +static int fs_crypt_write_stream_finish(struct fs_file *_file, bool success) +{ + struct crypt_fs_file *file = CRYPT_FILE(_file); + struct istream *input; + int ret; + + if (_file->output != NULL) { + if (_file->output == file->super_output) + _file->output = NULL; + else + o_stream_unref(&_file->output); + } + if (!success) { + if (file->super_output != NULL) { + /* no encryption */ + i_assert(file->temp_output == NULL); + fs_write_stream_abort_error(_file->parent, &file->super_output, + "write(%s) failed: %s", + o_stream_get_name(file->super_output), + o_stream_get_error(file->super_output)); + } else { + o_stream_destroy(&file->temp_output); + } + return -1; + } + + if (file->super_output != NULL) { + /* no encrypt */ + i_assert(file->temp_output == NULL); + return fs_write_stream_finish(_file->parent, &file->super_output); + } + if (file->temp_output == NULL) { + /* finishing up */ + i_assert(file->super_output == NULL); + return fs_write_stream_finish_async(_file->parent); + } + + /* finish writing the temporary file */ + input = iostream_temp_finish(&file->temp_output, IO_BLOCK_SIZE); + file->super_output = fs_write_stream(_file->parent); + o_stream_nsend_istream(file->super_output, input); + ret = fs_write_stream_finish(_file->parent, &file->super_output); + i_stream_unref(&input); + return ret; +} diff --git a/src/plugins/mail-crypt/fs-crypt-settings.c b/src/plugins/mail-crypt/fs-crypt-settings.c new file mode 100644 index 0000000..ba70f8a --- /dev/null +++ b/src/plugins/mail-crypt/fs-crypt-settings.c @@ -0,0 +1,35 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "settings-parser.h" +#include "service-settings.h" +#include "mail-storage-settings.h" +#include "fs-crypt-settings.h" + +static const struct setting_define fs_crypt_setting_defines[] = { + { .type = SET_STRLIST, .key = "plugin", + .offset = offsetof(struct fs_crypt_settings, plugin_envs) }, + + SETTING_DEFINE_LIST_END +}; + +const struct fs_crypt_settings fs_crypt_default_settings = { + .plugin_envs = ARRAY_INIT +}; + +static const struct setting_parser_info *fs_crypt_setting_dependencies[] = { + NULL +}; + +const struct setting_parser_info fs_crypt_setting_parser_info = { + .module_name = "fs-crypt", + .defines = fs_crypt_setting_defines, + .defaults = &fs_crypt_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct fs_crypt_settings), + + .parent_offset = SIZE_MAX, + .dependencies = fs_crypt_setting_dependencies +}; diff --git a/src/plugins/mail-crypt/fs-crypt-settings.h b/src/plugins/mail-crypt/fs-crypt-settings.h new file mode 100644 index 0000000..df1a7b1 --- /dev/null +++ b/src/plugins/mail-crypt/fs-crypt-settings.h @@ -0,0 +1,11 @@ +#ifndef FS_CRYPT_SETTINGS_H +#define FS_CRYPT_SETTINGS_H + +struct fs_crypt_settings { + ARRAY(const char *) plugin_envs; +}; + +extern const struct setting_parser_info fs_crypt_setting_parser_info; + +#endif + diff --git a/src/plugins/mail-crypt/fs-crypt.c b/src/plugins/mail-crypt/fs-crypt.c new file mode 100644 index 0000000..0a81c69 --- /dev/null +++ b/src/plugins/mail-crypt/fs-crypt.c @@ -0,0 +1,65 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ +#define FS_CLASS_CRYPT fs_class_crypt +#include "fs-crypt-common.c" + +static +int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r) +{ + const char *error; + + if (fs->keys_loaded) + return 0; + if (fs->public_key_path != NULL || fs->private_key_path != NULL) { + /* overrides using settings */ + if (fs_crypt_load_keys_from_path(fs, error_r) < 0) + return -1; + fs->keys_loaded = TRUE; + return 0; + } + if (mail_crypt_global_keys_load_pluginenv(fs->set_prefix, &fs->keys, + &error) < 0) { + *error_r = t_strdup_printf("%s: %s", fs->set_prefix, error); + return -1; + } + fs->keys_loaded = TRUE; + return 0; +} + +const struct fs fs_class_crypt = { + .name = "crypt", + .v = { + fs_crypt_alloc, + fs_crypt_init, + NULL, + fs_crypt_free, + fs_wrapper_get_properties, + fs_crypt_file_alloc, + fs_crypt_file_init, + fs_crypt_file_deinit, + fs_crypt_file_close, + fs_wrapper_file_get_path, + fs_wrapper_set_async_callback, + fs_wrapper_wait_async, + fs_wrapper_set_metadata, + fs_wrapper_get_metadata, + fs_wrapper_prefetch, + fs_read_via_stream, + fs_crypt_read_stream, + fs_write_via_stream, + fs_crypt_write_stream, + fs_crypt_write_stream_finish, + fs_wrapper_lock, + fs_wrapper_unlock, + fs_wrapper_exists, + fs_wrapper_stat, + fs_wrapper_copy, + fs_wrapper_rename, + fs_wrapper_delete, + fs_wrapper_iter_alloc, + fs_wrapper_iter_init, + fs_wrapper_iter_next, + fs_wrapper_iter_deinit, + NULL, + fs_wrapper_get_nlinks, + } +}; diff --git a/src/plugins/mail-crypt/fs-mail-crypt.c b/src/plugins/mail-crypt/fs-mail-crypt.c new file mode 100644 index 0000000..b017d3f --- /dev/null +++ b/src/plugins/mail-crypt/fs-mail-crypt.c @@ -0,0 +1,72 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ +#define FS_CLASS_CRYPT fs_class_mail_crypt +#include "fs-crypt-common.c" + +static +int fs_crypt_load_keys(struct crypt_fs *fs, const char **error_r) +{ + struct mailbox_list *list = mailbox_list_fs_get_list(&fs->fs); + const char *error; + + if (fs->keys_loaded) + return 0; + if (fs->public_key_path != NULL || fs->private_key_path != NULL) { + /* overrides using settings */ + if (fs_crypt_load_keys_from_path(fs, error_r) < 0) + return -1; + fs->keys_loaded = TRUE; + return 0; + } + if (list == NULL) { + *error_r = "fs-mail-crypt can be used only via lib-storage"; + return -1; + } + + if (mail_crypt_global_keys_load(mailbox_list_get_namespace(list)->user, + fs->set_prefix, &fs->keys, FALSE, + &error) < 0) { + *error_r = t_strdup_printf("%s: %s", fs->set_prefix, error); + return -1; + } + fs->keys_loaded = TRUE; + return 0; +} + +const struct fs fs_class_mail_crypt = { + .name = "mail-crypt", + .v = { + fs_crypt_alloc, + fs_crypt_init, + NULL, + fs_crypt_free, + fs_wrapper_get_properties, + fs_crypt_file_alloc, + fs_crypt_file_init, + fs_crypt_file_deinit, + fs_crypt_file_close, + fs_wrapper_file_get_path, + fs_wrapper_set_async_callback, + fs_wrapper_wait_async, + fs_wrapper_set_metadata, + fs_wrapper_get_metadata, + fs_wrapper_prefetch, + fs_read_via_stream, + fs_crypt_read_stream, + fs_write_via_stream, + fs_crypt_write_stream, + fs_crypt_write_stream_finish, + fs_wrapper_lock, + fs_wrapper_unlock, + fs_wrapper_exists, + fs_wrapper_stat, + fs_wrapper_copy, + fs_wrapper_rename, + fs_wrapper_delete, + fs_wrapper_iter_alloc, + fs_wrapper_iter_init, + fs_wrapper_iter_next, + fs_wrapper_iter_deinit, + NULL, + fs_wrapper_get_nlinks, + } +}; diff --git a/src/plugins/mail-crypt/mail-crypt-acl-plugin.c b/src/plugins/mail-crypt/mail-crypt-acl-plugin.c new file mode 100644 index 0000000..71ab1e7 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-acl-plugin.c @@ -0,0 +1,431 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop-private.h" +#include "str.h" +#include "sha2.h" +#include "module-dir.h" +#include "var-expand.h" +#include "hex-binary.h" +#include "mail-namespace.h" +#include "mail-storage-hooks.h" +#include "mail-storage-service.h" +#include "acl-plugin.h" +#include "acl-api-private.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" + +#define MAIL_CRYPT_ACL_LIST_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mail_crypt_acl_mailbox_list_module) + +struct mail_crypt_acl_mailbox_list { + union mailbox_list_module_context module_ctx; + struct acl_backend_vfuncs acl_vprev; +}; + +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_acl_mailbox_list_module, + &mailbox_list_module_register); + +void mail_crypt_acl_plugin_init(struct module *module); +void mail_crypt_acl_plugin_deinit(void); + +static int +mail_crypt_acl_has_user_read_right(struct acl_object *aclobj, + const char *username, + const char **error_r) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret = 0; + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (rights.id_type == ACL_ID_USER && + strcmp(rights.identifier, username) == 0) { + ret = str_array_find(rights.rights, MAIL_ACL_READ) ? 1 : 0; + break; + } + } + if (acl_object_list_deinit(&iter) < 0) { + *error_r = "Failed to iterate ACL objects"; + return -1; + } + + return ret; +} + +static int mail_crypt_acl_has_nonuser_read_right(struct acl_object *aclobj, + const char **error_r) +{ + struct acl_object_list_iter *iter; + struct acl_rights rights; + int ret = 0; + + iter = acl_object_list_init(aclobj); + while (acl_object_list_next(iter, &rights)) { + if (rights.id_type != ACL_ID_USER && + rights.id_type != ACL_ID_OWNER && + rights.rights != NULL && + str_array_find(rights.rights, MAIL_ACL_READ)) { + ret = 1; + break; + } + } + if (acl_object_list_deinit(&iter) < 0) { + *error_r = "Failed to iterate ACL objects"; + return -1; + } + return ret; +} + +static int +mail_crypt_acl_unset_private_keys(struct mailbox *src_box, + const char *dest_user, + enum mail_attribute_type type, + const char **error_r) +{ + ARRAY_TYPE(const_string) digests; + const char *error; + int ret = 1; + + if (mailbox_open(src_box) < 0) { + *error_r = t_strdup_printf("mail-crypt-acl-plugin: " + "mailbox_open(%s) failed: %s", + mailbox_get_vname(src_box), + mailbox_get_last_internal_error(src_box, NULL)); + return -1; + } + + t_array_init(&digests, 4); + if (mail_crypt_box_get_pvt_digests(src_box, pool_datastack_create(), + type, &digests, &error) < 0) { + *error_r = t_strdup_printf("mail-crypt-acl-plugin: " + "Failed to lookup public key digests: %s", + error); + mailbox_free(&src_box); + return -1; + } + + struct mailbox_transaction_context *t; + t = mailbox_transaction_begin(src_box, 0, __func__); + + const char *hash; + array_foreach_elem(&digests, hash) { + const char *ptr; + /* if the id contains username part, skip to key public id */ + if ((ptr = strchr(hash, '/')) != NULL) + ptr++; + else + ptr = hash; + if ((ret = mail_crypt_box_unset_shared_key(t, ptr, dest_user, + error_r)) < 0) { + ret = -1; + break; + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + *error_r = t_strdup_printf("mail-crypt-acl-plugin: " + "mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(src_box), + mailbox_get_last_internal_error(src_box, NULL)); + return -1; + } + return 0; +} + +static int +mail_crypt_acl_user_create(struct mail_user *user, const char *dest_username, + struct mail_user **dest_user_r, + struct mail_storage_service_user **dest_service_user_r, + const char **error_r) +{ + const struct mail_storage_service_input *old_input; + struct mail_storage_service_input input; + struct mail_storage_service_ctx *service_ctx; + struct ioloop_context *cur_ioloop_ctx; + + int ret; + + i_assert(user->_service_user != NULL); + service_ctx = mail_storage_service_user_get_service_ctx(user->_service_user); + old_input = mail_storage_service_user_get_input(user->_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + + i_zero(&input); + input.module = old_input->module; + input.service = old_input->service; + input.username = dest_username; + input.session_id_prefix = user->session_id; + input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS | + MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT; + input.flags_override_remove = MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES; + + ret = mail_storage_service_lookup_next(service_ctx, &input, + dest_service_user_r, + dest_user_r, error_r); + + return ret; +} + +static int +mail_crypt_acl_update_private_key(struct mailbox *src_box, + struct mail_user *dest_user, bool set, + bool disallow_insecure, + const char **error_r) +{ + struct dcrypt_public_key *key = NULL; + struct dcrypt_private_key *priv_key; + int ret = 0; + + if (!set) { + return mail_crypt_acl_unset_private_keys(src_box, + dest_user->username, + MAIL_ATTRIBUTE_TYPE_SHARED, + error_r); + } + + if (dest_user != NULL) { + /* get public key from target user */ + if ((ret = mail_crypt_user_get_public_key(dest_user, + &key, error_r)) <= 0) { + if (ret == 0 && disallow_insecure) { + *error_r = t_strdup_printf("User %s has no active public key", + dest_user->username); + return -1; + } else if (ret < 0) { + return -1; + } else if (ret == 0) { + /* perform insecure sharing */ + dest_user = NULL; + key = NULL; + } + } + } + + ARRAY_TYPE(dcrypt_private_key) keys; + t_array_init(&keys, 8); + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(src_box, 0, __func__); + + /* get private keys from box */ + if (mail_crypt_box_get_private_keys(src_box, &keys, error_r) < 0 || + mail_crypt_box_share_private_keys(t, key, + dest_user == NULL ? NULL : + dest_user->username, + &keys, error_r) < 0) + ret = -1; + if (key != NULL) + dcrypt_key_unref_public(&key); + + if (ret >= 0) { + array_foreach_elem(&keys, priv_key) + dcrypt_key_unref_private(&priv_key); + } + + if (mailbox_transaction_commit(&t) < 0) { + *error_r = mailbox_get_last_internal_error(src_box, NULL); + ret = -1; + } + + return ret; +} + +static int mail_crypt_acl_object_update(struct acl_object *aclobj, + const struct acl_rights_update *update) +{ + const char *error; + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(aclobj->backend->list); + const char *username; + struct mail_user *dest_user; + struct mail_storage_service_user *dest_service_user; + struct ioloop_context *cur_ioloop_ctx; + bool have_rights; + int ret = 0; + + if (mlist->acl_vprev.object_update(aclobj, update) < 0) + return -1; + + bool disallow_insecure = + mail_crypt_acl_secure_sharing_enabled(aclobj->backend->list->ns->user); + + const char *box_name = mailbox_list_get_vname(aclobj->backend->list, + aclobj->name); + struct mailbox *box = mailbox_alloc(aclobj->backend->list, box_name, 0); + + switch (update->rights.id_type) { + case ACL_ID_USER: + /* setting rights for specific user: we can encrypt the + mailbox key for the user. */ + username = update->rights.identifier; + ret = mail_crypt_acl_has_user_read_right(aclobj, username, &error); + + if (ret < 0) { + i_error("mail-crypt-acl-plugin: " + "mail_crypt_acl_has_user_read_right(%s) failed: %s", + username, + error); + break; + } + + have_rights = ret > 0; + + ret = mail_crypt_acl_user_create(aclobj->backend->list->ns->user, + username, &dest_user, + &dest_service_user, &error); + + /* to make sure we get correct logging context */ + if (ret > 0) + mail_storage_service_io_deactivate_user(dest_service_user); + mail_storage_service_io_activate_user( + aclobj->backend->list->ns->user->_service_user + ); + + if (ret <= 0) { + i_error("mail-crypt-acl-plugin: " + "Cannot initialize destination user %s: %s", + username, error); + break; + } else { + i_assert(dest_user != NULL); + if ((ret = mailbox_open(box)) < 0) { + i_error("mail-crypt-acl-plugin: " + "mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + } else if ((ret = mail_crypt_acl_update_private_key(box, dest_user, + have_rights, + disallow_insecure, + &error)) < 0) { + i_error("mail-crypt-acl-plugin: " + "acl_update_private_key(%s, %s) failed: %s", + mailbox_get_vname(box), + username, + error); + } + } + + /* logging context swap again */ + mail_storage_service_io_deactivate_user( + aclobj->backend->list->ns->user->_service_user + ); + mail_storage_service_io_activate_user(dest_service_user); + + mail_user_deinit(&dest_user); + mail_storage_service_user_unref(&dest_service_user); + + if ((cur_ioloop_ctx = io_loop_get_current_context(current_ioloop)) != NULL) + io_loop_context_deactivate(cur_ioloop_ctx); + mail_storage_service_io_activate_user( + aclobj->backend->list->ns->user->_service_user + ); + break; + case ACL_ID_OWNER: + /* we should be the one doing this? ignore */ + break; + case ACL_ID_ANYONE: + case ACL_ID_AUTHENTICATED: + case ACL_ID_GROUP: + case ACL_ID_GROUP_OVERRIDE: + if (disallow_insecure) { + i_error("mail-crypt-acl-plugin: " + "Secure key sharing is enabled -" + "Remove or set plugin { %s = no }", + MAIL_CRYPT_ACL_SECURE_SHARE_SETTING); + ret = -1; + break; + } + /* the mailbox key needs to be stored unencrypted. for groups + we could in theory use per-group encrypted keys, which the + users belonging to the group would able to decrypt with + their private key, but that becomes quite complicated. */ + if ((ret = mail_crypt_acl_has_nonuser_read_right(aclobj, &error)) < 0) { + i_error("mail-crypt-acl-plugin: %s", error); + } else if ((ret = mailbox_open(box)) < 0) { + i_error("mail-crypt-acl-plugin: " + "mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + } else if ((ret = mail_crypt_acl_update_private_key(box, + NULL, + TRUE, + disallow_insecure, + &error)) < 0) { + i_error("mail-crypt-acl-plugin: " + "acl_update_private_key(%s, %s) failed: %s", + mailbox_get_vname(box), + "", + error); + } + break; + case ACL_ID_TYPE_COUNT: + i_unreached(); + } + + mailbox_free(&box); + return ret; +} + +static void +mail_crypt_acl_mail_namespace_storage_added(struct mail_namespace *ns) +{ + struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ns->list); + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(ns->list); + struct acl_backend *backend; + + if (alist == NULL) + return; + + /* FIXME: this method works only if there's a single plugin doing it. + if there are ever multiple plugins hooking into ACL commands the + ACL core code would need some changing to make it work correctly. */ + backend = alist->rights.backend; + mlist->acl_vprev = backend->v; + backend->v.object_update = mail_crypt_acl_object_update; +} + +static void mail_crypt_acl_mailbox_list_deinit(struct mailbox_list *list) +{ + struct mail_crypt_acl_mailbox_list *mlist = + MAIL_CRYPT_ACL_LIST_CONTEXT(list); + + mlist->module_ctx.super.deinit(list); +} + +static void mail_crypt_acl_mailbox_list_created(struct mailbox_list *list) +{ + struct mailbox_list_vfuncs *v = list->vlast; + struct mail_crypt_acl_mailbox_list *mlist; + + mlist = p_new(list->pool, struct mail_crypt_acl_mailbox_list, 1); + mlist->module_ctx.super = *v; + list->vlast = &mlist->module_ctx.super; + v->deinit = mail_crypt_acl_mailbox_list_deinit; + + MODULE_CONTEXT_SET(list, mail_crypt_acl_mailbox_list_module, mlist); +} + +static struct mail_storage_hooks mail_crypt_acl_mail_storage_hooks = { + .mailbox_list_created = mail_crypt_acl_mailbox_list_created, + .mail_namespace_storage_added = mail_crypt_acl_mail_namespace_storage_added +}; + +void mail_crypt_acl_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &mail_crypt_acl_mail_storage_hooks); +} + +void mail_crypt_acl_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mail_crypt_acl_mail_storage_hooks); +} + +const char *mail_crypt_acl_plugin_dependencies[] = { "acl", NULL }; diff --git a/src/plugins/mail-crypt/mail-crypt-common.h b/src/plugins/mail-crypt/mail-crypt-common.h new file mode 100644 index 0000000..57e5e2f --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-common.h @@ -0,0 +1,30 @@ +#ifndef MAIL_CRYPT_COMMON_H +#define MAIL_CRYPT_COMMON_H + +#include "dcrypt.h" + +#define MAIL_CRYPT_PW_CIPHER "aes-256-ctr" +#define MAIL_CRYPT_KEY_CIPHER "ecdh-aes-256-ctr" +#define MAIL_CRYPT_ENC_ALGORITHM "aes-256-gcm-sha256" +#define MAIL_CRYPT_KEY_ID_ALGORITHM "sha256" +#define MAIL_CRYPT_KEY_ATTRIBUTE_FORMAT DCRYPT_FORMAT_DOVECOT +#define MAIL_CRYPT_ACL_SECURE_SHARE_SETTING "mail_crypt_acl_require_secure_key_sharing" +#define MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY "mail_crypt_require_encrypted_user_key" +#define MAIL_CRYPT_HASH_BUF_SIZE 128 +#define MAIL_CRYPT_KEY_BUF_SIZE 1024 +#define ACTIVE_KEY_NAME "active" +#define PUBKEYS_PREFIX "pubkeys/" +#define PRIVKEYS_PREFIX "privkeys/" + +#define BOX_CRYPT_PREFIX MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"crypt/" +#define USER_CRYPT_PREFIX \ + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER \ + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT"crypt/" + +#define MAIL_CRYPT_USERENV_PASSWORD "mail_crypt_private_password" +#define MAIL_CRYPT_USERENV_KEY "mail_crypt_private_key" +#define MAIL_CRYPT_USERENV_CURVE "mail_crypt_curve" + +ARRAY_DEFINE_TYPE(dcrypt_private_key, struct dcrypt_private_key*); + +#endif diff --git a/src/plugins/mail-crypt/mail-crypt-global-key.c b/src/plugins/mail-crypt/mail-crypt-global-key.c new file mode 100644 index 0000000..01cb937 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-global-key.c @@ -0,0 +1,172 @@ +/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hex-binary.h" +#include "base64.h" +#include "mail-user.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" + +int mail_crypt_load_global_public_key(const char *set_key, const char *key_data, + struct mail_crypt_global_keys *global_keys, + const char **error_r) +{ + const char *error; + enum dcrypt_key_format format; + enum dcrypt_key_kind kind; + if (!dcrypt_key_string_get_info(key_data, &format, NULL, + &kind, NULL, NULL, NULL, &error)) { + key_data = str_c(t_base64_decode_str(key_data)); + if (!dcrypt_key_string_get_info(key_data, &format, NULL, + &kind, NULL, NULL, NULL, &error)) { + *error_r = t_strdup_printf("%s: Couldn't parse public key: %s", + set_key, error); + return -1; + } + } + if (kind != DCRYPT_KEY_KIND_PUBLIC) { + *error_r = t_strdup_printf("%s: key is not public", set_key); + return -1; + } + if (!dcrypt_key_load_public(&global_keys->public_key, key_data, &error)) { + *error_r = t_strdup_printf("%s: Couldn't load public key: %s", + set_key, error); + return -1; + } + return 0; +} + +static int +mail_crypt_key_get_ids(struct dcrypt_private_key *key, + const char **key_id_r, const char **key_id_old_r, + const char **error_r) +{ + const char *error; + buffer_t *key_id; + + *key_id_r = NULL; + *key_id_old_r = NULL; + + /* new key ID */ + key_id = t_buffer_create(MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_private(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error)) { + *error_r = t_strdup_printf("Failed to get private key ID: %s", error); + return -1; + } + *key_id_r = binary_to_hex(key_id->data, key_id->used); + + buffer_set_used_size(key_id, 0); + + /* old key ID */ + if (dcrypt_key_type_private(key) != DCRYPT_KEY_EC) + return 0; + + if (!dcrypt_key_id_private_old(key, key_id, &error)) { + *error_r = t_strdup_printf("Failed to get private key old ID: %s", + error); + return -1; + } + *key_id_old_r = binary_to_hex(key_id->data, key_id->used); + return 0; +} + +int mail_crypt_load_global_private_key(const char *set_key, const char *key_data, + const char *set_pw, const char *key_password, + struct mail_crypt_global_keys *global_keys, + const char **error_r) +{ + enum dcrypt_key_format format; + enum dcrypt_key_kind kind; + enum dcrypt_key_encryption_type enc_type; + const char *error; + + if (!dcrypt_key_string_get_info(key_data, &format, NULL, &kind, + &enc_type, NULL, NULL, &error)) { + key_data = str_c(t_base64_decode_str(key_data)); + if (!dcrypt_key_string_get_info(key_data, &format, NULL, &kind, + &enc_type, NULL, NULL, &error)) { + *error_r = t_strdup_printf("%s: Couldn't parse private" + " key: %s", set_key, error); + return -1; + } + } + if (kind != DCRYPT_KEY_KIND_PRIVATE) { + *error_r = t_strdup_printf("%s: key is not private", set_key); + return -1; + } + + if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD) { + /* Fail here if password is not set since openssl will prompt + * for it otherwise */ + if (key_password == NULL) { + *error_r = t_strdup_printf("%s: %s unset, no password to decrypt the key", + set_key, set_pw); + return -1; + } + } + + struct dcrypt_private_key *key = NULL; + if (!dcrypt_key_load_private(&key, key_data, key_password, NULL, &error)) { + *error_r = t_strdup_printf("%s: Couldn't load private key: %s", + set_key, error); + return -1; + } + + const char *key_id, *key_id_old; + if (mail_crypt_key_get_ids(key, &key_id, &key_id_old, error_r) < 0) { + dcrypt_key_unref_private(&key); + return -1; + } + + struct mail_crypt_global_private_key *priv_key = + array_append_space(&global_keys->private_keys); + priv_key->key = key; + priv_key->key_id = i_strdup(key_id); + priv_key->key_id_old = i_strdup(key_id_old); + return 0; +} + +void mail_crypt_global_keys_init(struct mail_crypt_global_keys *global_keys_r) +{ + i_zero(global_keys_r); + i_array_init(&global_keys_r->private_keys, 4); +} + +void mail_crypt_global_keys_free(struct mail_crypt_global_keys *global_keys) +{ + struct mail_crypt_global_private_key *priv_key; + + if (global_keys->public_key != NULL) + dcrypt_key_unref_public(&global_keys->public_key); + + if (!array_is_created(&global_keys->private_keys)) + return; + array_foreach_modifiable(&global_keys->private_keys, priv_key) { + dcrypt_key_unref_private(&priv_key->key); + i_free(priv_key->key_id); + i_free(priv_key->key_id_old); + } + array_free(&global_keys->private_keys); +} + +struct dcrypt_private_key * +mail_crypt_global_key_find(struct mail_crypt_global_keys *global_keys, + const char *pubkey_digest) +{ + const struct mail_crypt_global_private_key *priv_key; + + if (!array_is_created(&global_keys->private_keys)) + return NULL; + + array_foreach(&global_keys->private_keys, priv_key) { + if (strcmp(priv_key->key_id, pubkey_digest) == 0) + return priv_key->key; + if (priv_key->key_id_old != NULL && + strcmp(priv_key->key_id_old, pubkey_digest) == 0) + return priv_key->key; + } + return NULL; +} diff --git a/src/plugins/mail-crypt/mail-crypt-global-key.h b/src/plugins/mail-crypt/mail-crypt-global-key.h new file mode 100644 index 0000000..6f4679a --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-global-key.h @@ -0,0 +1,38 @@ +#ifndef MAIL_CRYPT_GLOBAL_KEY_H +#define MAIL_CRYPT_GLOBAL_KEY_H + +struct mail_crypt_global_private_key { + struct dcrypt_private_key *key; + char *key_id, *key_id_old; +}; + +struct mail_crypt_global_keys { + struct dcrypt_public_key *public_key; + ARRAY(struct mail_crypt_global_private_key) private_keys; +}; + +struct mail_user; + +int mail_crypt_global_keys_load(struct mail_user *user, const char *set_prefix, + struct mail_crypt_global_keys *global_keys_r, + bool ignore_privkey_errors, + const char **error_r); +int mail_crypt_global_keys_load_pluginenv(const char *set_prefix, + struct mail_crypt_global_keys *global_keys_r, + const char **error_r); +void mail_crypt_global_keys_init(struct mail_crypt_global_keys *global_keys_r); +void mail_crypt_global_keys_free(struct mail_crypt_global_keys *global_keys); + +int mail_crypt_load_global_public_key(const char *set_key, const char *key_data, + struct mail_crypt_global_keys *global_keys, + const char **error_r); +int mail_crypt_load_global_private_key(const char *set_key, const char *key_data, + const char *set_pw, const char *key_password, + struct mail_crypt_global_keys *global_keys, + const char **error_r); + +struct dcrypt_private_key * +mail_crypt_global_key_find(struct mail_crypt_global_keys *global_keys, + const char *pubkey_digest); + +#endif diff --git a/src/plugins/mail-crypt/mail-crypt-key.c b/src/plugins/mail-crypt/mail-crypt-key.c new file mode 100644 index 0000000..22c86e3 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-key.c @@ -0,0 +1,1242 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "dict.h" +#include "array.h" +#include "var-expand.h" +#include "mail-storage.h" +#include "mailbox-attribute.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" +#include "mail-user.h" +#include "hex-binary.h" +#include "safe-memset.h" +#include "base64.h" +#include "sha2.h" + +struct mail_crypt_key_cache_entry { + struct mail_crypt_key_cache_entry *next; + + char *pubid; + /* this is lazily initialized */ + struct dcrypt_keypair pair; +}; + +static +int mail_crypt_get_key_cache(struct mail_crypt_key_cache_entry *cache, + const char *pubid, + struct dcrypt_private_key **privkey_r, + struct dcrypt_public_key **pubkey_r) +{ + for(struct mail_crypt_key_cache_entry *ent = cache; + ent != NULL; ent = ent->next) + { + if (strcmp(pubid, ent->pubid) == 0) { + if (privkey_r != NULL && ent->pair.priv != NULL) { + dcrypt_key_ref_private(ent->pair.priv); + *privkey_r = ent->pair.priv; + return 1; + } else if (pubkey_r != NULL && ent->pair.pub != NULL) { + dcrypt_key_ref_public(ent->pair.pub); + *pubkey_r = ent->pair.pub; + return 1; + } else if ((privkey_r == NULL && pubkey_r == NULL) || + (ent->pair.priv == NULL && + ent->pair.pub == NULL)) { + i_unreached(); + } + } + } + return 0; +} + +static +void mail_crypt_put_key_cache(struct mail_crypt_key_cache_entry **cache, + const char *pubid, + struct dcrypt_private_key *privkey, + struct dcrypt_public_key *pubkey) +{ + for(struct mail_crypt_key_cache_entry *ent = *cache; + ent != NULL; ent = ent->next) + { + if (strcmp(pubid, ent->pubid) == 0) { + if (privkey != NULL) { + if (ent->pair.priv == NULL) { + ent->pair.priv = privkey; + dcrypt_key_ref_private(ent->pair.priv); + } + } else if (pubkey != NULL) { + if (ent->pair.pub == NULL) { + ent->pair.pub = pubkey; + dcrypt_key_ref_public(ent->pair.pub); + } + } else + i_unreached(); + return; + } + } + + /* not found */ + struct mail_crypt_key_cache_entry *ent = + i_new(struct mail_crypt_key_cache_entry, 1); + ent->pubid = i_strdup(pubid); + ent->pair.priv = privkey; + ent->pair.pub = pubkey; + if (ent->pair.priv != NULL) + dcrypt_key_ref_private(ent->pair.priv); + if (ent->pair.pub != NULL) + dcrypt_key_ref_public(ent->pair.pub); + + if (*cache == NULL) { + *cache = ent; + } else { + ent->next = *cache; + *cache = ent; + } +} + +void mail_crypt_key_cache_destroy(struct mail_crypt_key_cache_entry **cache) +{ + struct mail_crypt_key_cache_entry *next, *cur = *cache; + + *cache = NULL; + + while(cur != NULL) { + next = cur->next; + i_free(cur->pubid); + if (cur->pair.priv != NULL) + dcrypt_key_unref_private(&cur->pair.priv); + if (cur->pair.pub != NULL) + dcrypt_key_unref_public(&cur->pair.pub); + i_free(cur); + cur = next; + } +} + +int mail_crypt_private_key_id_match(struct dcrypt_private_key *key, + const char *pubid, const char **error_r) +{ + i_assert(key != NULL); + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_private(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, + error_r)) + return -1; + const char *hash = binary_to_hex(key_id->data, key_id->used); + if (strcmp(pubid, hash) == 0) return 1; + + buffer_set_used_size(key_id, 0); + if (!dcrypt_key_id_private_old(key, key_id, error_r)) { + return -1; + } + hash = binary_to_hex(key_id->data, key_id->used); + + if (strcmp(pubid, hash) != 0) { + *error_r = t_strdup_printf("Key %s does not match given ID %s", + hash, pubid); + return 0; + } + return 1; +} + +int mail_crypt_public_key_id_match(struct dcrypt_public_key *key, + const char *pubid, const char **error_r) +{ + i_assert(key != NULL); + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_public(key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, + error_r)) + return -1; + const char *hash = binary_to_hex(key_id->data, key_id->used); + if (strcmp(pubid, hash) == 0) return 1; + + buffer_set_used_size(key_id, 0); + if (!dcrypt_key_id_public_old(key, key_id, error_r)) { + return -1; + } + hash = binary_to_hex(key_id->data, key_id->used); + + if (strcmp(pubid, hash) != 0) { + *error_r = t_strdup_printf("Key %s does not match given ID %s", + hash, pubid); + return 0; + } + return 1; +} + +static +int mail_crypt_env_get_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mail_crypt_global_keys global_keys; + int ret = 0; + if (mail_crypt_global_keys_load(user, "mail_crypt", &global_keys, + TRUE, error_r) < 0) { + mail_crypt_global_keys_free(&global_keys); + return -1; + } + + /* see if we got a key */ + struct dcrypt_private_key *key = + mail_crypt_global_key_find(&global_keys, pubid); + + if (key != NULL) { + dcrypt_key_ref_private(key); + *key_r = key; + ret = 1; + } + + mail_crypt_global_keys_free(&global_keys); + + return ret; +} + +static +const char *mail_crypt_get_key_path(bool user_key, bool public, const char *pubid) +{ + const char *ret = t_strdup_printf("%s%s%s", + user_key ? USER_CRYPT_PREFIX : + BOX_CRYPT_PREFIX, + public ? PUBKEYS_PREFIX : + PRIVKEYS_PREFIX, + pubid); + return ret; +} + +static +int mail_crypt_decrypt_private_key(struct mailbox *box, const char *pubid, + const char *data, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + enum dcrypt_key_kind key_kind; + enum dcrypt_key_encryption_type enc_type; + const char *enc_hash = NULL, *key_hash = NULL, *pw = NULL; + struct dcrypt_private_key *key = NULL, *dec_key = NULL; + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + int ret = 0; + + i_assert(pubid != NULL); + i_assert(data != NULL); + + /* see what the key needs for decrypting */ + if (!dcrypt_key_string_get_info(data, NULL, NULL, &key_kind, + &enc_type, &enc_hash, &key_hash, error_r)) { + return -1; + } + + if (key_kind != DCRYPT_KEY_KIND_PRIVATE) { + *error_r = t_strdup_printf("Cannot use key %s: " + "Expected private key, got public key", + pubid); + return -1; + } + + if (key_hash != NULL && strcmp(key_hash, pubid) != 0) { + *error_r = t_strdup_printf("Cannot use key %s: " + "Incorrect key hash %s stored", + pubid, + key_hash); + return -1; + } + + /* see if it needs decrypting */ + if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE) { + /* no key or password */ + } else if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD) { + pw = mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD); + if (pw == NULL) { + *error_r = t_strdup_printf("Cannot decrypt key %s: " + "Password not available", + pubid); + return -1; + } + } else if (enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_KEY) { + if ((ret = mail_crypt_user_get_private_key(user, enc_hash, + &dec_key, error_r)) <= 0) { + /* last resort, look at environment */ + if (ret == 0 && (ret = mail_crypt_env_get_private_key(user, enc_hash, + &dec_key, error_r)) == 0) { + *error_r = t_strdup_printf("Cannot decrypt key %s: " + "Private key %s not available:", + pubid, enc_hash); + return -1; + } else if (ret < 0) { + *error_r = t_strdup_printf("Cannot decrypt key %s: %s", + pubid, *error_r); + return ret; + } + } + } + + bool res = dcrypt_key_load_private(&key, data, pw, dec_key, error_r); + + if (dec_key != NULL) + dcrypt_key_unref_private(&dec_key); + + if (!res) + return -1; + + if (mail_crypt_private_key_id_match(key, pubid, error_r) <= 0) { + if (key != NULL) + dcrypt_key_unref_private(&key); + return -1; + } + + i_assert(key != NULL); + + *key_r = key; + + return 1; +} + +int mail_crypt_get_private_key(struct mailbox *box, const char *pubid, + bool user_key, bool shared, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + + /* check cache */ + if (mail_crypt_get_key_cache(muser->key_cache, pubid, key_r, NULL) > 0) { + return 1; + } + + struct mail_attribute_value value; + struct dcrypt_private_key *key; + int ret; + const char *attr_name = mail_crypt_get_key_path(user_key, FALSE, pubid); + + if ((ret = mailbox_attribute_get(box, + shared ? MAIL_ATTRIBUTE_TYPE_SHARED : + MAIL_ATTRIBUTE_TYPE_PRIVATE, + attr_name, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s%s) failed: %s", + mailbox_get_vname(box), + shared ? "/shared/" : + "/priv/", + attr_name, + mailbox_get_last_internal_error(box, NULL)); + } + return ret; + } + + if ((ret = mail_crypt_decrypt_private_key(box, pubid, value.value, + &key, error_r)) <= 0) + return ret; + + i_assert(key != NULL); + + mail_crypt_put_key_cache(&muser->key_cache, pubid, key, NULL); + + *key_r = key; + + return 1; +} + +int mail_crypt_user_get_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + struct mail_attribute_value value; + int ret; + + /* try retrieve currently active user key */ + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("mailbox_open(%s) failed: %s", + "INBOX", + mailbox_get_last_internal_error(box, NULL)); + return -1; + } + + if (pubid == NULL) { + if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED, + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_internal_error(box, NULL)); + } + } else { + pubid = value.value; + ret = 1; + } + } else + ret = 1; + + /* try to open key */ + if (ret > 0) + ret = mail_crypt_get_private_key(box, pubid, TRUE, FALSE, + key_r, error_r); + mailbox_free(&box); + return ret; +} + +int mail_crypt_box_get_private_key(struct mailbox *box, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mail_attribute_value value; + int ret; + /* get active key */ + if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED, + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_internal_error(box, NULL)); + } + return ret; + } + + return mail_crypt_get_private_key(box, value.value, + FALSE, FALSE, + key_r, error_r); +} + +static +int mail_crypt_set_private_key(struct mailbox_transaction_context *t, + bool user_key, bool shared, const char *pubid, + struct dcrypt_public_key *enc_key, + struct dcrypt_private_key *key, + const char **error_r) +{ + /* folder keys must be encrypted with some other key, + unless they are shared keys */ + i_assert(user_key || shared || enc_key != NULL); + + buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE); + const char *pw = NULL; + const char *algo = NULL; + struct mail_user *user = mail_storage_get_user( + mailbox_get_storage( + mailbox_transaction_get_mailbox(t))); + const char *attr_name = mail_crypt_get_key_path(user_key, FALSE, pubid); + struct mail_attribute_value value; + int ret; + + if (enc_key != NULL) { + algo = MAIL_CRYPT_KEY_CIPHER; + } else if (user_key && + (pw = mail_user_plugin_getenv(user,MAIL_CRYPT_USERENV_PASSWORD)) + != NULL) { + algo = MAIL_CRYPT_PW_CIPHER; + } + + /* export key */ + if (!dcrypt_key_store_private(key, DCRYPT_FORMAT_DOVECOT, algo, data, + pw, enc_key, error_r)) { + return -1; + } + + /* store it */ + value.value_stream = NULL; + value.value = str_c(data); + value.last_change = 0; + + if ((ret = mailbox_attribute_set(t, + shared ? MAIL_ATTRIBUTE_TYPE_SHARED : + MAIL_ATTRIBUTE_TYPE_PRIVATE, + attr_name, + &value)) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s/%s) failed: %s", + mailbox_get_vname(mailbox_transaction_get_mailbox(t)), + shared ? "/shared" : "/priv", + attr_name, + mailbox_get_last_internal_error( + mailbox_transaction_get_mailbox(t), NULL)); + } + + safe_memset(buffer_get_modifiable_data(data, NULL), 0, data->used); + + return ret; +} + +int mail_crypt_user_set_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key *key, + const char **error_r) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + struct dcrypt_private_key *env_key = NULL; + struct dcrypt_public_key *enc_key = NULL; + struct mailbox_transaction_context *t; + int ret; + + if ((ret = mail_crypt_env_get_private_key(user, NULL, &env_key, + error_r)) < 0) { + return -1; + } else if (ret > 0) { + dcrypt_key_convert_private_to_public(env_key, &enc_key); + dcrypt_key_unref_private(&env_key); + } + + if (mail_user_plugin_getenv(user, MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY) != NULL && + mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_PASSWORD) == NULL && + mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_KEY) == NULL) + { + *error_r = MAIL_CRYPT_REQUIRE_ENCRYPTED_USER_KEY " set, cannot " + "generate user keypair without password or key"; + return -1; + } + + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("mailbox_open(%s) failed: %s", + "INBOX", + mailbox_get_last_internal_error(box, NULL)); + return -1; + } + + t = mailbox_transaction_begin(box, 0, __func__); + + if ((ret = mail_crypt_set_private_key(t, TRUE, FALSE, pubid, enc_key, key, + error_r)) < 0) { + mailbox_transaction_rollback(&t); + } else if ((ret = mailbox_transaction_commit(&t)) < 0) { + *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + } + + mailbox_free(&box); + + return ret; +} + +int mail_crypt_box_set_private_key(struct mailbox *box, const char *pubid, + struct dcrypt_private_key *key, + struct dcrypt_public_key *user_key, + const char **error_r) +{ + int ret; + struct mailbox_transaction_context *t; + + t = mailbox_transaction_begin(box, 0, __func__); + if ((ret = mail_crypt_set_private_key(t, FALSE, FALSE, pubid, user_key, + key, error_r)) < 0) { + mailbox_transaction_rollback(&t); + } else if ((ret = mailbox_transaction_commit(&t)) < 0) { + *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + } + + return ret; +} + +static +int mail_crypt_get_public_key(struct mailbox *box, const char *pubid, + bool user_key, struct dcrypt_public_key **key_r, + const char **error_r) +{ + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + + /* check cache */ + if (mail_crypt_get_key_cache(muser->key_cache, pubid, NULL, key_r) > 0) { + return 1; + } + + enum dcrypt_key_kind key_kind; + const char *key_hash = NULL; + struct dcrypt_public_key *key; + struct mail_attribute_value value; + int ret; + const char *attr_name = mail_crypt_get_key_path(user_key, TRUE, pubid); + + if ((ret = mailbox_attribute_get(box, + MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s", + mailbox_get_vname(box), + attr_name, + mailbox_get_last_internal_error(box, NULL)); + } + return ret; + } + + if (!dcrypt_key_string_get_info(value.value, NULL, NULL, &key_kind, + NULL, NULL, &key_hash, error_r)) { + return -1; + } + + if (key_kind != DCRYPT_KEY_KIND_PUBLIC) { + *error_r = t_strdup_printf("Cannot use key %s: " + "Expected public key, got private key", + pubid); + return -1; + } + + if (key_hash != NULL && strcmp(key_hash, pubid) != 0) { + *error_r = t_strdup_printf("Cannot use key %s: " + "Incorrect key hash %s stored", + pubid, key_hash); + return -1; + } + + /* load the key */ + if (!dcrypt_key_load_public(&key, value.value, error_r)) { + return -1; + } + + if (pubid != NULL && + mail_crypt_public_key_id_match(key, pubid, error_r) <= 0) { + dcrypt_key_unref_public(&key); + return -1; + } + + mail_crypt_put_key_cache(&muser->key_cache, pubid, NULL, key); + + *key_r = key; + + return 1; +} + +int mail_crypt_user_get_public_key(struct mail_user *user, + struct dcrypt_public_key **key_r, + const char **error_r) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + struct mail_attribute_value value; + int ret; + + /* try retrieve currently active user key */ + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("mailbox_open(%s) failed: %s", + "INBOX", + mailbox_get_last_internal_error(box, NULL)); + return -1; + } + + if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED, + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_internal_error(box, NULL)); + } + } else { + ret = mail_crypt_get_public_key(box, value.value, TRUE, key_r, error_r); + } + + mailbox_free(&box); + return ret; +} + +int mail_crypt_box_get_public_key(struct mailbox *box, + struct dcrypt_public_key **key_r, + const char **error_r) +{ + struct mail_attribute_value value; + int ret; + + if ((ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED, + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_internal_error(box, NULL)); + } + return ret; + } + return mail_crypt_get_public_key(box, value.value, FALSE, key_r, error_r); +} + +static +int mail_crypt_set_public_key(struct mailbox_transaction_context *t, + bool user_key, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r) +{ + buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE); + const char *attr_name = mail_crypt_get_key_path(user_key, TRUE, pubid); + struct mail_attribute_value value; + + /* export key */ + if (!dcrypt_key_store_public(key, DCRYPT_FORMAT_DOVECOT, data, + error_r)) { + return -1; + } + + /* store it */ + value.value_stream = NULL; + value.value = str_c(data); + value.last_change = 0; + + if (mailbox_attribute_set(t, + MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name, + &value) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s/%s) failed: %s", + mailbox_get_vname(mailbox_transaction_get_mailbox(t)), + "/shared", + attr_name, + mailbox_get_last_internal_error( + mailbox_transaction_get_mailbox(t), NULL)); + return -1; + } + + return 0; +} + +int mail_crypt_user_set_public_key(struct mail_user *user, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r) +{ + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + struct mailbox_transaction_context *t; + struct mail_attribute_value value; + int ret; + + /* try retrieve currently active user key */ + if (mailbox_open(box) < 0) { + *error_r = t_strdup_printf("mailbox_open(%s) failed: %s", + "INBOX", + mailbox_get_last_internal_error(box, NULL)); + return -1; + } + + t = mailbox_transaction_begin(box, 0, __func__); + + if ((ret = mail_crypt_set_public_key(t, TRUE, pubid, key, + error_r)) == 0) { + value.value_stream = NULL; + value.value = pubid; + value.last_change = 0; + + if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED, + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + USER_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_internal_error(box, NULL)); + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + ret = -1; + } + + mailbox_free(&box); + + return ret; +} + +int mail_crypt_box_set_public_key(struct mailbox *box, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r) +{ + int ret; + struct mailbox_transaction_context *t; + struct mail_attribute_value value; + + t = mailbox_transaction_begin(box, 0, __func__); + if ((ret = mail_crypt_set_public_key(t, FALSE, pubid, key, + error_r)) == 0) { + value.value_stream = NULL; + value.value = pubid; + value.last_change = 0; + + if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED, + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + &value)) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s", + mailbox_get_vname(box), + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME, + mailbox_get_last_internal_error(box, NULL)); + } + } + + if (ret < 0) { + mailbox_transaction_rollback(&t); + } else if (mailbox_transaction_commit(&t) < 0) { + *error_r = t_strdup_printf("mailbox_transaction_commit(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_internal_error(box, NULL)); + ret = -1; + } + + return ret; + +} + +static +int mail_crypt_user_set_keys(struct mail_user *user, + const char *pubid, + struct dcrypt_private_key *privkey, + struct dcrypt_public_key *pubkey, + const char **error_r) +{ + if (mail_crypt_user_set_private_key(user, pubid, privkey, error_r) < 0) + return -1; + if (mail_crypt_user_set_public_key(user, pubid, pubkey, error_r) < 0) + return -1; + return 0; +} + +static +int mail_crypt_box_set_keys(struct mailbox *box, + const char *pubid, + struct dcrypt_private_key *privkey, + struct dcrypt_public_key *user_key, + struct dcrypt_public_key *pubkey, + const char **error_r) +{ + if (mail_crypt_box_set_private_key(box, pubid, privkey, user_key, + error_r) < 0) + return -1; + if (mail_crypt_box_set_public_key(box, pubid, pubkey, error_r) < 0) + return -1; + return 0; +} + +int mail_crypt_box_get_shared_key(struct mailbox *box, + const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r) +{ + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + + struct dcrypt_private_key *key = NULL; + struct mail_attribute_value value; + int ret; + + /* check cache */ + if (mail_crypt_get_key_cache(muser->key_cache, pubid, key_r, NULL) > 0) { + return 1; + } + + const char *hexname = + binary_to_hex((const unsigned char*)user->username, + strlen(user->username)); + + const char *attr_name = t_strdup_printf(BOX_CRYPT_PREFIX + PRIVKEYS_PREFIX"%s/%s", + hexname, + pubid); + + if ((ret = mailbox_attribute_get(box, + MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s", + mailbox_get_vname(box), + attr_name, + mailbox_get_last_internal_error(box, NULL)); + return ret; + } + return mail_crypt_get_private_key(box, pubid, FALSE, TRUE, key_r, + error_r); + } else { + if ((ret = mail_crypt_decrypt_private_key(box, pubid, value.value, + &key, error_r)) <= 0) + return ret; + } + + mail_crypt_put_key_cache(&muser->key_cache, pubid, key, NULL); + + *key_r = key; + + return 1; +} + +int mail_crypt_box_set_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + struct dcrypt_private_key *privkey, + const char *target_uid, + struct dcrypt_public_key *user_key, + const char **error_r) +{ + struct mail_attribute_value value; + buffer_t *data = t_str_new(MAIL_CRYPT_KEY_BUF_SIZE); + int ret; + const char *attr_name; + const char *algo = NULL; + + i_assert(target_uid == NULL || user_key != NULL); + + if (target_uid != NULL) { + /* hash target UID */ + algo = MAIL_CRYPT_KEY_CIPHER; + const char *hexname = + binary_to_hex((const unsigned char*)target_uid, + strlen(target_uid)); + attr_name = t_strdup_printf(BOX_CRYPT_PREFIX + PRIVKEYS_PREFIX"%s/%s", + hexname, + pubid); + } else { + attr_name = t_strdup_printf(BOX_CRYPT_PREFIX + PRIVKEYS_PREFIX"%s", + pubid); + } + + if (!dcrypt_key_store_private(privkey, DCRYPT_FORMAT_DOVECOT, + algo, data, + NULL, user_key, error_r)) { + return -1; + } + + value.value_stream = NULL; + value.value = str_c(data); + value.last_change = 0; + + if ((ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name, &value)) < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, /shared/%s) failed: %s", + mailbox_get_vname( + mailbox_transaction_get_mailbox(t)), + attr_name, + mailbox_get_last_internal_error( + mailbox_transaction_get_mailbox(t), + NULL)); + } + + safe_memset(buffer_get_modifiable_data(data, NULL), 0, data->used); + + return ret; +} + +int mail_crypt_box_unset_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + const char *target_uid, + const char **error_r) +{ + int ret; + + const char *hexname = + binary_to_hex((const unsigned char*)target_uid, + strlen(target_uid)); + + const char *attr_name = t_strdup_printf(BOX_CRYPT_PREFIX + PRIVKEYS_PREFIX"%s/%s", + hexname, + pubid); + + if ((ret = mailbox_attribute_unset(t, MAIL_ATTRIBUTE_TYPE_SHARED, + attr_name)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_unset(%s, " + " /shared/%s): failed: %s", + mailbox_get_vname( + mailbox_transaction_get_mailbox(t)), + attr_name, + mailbox_get_last_internal_error( + mailbox_transaction_get_mailbox(t), + NULL)); + } + } + + return ret; +} + +static +int mail_crypt_generate_keypair(const char *curve, + struct dcrypt_keypair *pair_r, + const char **pubid_r, + const char **error_r) +{ + if (curve == NULL) { + *error_r = MAIL_CRYPT_USERENV_CURVE " not set, cannot generate EC key"; + return -1; + } + + if (!dcrypt_keypair_generate(pair_r, DCRYPT_KEY_EC, 0, curve, error_r)) { + return -1; + } + + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + if (!dcrypt_key_id_public(pair_r->pub, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, + error_r)) { + dcrypt_keypair_unref(pair_r); + return -1; + } + + *pubid_r = binary_to_hex(key_id->data, key_id->used); + + return 0; +} + +int mail_crypt_user_generate_keypair(struct mail_user *user, + struct dcrypt_keypair *pair, + const char **pubid_r, + const char **error_r) +{ + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + const char *curve = mail_user_plugin_getenv(user, MAIL_CRYPT_USERENV_CURVE); + + if (mail_crypt_generate_keypair(curve, pair, pubid_r, error_r) < 0) { + return -1; + } + + if (mail_crypt_user_set_keys(user, *pubid_r, + pair->priv, pair->pub, error_r) < 0) { + dcrypt_keypair_unref(pair); + return -1; + } + + mail_crypt_put_key_cache(&muser->key_cache, *pubid_r, pair->priv, pair->pub); + + return 0; +} + +int mail_crypt_box_generate_keypair(struct mailbox *box, + struct dcrypt_keypair *pair, + struct dcrypt_public_key *user_key, + const char **pubid_r, + const char **error_r) +{ + int ret; + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_crypt_user *muser = mail_crypt_get_mail_crypt_user(user); + const char *curve = mail_user_plugin_getenv(user, + MAIL_CRYPT_USERENV_CURVE); + + if (user_key == NULL) { + if ((ret = mail_crypt_user_get_public_key(user, + &user_key, + error_r)) <= 0) { + if (ret < 0) + return ret; + /* generate keypair */ + struct dcrypt_keypair user_pair; + const char *user_pubid; + if (mail_crypt_user_generate_keypair(user, &user_pair, + &user_pubid, + error_r) < 0) { + return -1; + } + + mail_crypt_put_key_cache(&muser->key_cache, user_pubid, + user_pair.priv, user_pair.pub); + + user_key = user_pair.pub; + dcrypt_key_unref_private(&user_pair.priv); + } + } else { + dcrypt_key_ref_public(user_key); + } + + if ((ret = mail_crypt_generate_keypair(curve, pair, pubid_r, error_r)) < 0) { + /* failed */ + } else if ((ret = mail_crypt_box_set_keys(box, *pubid_r, + pair->priv, user_key, pair->pub, + error_r)) < 0) { + dcrypt_keypair_unref(pair); + } else { + mail_crypt_put_key_cache(&muser->key_cache, *pubid_r, pair->priv, + pair->pub); + } + + dcrypt_key_unref_public(&user_key); + + return ret; +} + +int mail_crypt_box_get_pvt_digests(struct mailbox *box, pool_t pool, + enum mail_attribute_type type, + ARRAY_TYPE(const_string) *digests, + const char **error_r) +{ + struct mailbox_attribute_iter *iter; + const char *key; + int ret; + + iter = mailbox_attribute_iter_init(box, type, + BOX_CRYPT_PREFIX PRIVKEYS_PREFIX); + while ((key = mailbox_attribute_iter_next(iter)) != NULL) { + key = p_strdup(pool, key); + array_push_back(digests, &key); + } + ret = mailbox_attribute_iter_deinit(&iter); + if (ret < 0) + *error_r = mailbox_get_last_internal_error(box, NULL); + return ret; +} + +int mail_crypt_box_get_private_keys(struct mailbox *box, + ARRAY_TYPE(dcrypt_private_key) *keys_r, + const char **error_r) +{ + struct mailbox_attribute_iter *iter; + iter = mailbox_attribute_iter_init(box, MAIL_ATTRIBUTE_TYPE_PRIVATE, + BOX_CRYPT_PREFIX PRIVKEYS_PREFIX); + const char *id; + int ret; + + while ((id = mailbox_attribute_iter_next(iter)) != NULL) { + struct dcrypt_private_key *key = NULL; + if ((ret = mail_crypt_get_private_key(box, id, FALSE, FALSE, + &key, error_r)) < 0) { + (void)mailbox_attribute_iter_deinit(&iter); + return -1; + } else if (ret > 0) + array_push_back(keys_r, &key); + } + + ret = mailbox_attribute_iter_deinit(&iter); + if (ret < 0) + *error_r = mailbox_get_last_internal_error(box, NULL); + return ret; +} + +int mail_crypt_box_share_private_keys(struct mailbox_transaction_context *t, + struct dcrypt_public_key *dest_pub_key, + const char *dest_user, + const ARRAY_TYPE(dcrypt_private_key) *priv_keys, + const char **error_r) +{ + i_assert(dest_user == NULL || dest_pub_key != NULL); + + struct dcrypt_private_key *priv_key; + buffer_t *key_id = t_str_new(MAIL_CRYPT_HASH_BUF_SIZE); + int ret = 0; + + array_foreach_elem(priv_keys, priv_key) { + ret = -1; + if (!dcrypt_key_id_private(priv_key, MAIL_CRYPT_KEY_ID_ALGORITHM, + key_id, error_r) || + (ret = mail_crypt_box_set_shared_key(t, + binary_to_hex(key_id->data, + key_id->used), + priv_key, dest_user, + dest_pub_key, error_r)) < 0) + break; + } + + return ret; +} + +int +mail_crypt_user_get_or_gen_public_key(struct mail_user *user, + struct dcrypt_public_key **pub_r, + const char **error_r) +{ + i_assert(user != NULL); + i_assert(pub_r != NULL); + i_assert(error_r != NULL); + + int ret; + if ((ret = mail_crypt_user_get_public_key(user, pub_r, error_r)) == 0) { + struct dcrypt_keypair pair; + const char *pubid = NULL; + if (mail_crypt_user_generate_keypair(user, &pair, + &pubid, error_r) < 0) { + return -1; + } + *pub_r = pair.pub; + dcrypt_key_unref_private(&pair.priv); + } else + return ret; + return 0; +} + +int +mail_crypt_box_get_or_gen_public_key(struct mailbox *box, + struct dcrypt_public_key **pub_r, + const char **error_r) +{ + i_assert(box != NULL); + i_assert(pub_r != NULL); + i_assert(error_r != NULL); + + struct mail_user *user = + mail_storage_get_user(mailbox_get_storage(box)); + int ret; + if ((ret = mail_crypt_box_get_public_key(box, pub_r, error_r)) == 0) { + struct dcrypt_public_key *user_key; + if (mail_crypt_user_get_or_gen_public_key(user, &user_key, + error_r) < 0) { + return -1; + } + + struct dcrypt_keypair pair; + const char *pubid = NULL; + if (mail_crypt_box_generate_keypair(box, &pair, user_key, + &pubid, error_r) < 0) { + return -1; + } + *pub_r = pair.pub; + dcrypt_key_unref_public(&user_key); + dcrypt_key_unref_private(&pair.priv); + } else + return ret; + return 0; +} + +bool mail_crypt_acl_secure_sharing_enabled(struct mail_user *user) +{ + const char *env = + mail_user_plugin_getenv(user, MAIL_CRYPT_ACL_SECURE_SHARE_SETTING); + + /* disabled by default */ + bool ret = FALSE; + + if (env != NULL) { + /* enable unless specifically + requested not to */ + ret = TRUE; + switch (env[0]) { + case 'n': + case 'N': + case '0': + case 'f': + case 'F': + ret = FALSE; + } + } + + return ret; +} + +static const struct mailbox_attribute_internal mailbox_internal_attributes[] = { + { .type = MAIL_ATTRIBUTE_TYPE_PRIVATE, + .key = BOX_CRYPT_PREFIX, + .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN + }, + { .type = MAIL_ATTRIBUTE_TYPE_SHARED, + .key = BOX_CRYPT_PREFIX, + .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN + }, + { .type = MAIL_ATTRIBUTE_TYPE_PRIVATE, + .key = USER_CRYPT_PREFIX, + .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN + }, + { .type = MAIL_ATTRIBUTE_TYPE_SHARED, + .key = USER_CRYPT_PREFIX, + .flags = MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN + } +}; + +void mail_crypt_key_register_mailbox_internal_attributes(void) +{ + mailbox_attribute_register_internals(mailbox_internal_attributes, + N_ELEMENTS(mailbox_internal_attributes)); +} diff --git a/src/plugins/mail-crypt/mail-crypt-key.h b/src/plugins/mail-crypt/mail-crypt-key.h new file mode 100644 index 0000000..f4a724a --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-key.h @@ -0,0 +1,119 @@ +#ifndef MAIL_CRYPT_KEY +#define MAIL_CRYPT_KEY + +#include "mail-crypt-common.h" +#include "mail-crypt-global-key.h" +#include "mail-storage.h" + +/* + For mailboxes: + + shared/<mailbox GUID>/.../crypt/active = digest for the active public key + that is used for encrypting new emails + shared/<mailbox GUID>/.../crypt/pubkeys/<digest> = <key> + private/<mailbox GUID>/.../crypt/privkeys/<digest> = <key> + + Similarly for users: + + shared/<INBOX GUID>/.../crypt/active = digest for the active public key that + is used for encrypting new folder keys + shared/<INBOX GUID>/.../crypt/pubkeys/<digest> = <key> + private/<INBOX GUID>/.../crypt/privkeys/<digest> = <key> +*/ + +struct mail_crypt_key_cache_entry; + +/** + * key cache management functions + */ +void mail_crypt_key_cache_destroy(struct mail_crypt_key_cache_entry **cache); +void mail_crypt_key_register_mailbox_internal_attributes(void); + +/* returns -1 on error, 0 not found, 1 = found */ +int mail_crypt_get_private_key(struct mailbox *box, const char *pubid, + bool user_key, bool shared, + struct dcrypt_private_key **key_r, + const char **error_r); +int mail_crypt_user_get_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r); +int mail_crypt_box_get_private_key(struct mailbox *box, + struct dcrypt_private_key **key_r, + const char **error_r); +int mail_crypt_box_get_private_keys(struct mailbox *box, + ARRAY_TYPE(dcrypt_private_key) *keys_r, + const char **error_r); +int mail_crypt_user_get_public_key(struct mail_user *user, + struct dcrypt_public_key **key_r, + const char **error_r); +int mail_crypt_box_get_public_key(struct mailbox *box, + struct dcrypt_public_key **key_r, + const char **error_r); +int mail_crypt_box_get_shared_key(struct mailbox *box, + const char *pubid, + struct dcrypt_private_key **key_r, + const char **error_r); +/* returns -1 on error, 0 no match , 1 = match */ +int mail_crypt_private_key_id_match(struct dcrypt_private_key *key, + const char *pubid, const char **error_r); +int mail_crypt_public_key_id_match(struct dcrypt_public_key *key, + const char *pubid, const char **error_r); +/* returns -1 on error, 0 = ok */ +int mail_crypt_user_set_private_key(struct mail_user *user, const char *pubid, + struct dcrypt_private_key *key, + const char **error_r); +int mail_crypt_box_set_private_key(struct mailbox *box, const char *pubid, + struct dcrypt_private_key *key, + struct dcrypt_public_key *user_key, + const char **error_r); +int mail_crypt_user_set_public_key(struct mail_user *user, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r); +int mail_crypt_box_set_public_key(struct mailbox *box, const char *pubid, + struct dcrypt_public_key *key, + const char **error_r); +int mail_crypt_user_generate_keypair(struct mail_user *user, + struct dcrypt_keypair *pair, + const char **pubid_r, + const char **error_r); +int mail_crypt_box_generate_keypair(struct mailbox *box, + struct dcrypt_keypair *pair, + struct dcrypt_public_key *user_key, + const char **pubid_r, + const char **error_r); +/* returns -1 on error, 0 = ok */ +int mail_crypt_box_set_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + struct dcrypt_private_key *privkey, + const char *target_uid, + struct dcrypt_public_key *user_key, + const char **error_r); +int mail_crypt_box_unset_shared_key(struct mailbox_transaction_context *t, + const char *pubid, + const char *target_uid, + const char **error_r); +int mail_crypt_box_share_private_keys(struct mailbox_transaction_context *t, + struct dcrypt_public_key *dest_pub_key, + const char *dest_user, + const ARRAY_TYPE(dcrypt_private_key) *priv_keys, + const char **error_r); +/* returns -1 on error, 0 = ok + these will also attempt to generate a keypair +*/ +int mail_crypt_user_get_or_gen_public_key(struct mail_user *user, + struct dcrypt_public_key **pub_key_r, + const char **error_r); +int mail_crypt_box_get_or_gen_public_key(struct mailbox *box, + struct dcrypt_public_key **pub_key_r, + const char **error_r); + +/* Lookup all private keys' digests. Returns 0 if ok, -1 on error. */ +int mail_crypt_box_get_pvt_digests(struct mailbox *box, pool_t pool, + enum mail_attribute_type type, + ARRAY_TYPE(const_string) *digests, + const char **error_r); + +/* is secure sharing enabled */ +bool mail_crypt_acl_secure_sharing_enabled(struct mail_user *user); + +#endif diff --git a/src/plugins/mail-crypt/mail-crypt-plugin.c b/src/plugins/mail-crypt/mail-crypt-plugin.c new file mode 100644 index 0000000..21da348 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-plugin.c @@ -0,0 +1,486 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +/* FIXME: cache handling could be useful to move to Dovecot core, so that if + we're using this plugin together with zlib plugin there would be just one + cache. */ + +#include "lib.h" +#include "ioloop.h" +#include "randgen.h" +#include "module-dir.h" +#include "str.h" +#include "safe-mkstemp.h" +#include "istream.h" +#include "istream-decrypt.h" +#include "istream-seekable.h" +#include "ostream.h" +#include "ostream-encrypt.h" +#include "mail-user.h" +#include "mail-copy.h" +#include "index-storage.h" +#include "index-mail.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" +#include "sha2.h" +#include "dcrypt-iostream.h" +#include "hex-binary.h" + +struct mail_crypt_mailbox { + union mailbox_module_context module_ctx; + struct dcrypt_public_key *pub_key; +}; + +const char *mail_crypt_plugin_version = DOVECOT_ABI_VERSION; + +#define MAIL_CRYPT_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mail_crypt_mail_module) +#define MAIL_CRYPT_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mail_crypt_storage_module) +#define MAIL_CRYPT_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, mail_crypt_user_module) +#define MAIL_CRYPT_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mail_crypt_user_module) + +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_user_module, + &mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mail_crypt_mail_module, + &mail_module_register); + +struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user) +{ + return MAIL_CRYPT_USER_CONTEXT(user); +} + +static bool mail_crypt_is_stream_encrypted(struct istream *input) +{ + const unsigned char *data = NULL; + size_t size; + + if (i_stream_read_data(input, &data, &size, + sizeof(IOSTREAM_CRYPT_MAGIC)) <= 0) { + return FALSE; + } + + if (memcmp(data, IOSTREAM_CRYPT_MAGIC, + sizeof(IOSTREAM_CRYPT_MAGIC)) != 0) { + return FALSE; + } + return TRUE; +} + +static void mail_crypt_cache_close(struct mail_crypt_user *muser) +{ + struct mail_crypt_cache *cache = &muser->cache; + + timeout_remove(&cache->to); + i_stream_unref(&cache->input); + i_zero(cache); +} + +static struct istream * +mail_crypt_cache_open(struct mail_crypt_user *muser, struct mail *mail, + struct istream *input) +{ + struct mail_crypt_cache *cache = &muser->cache; + struct istream *inputs[2]; + string_t *temp_prefix = t_str_new(128); + + mail_crypt_cache_close(muser); + + input->seekable = FALSE; + inputs[0] = input; + inputs[1] = NULL; + mail_user_set_get_temp_prefix(temp_prefix, mail->box->storage->user->set); + input = i_stream_create_seekable_path(inputs, + i_stream_get_max_buffer_size(inputs[0]), + str_c(temp_prefix)); + i_stream_unref(&inputs[0]); + + if (mail->uid > 0) { + cache->to = timeout_add(MAIL_CRYPT_MAIL_CACHE_EXPIRE_MSECS, + mail_crypt_cache_close, muser); + cache->box = mail->box; + cache->uid = mail->uid; + cache->input = input; + /* index-mail wants the stream to be destroyed at close, so create + a new stream instead of just increasing reference. */ + return i_stream_create_limit(cache->input, UOFF_T_MAX); + } + + return input; +} + +static int mail_crypt_istream_get_private_key(const char *pubkey_digest, + struct dcrypt_private_key **priv_key_r, + const char **error_r, + void *context) +{ + /* mailbox_crypt_search_all_private_keys requires error_r != NULL */ + i_assert(error_r != NULL); + int ret; + struct mail *_mail = context; + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT_REQUIRE(_mail->box->storage->user); + + *priv_key_r = mail_crypt_global_key_find(&muser->global_keys, + pubkey_digest); + if (*priv_key_r != NULL) { + dcrypt_key_ref_private(*priv_key_r); + return 1; + } + + struct mail_namespace *ns = mailbox_get_namespace(_mail->box); + + if (ns->type == MAIL_NAMESPACE_TYPE_SHARED) { + ret = mail_crypt_box_get_shared_key(_mail->box, pubkey_digest, + priv_key_r, error_r); + /* priv_key_r is already referenced */ + } else if (ns->type != MAIL_NAMESPACE_TYPE_PUBLIC) { + ret = mail_crypt_get_private_key(_mail->box, pubkey_digest, + FALSE, FALSE, priv_key_r, + error_r); + /* priv_key_r is already referenced */ + } else { + *error_r = "Public emails cannot have keys"; + ret = -1; + } + + i_assert(ret <= 0 || *priv_key_r != NULL); + + return ret; +} + +static int +mail_crypt_istream_opened(struct mail *_mail, struct istream **stream) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_user *user = _mail->box->storage->user; + struct mail_crypt_user *muser = MAIL_CRYPT_USER_CONTEXT_REQUIRE(user); + struct mail_crypt_cache *cache = &muser->cache; + union mail_module_context *mmail = MAIL_CRYPT_MAIL_CONTEXT(mail); + struct istream *input; + + if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) { + /* use the cached stream. when doing partial reads it should + already be seeked into the wanted offset. */ + i_stream_unref(stream); + i_stream_seek(cache->input, 0); + *stream = i_stream_create_limit(cache->input, UOFF_T_MAX); + return mmail->super.istream_opened(_mail, stream); + } + + /* decryption is the outmost stream, so add it before others + (e.g. zlib) */ + if (!mail_crypt_is_stream_encrypted(*stream)) + return mmail->super.istream_opened(_mail, stream); + + input = *stream; + *stream = i_stream_create_decrypt_callback(input, + mail_crypt_istream_get_private_key, _mail); + i_stream_unref(&input); + + *stream = mail_crypt_cache_open(muser, _mail, *stream); + return mmail->super.istream_opened(_mail, stream); +} + +static void mail_crypt_close(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *mmail = MAIL_CRYPT_MAIL_CONTEXT(mail); + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT_REQUIRE(_mail->box->storage->user); + struct mail_crypt_cache *cache = &muser->cache; + uoff_t size; + + if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) { + /* make sure we have read the entire email into the seekable + stream (which causes the original input stream to be + unrefed). we can't safely keep the original input stream + open after the mail is closed. */ + if (i_stream_get_size(cache->input, TRUE, &size) < 0) + mail_crypt_cache_close(muser); + } + mmail->super.close(_mail); +} + +static void mail_crypt_mail_allocated(struct mail *_mail) +{ + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(_mail->box->storage->user); + if (muser == NULL) return; + + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + union mail_module_context *mmail; + + mmail = p_new(mail->pool, union mail_module_context, 1); + mmail->super = *v; + mail->vlast = &mmail->super; + + v->istream_opened = mail_crypt_istream_opened; + v->close = mail_crypt_close; + MODULE_CONTEXT_SET_SELF(mail, mail_crypt_mail_module, mmail); +} + +static int mail_crypt_mail_save_finish(struct mail_save_context *ctx) +{ + struct mailbox *box = ctx->transaction->box; + union mailbox_module_context *zbox = MAIL_CRYPT_CONTEXT(box); + struct istream *input; + + if (zbox->super.save_finish(ctx) < 0) + return -1; + + /* we're here only if mail-crypt plugin is disabled. we want to make + sure that even though we're saving an unencrypted mail, the mail + can't be faked to look like an encrypted mail. */ + if (mail_get_stream(ctx->dest_mail, NULL, NULL, &input) < 0) + return -1; + + if (mail_crypt_is_stream_encrypted(input)) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Saving mails encrypted by client isn't supported"); + return -1; + } + return 0; +} + +static int +mail_crypt_mail_save_begin(struct mail_save_context *ctx, + struct istream *input) +{ + const char *pubid; + struct mailbox *box = ctx->transaction->box; + struct mail_crypt_mailbox *mbox = MAIL_CRYPT_CONTEXT(box); + struct mail_crypt_user *muser = MAIL_CRYPT_USER_CONTEXT(box->storage->user); + + enum io_stream_encrypt_flags enc_flags = 0; + if (muser != NULL) { + if (muser->save_version == 1) { + enc_flags = IO_STREAM_ENC_VERSION_1; + } else if (muser->save_version == 2) { + enc_flags = IO_STREAM_ENC_INTEGRITY_AEAD; + } else { + i_assert(muser->save_version == 0); + } + } + + if (mbox->module_ctx.super.save_begin(ctx, input) < 0) + return -1; + + if (enc_flags == 0) + return 0; + + struct dcrypt_public_key *pub_key; + if (muser->global_keys.public_key != NULL) + pub_key = muser->global_keys.public_key; + else if (mbox->pub_key != NULL) + pub_key = mbox->pub_key; + else { + const char *error; + int ret; + + if ((ret = mail_crypt_box_get_public_key(box, &pub_key, + &error)) <= 0) + { + struct dcrypt_keypair pair; + + if (ret < 0) { + mail_storage_set_error(box->storage, + MAIL_ERROR_PARAMS, + t_strdup_printf("get_public_key(%s) failed: %s", + mailbox_get_vname(box), + error)); + return ret; + } + + if (muser->save_version < 2) { + mail_storage_set_error(box->storage, + MAIL_ERROR_PARAMS, + t_strdup_printf("generate_keypair(%s) failed: " + "unsupported save_version=%d", + mailbox_get_vname(box), + muser->save_version)); + return -1; + } + + if (mail_crypt_box_generate_keypair(box, &pair, NULL, + &pubid, &error) < 0) { + mail_storage_set_error(box->storage, + MAIL_ERROR_PARAMS, + t_strdup_printf("generate_keypair(%s) failed: %s", + mailbox_get_vname(box), + error)); + return -1; + } + pub_key = pair.pub; + dcrypt_key_unref_private(&pair.priv); + + } + mbox->pub_key = pub_key; + } + + /* encryption is the outermost layer (zlib etc. are inside) */ + struct ostream *output = o_stream_create_encrypt(ctx->data.output, + MAIL_CRYPT_ENC_ALGORITHM, pub_key, enc_flags); + + o_stream_unref(&ctx->data.output); + ctx->data.output = output; + o_stream_cork(ctx->data.output); + return 0; +} + +static int +mail_crypt_mailbox_copy(struct mail_save_context *ctx, struct mail *mail) +{ + struct mail_crypt_mailbox *mbox = MAIL_CRYPT_CONTEXT(ctx->transaction->box); + + if (ctx->transaction->box != mail->box) + return mail_storage_copy(ctx, mail); + return mbox->module_ctx.super.copy(ctx, mail); +} + +static void mail_crypt_mailbox_close(struct mailbox *box) +{ + struct mail_crypt_mailbox *mbox = MAIL_CRYPT_CONTEXT(box); + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(box->storage->user); + + if (mbox->pub_key != NULL) + dcrypt_key_unref_public(&mbox->pub_key); + if (muser != NULL && muser->cache.box == box) + mail_crypt_cache_close(muser); + mbox->module_ctx.super.close(box); +} + +static void mail_crypt_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct mail_crypt_user *muser = + MAIL_CRYPT_USER_CONTEXT(box->storage->user); + struct mail_crypt_mailbox *mbox; + enum mail_storage_class_flags class_flags = box->storage->class_flags; + + mbox = p_new(box->pool, struct mail_crypt_mailbox, 1); + mbox->module_ctx.super = *v; + box->vlast = &mbox->module_ctx.super; + v->close = mail_crypt_mailbox_close; + + MODULE_CONTEXT_SET(box, mail_crypt_storage_module, mbox); + + if ((class_flags & MAIL_STORAGE_CLASS_FLAG_BINARY_DATA) != 0) { + v->save_begin = mail_crypt_mail_save_begin; + + /* if global keys are used, re-encrypting on copy/move + is not necessary, so do not attempt to do it. + with per-folder keys, emails must be re-encrypted + when moving to another folder */ + if (muser == NULL || muser->save_version == 0 || + muser->global_keys.public_key == NULL) + v->copy = mail_crypt_mailbox_copy; + if (muser == NULL || muser->save_version == 0) + v->save_finish = mail_crypt_mail_save_finish; + } +} + +static void mail_crypt_mail_user_deinit(struct mail_user *user) +{ + struct mail_crypt_user *muser = MAIL_CRYPT_USER_CONTEXT_REQUIRE(user); + + mail_crypt_key_cache_destroy(&muser->key_cache); + mail_crypt_global_keys_free(&muser->global_keys); + mail_crypt_cache_close(muser); + muser->module_ctx.super.deinit(user); +} + +static void mail_crypt_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct mail_crypt_user *muser; + const char *error = NULL; + + muser = p_new(user->pool, struct mail_crypt_user, 1); + muser->module_ctx.super = *v; + user->vlast = &muser->module_ctx.super; + + const char *curve = mail_user_plugin_getenv(user, "mail_crypt_curve"); + buffer_t *tmp = t_str_new(64); + if (curve == NULL || *curve == '\0') { + e_debug(user->event, "mail_crypt_plugin: mail_crypt_curve setting " + "missing - generating EC keys disabled"); + } else if (!dcrypt_name2oid(curve, tmp, &error)) { + user->error = p_strdup_printf(user->pool, + "mail_crypt_plugin: " + "invalid mail_crypt_curve setting %s: %s", + curve, error); + } else { + muser->curve = p_strdup(user->pool, curve); + } + + const char *version = mail_user_plugin_getenv(user, + "mail_crypt_save_version"); + + if (version == NULL || *version == '\0') { + user->error = p_strdup_printf(user->pool, + "mail_crypt_plugin: " + "mail_crypt_save_version setting missing"); + } else if (version[0] == '0') { + muser->save_version = 0; + } else if (version[0] == '1') { + muser->save_version = 1; + } else if (version[0] == '2') { + muser->save_version = 2; + } else { + user->error = p_strdup_printf(user->pool, + "mail_crypt_plugin: Invalid " + "mail_crypt_save_version %s: use 0, 1, or 2 ", + version); + } + + if (mail_crypt_global_keys_load(user, "mail_crypt_global", + &muser->global_keys, FALSE, &error) < 0) { + user->error = p_strdup_printf(user->pool, + "mail_crypt_plugin: %s", error); + } + + v->deinit = mail_crypt_mail_user_deinit; + MODULE_CONTEXT_SET(user, mail_crypt_user_module, muser); +} + +static struct mail_storage_hooks mail_crypt_mail_storage_hooks = { + .mail_user_created = mail_crypt_mail_user_created, + .mail_allocated = mail_crypt_mail_allocated +}; + +static struct mail_storage_hooks mail_crypt_mail_storage_hooks_post = { + .mailbox_allocated = mail_crypt_mailbox_allocated +}; + +static struct module crypto_post_module = { + .path = "lib95_mail_crypt_plugin.so" +}; + +void mail_crypt_plugin_init(struct module *module) +{ + const char* error; + if (!dcrypt_initialize("openssl", NULL, &error)) + i_fatal("dcrypt_initialize(): %s", error); + mail_storage_hooks_add(module, &mail_crypt_mail_storage_hooks); + /* when this plugin is loaded, there's the potential chance for + mixed delivery between encrypted and non-encrypted recipients. + The mail_crypt_mailbox_allocated() hook ensures encrypted + content isn't copied as such into cleartext recipients + (and the other way around) */ + mail_storage_hooks_add_forced(&crypto_post_module, + &mail_crypt_mail_storage_hooks_post); + mail_crypt_key_register_mailbox_internal_attributes(); +} + +void mail_crypt_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mail_crypt_mail_storage_hooks); + mail_storage_hooks_remove(&mail_crypt_mail_storage_hooks_post); +} diff --git a/src/plugins/mail-crypt/mail-crypt-plugin.h b/src/plugins/mail-crypt/mail-crypt-plugin.h new file mode 100644 index 0000000..4556a92 --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-plugin.h @@ -0,0 +1,32 @@ +#ifndef MAIL_CRYPT_PLUGIN_H +#define MAIL_CRYPT_PLUGIN_H + +struct mailbox; +struct module; + +struct mail_crypt_cache { + struct timeout *to; + struct mailbox *box; + uint32_t uid; + + struct istream *input; +}; + +struct mail_crypt_user { + union mail_user_module_context module_ctx; + + struct mail_crypt_global_keys global_keys; + struct mail_crypt_cache cache; + struct mail_crypt_key_cache_entry *key_cache; + const char *curve; + int save_version; +}; + +void mail_crypt_plugin_init(struct module *module); +void mail_crypt_plugin_deinit(void); + +#define MAIL_CRYPT_MAIL_CACHE_EXPIRE_MSECS (60*1000) + +struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user); + +#endif diff --git a/src/plugins/mail-crypt/mail-crypt-pluginenv.c b/src/plugins/mail-crypt/mail-crypt-pluginenv.c new file mode 100644 index 0000000..68cf94f --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-pluginenv.c @@ -0,0 +1,106 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ +#include "lib.h" +#include "str.h" +#include "array.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "fs-crypt-settings.h" + +static const struct fs_crypt_settings * +fs_crypt_load_settings(void) +{ + static const struct setting_parser_info *set_roots[] = { + &fs_crypt_setting_parser_info, + NULL + }; + struct master_service_settings_input input; + struct master_service_settings_output output; + const char *error; + + i_zero(&input); + input.roots = set_roots; + input.module = "fs-crypt"; + input.service = "fs-crypt"; + if (master_service_settings_read(master_service, &input, + &output, &error) < 0) + i_fatal("Error reading configuration: %s", error); + + return master_service_settings_get_others(master_service)[0]; +} + +static +const char *mail_crypt_plugin_getenv(const struct fs_crypt_settings *set, + const char *name) +{ + const char *const *envs; + unsigned int i, count; + + if (set == NULL) + return NULL; + + if (!array_is_created(&set->plugin_envs)) + return NULL; + + envs = array_get(&set->plugin_envs, &count); + for (i = 0; i < count; i += 2) { + if (strcmp(envs[i], name) == 0) + return envs[i+1]; + } + return NULL; +} + +static int +mail_crypt_load_global_private_keys(const struct fs_crypt_settings *set, + const char *set_prefix, + struct mail_crypt_global_keys *global_keys, + const char **error_r) +{ + string_t *set_key = t_str_new(64); + str_append(set_key, set_prefix); + str_append(set_key, "_private_key"); + size_t prefix_len = str_len(set_key); + + unsigned int i = 1; + const char *key_data; + while ((key_data = mail_crypt_plugin_getenv(set, str_c(set_key))) != NULL) { + const char *set_pw = t_strconcat(str_c(set_key), "_password", NULL); + const char *password = mail_crypt_plugin_getenv(set, set_pw); + if (mail_crypt_load_global_private_key(str_c(set_key), key_data, + set_pw, password, + global_keys, error_r) < 0) + return -1; + str_truncate(set_key, prefix_len); + str_printfa(set_key, "%u", ++i); + } + return 0; +} + +int mail_crypt_global_keys_load_pluginenv(const char *set_prefix, + struct mail_crypt_global_keys *global_keys_r, + const char **error_r) +{ + const struct fs_crypt_settings *set = fs_crypt_load_settings(); + + const char *set_key = t_strconcat(set_prefix, "_public_key", NULL); + const char *key_data = mail_crypt_plugin_getenv(set, set_key); + int ret = 0; + + mail_crypt_global_keys_init(global_keys_r); + if (key_data != NULL) { + if (mail_crypt_load_global_public_key(set_key, key_data, + global_keys_r, error_r) < 0) + ret = -1; + } + + if (ret == 0 && + mail_crypt_load_global_private_keys(set, set_prefix, global_keys_r, + error_r) < 0) + ret = -1; + + if (ret != 0) + mail_crypt_global_keys_free(global_keys_r); + return ret; +} diff --git a/src/plugins/mail-crypt/mail-crypt-userenv.c b/src/plugins/mail-crypt/mail-crypt-userenv.c new file mode 100644 index 0000000..b152a7f --- /dev/null +++ b/src/plugins/mail-crypt/mail-crypt-userenv.c @@ -0,0 +1,66 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ +#include "lib.h" +#include "str.h" +#include "mail-user.h" +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" + +static int +mail_crypt_load_global_private_keys(struct mail_user *user, + const char *set_prefix, + struct mail_crypt_global_keys *global_keys, + bool ignore_errors, + const char **error_r) +{ + string_t *set_key = t_str_new(64); + str_append(set_key, set_prefix); + str_append(set_key, "_private_key"); + size_t prefix_len = str_len(set_key); + + unsigned int i = 1; + const char *key_data; + while ((key_data = mail_user_plugin_getenv(user, str_c(set_key))) != NULL) { + const char *set_pw = t_strconcat(str_c(set_key), "_password", NULL); + const char *password = mail_user_plugin_getenv(user, set_pw); + if (mail_crypt_load_global_private_key(str_c(set_key), key_data, + set_pw, password, + global_keys, + error_r) < 0) { + /* skip this key */ + if (ignore_errors) { + e_debug(user->event, "mail-crypt-plugin: " + "mail_crypt_load_global_private_key failed: %s", + *error_r); + *error_r = NULL; + continue; + } + return -1; + } + str_truncate(set_key, prefix_len); + str_printfa(set_key, "%u", ++i); + } + return 0; +} + +int mail_crypt_global_keys_load(struct mail_user *user, const char *set_prefix, + struct mail_crypt_global_keys *global_keys_r, + bool ignore_privkey_errors, + const char **error_r) +{ + const char *set_key = t_strconcat(set_prefix, "_public_key", NULL); + const char *key_data = mail_user_plugin_getenv(user, set_key); + + mail_crypt_global_keys_init(global_keys_r); + if (key_data != NULL) { + if (mail_crypt_load_global_public_key(set_key, + key_data, + global_keys_r, + error_r) < 0) + return -1; + } + if (mail_crypt_load_global_private_keys(user, set_prefix, global_keys_r, + ignore_privkey_errors, + error_r) < 0) + return -1; + return 0; +} diff --git a/src/plugins/mail-crypt/test-mail-global-key.c b/src/plugins/mail-crypt/test-mail-global-key.c new file mode 100644 index 0000000..4c5ed38 --- /dev/null +++ b/src/plugins/mail-crypt/test-mail-global-key.c @@ -0,0 +1,130 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-common.h" +#include "randgen.h" +#include "array.h" +#include "dcrypt.h" +#include "hex-binary.h" + +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "fs-crypt-settings.h" + +#include "mail-crypt-pluginenv.c" + +static struct fs_crypt_settings fs_set; + +static const char *settings[] = { + "mail_crypt_global_private_key", + "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ1lJdWZKWlplMlk2aUZ6NXgKa29Jb3lzYjNkWkxaV3N5ZWtqT2MvR2pzTGQyaFJBTkNBQVNuSVdnUXVoRThqcUFMY21maXVuUnlFazd2a3EveQphOXZZSzUwYjNjRmhDc0xVNHRmVlRMa0IxWS82VmxaajYzUUtNelhOdms1RzVPRDFvZkVsY3B5agotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==", + "mail_crypt_global_public_key", + "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFcHlGb0VMb1JQSTZnQzNKbjRycDBjaEpPNzVLdgo4bXZiMkN1ZEc5M0JZUXJDMU9MWDFVeTVBZFdQK2xaV1krdDBDak0xemI1T1J1VGc5YUh4SlhLY293PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==", + "mail_crypt_global_private_key2", + "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLQpNSUhlTUVrR0NTcUdTSWIzRFFFRkRUQThNQnNHQ1NxR1NJYjNEUUVGRERBT0JBaXA2cUpja1FET3F3SUNDQUF3CkhRWUpZSVpJQVdVREJBRXFCQkFXN09oUFRlU0xSOExLcGYwZjZHa3ZCSUdRZk5rYUpodnM2VWVWS2RkN2NzdFMKMURSNXJYTWtON09FbVNjTTljRlk2UDVrMzdnY1VJUFZudTQrOTFYZUE1MTU2cnBpUEpycEdkZnprcjhPNVFqZApsMWRycmR6Z0hqZHE4T2VmbUR1MEEzMjRZd25SS3hGRExUcjlHMkxVMkhoYmV6a0xjV1FwMVJISDZsNXRRcUtwCjZid05iMnc3OXhCb01YSjN6MVZqcElOZk9wRnJ6M3lucVlqUXhseTIrQjg2Ci0tLS0tRU5EIEVOQ1JZUFRFRCBQUklWQVRFIEtFWS0tLS0tCg==", + "mail_crypt_global_private_key2_password", + "password", +}; + +int +mail_crypt_load_global_private_keys(const struct fs_crypt_settings *set, + const char *set_prefix, + struct mail_crypt_global_keys *global_keys, + const char **error_r); + +static void test_setup(void) +{ + struct dcrypt_settings set = { + .module_dir = top_builddir "/src/lib-dcrypt/.libs" + }; + if (!dcrypt_initialize(NULL, &set, NULL)) { + i_info("No functional dcrypt backend found - skipping tests"); + test_exit(0); + } + i_array_init(&fs_set.plugin_envs, 8); + array_append(&fs_set.plugin_envs, settings, N_ELEMENTS(settings)); +} + +static void test_try_load_keys(void) +{ + const char *pubid1 = "c79e262924842de291a8bcd413f4122a570abd033adeff7c1cdfdc9d05998c75"; + const char *pubid2 = "aaf927444bff8b63425e852c6b3f769e8221b952b42cf886fae7d326c5be098e"; + buffer_t *key_id = t_buffer_create(128); + + const char *error = NULL; + test_begin("try_load_keys"); + + struct mail_crypt_global_keys keys; + i_zero(&keys); + mail_crypt_global_keys_init(&keys); + + const char *set_prefix = "mail_crypt_global"; + const char *set_key = t_strconcat(set_prefix, "_public_key", NULL); + const char *key_data = mail_crypt_plugin_getenv(&fs_set, set_key); + + test_assert(key_data != NULL); + + if (key_data != NULL) { + test_assert(mail_crypt_load_global_public_key(set_key, key_data, + &keys, &error) == 0); + test_assert(mail_crypt_load_global_private_keys(&fs_set, set_prefix, + &keys, &error) == 0); + /* did we get two private keys? */ + test_assert(array_count(&keys.private_keys) == 2); + + /* public key id checks */ + + buffer_set_used_size(key_id, 0); + test_assert(dcrypt_key_id_public(keys.public_key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE); + test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid1) == 0); + + const struct mail_crypt_global_private_key *key = + array_front(&keys.private_keys); + + buffer_set_used_size(key_id, 0); + test_assert(dcrypt_key_id_private(key->key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE); + test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid1) == 0); + + key = array_idx(&keys.private_keys, 1); + buffer_set_used_size(key_id, 0); + test_assert(dcrypt_key_id_private(key->key, MAIL_CRYPT_KEY_ID_ALGORITHM, key_id, &error) == TRUE); + test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), pubid2) == 0); + + } + + mail_crypt_global_keys_free(&keys); + + test_end(); +} + +static void test_empty_keyset(void) +{ + test_begin("test_empty_keyset"); + + /* this should not crash */ + struct mail_crypt_global_keys keys; + i_zero(&keys); + test_assert(mail_crypt_global_key_find(&keys, "423423423423") == NULL); + + test_end(); +} + +static void test_teardown(void) +{ + array_free(&fs_set.plugin_envs); + dcrypt_deinitialize(); +} + +int main(void) +{ + void (*tests[])(void) = { + test_setup, + test_try_load_keys, + test_empty_keyset, + test_teardown, + NULL + }; + + int ret = test_run(tests); + return ret; +} diff --git a/src/plugins/mail-crypt/test-mail-key.c b/src/plugins/mail-crypt/test-mail-key.c new file mode 100644 index 0000000..ac89835 --- /dev/null +++ b/src/plugins/mail-crypt/test-mail-key.c @@ -0,0 +1,424 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-common.h" +#include "hex-binary.h" +#include "master-service.h" +#include "test-mail-storage-common.h" +#include "dcrypt.h" + +#include "mail-crypt-common.h" +#include "mail-crypt-key.h" +#include "mail-crypt-plugin.h" + +static const char *mcp_old_user_key = "1\t716\t0\t048FD04FD3612B22D32790C592CF21CEF417EFD2EA34AE5F688FA5B51BED29E05A308B68DA78E16E90B47A11E133BD9A208A2894FD01B0BEE865CE339EA3FB17AC\td0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0"; +static const char *mcp_old_user_key_id = "d0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0"; +static const char *mcp_old_box_key = "1\t716\t1\t0567e6bf9579813ae967314423b0fceb14bda24749303923de9a9bb9370e0026f995901a57e63113eeb2baf0c940e978d00686cbb52bd5014bc318563375876255\t0300E46DA2125427BE968EB3B649910CDC4C405E5FFDE18D433A97CABFEE28CEEFAE9EE356C792004FFB80981D67E741B8CC036A34235A8D2E1F98D1658CFC963D07EB\td0cfaca5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615e77a0\t7c9a1039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00fa4f"; +static const char *mcp_old_box_key_id = "7c9a1039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00fa4f"; + +static struct test_mail_storage_ctx *test_ctx; +static const char *test_user_key_id; +static const char *test_box_key_id; + +static struct mail_crypt_user mail_crypt_user; + +struct mail_crypt_user *mail_crypt_get_mail_crypt_user(struct mail_user *user ATTR_UNUSED) +{ + return &mail_crypt_user; +} + +static +int test_mail_attribute_get(struct mailbox *box, bool user_key, bool shared, + const char *pubid, const char **value_r, const char **error_r) +{ + const char *attr_name; + enum mail_attribute_type attr_type; + + if (strcmp(pubid, ACTIVE_KEY_NAME) == 0) { + attr_name = user_key ? USER_CRYPT_PREFIX ACTIVE_KEY_NAME : + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME; + attr_type = MAIL_ATTRIBUTE_TYPE_SHARED; + } else { + attr_name = t_strdup_printf("%s%s%s", + user_key ? USER_CRYPT_PREFIX : + BOX_CRYPT_PREFIX, + shared ? PUBKEYS_PREFIX : + PRIVKEYS_PREFIX, + pubid); + attr_type = shared ? MAIL_ATTRIBUTE_TYPE_SHARED : MAIL_ATTRIBUTE_TYPE_PRIVATE; + } + struct mail_attribute_value value; + + int ret; + + if ((ret = mailbox_attribute_get(box, attr_type, + attr_name, &value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_get(%s, %s) failed: %s", + mailbox_get_vname(box), + attr_name, + mailbox_get_last_internal_error(box, NULL)); + } + } else { + *value_r = t_strdup(value.value); + } + return ret; +} + +static int +test_mail_attribute_set(struct mailbox_transaction_context *t, + bool user_key, bool shared, const char *pubid, + const char *value, const char **error_r) +{ + const char *attr_name; + enum mail_attribute_type attr_type; + + if (strcmp(pubid, ACTIVE_KEY_NAME) == 0) { + attr_name = user_key ? USER_CRYPT_PREFIX ACTIVE_KEY_NAME : + BOX_CRYPT_PREFIX ACTIVE_KEY_NAME; + attr_type = MAIL_ATTRIBUTE_TYPE_SHARED; + } else { + attr_name = t_strdup_printf("%s%s%s", + user_key ? USER_CRYPT_PREFIX : + BOX_CRYPT_PREFIX, + shared ? PUBKEYS_PREFIX : + PRIVKEYS_PREFIX, + pubid); + attr_type = shared ? MAIL_ATTRIBUTE_TYPE_SHARED : MAIL_ATTRIBUTE_TYPE_PRIVATE; + } + + struct mail_attribute_value attr_value; + + int ret; + + i_zero(&attr_value); + attr_value.value = value; + + if ((ret = mailbox_attribute_set(t, attr_type, + attr_name, &attr_value)) <= 0) { + if (ret < 0) { + *error_r = t_strdup_printf("mailbox_attribute_set(%s, %s) failed: %s", + mailbox_get_vname(mailbox_transaction_get_mailbox(t)), + attr_name, + mailbox_get_last_internal_error(mailbox_transaction_get_mailbox(t), NULL)); + } + } + + return ret; +} + + +static void test_generate_user_key(void) +{ + struct dcrypt_keypair pair; + const char *pubid; + const char *error = NULL; + + test_begin("generate user key"); + + /* try to generate a keypair for user */ + if (mail_crypt_user_generate_keypair(test_ctx->user, &pair, + &pubid, &error) < 0) { + i_error("generate_keypair failed: %s", error); + test_exit(1); + } + + test_assert(pubid != NULL); + + test_user_key_id = p_strdup(test_ctx->pool, pubid); + + dcrypt_keypair_unref(&pair); + error = NULL; + + /* keys ought to be in cache or somewhere...*/ + if (mail_crypt_user_get_private_key(test_ctx->user, NULL, &pair.priv, &error) <= 0) + { + i_error("Cannot get user private key: %s", error); + } + + test_assert(pair.priv != NULL); + + if (pair.priv != NULL) + dcrypt_key_unref_private(&pair.priv); + + test_end(); +} + +static void test_generate_inbox_key(void) +{ + struct dcrypt_public_key *user_key; + struct dcrypt_keypair pair; + const char *error = NULL, *pubid = NULL; + + test_begin("generate inbox key"); + + if (mail_crypt_user_get_public_key(test_ctx->user, &user_key, + &error) <= 0) { + i_error("Cannot get user private key: %s", error); + } + struct mail_namespace *ns = + mail_namespace_find_inbox(test_ctx->user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + if (mailbox_open(box) < 0) + i_fatal("mailbox_open(INBOX) failed: %s", + mailbox_get_last_internal_error(box, NULL)); + if (mail_crypt_box_generate_keypair(box, &pair, user_key, &pubid, + &error) < 0) { + i_error("generate_keypair failed: %s", error); + test_exit(1); + } + + i_assert(pubid != NULL); + + dcrypt_keypair_unref(&pair); + dcrypt_key_unref_public(&user_key); + mailbox_free(&box); + + test_box_key_id = p_strdup(test_ctx->pool, pubid); + + test_end(); +} + +static void test_cache_reset(void) +{ + struct dcrypt_keypair pair; + const char *error = NULL; + + test_begin("cache reset"); + + struct mail_crypt_user *muser = + mail_crypt_get_mail_crypt_user(test_ctx->user); + mail_crypt_key_cache_destroy(&muser->key_cache); + + test_assert(mail_crypt_user_get_private_key(test_ctx->user, NULL, + &pair.priv, &error) > 0); + if (error != NULL) + i_error("mail_crypt_user_get_private_key() failed: %s", error); + error = NULL; + test_assert(mail_crypt_user_get_public_key(test_ctx->user, + &pair.pub, &error) > 0); + if (error != NULL) + i_error("mail_crypt_user_get_public_key() failed: %s", error); + + dcrypt_keypair_unref(&pair); + + test_end(); +} + +static void test_verify_keys(void) +{ + const char *value = "", *error = NULL; + + const char *enc_id; + enum dcrypt_key_encryption_type enc_type; + + test_begin("verify keys"); + + struct dcrypt_private_key *privkey = NULL, *user_key = NULL; + struct dcrypt_public_key *pubkey = NULL; + + struct mail_namespace *ns = + mail_namespace_find_inbox(test_ctx->user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + if (mailbox_open(box) < 0) + i_fatal("mailbox_open(INBOX) failed: %s", + mailbox_get_last_internal_error(box, NULL)); + /* verify links */ + + /* user's public key */ + test_assert(test_mail_attribute_get(box, TRUE, TRUE, ACTIVE_KEY_NAME, + &value, &error) > 0); + test_assert(strcmp(value, test_user_key_id) == 0); + + test_assert(test_mail_attribute_get(box, TRUE, TRUE, value, &value, + &error) > 0); + + /* load key */ + test_assert(dcrypt_key_load_public(&pubkey, value, &error) == TRUE); + + /* see if it matches */ + test_assert(mail_crypt_public_key_id_match(pubkey, test_user_key_id, + &error) > 0); + dcrypt_key_unref_public(&pubkey); + + /* user's private key */ + test_assert(test_mail_attribute_get(box, TRUE, FALSE, ACTIVE_KEY_NAME, + &value, &error) > 0); + test_assert(strcmp(value, test_user_key_id) == 0); + + test_assert(test_mail_attribute_get(box, TRUE, FALSE, value, &value, + &error) > 0); + + /* load key */ + test_assert(dcrypt_key_load_private(&user_key, value, NULL, NULL, + &error) == TRUE); + + /* see if it matches */ + test_assert(mail_crypt_private_key_id_match(user_key, test_user_key_id, + &error) > 0); + + + + + /* inbox's public key */ + test_assert(test_mail_attribute_get(box, FALSE, TRUE, ACTIVE_KEY_NAME, + &value, &error) > 0); + test_assert(strcmp(value, test_box_key_id) == 0); + + test_assert(test_mail_attribute_get(box, FALSE, TRUE, value, &value, + &error) > 0); + + /* load key */ + test_assert(dcrypt_key_load_public(&pubkey, value, &error) == TRUE); + + /* see if it matches */ + test_assert(mail_crypt_public_key_id_match(pubkey, test_box_key_id, + &error) > 0); + dcrypt_key_unref_public(&pubkey); + + /* user's private key */ + test_assert(test_mail_attribute_get(box, FALSE, FALSE, ACTIVE_KEY_NAME, + &value, &error) > 0); + test_assert(strcmp(value, test_box_key_id) == 0); + + test_assert(test_mail_attribute_get(box, FALSE, FALSE, value, &value, + &error) > 0); + + test_assert(dcrypt_key_string_get_info(value, NULL, NULL, NULL, + &enc_type, &enc_id, NULL, + &error) == TRUE); + + test_assert(enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_KEY); + test_assert(strcmp(enc_id, test_user_key_id) == 0); + + /* load key */ + test_assert(dcrypt_key_load_private(&privkey, value, NULL, user_key, + &error) == TRUE); + + /* see if it matches */ + test_assert(mail_crypt_private_key_id_match(privkey, test_box_key_id, + &error) > 0); + dcrypt_key_unref_private(&privkey); + dcrypt_key_unref_private(&user_key); + + mailbox_free(&box); + + test_end(); +} + +static void test_old_key(void) +{ + test_begin("old keys"); + + const char *error = NULL; + struct dcrypt_private_key *privkey = NULL; + + struct mail_namespace *ns = + mail_namespace_find_inbox(test_ctx->user->namespaces); + struct mailbox *box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + if (mailbox_open(box) < 0) + i_fatal("mailbox_open(INBOX) failed: %s", + mailbox_get_last_internal_error(box, NULL)); + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, 0, __func__); + + test_mail_attribute_set(t, TRUE, FALSE, mcp_old_user_key_id, + mcp_old_user_key, &error); + test_mail_attribute_set(t, FALSE, FALSE, mcp_old_box_key_id, + mcp_old_box_key, &error); + + (void)mailbox_transaction_commit(&t); + + error = NULL; + + /* try to load old key */ + test_assert(mail_crypt_get_private_key(box, mcp_old_box_key_id, FALSE, FALSE, + &privkey, &error) > 0); + + if (error != NULL) + i_error("mail_crypt_get_private_key(%s) failed: %s", + mcp_old_box_key_id, + error); + + test_assert(privkey != NULL); + + if (privkey != NULL) { + buffer_t *key_id = t_buffer_create(32); + test_assert(dcrypt_key_id_private_old(privkey, key_id, &error)); + test_assert(strcmp(binary_to_hex(key_id->data, key_id->used), mcp_old_box_key_id) == 0); + dcrypt_key_unref_private(&privkey); + } + + mailbox_free(&box); + + test_end(); +} + +static void test_setup(void) +{ + struct dcrypt_settings set = { + .module_dir = top_builddir "/src/lib-dcrypt/.libs" + }; + if (!dcrypt_initialize(NULL, &set, NULL)) { + i_info("No functional dcrypt backend found - skipping tests"); + test_exit(0); + } + test_ctx = test_mail_storage_init(); + const char *username = "mcp_test@example.com"; + const char *const extra_input[] = { + t_strdup_printf("mail_crypt_curve=prime256v1"), + t_strdup_printf("mail_attribute_dict=file:%s/%s/dovecot-attributes", + test_ctx->home_root, username), + NULL + }; + struct test_mail_storage_settings storage_set = { + .username = username, + .driver = "maildir", + .hierarchy_sep = "/", + .extra_input = extra_input, + }; + test_mail_storage_init_user(test_ctx, &storage_set); + + mail_crypt_key_register_mailbox_internal_attributes(); +} + +static void test_teardown(void) +{ + struct mail_crypt_user *muser = + mail_crypt_get_mail_crypt_user(test_ctx->user); + mail_crypt_key_cache_destroy(&muser->key_cache); + + test_mail_storage_deinit_user(test_ctx); + test_mail_storage_deinit(&test_ctx); + dcrypt_deinitialize(); +} + +int main(int argc, char **argv) +{ + void (*tests[])(void) = { + test_setup, + test_generate_user_key, + test_generate_inbox_key, + test_cache_reset, + test_verify_keys, + test_old_key, + test_teardown, + NULL + }; + + + master_service = master_service_init("test-mail-key", + MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_DONT_SEND_STATS | + MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS | + MASTER_SERVICE_FLAG_NO_SSL_INIT | + MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME, + &argc, &argv, ""); + int ret = test_run(tests); + master_service_deinit(&master_service); + return ret; +} diff --git a/src/plugins/mail-log/Makefile.am b/src/plugins/mail-log/Makefile.am new file mode 100644 index 0000000..2f3fbc6 --- /dev/null +++ b/src/plugins/mail-log/Makefile.am @@ -0,0 +1,24 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/notify + +NOPLUGIN_LDFLAGS = +lib20_mail_log_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_mail_log_plugin.la + +if DOVECOT_PLUGIN_DEPS +lib20_mail_log_plugin_la_LIBADD = \ + ../notify/lib15_notify_plugin.la +endif + +lib20_mail_log_plugin_la_SOURCES = \ + mail-log-plugin.c + +noinst_HEADERS = \ + mail-log-plugin.h diff --git a/src/plugins/mail-log/Makefile.in b/src/plugins/mail-log/Makefile.in new file mode 100644 index 0000000..3032360 --- /dev/null +++ b/src/plugins/mail-log/Makefile.in @@ -0,0 +1,826 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/mail-log +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +@DOVECOT_PLUGIN_DEPS_TRUE@lib20_mail_log_plugin_la_DEPENDENCIES = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../notify/lib15_notify_plugin.la +am_lib20_mail_log_plugin_la_OBJECTS = mail-log-plugin.lo +lib20_mail_log_plugin_la_OBJECTS = \ + $(am_lib20_mail_log_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_mail_log_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_mail_log_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/mail-log-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_mail_log_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_mail_log_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/notify + +lib20_mail_log_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_mail_log_plugin.la + +@DOVECOT_PLUGIN_DEPS_TRUE@lib20_mail_log_plugin_la_LIBADD = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../notify/lib15_notify_plugin.la + +lib20_mail_log_plugin_la_SOURCES = \ + mail-log-plugin.c + +noinst_HEADERS = \ + mail-log-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/mail-log/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/mail-log/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_mail_log_plugin.la: $(lib20_mail_log_plugin_la_OBJECTS) $(lib20_mail_log_plugin_la_DEPENDENCIES) $(EXTRA_lib20_mail_log_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_mail_log_plugin_la_LINK) -rpath $(moduledir) $(lib20_mail_log_plugin_la_OBJECTS) $(lib20_mail_log_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-log-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/mail-log-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/mail-log-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/mail-log/mail-log-plugin.c b/src/plugins/mail-log/mail-log-plugin.c new file mode 100644 index 0000000..f01cd0d --- /dev/null +++ b/src/plugins/mail-log/mail-log-plugin.c @@ -0,0 +1,546 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "llist.h" +#include "str.h" +#include "str-sanitize.h" +#include "imap-util.h" +#include "mail-user.h" +#include "mail-storage-private.h" +#include "notify-plugin.h" +#include "mail-log-plugin.h" + + +#define MAILBOX_NAME_LOG_LEN 64 +#define HEADER_LOG_LEN 80 + +#define MAIL_LOG_USER_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mail_log_user_module) + +enum mail_log_field { + MAIL_LOG_FIELD_UID = 0x01, + MAIL_LOG_FIELD_BOX = 0x02, + MAIL_LOG_FIELD_MSGID = 0x04, + MAIL_LOG_FIELD_PSIZE = 0x08, + MAIL_LOG_FIELD_VSIZE = 0x10, + MAIL_LOG_FIELD_FLAGS = 0x20, + MAIL_LOG_FIELD_FROM = 0x40, + MAIL_LOG_FIELD_SUBJECT = 0x80 +}; +#define MAIL_LOG_DEFAULT_FIELDS \ + (MAIL_LOG_FIELD_UID | MAIL_LOG_FIELD_BOX | \ + MAIL_LOG_FIELD_MSGID | MAIL_LOG_FIELD_PSIZE) + +enum mail_log_event { + MAIL_LOG_EVENT_DELETE = 0x01, + MAIL_LOG_EVENT_UNDELETE = 0x02, + MAIL_LOG_EVENT_EXPUNGE = 0x04, + MAIL_LOG_EVENT_SAVE = 0x08, + MAIL_LOG_EVENT_COPY = 0x10, + MAIL_LOG_EVENT_MAILBOX_CREATE = 0x20, + MAIL_LOG_EVENT_MAILBOX_DELETE = 0x40, + MAIL_LOG_EVENT_MAILBOX_RENAME = 0x80, + MAIL_LOG_EVENT_FLAG_CHANGE = 0x100 +}; +#define MAIL_LOG_DEFAULT_EVENTS \ + (MAIL_LOG_EVENT_DELETE | MAIL_LOG_EVENT_UNDELETE | \ + MAIL_LOG_EVENT_EXPUNGE | MAIL_LOG_EVENT_SAVE | MAIL_LOG_EVENT_COPY | \ + MAIL_LOG_EVENT_MAILBOX_DELETE | MAIL_LOG_EVENT_MAILBOX_RENAME) + +static const char *field_names[] = { + "uid", + "box", + "msgid", + "size", + "vsize", + "flags", + "from", + "subject", + NULL +}; + +static const char *event_names[] = { + "delete", + "undelete", + "expunge", + "save", + "copy", + "mailbox_create", + "mailbox_delete", + "mailbox_rename", + "flag_change", + NULL +}; + +struct mail_log_user { + union mail_user_module_context module_ctx; + + enum mail_log_field fields; + enum mail_log_event events; + bool cached_only; +}; + +struct mail_log_message { + struct mail_log_message *prev, *next; + + enum mail_log_event event; + bool ignore; + const char *pretext, *text; +}; + +struct mail_log_mail_txn_context { + pool_t pool; + struct mail_log_message *messages, *messages_tail; +}; + +static MODULE_CONTEXT_DEFINE_INIT(mail_log_user_module, + &mail_user_module_register); + +static enum mail_log_field mail_log_field_find(const char *name) +{ + unsigned int i; + + for (i = 0; field_names[i] != NULL; i++) { + if (strcmp(name, field_names[i]) == 0) + return 1 << i; + } + return 0; +} + +static enum mail_log_event mail_log_event_find(const char *name) +{ + unsigned int i; + + if (strcmp(name, "append") == 0) { + /* v1.x backwards compatibility */ + name = "save"; + } + for (i = 0; event_names[i] != NULL; i++) { + if (strcmp(name, event_names[i]) == 0) + return 1 << i; + } + return 0; +} + +static enum mail_log_field mail_log_parse_fields(const char *str) +{ + const char *const *tmp; + static enum mail_log_field field, fields = 0; + + for (tmp = t_strsplit_spaces(str, ", "); *tmp != NULL; tmp++) { + field = mail_log_field_find(*tmp); + if (field == 0) + i_fatal("Unknown field in mail_log_fields: '%s'", *tmp); + fields |= field; + } + return fields; +} + +static enum mail_log_event mail_log_parse_events(const char *str) +{ + const char *const *tmp; + static enum mail_log_event event, events = 0; + + for (tmp = t_strsplit_spaces(str, ", "); *tmp != NULL; tmp++) { + event = mail_log_event_find(*tmp); + if (event == 0) + i_fatal("Unknown event in mail_log_events: '%s'", *tmp); + events |= event; + } + return events; +} + +static void mail_log_mail_user_created(struct mail_user *user) +{ + struct mail_log_user *muser; + const char *str; + + muser = p_new(user->pool, struct mail_log_user, 1); + MODULE_CONTEXT_SET(user, mail_log_user_module, muser); + + str = mail_user_plugin_getenv(user, "mail_log_fields"); + muser->fields = str == NULL ? MAIL_LOG_DEFAULT_FIELDS : + mail_log_parse_fields(str); + + str = mail_user_plugin_getenv(user, "mail_log_events"); + muser->events = str == NULL ? MAIL_LOG_DEFAULT_EVENTS : + mail_log_parse_events(str); + + muser->cached_only = + mail_user_plugin_getenv_bool(user, "mail_log_cached_only"); +} + +static void mail_log_append_mailbox_name(string_t *str, struct mail *mail) +{ + const char *mailbox_str; + + mailbox_str = mailbox_get_vname(mail->box); + str_printfa(str, "box=%s", + str_sanitize(mailbox_str, MAILBOX_NAME_LOG_LEN)); +} + +static void +mail_log_append_mail_header(string_t *str, struct mail *mail, + const char *name, const char *header) +{ + const char *value; + + if (mail_get_first_header_utf8(mail, header, &value) <= 0) + value = ""; + str_printfa(str, "%s=%s", name, str_sanitize(value, HEADER_LOG_LEN)); +} + +static void +mail_log_append_uid(struct mail_log_mail_txn_context *ctx, + struct mail_log_message *msg, string_t *str, uint32_t uid) +{ + if (uid != 0) + str_printfa(str, "uid=%u", uid); + else { + /* we don't know the uid yet, assign it later */ + str_printfa(str, "uid="); + msg->pretext = p_strdup(ctx->pool, str_c(str)); + str_truncate(str, 0); + } +} + +static void +mail_log_update_wanted_fields(struct mail *mail, enum mail_log_field fields) +{ + enum mail_fetch_field wanted_fields = 0; + struct mailbox_header_lookup_ctx *wanted_headers = NULL; + const char *headers[4]; + unsigned int hdr_idx = 0; + + if ((fields & MAIL_LOG_FIELD_MSGID) != 0) + headers[hdr_idx++] = "Message-ID"; + if ((fields & MAIL_LOG_FIELD_FROM) != 0) + headers[hdr_idx++] = "From"; + if ((fields & MAIL_LOG_FIELD_SUBJECT) != 0) + headers[hdr_idx++] = "Subject"; + if (hdr_idx > 0) { + i_assert(hdr_idx < N_ELEMENTS(headers)); + headers[hdr_idx] = NULL; + wanted_headers = mailbox_header_lookup_init(mail->box, headers); + } + + if ((fields & MAIL_LOG_FIELD_PSIZE) != 0) + wanted_fields |= MAIL_FETCH_PHYSICAL_SIZE; + if ((fields & MAIL_LOG_FIELD_VSIZE) != 0) + wanted_fields |= MAIL_FETCH_VIRTUAL_SIZE; + + mail_add_temp_wanted_fields(mail, wanted_fields, wanted_headers); + mailbox_header_lookup_unref(&wanted_headers); +} + +static void +mail_log_append_mail_message_real(struct mail_log_mail_txn_context *ctx, + struct mail *mail, enum mail_log_event event, + const char *desc) +{ + struct mail_log_user *muser = + MAIL_LOG_USER_CONTEXT(mail->box->storage->user); + struct mail_log_message *msg; + string_t *text; + uoff_t size; + + msg = p_new(ctx->pool, struct mail_log_message, 1); + + /* avoid parsing through the message multiple times */ + mail_log_update_wanted_fields(mail, muser->fields); + + text = t_str_new(128); + str_append(text, desc); + str_append(text, ": "); + if ((muser->fields & MAIL_LOG_FIELD_BOX) != 0) { + mail_log_append_mailbox_name(text, mail); + str_append(text, ", "); + } + if ((muser->fields & MAIL_LOG_FIELD_UID) != 0) { + if (event != MAIL_LOG_EVENT_SAVE && + event != MAIL_LOG_EVENT_COPY) + mail_log_append_uid(ctx, msg, text, mail->uid); + else { + /* with mbox mail->uid contains the uid, but handle + this consistently with all mailbox formats */ + mail_log_append_uid(ctx, msg, text, 0); + } + /* make sure UID is assigned to this mail */ + mail->transaction->flags |= MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS; + str_append(text, ", "); + } + if ((muser->fields & MAIL_LOG_FIELD_MSGID) != 0) { + mail_log_append_mail_header(text, mail, "msgid", "Message-ID"); + str_append(text, ", "); + } + if ((muser->fields & MAIL_LOG_FIELD_PSIZE) != 0) { + if (mail_get_physical_size(mail, &size) == 0) + str_printfa(text, "size=%"PRIuUOFF_T, size); + else + str_printfa(text, "size=error"); + str_append(text, ", "); + } + if ((muser->fields & MAIL_LOG_FIELD_VSIZE) != 0) { + if (mail_get_virtual_size(mail, &size) == 0) + str_printfa(text, "vsize=%"PRIuUOFF_T, size); + else + str_printfa(text, "vsize=error"); + str_append(text, ", "); + } + if ((muser->fields & MAIL_LOG_FIELD_FROM) != 0) { + mail_log_append_mail_header(text, mail, "from", "From"); + str_append(text, ", "); + } + if ((muser->fields & MAIL_LOG_FIELD_SUBJECT) != 0) { + mail_log_append_mail_header(text, mail, "subject", "Subject"); + str_append(text, ", "); + } + if ((muser->fields & MAIL_LOG_FIELD_FLAGS) != 0) { + str_printfa(text, "flags=("); + imap_write_flags(text, mail_get_flags(mail), + mail_get_keywords(mail)); + str_append(text, "), "); + } + str_truncate(text, str_len(text)-2); + + msg->event = event; + msg->text = p_strdup(ctx->pool, str_c(text)); + DLLIST2_APPEND(&ctx->messages, &ctx->messages_tail, msg); +} + +static void mail_log_add_dummy_msg(struct mail_log_mail_txn_context *ctx, + enum mail_log_event event) +{ + struct mail_log_message *msg; + + msg = p_new(ctx->pool, struct mail_log_message, 1); + msg->event = event; + msg->ignore = TRUE; + DLLIST2_APPEND(&ctx->messages, &ctx->messages_tail, msg); +} + +static void +mail_log_append_mail_message(struct mail_log_mail_txn_context *ctx, + struct mail *mail, enum mail_log_event event, + const char *desc) +{ + struct mail_log_user *muser = + MAIL_LOG_USER_CONTEXT(mail->box->storage->user); + + if ((muser->events & event) == 0) { + if (event == MAIL_LOG_EVENT_SAVE || + event == MAIL_LOG_EVENT_COPY) + mail_log_add_dummy_msg(ctx, event); + return; + } + + T_BEGIN { + enum mail_lookup_abort orig_lookup_abort = mail->lookup_abort; + + if (event != MAIL_LOG_EVENT_SAVE && muser->cached_only) + mail->lookup_abort = MAIL_LOOKUP_ABORT_NOT_IN_CACHE; + mail_log_append_mail_message_real(ctx, mail, event, desc); + mail->lookup_abort = orig_lookup_abort; + } T_END; +} + +static void * +mail_log_mail_transaction_begin(struct mailbox_transaction_context *t ATTR_UNUSED) +{ + pool_t pool; + struct mail_log_mail_txn_context *ctx; + + pool = pool_alloconly_create("mail-log", 2048); + ctx = p_new(pool, struct mail_log_mail_txn_context, 1); + ctx->pool = pool; + return ctx; +} + +static void mail_log_mail_save(void *txn, struct mail *mail) +{ + struct mail_log_mail_txn_context *ctx = + (struct mail_log_mail_txn_context *)txn; + + mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_SAVE, "save"); +} + +static void mail_log_mail_copy(void *txn, struct mail *src, struct mail *dst) +{ + struct mail_log_mail_txn_context *ctx = + (struct mail_log_mail_txn_context *)txn; + struct mail_private *src_pmail = (struct mail_private *)src; + struct mailbox *src_box = src->box; + const char *desc; + + if (src_pmail->vmail != NULL) { + /* copying a mail from virtual storage. src points to the + backend mail, but we want to log the virtual mailbox name. */ + src_box = src_pmail->vmail->box; + } + desc = t_strdup_printf("copy from %s", + str_sanitize(mailbox_get_vname(src_box), + MAILBOX_NAME_LOG_LEN)); + mail_log_append_mail_message(ctx, dst, + MAIL_LOG_EVENT_COPY, desc); +} + +static void mail_log_mail_expunge(void *txn, struct mail *mail) +{ + struct mail_log_mail_txn_context *ctx = + (struct mail_log_mail_txn_context *)txn; + struct mail_private *p = (struct mail_private*)mail; + + mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_EXPUNGE, + p->autoexpunged ? "autoexpunge" : "expunge"); +} + +static void mail_log_mail_update_flags(void *txn, struct mail *mail, + enum mail_flags old_flags) +{ + struct mail_log_mail_txn_context *ctx = + (struct mail_log_mail_txn_context *)txn; + enum mail_flags new_flags = mail_get_flags(mail); + + if (((old_flags ^ new_flags) & MAIL_DELETED) == 0) { + mail_log_append_mail_message(ctx, mail, + MAIL_LOG_EVENT_FLAG_CHANGE, + "flag_change"); + } else if ((old_flags & MAIL_DELETED) == 0) { + mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_DELETE, + "delete"); + } else { + mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_UNDELETE, + "undelete"); + } +} + +static void +mail_log_mail_update_keywords(void *txn, struct mail *mail, + const char *const *old_keywords ATTR_UNUSED) +{ + struct mail_log_mail_txn_context *ctx = + (struct mail_log_mail_txn_context *)txn; + + mail_log_append_mail_message(ctx, mail, MAIL_LOG_EVENT_FLAG_CHANGE, + "flag_change"); +} + +static void mail_log_save(const struct mail_log_message *msg, uint32_t uid) +{ + if (msg->ignore) { + /* not logging this save/copy */ + } else if (msg->pretext == NULL) + i_info("%s", msg->text); + else if (uid != 0) + i_info("%s%u%s", msg->pretext, uid, msg->text); + else + i_info("%serror%s", msg->pretext, msg->text); +} + +static void +mail_log_mail_transaction_commit(void *txn, + struct mail_transaction_commit_changes *changes) +{ + struct mail_log_mail_txn_context *ctx = + (struct mail_log_mail_txn_context *)txn; + struct mail_log_message *msg; + struct seq_range_iter iter; + unsigned int n = 0; + uint32_t uid; + + seq_range_array_iter_init(&iter, &changes->saved_uids); + for (msg = ctx->messages; msg != NULL; msg = msg->next) { + if (msg->event == MAIL_LOG_EVENT_SAVE || + msg->event == MAIL_LOG_EVENT_COPY) { + if (!seq_range_array_iter_nth(&iter, n++, &uid)) + uid = 0; + mail_log_save(msg, uid); + } else { + i_assert(msg->pretext == NULL); + i_info("%s", msg->text); + } + } + i_assert(!seq_range_array_iter_nth(&iter, n, &uid)); + + pool_unref(&ctx->pool); +} + +static void mail_log_mail_transaction_rollback(void *txn) +{ + struct mail_log_mail_txn_context *ctx = + (struct mail_log_mail_txn_context *)txn; + + pool_unref(&ctx->pool); +} + +static void +mail_log_mailbox_create(struct mailbox *box) +{ + struct mail_log_user *muser = MAIL_LOG_USER_CONTEXT(box->storage->user); + + if ((muser->events & MAIL_LOG_EVENT_MAILBOX_CREATE) == 0) + return; + + i_info("Mailbox created: %s", + str_sanitize(mailbox_get_vname(box), MAILBOX_NAME_LOG_LEN)); +} + +static void +mail_log_mailbox_delete_commit(void *txn ATTR_UNUSED, struct mailbox *box) +{ + struct mail_log_user *muser = MAIL_LOG_USER_CONTEXT(box->storage->user); + + if ((muser->events & MAIL_LOG_EVENT_MAILBOX_DELETE) == 0) + return; + + i_info("Mailbox deleted: %s", + str_sanitize(mailbox_get_vname(box), MAILBOX_NAME_LOG_LEN)); +} + +static void +mail_log_mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + struct mail_log_user *muser = MAIL_LOG_USER_CONTEXT(src->storage->user); + + if ((muser->events & MAIL_LOG_EVENT_MAILBOX_RENAME) == 0) + return; + + i_info("Mailbox renamed: %s -> %s", + str_sanitize(mailbox_get_vname(src), MAILBOX_NAME_LOG_LEN), + str_sanitize(mailbox_get_vname(dest), MAILBOX_NAME_LOG_LEN)); +} + +static const struct notify_vfuncs mail_log_vfuncs = { + .mail_transaction_begin = mail_log_mail_transaction_begin, + .mail_save = mail_log_mail_save, + .mail_copy = mail_log_mail_copy, + .mail_expunge = mail_log_mail_expunge, + .mail_update_flags = mail_log_mail_update_flags, + .mail_update_keywords = mail_log_mail_update_keywords, + .mail_transaction_commit = mail_log_mail_transaction_commit, + .mail_transaction_rollback = mail_log_mail_transaction_rollback, + .mailbox_create = mail_log_mailbox_create, + .mailbox_delete_commit = mail_log_mailbox_delete_commit, + .mailbox_rename = mail_log_mailbox_rename +}; + +static struct notify_context *mail_log_ctx; + +static struct mail_storage_hooks mail_log_mail_storage_hooks = { + .mail_user_created = mail_log_mail_user_created +}; + +void mail_log_plugin_init(struct module *module) +{ + mail_log_ctx = notify_register(&mail_log_vfuncs); + mail_storage_hooks_add(module, &mail_log_mail_storage_hooks); +} + +void mail_log_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mail_log_mail_storage_hooks); + notify_unregister(mail_log_ctx); +} + +const char *mail_log_plugin_dependencies[] = { "notify", NULL }; diff --git a/src/plugins/mail-log/mail-log-plugin.h b/src/plugins/mail-log/mail-log-plugin.h new file mode 100644 index 0000000..0af47ac --- /dev/null +++ b/src/plugins/mail-log/mail-log-plugin.h @@ -0,0 +1,9 @@ +#ifndef MAIL_LOG_PLUGIN_H +#define MAIL_LOG_PLUGIN_H + +extern const char *mail_log_plugin_dependencies[]; + +void mail_log_plugin_init(struct module *module); +void mail_log_plugin_deinit(void); + +#endif diff --git a/src/plugins/mail-lua/Makefile.am b/src/plugins/mail-lua/Makefile.am new file mode 100644 index 0000000..1698cb4 --- /dev/null +++ b/src/plugins/mail-lua/Makefile.am @@ -0,0 +1,32 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-lua \ + $(LUA_CFLAGS) + +NOPLUGIN_LDFLAGS = + +module_LTLIBRARIES = \ + lib01_mail_lua_plugin.la + +lib01_mail_lua_plugin_la_LDFLAGS = -module -avoid-version $(LUA_LIBS) +lib01_mail_lua_plugin_la_LIBADD = \ + ../../lib-storage/libdovecot-storage-lua.la \ + $(LIBDOVECOT) +lib01_mail_lua_plugin_la_SOURCES = \ + mail-lua-plugin.c + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = \ + mail-lua-plugin.h + +test_programs = + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +noinst_PROGRAMS = $(test_programs) diff --git a/src/plugins/mail-lua/Makefile.in b/src/plugins/mail-lua/Makefile.in new file mode 100644 index 0000000..8c230a3 --- /dev/null +++ b/src/plugins/mail-lua/Makefile.in @@ -0,0 +1,873 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/plugins/mail-lua +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" \ + "$(DESTDIR)$(pkginc_libdir)" +LTLIBRARIES = $(module_LTLIBRARIES) +am__DEPENDENCIES_1 = +lib01_mail_lua_plugin_la_DEPENDENCIES = \ + ../../lib-storage/libdovecot-storage-lua.la \ + $(am__DEPENDENCIES_1) +am_lib01_mail_lua_plugin_la_OBJECTS = mail-lua-plugin.lo +lib01_mail_lua_plugin_la_OBJECTS = \ + $(am_lib01_mail_lua_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib01_mail_lua_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib01_mail_lua_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/mail-lua-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib01_mail_lua_plugin_la_SOURCES) +DIST_SOURCES = $(lib01_mail_lua_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-lua \ + $(LUA_CFLAGS) + +module_LTLIBRARIES = \ + lib01_mail_lua_plugin.la + +lib01_mail_lua_plugin_la_LDFLAGS = -module -avoid-version $(LUA_LIBS) +lib01_mail_lua_plugin_la_LIBADD = \ + ../../lib-storage/libdovecot-storage-lua.la \ + $(LIBDOVECOT) + +lib01_mail_lua_plugin_la_SOURCES = \ + mail-lua-plugin.c + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = \ + mail-lua-plugin.h + +test_programs = +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/mail-lua/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/mail-lua/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib01_mail_lua_plugin.la: $(lib01_mail_lua_plugin_la_OBJECTS) $(lib01_mail_lua_plugin_la_DEPENDENCIES) $(EXTRA_lib01_mail_lua_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib01_mail_lua_plugin_la_LINK) -rpath $(moduledir) $(lib01_mail_lua_plugin_la_OBJECTS) $(lib01_mail_lua_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-lua-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/mail-lua-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/mail-lua-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-moduleLTLIBRARIES \ + install-pdf install-pdf-am install-pkginc_libHEADERS \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/mail-lua/mail-lua-plugin.c b/src/plugins/mail-lua/mail-lua-plugin.c new file mode 100644 index 0000000..146d95b --- /dev/null +++ b/src/plugins/mail-lua/mail-lua-plugin.c @@ -0,0 +1,167 @@ +/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "module-dir.h" +#include "mail-lua-plugin.h" +#include "mail-storage-lua.h" +#include "mail-storage-private.h" +#include "mail-storage-hooks.h" +#include "dlua-script-private.h" + +#define MAIL_LUA_SCRIPT "mail_lua_script" +#define MAIL_LUA_USER_CREATED_FN "mail_user_created" +#define MAIL_LUA_USER_DEINIT_FN "mail_user_deinit" +#define MAIL_LUA_USER_DEINIT_PRE_FN "mail_user_deinit_pre" +#define MAIL_LUA_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, mail_lua_user_module) + +static MODULE_CONTEXT_DEFINE_INIT(mail_lua_user_module, + &mail_user_module_register); + +struct mail_lua_user_context { + union mail_user_module_context module_ctx; + struct dlua_script *script; +}; + +static int mail_lua_call_hook(struct dlua_script *script, + struct mail_user *user, + const char *hook, + const char **error_r) +{ + const char *error; + + if (!dlua_script_has_function(script, hook)) + return 0; + + if (user->mail_debug) + e_debug(user->event, "mail-lua: Calling %s(user)", hook); + + dlua_push_mail_user(script->L, user); + + if (dlua_pcall(script->L, hook, 1, 2, &error) < 0) { + *error_r = t_strdup_printf("%s(user) failed: %s", hook, error); + return -1; + } + + int ret = lua_tonumber(script->L, -2); + const char *errmsg = lua_tostring(script->L, -1); + + if (ret < 0) { + *error_r = t_strdup_printf("%s(user) failed: %s", + hook, errmsg); + } + + lua_pop(script->L, 2); + (void)lua_gc(script->L, LUA_GCCOLLECT, 0); + + return ret < 0 ? -1 : 1; +} + +static void mail_lua_user_deinit_pre(struct mail_user *user) +{ + struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user); + const char *error; + + if (luser == NULL) + return; + + if (mail_lua_call_hook(luser->script, user, MAIL_LUA_USER_DEINIT_PRE_FN, + &error) < 0) { + e_error(user->event, "mail-lua: %s", error); + } + + luser->module_ctx.super.deinit_pre(user); +} + +static void mail_lua_user_deinit(struct mail_user *user) +{ + struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user); + const char *error; + + if (luser == NULL) + return; + + luser->module_ctx.super.deinit(user); + + if (mail_lua_call_hook(luser->script, user, MAIL_LUA_USER_DEINIT_FN, + &error) < 0) { + e_error(user->event, "mail-lua: %s", error); + } + + dlua_script_unref(&luser->script); +} + +static void mail_lua_user_created(struct mail_user *user) +{ + struct mail_lua_user_context *luser; + struct mail_user_vfuncs *v = user->vlast; + struct dlua_script *script; + const char *error; + const char *script_fn = mail_user_plugin_getenv(user, MAIL_LUA_SCRIPT); + int ret; + + if (script_fn == NULL) + return; + + if (dlua_script_create_file(script_fn, &script, user->event, &error) < 0) { + user->error = p_strdup_printf(user->pool, "dlua_script_create_file(%s) failed: %s", + script_fn, error); + return; + } + + dlua_dovecot_register(script); + dlua_register_mail_storage(script); + + /* init */ + if (dlua_script_init(script, &error) < 0) { + user->error = p_strdup_printf(user->pool, "dlua_script_init(%s) failed: %s", + script_fn, error); + dlua_script_unref(&script); + return; + } + + /* call postlogin hook */ + if ((ret = mail_lua_call_hook(script, user, MAIL_LUA_USER_CREATED_FN, + &error)) <= 0) { + if (ret < 0) + user->error = p_strdup(user->pool, error); + dlua_script_unref(&script); + return; + } + + luser = p_new(user->pool, struct mail_lua_user_context, 1); + luser->module_ctx.super = *v; + v->deinit_pre = mail_lua_user_deinit_pre; + v->deinit = mail_lua_user_deinit; + luser->script = script; + user->vlast = &luser->module_ctx.super; + + MODULE_CONTEXT_SET(user, mail_lua_user_module, luser); +} + +bool mail_lua_plugin_get_script(struct mail_user *user, + struct dlua_script **script_r) +{ + struct mail_lua_user_context *luser = MAIL_LUA_USER_CONTEXT(user); + if (luser != NULL) { + *script_r = luser->script; + return TRUE; + } + return FALSE; +} + +static const struct mail_storage_hooks mail_lua_hooks = { + .mail_user_created = mail_lua_user_created, +}; + +void mail_lua_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &mail_lua_hooks); +} + +void mail_lua_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mail_lua_hooks); +} + +const char *mail_lua_plugin_dependencies[] = { NULL }; diff --git a/src/plugins/mail-lua/mail-lua-plugin.h b/src/plugins/mail-lua/mail-lua-plugin.h new file mode 100644 index 0000000..e3bfb98 --- /dev/null +++ b/src/plugins/mail-lua/mail-lua-plugin.h @@ -0,0 +1,14 @@ +#ifndef MAIL_LUA_PLUGIN_H +#define MAIL_LUA_PLUGIN_H 1 + +struct dlua_script; +struct mail_user; +struct module; + +void mail_lua_plugin_init(struct module *module); +void mail_lua_plugin_deinit(void); + +bool mail_lua_plugin_get_script(struct mail_user *user, + struct dlua_script **script_r); + +#endif diff --git a/src/plugins/mailbox-alias/Makefile.am b/src/plugins/mailbox-alias/Makefile.am new file mode 100644 index 0000000..fa5f785 --- /dev/null +++ b/src/plugins/mailbox-alias/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib20_mailbox_alias_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_mailbox_alias_plugin.la + +lib20_mailbox_alias_plugin_la_SOURCES = \ + mailbox-alias-plugin.c + +noinst_HEADERS = \ + mailbox-alias-plugin.h diff --git a/src/plugins/mailbox-alias/Makefile.in b/src/plugins/mailbox-alias/Makefile.in new file mode 100644 index 0000000..93d11a5 --- /dev/null +++ b/src/plugins/mailbox-alias/Makefile.in @@ -0,0 +1,821 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/mailbox-alias +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_mailbox_alias_plugin_la_LIBADD = +am_lib20_mailbox_alias_plugin_la_OBJECTS = mailbox-alias-plugin.lo +lib20_mailbox_alias_plugin_la_OBJECTS = \ + $(am_lib20_mailbox_alias_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_mailbox_alias_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(lib20_mailbox_alias_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/mailbox-alias-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_mailbox_alias_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_mailbox_alias_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +lib20_mailbox_alias_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_mailbox_alias_plugin.la + +lib20_mailbox_alias_plugin_la_SOURCES = \ + mailbox-alias-plugin.c + +noinst_HEADERS = \ + mailbox-alias-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/mailbox-alias/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/mailbox-alias/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_mailbox_alias_plugin.la: $(lib20_mailbox_alias_plugin_la_OBJECTS) $(lib20_mailbox_alias_plugin_la_DEPENDENCIES) $(EXTRA_lib20_mailbox_alias_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_mailbox_alias_plugin_la_LINK) -rpath $(moduledir) $(lib20_mailbox_alias_plugin_la_OBJECTS) $(lib20_mailbox_alias_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-alias-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/mailbox-alias-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/mailbox-alias-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/mailbox-alias/mailbox-alias-plugin.c b/src/plugins/mailbox-alias/mailbox-alias-plugin.c new file mode 100644 index 0000000..ebd433c --- /dev/null +++ b/src/plugins/mailbox-alias/mailbox-alias-plugin.c @@ -0,0 +1,356 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "mail-storage-hooks.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "mailbox-alias-plugin.h" + +#define MAILBOX_ALIAS_USER_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_user_module) +#define MAILBOX_ALIAS_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_storage_module) +#define MAILBOX_ALIAS_LIST_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_mailbox_list_module) + +struct mailbox_alias { + const char *old_vname, *new_vname; +}; + +struct mailbox_alias_user { + union mail_user_module_context module_ctx; + + ARRAY(struct mailbox_alias) aliases; +}; + +struct mailbox_alias_mailbox_list { + union mailbox_list_module_context module_ctx; +}; + +struct mailbox_alias_mailbox { + union mailbox_module_context module_ctx; +}; + +enum mailbox_symlink_existence { + MAILBOX_SYMLINK_EXISTENCE_NONEXISTENT, + MAILBOX_SYMLINK_EXISTENCE_SYMLINK, + MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK +}; + +static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_user_module, + &mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_mailbox_list_module, + &mailbox_list_module_register); + +const char *mailbox_alias_plugin_version = DOVECOT_ABI_VERSION; + +static const char * +mailbox_alias_find_new(struct mail_user *user, const char *new_vname) +{ + struct mailbox_alias_user *auser = MAILBOX_ALIAS_USER_CONTEXT(user); + const struct mailbox_alias *alias; + + array_foreach(&auser->aliases, alias) { + if (strcmp(alias->new_vname, new_vname) == 0) + return alias->old_vname; + } + return NULL; +} + +static int mailbox_symlink_exists(struct mailbox_list *list, const char *vname, + enum mailbox_symlink_existence *existence_r) +{ + struct mailbox_alias_mailbox_list *alist = + MAILBOX_ALIAS_LIST_CONTEXT(list); + struct stat st; + const char *symlink_name, *symlink_path; + int ret; + + symlink_name = alist->module_ctx.super.get_storage_name(list, vname); + ret = mailbox_list_get_path(list, symlink_name, + MAILBOX_LIST_PATH_TYPE_DIR, &symlink_path); + if (ret < 0) + return -1; + i_assert(ret > 0); + + if (lstat(symlink_path, &st) < 0) { + if (errno == ENOENT) { + *existence_r = MAILBOX_SYMLINK_EXISTENCE_NONEXISTENT; + return 0; + } + mailbox_list_set_critical(list, + "lstat(%s) failed: %m", symlink_path); + return -1; + } + if (S_ISLNK(st.st_mode)) + *existence_r = MAILBOX_SYMLINK_EXISTENCE_SYMLINK; + else + *existence_r = MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK; + return 0; +} + +static int mailbox_is_alias_symlink(struct mailbox *box) +{ + enum mailbox_symlink_existence existence; + + if (mailbox_alias_find_new(box->storage->user, box->vname) == NULL) + return 0; + if (mailbox_symlink_exists(box->list, box->vname, &existence) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + return -1; + } + return existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK ? 1 : 0; +} + +static int +mailbox_has_aliases(struct mailbox_list *list, const char *old_vname) +{ + struct mailbox_alias_user *auser = + MAILBOX_ALIAS_USER_CONTEXT(list->ns->user); + const struct mailbox_alias *alias; + enum mailbox_symlink_existence existence; + int ret = 0; + + array_foreach(&auser->aliases, alias) { + if (strcmp(alias->old_vname, old_vname) == 0) { + if (mailbox_symlink_exists(list, alias->new_vname, + &existence) < 0) + ret = -1; + else if (existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK) + return 1; + } + } + return ret; +} + +static int +mailbox_alias_create_symlink(struct mailbox *box, + const char *old_name, const char *new_name) +{ + const char *old_path, *new_path, *fname; + int ret; + + ret = mailbox_list_get_path(box->list, old_name, + MAILBOX_LIST_PATH_TYPE_DIR, &old_path); + if (ret > 0) { + ret = mailbox_list_get_path(box->list, new_name, + MAILBOX_LIST_PATH_TYPE_DIR, + &new_path); + } + if (ret < 0) + return -1; + if (ret == 0) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Mailbox aliases not supported by storage"); + return -1; + } + fname = strrchr(old_path, '/'); + i_assert(fname != NULL); + fname++; + i_assert(strncmp(new_path, old_path, fname-old_path) == 0); + + if (symlink(fname, new_path) < 0) { + if (errno == EEXIST) { + mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS, + "Mailbox already exists"); + return -1; + } + mailbox_set_critical(box, + "symlink(%s, %s) failed: %m", fname, new_path); + return -1; + } + return 0; +} + +static const char * +mailbox_alias_get_storage_name(struct mailbox_list *list, const char *vname) +{ + struct mailbox_alias_mailbox_list *alist = + MAILBOX_ALIAS_LIST_CONTEXT(list); + const char *old_vname; + enum mailbox_symlink_existence existence; + + /* access the old mailbox so that e.g. full text search won't + index the mailbox twice. this also means that deletion must be + careful to delete the symlink, box->name. */ + old_vname = mailbox_alias_find_new(list->ns->user, vname); + if (old_vname != NULL && + mailbox_symlink_exists(list, vname, &existence) == 0 && + existence != MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK) + vname = old_vname; + + return alist->module_ctx.super.get_storage_name(list, vname); +} + +static int +mailbox_alias_create(struct mailbox *box, const struct mailbox_update *update, + bool directory) +{ + struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(box); + struct mailbox_alias_mailbox_list *alist = + MAILBOX_ALIAS_LIST_CONTEXT(box->list); + const char *symlink_name; + int ret; + + ret = abox->module_ctx.super.create_box(box, update, directory); + if (mailbox_alias_find_new(box->storage->user, box->vname) == NULL) + return ret; + if (ret < 0 && mailbox_get_last_mail_error(box) != MAIL_ERROR_EXISTS) + return ret; + + /* all the code so far has actually only created the original + mailbox. now we'll create the symlink if it's missing. */ + symlink_name = alist->module_ctx.super. + get_storage_name(box->list, box->vname); + return mailbox_alias_create_symlink(box, box->name, symlink_name); +} + +static int mailbox_alias_delete(struct mailbox *box) +{ + struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(box); + struct mailbox_alias_mailbox_list *alist = + MAILBOX_ALIAS_LIST_CONTEXT(box->list); + const char *symlink_name; + int ret; + + ret = mailbox_has_aliases(box->list, box->vname); + if (ret < 0) + return -1; + if (ret > 0) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't delete mailbox while it has aliases"); + return -1; + } + + if ((ret = mailbox_is_alias_symlink(box)) < 0) + return -1; + if (ret > 0) { + /* we're deleting an alias mailbox. we'll need to handle this + explicitly since box->name points to the original mailbox */ + symlink_name = alist->module_ctx.super. + get_storage_name(box->list, box->vname); + if (mailbox_list_delete_symlink(box->list, symlink_name) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + return -1; + } + return 0; + } + + return abox->module_ctx.super.delete_box(box); +} + +static int mailbox_alias_rename(struct mailbox *src, struct mailbox *dest) +{ + struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(src); + int ret; + + if ((ret = mailbox_is_alias_symlink(src)) < 0) + return -1; + else if (ret > 0) { + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename alias mailboxes"); + return -1; + } + if ((ret = mailbox_is_alias_symlink(dest)) < 0) + return -1; + else if (ret > 0) { + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename to mailbox alias"); + return -1; + } + ret = mailbox_has_aliases(src->list, src->vname); + if (ret < 0) + return -1; + if (ret > 0) { + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename mailbox while it has aliases"); + return -1; + } + + return abox->module_ctx.super.rename_box(src, dest); +} + +static void mailbox_alias_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct mailbox_alias_user *auser; + struct mailbox_alias *alias; + string_t *oldkey, *newkey; + const char *old_vname, *new_vname; + unsigned int i; + + auser = p_new(user->pool, struct mailbox_alias_user, 1); + auser->module_ctx.super = *v; + user->vlast = &auser->module_ctx.super; + + p_array_init(&auser->aliases, user->pool, 8); + + oldkey = t_str_new(32); + newkey = t_str_new(32); + str_append(oldkey, "mailbox_alias_old"); + str_append(newkey, "mailbox_alias_new"); + for (i = 2;; i++) { + old_vname = mail_user_plugin_getenv(user, str_c(oldkey)); + new_vname = mail_user_plugin_getenv(user, str_c(newkey)); + if (old_vname == NULL || new_vname == NULL) + break; + + alias = array_append_space(&auser->aliases); + alias->old_vname = old_vname; + alias->new_vname = new_vname; + + str_truncate(oldkey, 0); + str_truncate(newkey, 0); + str_printfa(oldkey, "mailbox_alias_old%u", i); + str_printfa(newkey, "mailbox_alias_new%u", i); + } + + MODULE_CONTEXT_SET(user, mailbox_alias_user_module, auser); +} + +static void mailbox_alias_mailbox_list_created(struct mailbox_list *list) +{ + struct mailbox_list_vfuncs *v = list->vlast; + struct mailbox_alias_mailbox_list *alist; + + alist = p_new(list->pool, struct mailbox_alias_mailbox_list, 1); + alist->module_ctx.super = *v; + list->vlast = &alist->module_ctx.super; + + v->get_storage_name = mailbox_alias_get_storage_name; + MODULE_CONTEXT_SET(list, mailbox_alias_mailbox_list_module, alist); +} + +static void mailbox_alias_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct mailbox_alias_mailbox *abox; + + abox = p_new(box->pool, struct mailbox_alias_mailbox, 1); + abox->module_ctx.super = *v; + box->vlast = &abox->module_ctx.super; + + v->create_box = mailbox_alias_create; + v->delete_box = mailbox_alias_delete; + v->rename_box = mailbox_alias_rename; + MODULE_CONTEXT_SET(box, mailbox_alias_storage_module, abox); +} + +static struct mail_storage_hooks mailbox_alias_mail_storage_hooks = { + .mail_user_created = mailbox_alias_mail_user_created, + .mailbox_list_created = mailbox_alias_mailbox_list_created, + .mailbox_allocated = mailbox_alias_mailbox_allocated +}; + +void mailbox_alias_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &mailbox_alias_mail_storage_hooks); +} + +void mailbox_alias_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mailbox_alias_mail_storage_hooks); +} diff --git a/src/plugins/mailbox-alias/mailbox-alias-plugin.h b/src/plugins/mailbox-alias/mailbox-alias-plugin.h new file mode 100644 index 0000000..bd439b9 --- /dev/null +++ b/src/plugins/mailbox-alias/mailbox-alias-plugin.h @@ -0,0 +1,7 @@ +#ifndef MAILBOX_ALIAS_PLUGIN_H +#define MAILBOX_ALIAS_PLUGIN_H + +void mailbox_alias_plugin_init(struct module *module); +void mailbox_alias_plugin_deinit(void); + +#endif diff --git a/src/plugins/notify-status/Makefile.am b/src/plugins/notify-status/Makefile.am new file mode 100644 index 0000000..241e88d --- /dev/null +++ b/src/plugins/notify-status/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/plugins/notify + +NOPLUGIN_LDFLAGS = +lib20_notify_status_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_notify_status_plugin.la + +lib20_notify_status_plugin_la_SOURCES = \ + notify-status-plugin.c diff --git a/src/plugins/notify-status/Makefile.in b/src/plugins/notify-status/Makefile.in new file mode 100644 index 0000000..cb2bf23 --- /dev/null +++ b/src/plugins/notify-status/Makefile.in @@ -0,0 +1,817 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/notify-status +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_notify_status_plugin_la_LIBADD = +am_lib20_notify_status_plugin_la_OBJECTS = notify-status-plugin.lo +lib20_notify_status_plugin_la_OBJECTS = \ + $(am_lib20_notify_status_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_notify_status_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(lib20_notify_status_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/notify-status-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_notify_status_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_notify_status_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/plugins/notify + +lib20_notify_status_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_notify_status_plugin.la + +lib20_notify_status_plugin_la_SOURCES = \ + notify-status-plugin.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/notify-status/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/notify-status/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_notify_status_plugin.la: $(lib20_notify_status_plugin_la_OBJECTS) $(lib20_notify_status_plugin_la_DEPENDENCIES) $(EXTRA_lib20_notify_status_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_notify_status_plugin_la_LINK) -rpath $(moduledir) $(lib20_notify_status_plugin_la_OBJECTS) $(lib20_notify_status_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-status-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/notify-status-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/notify-status-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/notify-status/notify-status-plugin.c b/src/plugins/notify-status/notify-status-plugin.c new file mode 100644 index 0000000..f52ffa1 --- /dev/null +++ b/src/plugins/notify-status/notify-status-plugin.c @@ -0,0 +1,362 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "json-parser.h" +#include "str.h" +#include "var-expand.h" +#include "mail-user.h" +#include "mail-storage.h" +#include "mail-storage-private.h" +#include "mail-namespace.h" +#include "mail-storage-hooks.h" +#include "imap-match.h" +#include "dict.h" +#include "notify-plugin.h" + +#define NOTIFY_STATUS_SETTING_DICT_URI "notify_status_dict" +#define NOTIFY_STATUS_SETTING_MAILBOX_PREFIX "notify_status_mailbox" +#define NOTIFY_STATUS_SETTING_VALUE_TEMPLATE "notify_status_value" +#define NOTIFY_STATUS_SETTING_VALUE_TEMPLATE_DEFAULT "{\"messages\":%{messages},\"unseen\":%{unseen}}" +#define NOTIFY_STATUS_KEY "priv/status/%s" + +#define NOTIFY_STATUS_USER_CONTEXT(obj) \ + (struct notify_status_user*)MODULE_CONTEXT(obj, notify_status_user_module) + +static MODULE_CONTEXT_DEFINE_INIT(notify_status_user_module, + &mail_user_module_register); + +void notify_status_plugin_init(struct module *module); +void notify_status_plugin_deinit(void); + +const char *notify_status_plugin_version = DOVECOT_ABI_VERSION; +const char *notify_status_plugin_dependencies[] = { "notify", NULL }; + +ARRAY_DEFINE_TYPE(imap_match_glob, struct imap_match_glob*); + +struct notify_status_mail_txn { + struct mailbox *box; + bool changed:1; +}; + +struct notify_status_user { + union mail_user_module_context module_ctx; + + ARRAY_TYPE(imap_match_glob) patterns; + struct dict *dict; + const char *value_template; + struct notify_context *context; +}; + +static int notify_status_dict_init(struct mail_user *user, const char *uri, + struct dict **dict_r, const char **error_r) +{ + struct dict_settings set = { + .base_dir = user->set->base_dir, + .event_parent = user->event, + }; + if (dict_init(uri, &set, dict_r, error_r) < 0) { + *error_r = t_strdup_printf("dict_init(%s) failed: %s", + uri, *error_r); + return -1; + } + return 0; +} + +static void notify_status_mailbox_patterns_init(struct mail_user *user, + ARRAY_TYPE(imap_match_glob) *patterns) +{ + const char *value; + unsigned int i; + + p_array_init(patterns, user->pool, 2); + + for(i=1;;i++) { + struct imap_match_glob **glob; + const char *key = NOTIFY_STATUS_SETTING_MAILBOX_PREFIX; + if (i > 1) + key = t_strdup_printf("%s%u", key, i); + value = mail_user_plugin_getenv(user, key); + if (value == NULL) + return; + char sep = mail_namespace_get_sep(user->namespaces); + glob = array_append_space(patterns); + *glob = imap_match_init(user->pool, value, TRUE, sep); + } +} + +static bool notify_status_mailbox_enabled(struct mailbox *box) +{ + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user); + struct imap_match_glob *glob; + /* not enabled */ + if (nuser == NULL) + return FALSE; + + /* if no patterns defined, anything goes */ + if (array_count(&nuser->patterns) == 0) + return TRUE; + + array_foreach_elem(&nuser->patterns, glob) { + if ((imap_match(glob, mailbox_get_vname(box)) & IMAP_MATCH_YES) != 0) + return TRUE; + } + return FALSE; +} + +static void notify_update_callback(const struct dict_commit_result *result, + void *context ATTR_UNUSED) +{ + if (result->ret == DICT_COMMIT_RET_OK || + result->ret == DICT_COMMIT_RET_NOTFOUND) + return; + + i_error("notify-status: dict_transaction_commit failed: %s", + result->error == NULL ? "" : result->error); +} + +#define MAILBOX_STATUS_NOTIFY (STATUS_MESSAGES|STATUS_UNSEEN|\ + STATUS_RECENT|STATUS_UIDNEXT|\ + STATUS_UIDVALIDITY|\ + STATUS_HIGHESTMODSEQ|STATUS_FIRST_RECENT_UID|\ + STATUS_HIGHESTPVTMODSEQ) +static void notify_update_mailbox_status(struct mailbox *box) +{ + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user); + i_assert(nuser != NULL); + struct dict_transaction_context *t; + struct mailbox_status status; + + e_debug(box->event, "notify-status: Updating mailbox status"); + + box = mailbox_alloc(mailbox_get_namespace(box)->list, + mailbox_get_vname(box), MAILBOX_FLAG_READONLY); + + if (mailbox_open(box) < 0) { + i_error("notify-status: mailbox_open(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + } else if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) { + i_error("notify-status: mailbox_sync(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + } else if (mailbox_get_status(box, MAILBOX_STATUS_NOTIFY, + &status) < 0) { + i_error("notify-status: mailbox_get_status(%s) failed: %s", + mailbox_get_vname(box), + mailbox_get_last_error(box, NULL)); + } else { + string_t *username = t_str_new(strlen(user->username)); + string_t *mboxname = t_str_new(64); + + json_append_escaped(username, user->username); + json_append_escaped(mboxname, mailbox_get_vname(box)); + + const struct var_expand_table values[] = { + { '\0', str_c(username), "username" }, + { '\0', str_c(mboxname), "mailbox" }, + { '\0', dec2str(status.messages), "messages" }, + { '\0', dec2str(status.unseen), "unseen" }, + { '\0', dec2str(status.recent), "recent" }, + { '\0', dec2str(status.uidvalidity), "uidvalidity" }, + { '\0', dec2str(status.uidnext), "uidnext" }, + { '\0', dec2str(status.first_recent_uid), "first_recent_uid" }, + { '\0', dec2str(status.highest_modseq), "highest_modseq" }, + { '\0', dec2str(status.highest_pvt_modseq), "highest_pvt_modseq" }, + { '\0', NULL, NULL } + }; + const char *error; + const char *key = + t_strdup_printf(NOTIFY_STATUS_KEY, mailbox_get_vname(box)); + string_t *dest = t_str_new(64); + if (var_expand(dest, nuser->value_template, values, &error)<0) { + i_error("notify-status: var_expand(%s) failed: %s", + nuser->value_template, error); + } else { + const struct dict_op_settings *set = mail_user_get_dict_op_settings(user); + t = dict_transaction_begin(nuser->dict, set); + dict_set(t, key, str_c(dest)); + dict_transaction_commit_async(&t, notify_update_callback, NULL) ; + } + } + + mailbox_free(&box); +} + +static void notify_remove_mailbox_status(struct mailbox *box) +{ + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user); + i_assert(nuser != NULL); + struct dict_transaction_context *t; + + e_debug(box->event, "notify-status: Removing mailbox status"); + + const char *key = + t_strdup_printf(NOTIFY_STATUS_KEY, mailbox_get_vname(box)); + + const struct dict_op_settings *set = mail_user_get_dict_op_settings(user); + t = dict_transaction_begin(nuser->dict, set); + dict_unset(t, key); + dict_transaction_commit_async(&t, notify_update_callback, NULL) ; +} + +static void *notify_status_mail_transaction_begin(struct mailbox_transaction_context *t) +{ + struct notify_status_mail_txn *txn = i_new(struct notify_status_mail_txn, 1); + txn->box = mailbox_transaction_get_mailbox(t); + return txn; +} + +static void +notify_status_mail_transaction_commit(void *t, + struct mail_transaction_commit_changes *changes ATTR_UNUSED) +{ + struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t; + if (txn->changed && notify_status_mailbox_enabled(txn->box)) + notify_update_mailbox_status(txn->box); + i_free(txn); +} + +static void notify_status_mail_transaction_rollback(void *t) +{ + i_free(t); +} + +static void notify_status_mailbox_create(struct mailbox *box) +{ + if (notify_status_mailbox_enabled(box)) + notify_update_mailbox_status(box); +} + +static void notify_status_mailbox_delete_commit(void *txn ATTR_UNUSED, + struct mailbox *box) +{ + if (notify_status_mailbox_enabled(box)) + notify_remove_mailbox_status(box); +} + +static void notify_status_mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + if (notify_status_mailbox_enabled(src)) + notify_remove_mailbox_status(src); + if (notify_status_mailbox_enabled(dest)) + notify_update_mailbox_status(dest); +} + +static void notify_status_mail_save(void *t, struct mail *mail ATTR_UNUSED) +{ + struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t; + txn->changed = TRUE; +} + +static void notify_status_mail_copy(void *t, struct mail *src ATTR_UNUSED, + struct mail *dst ATTR_UNUSED) +{ + struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t; + txn->changed = TRUE; +} + +static void notify_status_mail_expunge(void *t, struct mail *mail ATTR_UNUSED) +{ + struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t; + txn->changed = TRUE; +} +static void notify_status_mail_update_flags(void *t, struct mail *mail, + enum mail_flags old_flags) +{ + struct notify_status_mail_txn *txn = (struct notify_status_mail_txn *)t; + if ((old_flags & MAIL_SEEN) != (mail_get_flags(mail) & MAIL_SEEN)) + txn->changed = TRUE; +} + +static const struct notify_vfuncs notify_vfuncs = +{ + .mail_transaction_begin = notify_status_mail_transaction_begin, + .mail_save = notify_status_mail_save, + .mail_copy = notify_status_mail_copy, + .mail_expunge = notify_status_mail_expunge, + .mail_update_flags = notify_status_mail_update_flags, + .mail_transaction_commit = notify_status_mail_transaction_commit, + .mail_transaction_rollback = notify_status_mail_transaction_rollback, + .mailbox_create = notify_status_mailbox_create, + .mailbox_delete_commit = notify_status_mailbox_delete_commit, + .mailbox_rename = notify_status_mailbox_rename, +}; + +static void notify_status_mail_user_deinit(struct mail_user *user) +{ + struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user); + i_assert(nuser != NULL); + + dict_wait(nuser->dict); + dict_deinit(&nuser->dict); + notify_unregister(nuser->context); + nuser->module_ctx.super.deinit(user); +} + +static void notify_status_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct notify_status_user *nuser; + struct dict *dict; + const char *error; + const char *template = mail_user_plugin_getenv(user, NOTIFY_STATUS_SETTING_VALUE_TEMPLATE); + const char *uri = mail_user_plugin_getenv(user, NOTIFY_STATUS_SETTING_DICT_URI); + + if (user->autocreated) + return; + + if (uri == NULL || *uri == '\0') { + e_debug(user->event, "notify-status: Disabled - Missing plugin/" + NOTIFY_STATUS_SETTING_DICT_URI" setting"); + return; + } + + if (template == NULL || *template == '\0') + template = NOTIFY_STATUS_SETTING_VALUE_TEMPLATE_DEFAULT; + + if (notify_status_dict_init(user, uri, &dict, &error) < 0) { + i_error("notify-status: %s", error); + return; + } + + nuser = p_new(user->pool, struct notify_status_user, 1); + nuser->module_ctx.super = *v; + nuser->dict = dict; + user->vlast = &nuser->module_ctx.super; + v->deinit = notify_status_mail_user_deinit; + /* either static value or lifetime is user object's lifetime */ + nuser->value_template = template; + + MODULE_CONTEXT_SET(user, notify_status_user_module, nuser); +} + +static void +notify_status_mail_namespaces_created(struct mail_namespace *namespaces) +{ + struct mail_user *user = namespaces->user; + struct notify_status_user *nuser = NOTIFY_STATUS_USER_CONTEXT(user); + if (nuser == NULL) + return; + notify_status_mailbox_patterns_init(user, &nuser->patterns); + nuser->context = notify_register(¬ify_vfuncs); +} + +static const struct mail_storage_hooks notify_storage_hooks = +{ + .mail_user_created = notify_status_mail_user_created, + .mail_namespaces_created = notify_status_mail_namespaces_created, +}; + +void notify_status_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, ¬ify_storage_hooks); +} + +void notify_status_plugin_deinit(void) +{ + mail_storage_hooks_remove(¬ify_storage_hooks); +} diff --git a/src/plugins/notify/Makefile.am b/src/plugins/notify/Makefile.am new file mode 100644 index 0000000..dfa286e --- /dev/null +++ b/src/plugins/notify/Makefile.am @@ -0,0 +1,25 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/maildir + +NOPLUGIN_LDFLAGS = +lib15_notify_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib15_notify_plugin.la + +lib15_notify_plugin_la_SOURCES = \ + notify-plugin.c \ + notify-storage.c + +headers = \ + notify-plugin.h \ + notify-plugin-private.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) diff --git a/src/plugins/notify/Makefile.in b/src/plugins/notify/Makefile.in new file mode 100644 index 0000000..109f11e --- /dev/null +++ b/src/plugins/notify/Makefile.in @@ -0,0 +1,852 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/notify +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" \ + "$(DESTDIR)$(pkginc_libdir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib15_notify_plugin_la_LIBADD = +am_lib15_notify_plugin_la_OBJECTS = notify-plugin.lo notify-storage.lo +lib15_notify_plugin_la_OBJECTS = $(am_lib15_notify_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib15_notify_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib15_notify_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/notify-plugin.Plo \ + ./$(DEPDIR)/notify-storage.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib15_notify_plugin_la_SOURCES) +DIST_SOURCES = $(lib15_notify_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/maildir + +lib15_notify_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib15_notify_plugin.la + +lib15_notify_plugin_la_SOURCES = \ + notify-plugin.c \ + notify-storage.c + +headers = \ + notify-plugin.h \ + notify-plugin-private.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/notify/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/notify/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib15_notify_plugin.la: $(lib15_notify_plugin_la_OBJECTS) $(lib15_notify_plugin_la_DEPENDENCIES) $(EXTRA_lib15_notify_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib15_notify_plugin_la_LINK) -rpath $(moduledir) $(lib15_notify_plugin_la_OBJECTS) $(lib15_notify_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-storage.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/notify-plugin.Plo + -rm -f ./$(DEPDIR)/notify-storage.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/notify-plugin.Plo + -rm -f ./$(DEPDIR)/notify-storage.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/notify/notify-plugin-private.h b/src/plugins/notify/notify-plugin-private.h new file mode 100644 index 0000000..cef0771 --- /dev/null +++ b/src/plugins/notify/notify-plugin-private.h @@ -0,0 +1,29 @@ +#ifndef NOTIFY_PLUGIN_PRIVATE_H +#define NOTIFY_PLUGIN_PRIVATE_H + +#include "notify-plugin.h" + +void notify_contexts_mail_transaction_begin(struct mailbox_transaction_context *t); +void notify_contexts_mail_save(struct mail *mail); +void notify_contexts_mail_copy(struct mail *src, struct mail *dst); +void notify_contexts_mail_expunge(struct mail *mail); +void notify_contexts_mail_update_flags(struct mail *mail, + enum mail_flags old_flags); +void notify_contexts_mail_update_keywords(struct mail *mail, + const char *const *old_keywords); +void notify_contexts_mail_transaction_commit(struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes); +void notify_contexts_mail_transaction_rollback(struct mailbox_transaction_context *t); +void notify_contexts_mailbox_create(struct mailbox *box); +void notify_contexts_mailbox_update(struct mailbox *box); +void notify_contexts_mailbox_delete_begin(struct mailbox *box); +void notify_contexts_mailbox_delete_commit(struct mailbox *box); +void notify_contexts_mailbox_delete_rollback(void); +void notify_contexts_mailbox_rename(struct mailbox *src, struct mailbox *dest); +void notify_contexts_mailbox_set_subscribed(struct mailbox *box, + bool subscribed); + +void notify_plugin_init_storage(struct module *module); +void notify_plugin_deinit_storage(void); + +#endif diff --git a/src/plugins/notify/notify-plugin.c b/src/plugins/notify/notify-plugin.c new file mode 100644 index 0000000..e9ff9bb --- /dev/null +++ b/src/plugins/notify/notify-plugin.c @@ -0,0 +1,265 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "mail-storage.h" +#include "notify-plugin-private.h" + + +struct notify_mail_txn { + struct notify_mail_txn *prev, *next; + struct mailbox_transaction_context *parent_mailbox_txn; + struct mail *tmp_mail; + void *txn; +}; + +struct notify_context { + struct notify_context *prev, *next; + struct notify_vfuncs v; + struct notify_mail_txn *mail_txn_list; + void *mailbox_delete_txn; +}; + +const char *notify_plugin_version = DOVECOT_ABI_VERSION; +static struct notify_context *ctx_list = NULL; + +static struct notify_mail_txn * +notify_context_find_mail_txn(struct notify_context *ctx, + struct mailbox_transaction_context *t) +{ + struct notify_mail_txn *mail_txn = ctx->mail_txn_list; + + for (; mail_txn != NULL; mail_txn = mail_txn->next) { + if (mail_txn->parent_mailbox_txn == t) + return mail_txn; + } + i_panic("no notify_mail_txn found"); +} + +void notify_contexts_mail_transaction_begin(struct mailbox_transaction_context *t) +{ + struct notify_context *ctx; + struct notify_mail_txn *mail_txn; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + mail_txn = i_new(struct notify_mail_txn, 1); + mail_txn->parent_mailbox_txn = t; + mail_txn->txn = ctx->v.mail_transaction_begin == NULL ? NULL : + ctx->v.mail_transaction_begin(t); + DLLIST_PREPEND(&ctx->mail_txn_list, mail_txn); + } +} + +void notify_contexts_mail_save(struct mail *mail) +{ + struct notify_context *ctx; + struct notify_mail_txn *mail_txn; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mail_save == NULL) + continue; + mail_txn = notify_context_find_mail_txn(ctx, mail->transaction); + ctx->v.mail_save(mail_txn->txn, mail); + } +} + +void notify_contexts_mail_copy(struct mail *src, struct mail *dst) +{ + struct notify_context *ctx; + struct notify_mail_txn *mail_txn; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mail_copy == NULL) + continue; + mail_txn = notify_context_find_mail_txn(ctx, dst->transaction); + ctx->v.mail_copy(mail_txn->txn, src, dst); + } +} + +void notify_contexts_mail_expunge(struct mail *mail) +{ + struct notify_context *ctx; + struct notify_mail_txn *mail_txn; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mail_expunge == NULL) + continue; + mail_txn = notify_context_find_mail_txn(ctx, mail->transaction); + ctx->v.mail_expunge(mail_txn->txn, mail); + } +} + +void notify_contexts_mail_update_flags(struct mail *mail, + enum mail_flags old_flags) +{ + struct notify_context *ctx; + struct notify_mail_txn *mail_txn; + + if (mail->saving) + return; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mail_update_flags == NULL) + continue; + mail_txn = notify_context_find_mail_txn(ctx, mail->transaction); + ctx->v.mail_update_flags(mail_txn->txn, mail, old_flags); + } +} + +void notify_contexts_mail_update_keywords(struct mail *mail, + const char *const *old_keywords) +{ + struct notify_context *ctx; + struct notify_mail_txn *mail_txn; + + if (mail->saving) + return; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mail_update_keywords == NULL) + continue; + mail_txn = notify_context_find_mail_txn(ctx, mail->transaction); + ctx->v.mail_update_keywords(mail_txn->txn, mail, old_keywords); + } +} + +void notify_contexts_mail_transaction_commit(struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes) +{ + struct notify_context *ctx; + struct notify_mail_txn *mail_txn; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + mail_txn = notify_context_find_mail_txn(ctx, t); + if (ctx->v.mail_transaction_commit != NULL) + ctx->v.mail_transaction_commit(mail_txn->txn, changes); + DLLIST_REMOVE(&ctx->mail_txn_list, mail_txn); + i_free(mail_txn); + } +} + +void notify_contexts_mail_transaction_rollback(struct mailbox_transaction_context *t) +{ + struct notify_context *ctx; + struct notify_mail_txn *mail_txn; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + mail_txn = notify_context_find_mail_txn(ctx, t); + if (ctx->v.mail_transaction_rollback != NULL) + ctx->v.mail_transaction_rollback(mail_txn->txn); + DLLIST_REMOVE(&ctx->mail_txn_list, mail_txn); + i_free(mail_txn); + } +} + +void notify_contexts_mailbox_create(struct mailbox *box) +{ + struct notify_context *ctx; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mailbox_create != NULL) + ctx->v.mailbox_create(box); + } +} + +void notify_contexts_mailbox_update(struct mailbox *box) +{ + struct notify_context *ctx; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mailbox_update != NULL) + ctx->v.mailbox_update(box); + } +} + +void notify_contexts_mailbox_delete_begin(struct mailbox *box) +{ + struct notify_context *ctx; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + ctx->mailbox_delete_txn = + ctx->v.mailbox_delete_begin == NULL ? NULL : + ctx->v.mailbox_delete_begin(box); + } +} + +void notify_contexts_mailbox_delete_commit(struct mailbox *box) +{ + struct notify_context *ctx; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mailbox_delete_commit != NULL) { + ctx->v.mailbox_delete_commit(ctx->mailbox_delete_txn, + box); + } + ctx->mailbox_delete_txn = NULL; + } +} + +void notify_contexts_mailbox_delete_rollback(void) +{ + struct notify_context *ctx; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mailbox_delete_rollback != NULL) + ctx->v.mailbox_delete_rollback(ctx->mailbox_delete_txn); + ctx->mailbox_delete_txn = NULL; + } +} + +void notify_contexts_mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + struct notify_context *ctx; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mailbox_rename != NULL) + ctx->v.mailbox_rename(src, dest); + } +} + +void notify_contexts_mailbox_set_subscribed(struct mailbox *box, + bool subscribed) +{ + struct notify_context *ctx; + + for (ctx = ctx_list; ctx != NULL; ctx = ctx->next) { + if (ctx->v.mailbox_set_subscribed != NULL) + ctx->v.mailbox_set_subscribed(box, subscribed); + } +} + +struct notify_context * +notify_register(const struct notify_vfuncs *v) +{ + struct notify_context *ctx; + + ctx = i_new(struct notify_context, 1); + ctx->v = *v; + DLLIST_PREPEND(&ctx_list, ctx); + return ctx; +} + +void notify_unregister(struct notify_context *ctx) +{ + struct notify_mail_txn *mail_txn = ctx->mail_txn_list; + + for (; mail_txn != NULL; mail_txn = mail_txn->next) { + if (ctx->v.mail_transaction_rollback != NULL) + ctx->v.mail_transaction_rollback(mail_txn->txn); + } + if (ctx->mailbox_delete_txn != NULL && + ctx->v.mailbox_delete_rollback != NULL) + ctx->v.mailbox_delete_rollback(ctx->mailbox_delete_txn); + DLLIST_REMOVE(&ctx_list, ctx); + i_free(ctx); +} + +void notify_plugin_init(struct module *module) +{ + notify_plugin_init_storage(module); +} + +void notify_plugin_deinit(void) +{ + notify_plugin_deinit_storage(); +} diff --git a/src/plugins/notify/notify-plugin.h b/src/plugins/notify/notify-plugin.h new file mode 100644 index 0000000..3c95071 --- /dev/null +++ b/src/plugins/notify/notify-plugin.h @@ -0,0 +1,43 @@ +#ifndef NOTIFY_PLUGIN_H +#define NOTIFY_PLUGIN_H + +#include "mail-types.h" + +struct mail; +struct mail_transaction_commit_changes; +struct mail_storage; +struct mailbox_transaction_context; +struct mailbox_list; +struct mailbox; +struct notify_context; +struct module; + +struct notify_vfuncs { + void *(*mail_transaction_begin)(struct mailbox_transaction_context *t); + void (*mail_save)(void *txn, struct mail *mail); + void (*mail_copy)(void *txn, struct mail *src, struct mail *dst); + void (*mail_expunge)(void *txn, struct mail *mail); + void (*mail_update_flags)(void *txn, struct mail *mail, + enum mail_flags old_flags); + void (*mail_update_keywords)(void *txn, struct mail *mail, + const char *const *old_keywords); + void (*mail_transaction_commit)(void *txn, + struct mail_transaction_commit_changes *changes); + void (*mail_transaction_rollback)(void *txn); + void (*mailbox_create)(struct mailbox *box); + void (*mailbox_update)(struct mailbox *box); + void *(*mailbox_delete_begin)(struct mailbox *box); + void (*mailbox_delete_commit)(void *txn, struct mailbox *box); + void (*mailbox_delete_rollback)(void *txn); + void (*mailbox_rename)(struct mailbox *src, struct mailbox *dest); + void (*mailbox_set_subscribed)(struct mailbox *box, bool subscribed); +}; + +struct notify_context * +notify_register(const struct notify_vfuncs *vfuncs); +void notify_unregister(struct notify_context *ctx); + +void notify_plugin_init(struct module *module); +void notify_plugin_deinit(void); + +#endif diff --git a/src/plugins/notify/notify-storage.c b/src/plugins/notify/notify-storage.c new file mode 100644 index 0000000..b7a6a3a --- /dev/null +++ b/src/plugins/notify/notify-storage.c @@ -0,0 +1,269 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "notify-plugin-private.h" + +#define NOTIFY_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, notify_storage_module) +#define NOTIFY_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, notify_mail_module) + +static MODULE_CONTEXT_DEFINE_INIT(notify_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(notify_mail_module, + &mail_module_register); + +static void +notify_mail_expunge(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *lmail = NOTIFY_MAIL_CONTEXT(mail); + + notify_contexts_mail_expunge(_mail); + lmail->super.expunge(_mail); +} + +static void +notify_mail_update_flags(struct mail *_mail, enum modify_type modify_type, + enum mail_flags flags) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *lmail = NOTIFY_MAIL_CONTEXT(mail); + enum mail_flags old_flags, new_flags; + + old_flags = mail_get_flags(_mail); + lmail->super.update_flags(_mail, modify_type, flags); + new_flags = mail_get_flags(_mail); + + if ((old_flags ^ new_flags) == 0) + return; + + notify_contexts_mail_update_flags(_mail, old_flags); +} + +static void +notify_mail_update_keywords(struct mail *_mail, enum modify_type modify_type, + struct mail_keywords *keywords) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *lmail = NOTIFY_MAIL_CONTEXT(mail); + const char *const *old_keywords, *const *new_keywords; + unsigned int i; + + old_keywords = mail_get_keywords(_mail); + lmail->super.update_keywords(_mail, modify_type, keywords); + new_keywords = mail_get_keywords(_mail); + + for (i = 0; old_keywords[i] != NULL && new_keywords[i] != NULL; i++) { + if (strcmp(old_keywords[i], new_keywords[i]) != 0) + break; + } + + if (old_keywords[i] == NULL && new_keywords[i] == NULL) + return; + + notify_contexts_mail_update_keywords(_mail, old_keywords); +} + +static void notify_mail_allocated(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + union mail_module_context *lmail; + + if ((_mail->transaction->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) != 0) + return; + + lmail = p_new(mail->pool, union mail_module_context, 1); + lmail->super = *v; + mail->vlast = &lmail->super; + + v->expunge = notify_mail_expunge; + v->update_flags = notify_mail_update_flags; + v->update_keywords = notify_mail_update_keywords; + MODULE_CONTEXT_SET_SELF(mail, notify_mail_module, lmail); +} + +static int +notify_copy(struct mail_save_context *ctx, struct mail *mail) +{ + union mailbox_module_context *lbox = + NOTIFY_CONTEXT(ctx->transaction->box); + int ret; + + if ((ret = lbox->super.copy(ctx, mail)) < 0) + return -1; + + if ((ctx->transaction->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) != 0) { + /* no notifications */ + } else if (ctx->saving) { + /* we came from mailbox_save_using_mail() */ + notify_contexts_mail_save(ctx->dest_mail); + } else { + notify_contexts_mail_copy(mail, ctx->dest_mail); + } + return ret; +} + +static int +notify_save_finish(struct mail_save_context *ctx) +{ + union mailbox_module_context *lbox = + NOTIFY_CONTEXT(ctx->transaction->box); + struct mail *dest_mail = ctx->copying_via_save ? NULL : ctx->dest_mail; + + if (lbox->super.save_finish(ctx) < 0) + return -1; + if (dest_mail != NULL && + (ctx->transaction->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) == 0) + notify_contexts_mail_save(dest_mail); + return 0; +} + +static struct mailbox_transaction_context * +notify_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + union mailbox_module_context *lbox = NOTIFY_CONTEXT(box); + struct mailbox_transaction_context *t; + + t = lbox->super.transaction_begin(box, flags, reason); + + if ((t->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) == 0) + notify_contexts_mail_transaction_begin(t); + return t; +} + +static int +notify_transaction_commit(struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes_r) +{ + union mailbox_module_context *lbox = NOTIFY_CONTEXT(t->box); + bool no_notify = (t->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) != 0; + + if ((lbox->super.transaction_commit(t, changes_r)) < 0) { + if (!no_notify) + notify_contexts_mail_transaction_rollback(t); + return -1; + } + + /* FIXME: note that t is already freed at this stage. it's not actually + being dereferenced anymore though. still, a bit unsafe.. */ + if (!no_notify) + notify_contexts_mail_transaction_commit(t, changes_r); + return 0; +} + +static void +notify_transaction_rollback(struct mailbox_transaction_context *t) +{ + union mailbox_module_context *lbox = NOTIFY_CONTEXT(t->box); + + if ((t->flags & MAILBOX_TRANSACTION_FLAG_NO_NOTIFY) == 0) + notify_contexts_mail_transaction_rollback(t); + lbox->super.transaction_rollback(t); +} + +static int +notify_mailbox_create(struct mailbox *box, const struct mailbox_update *update, + bool directory) +{ + union mailbox_module_context *lbox = NOTIFY_CONTEXT(box); + + if (lbox->super.create_box(box, update, directory) < 0) + return -1; + + notify_contexts_mailbox_create(box); + return 0; +} + +static int +notify_mailbox_update(struct mailbox *box, const struct mailbox_update *update) +{ + union mailbox_module_context *lbox = NOTIFY_CONTEXT(box); + + if (lbox->super.update_box(box, update) < 0) + return -1; + + notify_contexts_mailbox_update(box); + return 0; +} + +static int +notify_mailbox_delete(struct mailbox *box) +{ + union mailbox_module_context *lbox = NOTIFY_CONTEXT(box); + + notify_contexts_mailbox_delete_begin(box); + if (lbox->super.delete_box(box) < 0) { + notify_contexts_mailbox_delete_rollback(); + return -1; + } + notify_contexts_mailbox_delete_commit(box); + return 0; +} + +static int +notify_mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + union mailbox_module_context *lbox = NOTIFY_CONTEXT(src); + + if (lbox->super.rename_box(src, dest) < 0) + return -1; + + notify_contexts_mailbox_rename(src, dest); + return 0; +} + +static int +notify_mailbox_set_subscribed(struct mailbox *box, bool set) +{ + union mailbox_module_context *lbox = NOTIFY_CONTEXT(box); + + if (lbox->super.set_subscribed(box, set) < 0) + return -1; + + notify_contexts_mailbox_set_subscribed(box, set); + return 0; +} + +static void notify_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + union mailbox_module_context *lbox; + + lbox = p_new(box->pool, union mailbox_module_context, 1); + lbox->super = *v; + box->vlast = &lbox->super; + + v->copy = notify_copy; + v->save_finish = notify_save_finish; + v->transaction_begin = notify_transaction_begin; + v->transaction_commit = notify_transaction_commit; + v->transaction_rollback = notify_transaction_rollback; + v->create_box = notify_mailbox_create; + v->update_box = notify_mailbox_update; + v->delete_box = notify_mailbox_delete; + v->rename_box = notify_mailbox_rename; + v->set_subscribed = notify_mailbox_set_subscribed; + MODULE_CONTEXT_SET_SELF(box, notify_storage_module, lbox); +} + +static struct mail_storage_hooks notify_mail_storage_hooks = { + .mailbox_allocated = notify_mailbox_allocated, + .mail_allocated = notify_mail_allocated +}; + +void notify_plugin_init_storage(struct module *module) +{ + mail_storage_hooks_add(module, ¬ify_mail_storage_hooks); +} + +void notify_plugin_deinit_storage(void) +{ + mail_storage_hooks_remove(¬ify_mail_storage_hooks); +} diff --git a/src/plugins/old-stats/Makefile.am b/src/plugins/old-stats/Makefile.am new file mode 100644 index 0000000..8a95363 --- /dev/null +++ b/src/plugins/old-stats/Makefile.am @@ -0,0 +1,33 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-old-stats \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib90_old_stats_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib90_old_stats_plugin.la + +lib90_old_stats_plugin_la_SOURCES = \ + mail-stats.c \ + mail-stats-fill.c \ + mail-stats-connection.c \ + stats-plugin.c + +noinst_HEADERS = \ + mail-stats.h \ + mail-stats-connection.h \ + stats-plugin.h + +old_stats_moduledir = $(moduledir)/old-stats +old_stats_module_LTLIBRARIES = libold_stats_mail.la + +libold_stats_mail_la_LDFLAGS = -module -avoid-version +libold_stats_mail_la_LIBADD = mail-stats.lo $(LIBDOVECOT) +libold_stats_mail_la_DEPENDENCIES = mail-stats.lo +libold_stats_mail_la_SOURCES = diff --git a/src/plugins/old-stats/Makefile.in b/src/plugins/old-stats/Makefile.in new file mode 100644 index 0000000..be11193 --- /dev/null +++ b/src/plugins/old-stats/Makefile.in @@ -0,0 +1,898 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/old-stats +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" \ + "$(DESTDIR)$(old_stats_moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) $(old_stats_module_LTLIBRARIES) +lib90_old_stats_plugin_la_LIBADD = +am_lib90_old_stats_plugin_la_OBJECTS = mail-stats.lo \ + mail-stats-fill.lo mail-stats-connection.lo stats-plugin.lo +lib90_old_stats_plugin_la_OBJECTS = \ + $(am_lib90_old_stats_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib90_old_stats_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib90_old_stats_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__DEPENDENCIES_1 = +am_libold_stats_mail_la_OBJECTS = +libold_stats_mail_la_OBJECTS = $(am_libold_stats_mail_la_OBJECTS) +libold_stats_mail_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libold_stats_mail_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/mail-stats-connection.Plo \ + ./$(DEPDIR)/mail-stats-fill.Plo ./$(DEPDIR)/mail-stats.Plo \ + ./$(DEPDIR)/stats-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib90_old_stats_plugin_la_SOURCES) \ + $(libold_stats_mail_la_SOURCES) +DIST_SOURCES = $(lib90_old_stats_plugin_la_SOURCES) \ + $(libold_stats_mail_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-old-stats \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +lib90_old_stats_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib90_old_stats_plugin.la + +lib90_old_stats_plugin_la_SOURCES = \ + mail-stats.c \ + mail-stats-fill.c \ + mail-stats-connection.c \ + stats-plugin.c + +noinst_HEADERS = \ + mail-stats.h \ + mail-stats-connection.h \ + stats-plugin.h + +old_stats_moduledir = $(moduledir)/old-stats +old_stats_module_LTLIBRARIES = libold_stats_mail.la +libold_stats_mail_la_LDFLAGS = -module -avoid-version +libold_stats_mail_la_LIBADD = mail-stats.lo $(LIBDOVECOT) +libold_stats_mail_la_DEPENDENCIES = mail-stats.lo +libold_stats_mail_la_SOURCES = +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/old-stats/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/old-stats/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-old_stats_moduleLTLIBRARIES: $(old_stats_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(old_stats_module_LTLIBRARIES)'; test -n "$(old_stats_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(old_stats_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(old_stats_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(old_stats_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(old_stats_moduledir)"; \ + } + +uninstall-old_stats_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(old_stats_module_LTLIBRARIES)'; test -n "$(old_stats_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(old_stats_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(old_stats_moduledir)/$$f"; \ + done + +clean-old_stats_moduleLTLIBRARIES: + -test -z "$(old_stats_module_LTLIBRARIES)" || rm -f $(old_stats_module_LTLIBRARIES) + @list='$(old_stats_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib90_old_stats_plugin.la: $(lib90_old_stats_plugin_la_OBJECTS) $(lib90_old_stats_plugin_la_DEPENDENCIES) $(EXTRA_lib90_old_stats_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib90_old_stats_plugin_la_LINK) -rpath $(moduledir) $(lib90_old_stats_plugin_la_OBJECTS) $(lib90_old_stats_plugin_la_LIBADD) $(LIBS) + +libold_stats_mail.la: $(libold_stats_mail_la_OBJECTS) $(libold_stats_mail_la_DEPENDENCIES) $(EXTRA_libold_stats_mail_la_DEPENDENCIES) + $(AM_V_CCLD)$(libold_stats_mail_la_LINK) -rpath $(old_stats_moduledir) $(libold_stats_mail_la_OBJECTS) $(libold_stats_mail_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-stats-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-stats-fill.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-stats.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(old_stats_moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-old_stats_moduleLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/mail-stats-connection.Plo + -rm -f ./$(DEPDIR)/mail-stats-fill.Plo + -rm -f ./$(DEPDIR)/mail-stats.Plo + -rm -f ./$(DEPDIR)/stats-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES \ + install-old_stats_moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/mail-stats-connection.Plo + -rm -f ./$(DEPDIR)/mail-stats-fill.Plo + -rm -f ./$(DEPDIR)/mail-stats.Plo + -rm -f ./$(DEPDIR)/stats-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES \ + uninstall-old_stats_moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-old_stats_moduleLTLIBRARIES cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-moduleLTLIBRARIES \ + install-old_stats_moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES \ + uninstall-old_stats_moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/old-stats/mail-stats-connection.c b/src/plugins/old-stats/mail-stats-connection.c new file mode 100644 index 0000000..ca20fcb --- /dev/null +++ b/src/plugins/old-stats/mail-stats-connection.c @@ -0,0 +1,75 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "base64.h" +#include "hostpid.h" +#include "net.h" +#include "str.h" +#include "strescape.h" +#include "mail-storage.h" +#include "stats.h" +#include "stats-plugin.h" +#include "mail-stats-connection.h" + +int mail_stats_connection_connect(struct stats_connection *conn, + struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + string_t *str = t_str_new(128); + + str_append(str, "CONNECT\t"); + /* required fields */ + str_append(str, suser->stats_session_id); + str_append_c(str, '\t'); + str_append_tabescaped(str, user->username); + str_append_c(str, '\t'); + str_append_tabescaped(str, user->service); + str_printfa(str, "\t%s", my_pid); + + /* optional fields */ + if (user->conn.local_ip != NULL) { + str_append(str, "\tlip="); + str_append(str, net_ip2addr(user->conn.local_ip)); + } + if (user->conn.remote_ip != NULL) { + str_append(str, "\trip="); + str_append(str, net_ip2addr(user->conn.remote_ip)); + } + str_append_c(str, '\n'); + return stats_connection_send(conn, str); +} + +void mail_stats_connection_disconnect(struct stats_connection *conn, + struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + string_t *str = t_str_new(128); + + str_append(str, "DISCONNECT\t"); + str_append(str, suser->stats_session_id); + str_append_c(str, '\n'); + if (stats_connection_send(conn, str) < 0) { + /* we could retry this later, but stats process will forget it + anyway after 15 minutes. */ + } +} + +void mail_stats_connection_send_session(struct stats_connection *conn, + struct mail_user *user, + const struct stats *stats) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + string_t *str = t_str_new(256); + buffer_t *buf; + + buf = t_buffer_create(128); + stats_export(buf, stats); + + str_append(str, "UPDATE-SESSION\t"); + str_append(str, suser->stats_session_id); + str_append_c(str, '\t'); + base64_encode(buf->data, buf->used, str); + + str_append_c(str, '\n'); + (void)stats_connection_send(conn, str); +} diff --git a/src/plugins/old-stats/mail-stats-connection.h b/src/plugins/old-stats/mail-stats-connection.h new file mode 100644 index 0000000..f98b691 --- /dev/null +++ b/src/plugins/old-stats/mail-stats-connection.h @@ -0,0 +1,19 @@ +#ifndef MAIL_STATS_CONNECTION_H +#define MAIL_STATS_CONNECTION_H + +#include "stats-connection.h" + +struct mail_stats; +struct mail_user; + +int mail_stats_connection_connect(struct stats_connection *conn, + struct mail_user *user); +void mail_stats_connection_disconnect(struct stats_connection *conn, + struct mail_user *user); + +void mail_stats_connection_send_session(struct stats_connection *conn, + struct mail_user *user, + const struct stats *stats); +void mail_stats_connection_send(struct stats_connection *conn, const string_t *str); + +#endif diff --git a/src/plugins/old-stats/mail-stats-fill.c b/src/plugins/old-stats/mail-stats-fill.c new file mode 100644 index 0000000..10d8c39 --- /dev/null +++ b/src/plugins/old-stats/mail-stats-fill.c @@ -0,0 +1,156 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "time-util.h" +#include "stats-plugin.h" +#include "mail-stats.h" + +#include <sys/resource.h> + +#define PROC_IO_PATH "/proc/self/io" + +static bool proc_io_disabled = FALSE; +static int proc_io_fd = -1; + +static int +process_io_buffer_parse(const char *buf, struct mail_stats *stats) +{ + const char *const *tmp; + + tmp = t_strsplit(buf, "\n"); + for (; *tmp != NULL; tmp++) { + if (str_begins(*tmp, "rchar: ")) { + if (str_to_uint64(*tmp + 7, &stats->read_bytes) < 0) + return -1; + } else if (str_begins(*tmp, "wchar: ")) { + if (str_to_uint64(*tmp + 7, &stats->write_bytes) < 0) + return -1; + } else if (str_begins(*tmp, "syscr: ")) { + if (str_to_uint32(*tmp + 7, &stats->read_count) < 0) + return -1; + } else if (str_begins(*tmp, "syscw: ")) { + if (str_to_uint32(*tmp + 7, &stats->write_count) < 0) + return -1; + } + } + return 0; +} + +static int process_io_open(void) +{ + uid_t uid; + + if (proc_io_fd != -1) + return proc_io_fd; + + if (proc_io_disabled) + return -1; + + proc_io_fd = open(PROC_IO_PATH, O_RDONLY); + if (proc_io_fd == -1 && errno == EACCES) { + /* kludge: if we're running with permissions temporarily + dropped, get them temporarily back so we can open + /proc/self/io. */ + uid = geteuid(); + if (seteuid(0) == 0) { + proc_io_fd = open(PROC_IO_PATH, O_RDONLY); + if (seteuid(uid) < 0) { + /* oops, this is bad */ + i_fatal("stats: seteuid(%s) failed", dec2str(uid)); + } + } + errno = EACCES; + } + if (proc_io_fd == -1) { + /* ignore access errors too, certain security options can + prevent root access to this file when not owned by root */ + if (errno != ENOENT && errno != EACCES) + i_error("open(%s) failed: %m", PROC_IO_PATH); + proc_io_disabled = TRUE; + return -1; + } + return proc_io_fd; +} + +static void process_read_io_stats(struct mail_stats *stats) +{ + char buf[1024]; + int fd, ret; + + if ((fd = process_io_open()) == -1) + return; + + ret = pread(fd, buf, sizeof(buf), 0); + if (ret <= 0) { + if (ret == -1) + i_error("read(%s) failed: %m", PROC_IO_PATH); + else + i_error("read(%s) returned EOF", PROC_IO_PATH); + } else if (ret == sizeof(buf)) { + /* just shouldn't happen.. */ + i_error("%s is larger than expected", PROC_IO_PATH); + proc_io_disabled = TRUE; + } else { + buf[ret] = '\0'; + T_BEGIN { + if (process_io_buffer_parse(buf, stats) < 0) { + i_error("Invalid input in file %s", + PROC_IO_PATH); + proc_io_disabled = TRUE; + } + } T_END; + } +} + +static void +user_trans_stats_get(struct stats_user *suser, struct mail_stats *dest_r) +{ + struct stats_transaction_context *strans; + + mail_stats_add_transaction(dest_r, &suser->finished_transaction_stats); + for (strans = suser->transactions; strans != NULL; strans = strans->next) + mail_stats_add_transaction(dest_r, &strans->trans->stats); +} + +void mail_stats_fill(struct stats_user *suser, struct mail_stats *stats_r) +{ + static bool getrusage_broken = FALSE; + static struct rusage prev_usage; + struct rusage usage; + + i_zero(stats_r); + /* cputime */ + if (getrusage(RUSAGE_SELF, &usage) < 0) { + if (!getrusage_broken) { + i_error("getrusage() failed: %m"); + getrusage_broken = TRUE; + } + usage = prev_usage; + } else if (timeval_diff_usecs(&usage.ru_stime, &prev_usage.ru_stime) < 0) { + /* This seems to be a Linux bug. */ + usage.ru_stime = prev_usage.ru_stime; + } + prev_usage = usage; + + stats_r->user_cpu = usage.ru_utime; + stats_r->sys_cpu = usage.ru_stime; + stats_r->min_faults = usage.ru_minflt; + stats_r->maj_faults = usage.ru_majflt; + stats_r->vol_cs = usage.ru_nvcsw; + stats_r->invol_cs = usage.ru_nivcsw; + stats_r->disk_input = (unsigned long long)usage.ru_inblock * 512ULL; + stats_r->disk_output = (unsigned long long)usage.ru_oublock * 512ULL; + i_gettimeofday(&stats_r->clock_time); + process_read_io_stats(stats_r); + user_trans_stats_get(suser, stats_r); +} + +void mail_stats_global_preinit(void) +{ + (void)process_io_open(); +} + +void mail_stats_fill_global_deinit(void) +{ + i_close_fd(&proc_io_fd); +} diff --git a/src/plugins/old-stats/mail-stats.c b/src/plugins/old-stats/mail-stats.c new file mode 100644 index 0000000..3662d23 --- /dev/null +++ b/src/plugins/old-stats/mail-stats.c @@ -0,0 +1,168 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "time-util.h" +#include "stats.h" +#include "stats-parser.h" +#include "mail-stats.h" + +static struct stats_parser_field mail_stats_fields[] = { +#define E(parsename, name, type) { parsename, offsetof(struct mail_stats, name), sizeof(((struct mail_stats *)0)->name), type } +#define EN(parsename, name) E(parsename, name, STATS_PARSER_TYPE_UINT) + E("user_cpu", user_cpu, STATS_PARSER_TYPE_TIMEVAL), + E("sys_cpu", sys_cpu, STATS_PARSER_TYPE_TIMEVAL), + E("clock_time", clock_time, STATS_PARSER_TYPE_TIMEVAL), + EN("min_faults", min_faults), + EN("maj_faults", maj_faults), + EN("vol_cs", vol_cs), + EN("invol_cs", invol_cs), + EN("disk_input", disk_input), + EN("disk_output", disk_output), + + EN("read_count", read_count), + EN("read_bytes", read_bytes), + EN("write_count", write_count), + EN("write_bytes", write_bytes), + + /*EN("mopen", trans_stats.open_lookup_count), + EN("mstat", trans_stats.stat_lookup_count), + EN("mfstat", trans_stats.fstat_lookup_count),*/ + EN("mail_lookup_path", trans_lookup_path), + EN("mail_lookup_attr", trans_lookup_attr), + EN("mail_read_count", trans_files_read_count), + EN("mail_read_bytes", trans_files_read_bytes), + EN("mail_cache_hits", trans_cache_hit_count) +}; + +static size_t mail_stats_alloc_size(void) +{ + return sizeof(struct mail_stats); +} + +static unsigned int mail_stats_field_count(void) +{ + return N_ELEMENTS(mail_stats_fields); +} + +static const char *mail_stats_field_name(unsigned int n) +{ + i_assert(n < N_ELEMENTS(mail_stats_fields)); + + return mail_stats_fields[n].name; +} + +static void +mail_stats_field_value(string_t *str, const struct stats *stats, + unsigned int n) +{ + i_assert(n < N_ELEMENTS(mail_stats_fields)); + + stats_parser_value(str, &mail_stats_fields[n], stats); +} + +static bool +mail_stats_diff(const struct stats *stats1, const struct stats *stats2, + struct stats *diff_stats_r, const char **error_r) +{ + return stats_parser_diff(mail_stats_fields, N_ELEMENTS(mail_stats_fields), + stats1, stats2, diff_stats_r, error_r); +} + +static void mail_stats_add(struct stats *dest, const struct stats *src) +{ + stats_parser_add(mail_stats_fields, N_ELEMENTS(mail_stats_fields), + dest, src); +} + +static bool +mail_stats_have_changed(const struct stats *_prev, const struct stats *_cur) +{ + const struct mail_stats *prev = (const struct mail_stats *)_prev; + const struct mail_stats *cur = (const struct mail_stats *)_cur; + + if (cur->disk_input != prev->disk_input || + cur->disk_output != prev->disk_output || + cur->trans_lookup_path != prev->trans_lookup_path || + cur->trans_lookup_attr != prev->trans_lookup_attr || + cur->trans_files_read_count != prev->trans_files_read_count || + cur->trans_files_read_bytes != prev->trans_files_read_bytes || + cur->trans_cache_hit_count != prev->trans_cache_hit_count) + return TRUE; + + /* allow a tiny bit of changes that are caused by this + timeout handling */ + if (timeval_diff_msecs(&cur->user_cpu, &prev->user_cpu) != 0) + return TRUE; + if (timeval_diff_msecs(&cur->sys_cpu, &prev->sys_cpu) != 0) + return TRUE; + + if (cur->maj_faults > prev->maj_faults+10) + return TRUE; + if (cur->invol_cs > prev->invol_cs+10) + return TRUE; + /* don't check for read/write count/bytes changes, since they get + changed by stats checking itself */ + return FALSE; +} + +static void mail_stats_export(buffer_t *buf, const struct stats *_stats) +{ + const struct mail_stats *stats = (const struct mail_stats *)_stats; + + buffer_append(buf, stats, sizeof(*stats)); +} + +static bool +mail_stats_import(const unsigned char *data, size_t size, size_t *pos_r, + struct stats *_stats, const char **error_r) +{ + struct mail_stats *stats = (struct mail_stats *)_stats; + + if (size < sizeof(*stats)) { + *error_r = "mail_stats too small"; + return FALSE; + } + memcpy(stats, data, sizeof(*stats)); + *pos_r = sizeof(*stats); + return TRUE; +} + +void mail_stats_add_transaction(struct mail_stats *stats, + const struct mailbox_transaction_stats *trans_stats) +{ + stats->trans_lookup_path += trans_stats->open_lookup_count; + stats->trans_lookup_attr += trans_stats->stat_lookup_count + + trans_stats->fstat_lookup_count; + stats->trans_files_read_count += trans_stats->files_read_count; + stats->trans_files_read_bytes += trans_stats->files_read_bytes; + stats->trans_cache_hit_count += trans_stats->cache_hit_count; +} + +const struct stats_vfuncs mail_stats_vfuncs = { + "mail", + mail_stats_alloc_size, + mail_stats_field_count, + mail_stats_field_name, + mail_stats_field_value, + mail_stats_diff, + mail_stats_add, + mail_stats_have_changed, + mail_stats_export, + mail_stats_import +}; + +/* for the stats_mail plugin: */ +void old_stats_mail_init(void); +void old_stats_mail_deinit(void); + +static struct stats_item *mail_stats_item; + +void old_stats_mail_init(void) +{ + mail_stats_item = stats_register(&mail_stats_vfuncs); +} + +void old_stats_mail_deinit(void) +{ + stats_unregister(&mail_stats_item); +} diff --git a/src/plugins/old-stats/mail-stats.h b/src/plugins/old-stats/mail-stats.h new file mode 100644 index 0000000..7254b69 --- /dev/null +++ b/src/plugins/old-stats/mail-stats.h @@ -0,0 +1,41 @@ +#ifndef MAIL_STATS_H +#define MAIL_STATS_H + +#include <sys/time.h> +#include "mail-storage-private.h" + +struct stats_user; + +struct mail_stats { + /* user/system CPU time used */ + struct timeval user_cpu, sys_cpu; + /* clock time used (not counting the time in ioloop wait) */ + struct timeval clock_time; + /* minor / major page faults */ + uint32_t min_faults, maj_faults; + /* voluntary / involuntary context switches */ + uint32_t vol_cs, invol_cs; + /* disk input/output bytes */ + uint64_t disk_input, disk_output; + /* read()/write() syscall count and number of bytes */ + uint32_t read_count, write_count; + uint64_t read_bytes, write_bytes; + + /* based on struct mailbox_transaction_stats: */ + uint32_t trans_lookup_path; + uint32_t trans_lookup_attr; + uint32_t trans_files_read_count; + uint64_t trans_files_read_bytes; + uint64_t trans_cache_hit_count; +}; + +extern const struct stats_vfuncs mail_stats_vfuncs; + +void mail_stats_fill(struct stats_user *suser, struct mail_stats *mail_stats); +void mail_stats_add_transaction(struct mail_stats *stats, + const struct mailbox_transaction_stats *trans_stats); + +void mail_stats_global_preinit(void); +void mail_stats_fill_global_deinit(void); + +#endif diff --git a/src/plugins/old-stats/stats-plugin.c b/src/plugins/old-stats/stats-plugin.c new file mode 100644 index 0000000..9d33016 --- /dev/null +++ b/src/plugins/old-stats/stats-plugin.c @@ -0,0 +1,481 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "llist.h" +#include "str.h" +#include "time-util.h" +#include "settings-parser.h" +#include "mail-stats.h" +#include "stats.h" +#include "mail-stats-connection.h" +#include "stats-plugin.h" + +#define STATS_CONTEXT(obj) \ + MODULE_CONTEXT(obj, stats_storage_module) +#define STATS_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, stats_storage_module) + +/* If session isn't refreshed every 15 minutes, it's dropped. + Must be smaller than MAIL_SESSION_IDLE_TIMEOUT_MSECS in stats server */ +#define SESSION_STATS_FORCE_REFRESH_SECS (5*60) +#define REFRESH_CHECK_INTERVAL 100 +#define MAIL_STATS_FIFO_NAME "old-stats-mail" + +struct stats_storage { + union mail_storage_module_context module_ctx; + + struct mail_storage_callbacks old_callbacks; + void *old_context; +}; + +struct stats_mailbox { + union mailbox_module_context module_ctx; +}; + +const char *stats_plugin_version = DOVECOT_ABI_VERSION; + +struct stats_user_module stats_user_module = + MODULE_CONTEXT_INIT(&mail_user_module_register); +struct stats_storage_module stats_storage_module = + MODULE_CONTEXT_INIT(&mail_storage_module_register); + +static struct stats_item *mail_stats_item; +static struct stats_connection *global_stats_conn = NULL; +static struct mail_user *stats_global_user = NULL; +static unsigned int stats_user_count = 0; + +static void session_stats_refresh_timeout(struct mail_user *user); + +static void stats_io_activate(struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + struct mail_stats *mail_stats; + + if (stats_user_count == 1) { + /* the first user sets the global user. the second user sets + it to NULL. when we get back to one user we'll need to set + the global user again somewhere. do it here. */ + stats_global_user = user; + /* skip time spent waiting in ioloop */ + mail_stats = stats_fill_ptr(suser->pre_io_stats, mail_stats_item); + mail_stats->clock_time = ioloop_timeval; + } else { + i_assert(stats_global_user == NULL); + + mail_user_stats_fill(user, suser->pre_io_stats); + } +} + +static void stats_add_session(struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + struct stats *new_stats, *diff_stats; + const char *error; + + new_stats = stats_alloc(pool_datastack_create()); + diff_stats = stats_alloc(pool_datastack_create()); + + mail_user_stats_fill(user, new_stats); + /* we'll count new_stats-pre_io_stats and add the changes to + session_stats. the new_stats can't be directly copied to + session_stats because there are some fields that don't start from + zero, like clock_time. (actually with stats_global_user code we're + requiring that clock_time is the only such field..) */ + if (!stats_diff(suser->pre_io_stats, new_stats, diff_stats, &error)) + i_error("stats: session stats shrank: %s", error); + stats_add(suser->session_stats, diff_stats); + /* copying is only needed if stats_global_user=NULL */ + stats_copy(suser->pre_io_stats, new_stats); +} + +static bool +session_stats_need_send(struct stats_user *suser, time_t now, + bool *changed_r, unsigned int *to_next_secs_r) +{ + unsigned int diff; + + *to_next_secs_r = SESSION_STATS_FORCE_REFRESH_SECS; + + if (stats_have_changed(suser->last_sent_session_stats, + suser->session_stats)) { + *to_next_secs_r = suser->refresh_secs; + *changed_r = TRUE; + return TRUE; + } + *changed_r = FALSE; + + diff = now - suser->last_session_update; + if (diff >= SESSION_STATS_FORCE_REFRESH_SECS) + return TRUE; + *to_next_secs_r = SESSION_STATS_FORCE_REFRESH_SECS - diff; + + if (!suser->session_sent_duplicate) { + if (suser->last_session_update != now) { + /* send one duplicate notification so stats reader + knows that this session is idle now */ + return TRUE; + } + } + return FALSE; +} + +static void session_stats_refresh(struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + unsigned int to_next_secs; + time_t now = time(NULL); + bool changed; + + if (!suser->stats_connected) { + if (mail_stats_connection_connect(suser->stats_conn, user) == 0) + suser->stats_connected = TRUE; + } + if (session_stats_need_send(suser, now, &changed, &to_next_secs) && + suser->stats_connected) { + suser->session_sent_duplicate = !changed; + suser->last_session_update = now; + stats_copy(suser->last_sent_session_stats, suser->session_stats); + mail_stats_connection_send_session(suser->stats_conn, user, + suser->session_stats); + } + + timeout_remove(&suser->to_stats_timeout); + suser->to_stats_timeout = + timeout_add(to_next_secs*1000, + session_stats_refresh_timeout, user); +} + +static struct mailbox_transaction_context * +stats_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(box->storage->user); + struct stats_mailbox *sbox = STATS_CONTEXT_REQUIRE(box); + struct mailbox_transaction_context *trans; + struct stats_transaction_context *strans; + + trans = sbox->module_ctx.super.transaction_begin(box, flags, reason); + trans->stats_track = TRUE; + + strans = i_new(struct stats_transaction_context, 1); + strans->trans = trans; + DLLIST_PREPEND(&suser->transactions, strans); + + MODULE_CONTEXT_SET(trans, stats_storage_module, strans); + return trans; +} + +static void stats_transaction_free(struct stats_user *suser, + struct stats_transaction_context *strans) +{ + const struct mailbox_transaction_stats *src = &strans->trans->stats; + struct mailbox_transaction_stats *dest = + &suser->finished_transaction_stats; + + DLLIST_REMOVE(&suser->transactions, strans); + + dest->open_lookup_count += src->open_lookup_count; + dest->stat_lookup_count += src->stat_lookup_count; + dest->fstat_lookup_count += src->fstat_lookup_count; + dest->files_read_count += src->files_read_count; + dest->files_read_bytes += src->files_read_bytes; + dest->cache_hit_count += src->cache_hit_count; + i_free(strans); +} + +static int +stats_transaction_commit(struct mailbox_transaction_context *ctx, + struct mail_transaction_commit_changes *changes_r) +{ + struct stats_transaction_context *strans = STATS_CONTEXT_REQUIRE(ctx); + struct stats_mailbox *sbox = STATS_CONTEXT_REQUIRE(ctx->box); + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(ctx->box->storage->user); + + stats_transaction_free(suser, strans); + return sbox->module_ctx.super.transaction_commit(ctx, changes_r); +} + +static void +stats_transaction_rollback(struct mailbox_transaction_context *ctx) +{ + struct stats_transaction_context *strans = STATS_CONTEXT_REQUIRE(ctx); + struct stats_mailbox *sbox = STATS_CONTEXT_REQUIRE(ctx->box); + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(ctx->box->storage->user); + + stats_transaction_free(suser, strans); + sbox->module_ctx.super.transaction_rollback(ctx); +} + +static bool stats_search_next_nonblock(struct mail_search_context *ctx, + struct mail **mail_r, bool *tryagain_r) +{ + struct stats_mailbox *sbox = STATS_CONTEXT_REQUIRE(ctx->transaction->box); + struct mail_user *user = ctx->transaction->box->storage->user; + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + bool ret; + + ret = sbox->module_ctx.super. + search_next_nonblock(ctx, mail_r, tryagain_r); + if (!ret && !*tryagain_r) { + /* end of search */ + return FALSE; + } + + if (*tryagain_r || + ++suser->refresh_check_counter % REFRESH_CHECK_INTERVAL == 0) { + /* a) retrying, so this is a long running search. + b) we've returned enough matches */ + if (time(NULL) != suser->last_session_update) + session_stats_refresh(user); + } + return ret; +} + +static void +stats_notify_ok(struct mailbox *box, const char *text, void *context) +{ + struct stats_storage *sstorage = STATS_CONTEXT_REQUIRE(box->storage); + + /* most importantly we want to refresh stats for very long running + mailbox syncs */ + session_stats_refresh(box->storage->user); + + if (sstorage->old_callbacks.notify_ok != NULL) + sstorage->old_callbacks.notify_ok(box, text, context); +} + +static void stats_register_notify_callbacks(struct mail_storage *storage) +{ + struct stats_storage *sstorage = STATS_CONTEXT(storage); + + if (sstorage != NULL) + return; + + sstorage = p_new(storage->pool, struct stats_storage, 1); + sstorage->old_callbacks = storage->callbacks; + storage->callbacks.notify_ok = stats_notify_ok; + + MODULE_CONTEXT_SET(storage, stats_storage_module, sstorage); +} + +static void stats_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct stats_mailbox *sbox; + struct stats_user *suser = STATS_USER_CONTEXT(box->storage->user); + + if (suser == NULL) + return; + + stats_register_notify_callbacks(box->storage); + + sbox = p_new(box->pool, struct stats_mailbox, 1); + sbox->module_ctx.super = *v; + box->vlast = &sbox->module_ctx.super; + + v->transaction_begin = stats_transaction_begin; + v->transaction_commit = stats_transaction_commit; + v->transaction_rollback = stats_transaction_rollback; + v->search_next_nonblock = stats_search_next_nonblock; + MODULE_CONTEXT_SET(box, stats_storage_module, sbox); +} + +static void session_stats_refresh_timeout(struct mail_user *user) +{ + if (stats_global_user != NULL) + stats_add_session(user); + session_stats_refresh(user); +} + +static void stats_io_deactivate(struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + unsigned int last_update_secs; + + if (stats_global_user == NULL) + stats_add_session(user); + + last_update_secs = time(NULL) - suser->last_session_update; + if (last_update_secs >= suser->refresh_secs) { + if (stats_global_user != NULL) + stats_add_session(user); + session_stats_refresh(user); + } else if (suser->to_stats_timeout == NULL) { + suser->to_stats_timeout = + timeout_add(suser->refresh_secs*1000, + session_stats_refresh_timeout, user); + } +} + +static void stats_user_stats_fill(struct mail_user *user, struct stats *stats) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + struct mail_stats *mail_stats; + + mail_stats = stats_fill_ptr(stats, mail_stats_item); + mail_stats_fill(suser, mail_stats); + + suser->module_ctx.super.stats_fill(user, stats); +} + +static void stats_user_deinit(struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT_REQUIRE(user); + struct stats_connection *stats_conn = suser->stats_conn; + + i_assert(stats_user_count > 0); + + stats_user_count--; + if (stats_global_user != NULL) { + /* we were updating the session lazily. do one final update. */ + i_assert(stats_global_user == user); + stats_add_session(user); + stats_global_user = NULL; + } + + io_loop_context_remove_callbacks(suser->ioloop_ctx, + stats_io_activate, + stats_io_deactivate, user); + /* send final stats before disconnection */ + session_stats_refresh(user); + if (suser->stats_connected) + mail_stats_connection_disconnect(stats_conn, user); + + timeout_remove(&suser->to_stats_timeout); + suser->module_ctx.super.deinit(user); + + stats_connection_unref(&stats_conn); +} + +static void stats_user_created(struct mail_user *user) +{ + struct ioloop_context *ioloop_ctx = + io_loop_get_current_context(current_ioloop); + struct stats_user *suser; + struct mail_user_vfuncs *v = user->vlast; + const char *path, *str, *error; + unsigned int refresh_secs; + + if (ioloop_ctx == NULL) { + /* we're probably running some test program, or at least + mail-storage-service wasn't used to create this user. + disable stats tracking. */ + return; + } + if (user->autocreated) { + /* lda / shared user. we're not tracking this one. */ + return; + } + + /* get refresh time */ + str = mail_user_plugin_getenv(user, "old_stats_refresh"); + if (str == NULL) + return; + if (settings_get_time(str, &refresh_secs, &error) < 0) { + i_error("stats: Invalid old_stats_refresh setting: %s", error); + return; + } + if (refresh_secs == 0) + return; + if (refresh_secs > SESSION_STATS_FORCE_REFRESH_SECS) { + i_warning("stats: stats_refresh too large, changing to %u", + SESSION_STATS_FORCE_REFRESH_SECS); + refresh_secs = SESSION_STATS_FORCE_REFRESH_SECS; + } + + if (global_stats_conn == NULL) { + path = mail_user_plugin_getenv(user, "old_stats_notify_path"); + if (path == NULL) + path = MAIL_STATS_FIFO_NAME; + if (path[0] != '/') + path = t_strconcat(user->set->base_dir, "/", path, NULL); + global_stats_conn = stats_connection_create(path); + } + stats_connection_ref(global_stats_conn); + + if (stats_user_count == 0) { + /* first user connection */ + stats_global_user = user; + } else if (stats_user_count == 1) { + /* second user connection. we'll need to start doing + per-io callback tracking now. (we might have been doing it + also previously but just temporarily quickly dropped to + having 1 user, in which case stats_global_user=NULL) */ + if (stats_global_user != NULL) { + stats_add_session(stats_global_user); + stats_global_user = NULL; + } + } + stats_user_count++; + + suser = p_new(user->pool, struct stats_user, 1); + suser->module_ctx.super = *v; + user->vlast = &suser->module_ctx.super; + v->deinit = stats_user_deinit; + v->stats_fill = stats_user_stats_fill; + + suser->refresh_secs = refresh_secs; + if (mail_user_plugin_getenv_bool(user, "old_stats_track_cmds")) + suser->track_commands = TRUE; + + suser->stats_conn = global_stats_conn; + if (user->session_id != NULL && user->session_id[0] != '\0') + suser->stats_session_id = user->session_id; + else { + guid_128_t guid; + + guid_128_generate(guid); + suser->stats_session_id = + p_strdup(user->pool, guid_128_to_string(guid)); + } + suser->last_session_update = time(NULL); + user->stats_enabled = TRUE; + + suser->ioloop_ctx = ioloop_ctx; + io_loop_context_add_callbacks(ioloop_ctx, + stats_io_activate, + stats_io_deactivate, user); + + suser->pre_io_stats = stats_alloc(user->pool); + suser->session_stats = stats_alloc(user->pool); + suser->last_sent_session_stats = stats_alloc(user->pool); + + MODULE_CONTEXT_SET(user, stats_user_module, suser); + if (mail_stats_connection_connect(suser->stats_conn, user) == 0) + suser->stats_connected = TRUE; + suser->to_stats_timeout = + timeout_add(suser->refresh_secs*1000, + session_stats_refresh_timeout, user); + /* fill the initial values. this is necessary for the process-global + values (e.g. getrusage()) if the process is reused for multiple + users. otherwise the next user will start with the previous one's + last values. */ + mail_user_stats_fill(user, suser->pre_io_stats); +} + +static struct mail_storage_hooks stats_mail_storage_hooks = { + .mailbox_allocated = stats_mailbox_allocated, + .mail_user_created = stats_user_created +}; + +void old_stats_plugin_init(struct module *module) +{ + mail_stats_item = stats_register(&mail_stats_vfuncs); + mail_storage_hooks_add(module, &stats_mail_storage_hooks); +} + +void old_stats_plugin_preinit(void) +{ + mail_stats_global_preinit(); +} + +void old_stats_plugin_deinit(void) +{ + if (global_stats_conn != NULL) + stats_connection_unref(&global_stats_conn); + mail_stats_fill_global_deinit(); + mail_storage_hooks_remove(&stats_mail_storage_hooks); + stats_unregister(&mail_stats_item); +} diff --git a/src/plugins/old-stats/stats-plugin.h b/src/plugins/old-stats/stats-plugin.h new file mode 100644 index 0000000..dedcfbd --- /dev/null +++ b/src/plugins/old-stats/stats-plugin.h @@ -0,0 +1,60 @@ +#ifndef STATS_PLUGIN_H +#define STATS_PLUGIN_H + +#include "module-context.h" +#include "mail-user.h" +#include "mail-storage-private.h" + +#define STATS_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, stats_user_module) + +#define STATS_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, stats_user_module) + +struct stats_user { + union mail_user_module_context module_ctx; + + struct ioloop_context *ioloop_ctx; + struct stats_connection *stats_conn; + const char *stats_session_id; + bool stats_connected; + + unsigned int refresh_secs; + bool track_commands; + unsigned int refresh_check_counter; + + /* current session statistics */ + struct stats *session_stats; + /* cumulative trans_stats for all already freed transactions. */ + struct mailbox_transaction_stats finished_transaction_stats; + /* stats before calling IO callback. after IO callback this value is + compared to current stats to see the difference */ + struct stats *pre_io_stats; + + time_t last_session_update; + struct timeout *to_stats_timeout; + /* stats that were last sent to stats server */ + struct stats *last_sent_session_stats; + bool session_sent_duplicate; + + /* list of all currently existing transactions for this user */ + struct stats_transaction_context *transactions; +}; + +struct stats_transaction_context { + union mailbox_transaction_module_context module_ctx; + + struct stats_transaction_context *prev, *next; + struct mailbox_transaction_context *trans; + + struct mailbox_transaction_stats prev_stats; +}; + +extern MODULE_CONTEXT_DEFINE(stats_user_module, &mail_user_module_register); +extern MODULE_CONTEXT_DEFINE(stats_storage_module, &mail_storage_module_register); + +void old_stats_plugin_init(struct module *module); +void old_stats_plugin_preinit(void); +void old_stats_plugin_deinit(void); + +#endif diff --git a/src/plugins/pop3-migration/Makefile.am b/src/plugins/pop3-migration/Makefile.am new file mode 100644 index 0000000..f58d065 --- /dev/null +++ b/src/plugins/pop3-migration/Makefile.am @@ -0,0 +1,41 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index + +NOPLUGIN_LDFLAGS = +lib05_pop3_migration_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib05_pop3_migration_plugin.la + +lib05_pop3_migration_plugin_la_SOURCES = \ + pop3-migration-plugin.c + +noinst_HEADERS = \ + pop3-migration-plugin.h + +noinst_PROGRAMS = $(test_programs) + +test_programs = \ + test-pop3-migration-plugin + +test_libs = \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) +test_deps = \ + $(module_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +test_pop3_migration_plugin_SOURCES = test-pop3-migration-plugin.c +test_pop3_migration_plugin_LDADD = pop3-migration-plugin.lo $(test_libs) +test_pop3_migration_plugin_DEPENDENCIES = $(test_deps) + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/plugins/pop3-migration/Makefile.in b/src/plugins/pop3-migration/Makefile.in new file mode 100644 index 0000000..70a8e00 --- /dev/null +++ b/src/plugins/pop3-migration/Makefile.in @@ -0,0 +1,872 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/plugins/pop3-migration +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-pop3-migration-plugin$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib05_pop3_migration_plugin_la_LIBADD = +am_lib05_pop3_migration_plugin_la_OBJECTS = pop3-migration-plugin.lo +lib05_pop3_migration_plugin_la_OBJECTS = \ + $(am_lib05_pop3_migration_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib05_pop3_migration_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(lib05_pop3_migration_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +am_test_pop3_migration_plugin_OBJECTS = \ + test-pop3-migration-plugin.$(OBJEXT) +test_pop3_migration_plugin_OBJECTS = \ + $(am_test_pop3_migration_plugin_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/pop3-migration-plugin.Plo \ + ./$(DEPDIR)/test-pop3-migration-plugin.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib05_pop3_migration_plugin_la_SOURCES) \ + $(test_pop3_migration_plugin_SOURCES) +DIST_SOURCES = $(lib05_pop3_migration_plugin_la_SOURCES) \ + $(test_pop3_migration_plugin_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index + +lib05_pop3_migration_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib05_pop3_migration_plugin.la + +lib05_pop3_migration_plugin_la_SOURCES = \ + pop3-migration-plugin.c + +noinst_HEADERS = \ + pop3-migration-plugin.h + +test_programs = \ + test-pop3-migration-plugin + +test_libs = \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) + +test_deps = \ + $(module_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +test_pop3_migration_plugin_SOURCES = test-pop3-migration-plugin.c +test_pop3_migration_plugin_LDADD = pop3-migration-plugin.lo $(test_libs) +test_pop3_migration_plugin_DEPENDENCIES = $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/pop3-migration/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/pop3-migration/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib05_pop3_migration_plugin.la: $(lib05_pop3_migration_plugin_la_OBJECTS) $(lib05_pop3_migration_plugin_la_DEPENDENCIES) $(EXTRA_lib05_pop3_migration_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib05_pop3_migration_plugin_la_LINK) -rpath $(moduledir) $(lib05_pop3_migration_plugin_la_OBJECTS) $(lib05_pop3_migration_plugin_la_LIBADD) $(LIBS) + +test-pop3-migration-plugin$(EXEEXT): $(test_pop3_migration_plugin_OBJECTS) $(test_pop3_migration_plugin_DEPENDENCIES) $(EXTRA_test_pop3_migration_plugin_DEPENDENCIES) + @rm -f test-pop3-migration-plugin$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_pop3_migration_plugin_OBJECTS) $(test_pop3_migration_plugin_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop3-migration-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-pop3-migration-plugin.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/pop3-migration-plugin.Plo + -rm -f ./$(DEPDIR)/test-pop3-migration-plugin.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/pop3-migration-plugin.Plo + -rm -f ./$(DEPDIR)/test-pop3-migration-plugin.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-moduleLTLIBRARIES \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/pop3-migration/pop3-migration-plugin.c b/src/plugins/pop3-migration/pop3-migration-plugin.c new file mode 100644 index 0000000..362597a --- /dev/null +++ b/src/plugins/pop3-migration/pop3-migration-plugin.c @@ -0,0 +1,1058 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "istream-header-filter.h" +#include "str.h" +#include "sha1.h" +#include "message-size.h" +#include "message-header-hash.h" +#include "message-header-parser.h" +#include "mail-cache.h" +#include "mail-namespace.h" +#include "mail-search-build.h" +#include "index-storage.h" +#include "index-mail.h" +#include "pop3-migration-plugin.h" + +#define POP3_MIGRATION_CONTEXT(obj) \ + MODULE_CONTEXT(obj, pop3_migration_storage_module) +#define POP3_MIGRATION_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, pop3_migration_storage_module) +#define POP3_MIGRATION_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, pop3_migration_mail_module) + +struct msg_map_common { + /* sha1(header) - set only when needed */ + unsigned char hdr_sha1[SHA1_RESULTLEN]; + bool hdr_sha1_set:1; +}; + +struct pop3_uidl_map { + struct msg_map_common common; + + uint32_t pop3_seq; + uint32_t imap_uid; + + /* UIDL */ + const char *pop3_uidl; + /* LIST size */ + uoff_t size; +}; + +struct imap_msg_map { + struct msg_map_common common; + + uint32_t uid, pop3_seq; + uoff_t psize; + const char *pop3_uidl; +}; + +struct pop3_migration_mail_storage { + union mail_storage_module_context module_ctx; + + const char *pop3_box_vname; + ARRAY(struct pop3_uidl_map) pop3_uidl_map; + + bool all_mailboxes:1; + bool pop3_all_hdr_sha1_set:1; + bool ignore_missing_uidls:1; + bool ignore_extra_uidls:1; + bool skip_size_check:1; + bool skip_uidl_cache:1; +}; + +struct pop3_migration_mailbox { + union mailbox_module_context module_ctx; + + ARRAY(struct imap_msg_map) imap_msg_map; + unsigned int first_unfound_idx; + + struct mail_cache_field cache_field; + + bool cache_field_registered:1; + bool uidl_synced:1; + bool uidl_sync_failed:1; +}; + +/* NOTE: these headers must be sorted */ +static const char *hdr_hash_skip_headers[] = { + "Content-Length", + "Return-Path", /* Yahoo IMAP has Return-Path, Yahoo POP3 doesn't */ + "Status", + "X-IMAP", + "X-IMAPbase", + "X-Keywords", + "X-Message-Flag", + "X-Status", + "X-UID", + "X-UIDL", + "X-Yahoo-Newman-Property" +}; +const char *pop3_migration_plugin_version = DOVECOT_ABI_VERSION; + +static MODULE_CONTEXT_DEFINE_INIT(pop3_migration_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(pop3_migration_mail_module, + &mail_module_register); + +static int imap_msg_map_uid_cmp(const struct imap_msg_map *map1, + const struct imap_msg_map *map2) +{ + if (map1->uid < map2->uid) + return -1; + if (map1->uid > map2->uid) + return 1; + return 0; +} + +static int pop3_uidl_map_pop3_seq_cmp(const struct pop3_uidl_map *map1, + const struct pop3_uidl_map *map2) +{ + if (map1->pop3_seq < map2->pop3_seq) + return -1; + if (map1->pop3_seq > map2->pop3_seq) + return 1; + return 0; +} + +static int pop3_uidl_map_uidl_cmp(const struct pop3_uidl_map *map1, + const struct pop3_uidl_map *map2) +{ + return strcmp(map1->pop3_uidl, map2->pop3_uidl); +} + +static int imap_msg_map_uidl_cmp(const struct imap_msg_map *map1, + const struct imap_msg_map *map2) +{ + return null_strcmp(map1->pop3_uidl, map2->pop3_uidl); +} + +static int pop3_uidl_map_hdr_cmp(const struct pop3_uidl_map *map1, + const struct pop3_uidl_map *map2) +{ + return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1, + sizeof(map1->common.hdr_sha1)); +} + +static int imap_msg_map_hdr_cmp(const struct imap_msg_map *map1, + const struct imap_msg_map *map2) +{ + return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1, + sizeof(map1->common.hdr_sha1)); +} + +struct pop3_hdr_context { + bool have_eoh; + bool stop; +}; + +static bool header_name_is_valid(const char *name) +{ + unsigned int i; + + for (i = 0; name[i] != '\0'; i++) { + if ((uint8_t)name[i] <= 0x20 || name[i] >= 0x7f) + return FALSE; + } + return TRUE; +} + +static bool header_value_want_skip(const struct message_header_line *hdr) +{ + for (size_t i = 0; i < hdr->value_len; i++) { + if (hdr->value[i] != ' ' && hdr->value[i] != '\t') + return FALSE; + } + /* "header: \r\n \r\n" - Zimbra's BODY[HEADER] strips this line away. */ + return TRUE; +} + +static void +pop3_header_filter_callback(struct header_filter_istream *input ATTR_UNUSED, + struct message_header_line *hdr, + bool *matched, struct pop3_hdr_context *ctx) +{ + if (hdr == NULL) + return; + if (hdr->eoh) { + ctx->have_eoh = TRUE; + if (ctx->stop) + *matched = TRUE; + } else { + if (hdr->value_len > 0 && hdr->middle_len == 0 && hdr->name_len == 0 && + i_memspn(hdr->value, hdr->value_len, "\r", 1U) == hdr->value_len) { + /* CR+CR+LF - some servers stop the header processing + here while others don't. To make sure they can be + matched correctly we want to stop here entirely. */ + ctx->stop = TRUE; + } else if (!hdr->continued && hdr->middle_len == 0) { + /* not a valid "key: value" header - + Zimbra's BODY[HEADER] strips this line away. */ + *matched = TRUE; + } else if (hdr->continued && header_value_want_skip(hdr)) { + *matched = TRUE; + } + if (ctx->stop) + *matched = TRUE; + else if (!header_name_is_valid(hdr->name)) { + /* Yahoo IMAP drops headers with invalid names, while + Yahoo POP3 preserves them. Drop them all. */ + *matched = TRUE; + } + } +} + +int pop3_migration_get_hdr_sha1(uint32_t mail_seq, struct istream *input, + unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN], + bool *have_eoh_r) +{ + const unsigned char *data; + size_t size; + struct message_header_hash_context hash_ctx; + struct sha1_ctxt sha1_ctx; + struct pop3_hdr_context hdr_ctx; + + i_zero(&hdr_ctx); + /* hide headers that might change or be different in IMAP vs. POP3 */ + input = i_stream_create_header_filter(input, HEADER_FILTER_HIDE_BODY | + HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, + hdr_hash_skip_headers, + N_ELEMENTS(hdr_hash_skip_headers), + pop3_header_filter_callback, &hdr_ctx); + + sha1_init(&sha1_ctx); + i_zero(&hash_ctx); + while (i_stream_read_more(input, &data, &size) > 0) { + message_header_hash_more(&hash_ctx, &hash_method_sha1, &sha1_ctx, + MESSAGE_HEADER_HASH_MAX_VERSION, + data, size); + i_stream_skip(input, size); + } + if (input->stream_errno != 0) { + i_error("pop3_migration: Failed to read header for msg %u: %s", + mail_seq, i_stream_get_error(input)); + i_stream_unref(&input); + return -1; + } + sha1_result(&sha1_ctx, sha1_r); + i_stream_unref(&input); + + *have_eoh_r = hdr_ctx.have_eoh; + return 0; +} + +static unsigned int get_cache_idx(struct mail *mail) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(mail->box); + + if (mbox->cache_field_registered) + return mbox->cache_field.idx; + + mbox->cache_field.name = "pop3-migration.hdr"; + mbox->cache_field.type = MAIL_CACHE_FIELD_FIXED_SIZE; + mbox->cache_field.field_size = SHA1_RESULTLEN; + mail_cache_register_fields(mail->box->cache, &mbox->cache_field, 1); + mbox->cache_field_registered = TRUE; + return mbox->cache_field.idx; +} + +static int +get_hdr_sha1(struct mail *mail, unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN]) +{ + struct istream *input; + const char *errstr; + enum mail_error error; + bool have_eoh; + int ret; + + if (mail_get_hdr_stream(mail, NULL, &input) < 0) { + errstr = mailbox_get_last_internal_error(mail->box, &error); + i_error("pop3_migration: Failed to get header for msg %u: %s", + mail->seq, errstr); + return error == MAIL_ERROR_EXPUNGED ? 0 : -1; + } + if (pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh) < 0) + return -1; + if (have_eoh) { + struct index_mail *imail = (struct index_mail *)mail; + + index_mail_cache_add_idx(imail, get_cache_idx(mail), + sha1_r, SHA1_RESULTLEN); + return 1; + } + + /* The empty "end of headers" line is missing. Either this means that + the headers ended unexpectedly (which is ok) or that the remote + server is buggy. Some servers have problems with + + 1) header line continuations that contain only whitespace and + 2) headers that have no ":". The header gets truncated when such + line is reached. + + At least Oracle IMS IMAP FETCH BODY[HEADER] handles 1) by not + returning the whitespace line and 2) by returning the line but + truncating the rest. POP3 TOP instead returns the entire header. + This causes the IMAP and POP3 hashes not to match. + + If there's LF+CR+CR+LF in the middle of headers, Courier IMAP's + FETCH BODY[HEADER] stops after that, but Courier POP3's TOP doesn't. + + So we'll try to avoid this by falling back to full FETCH BODY[] + (and/or RETR) and we'll parse the header ourself from it. This + should work around any similar bugs in all IMAP/POP3 servers. */ + if (mail_get_stream_because(mail, NULL, NULL, "pop3-migration", &input) < 0) { + errstr = mailbox_get_last_internal_error(mail->box, &error); + i_error("pop3_migration: Failed to get body for msg %u: %s", + mail->seq, errstr); + return error == MAIL_ERROR_EXPUNGED ? 0 : -1; + } + ret = pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh); + if (ret == 0) { + if (!have_eoh) + i_warning("pop3_migration: Truncated email with UID %u stored as truncated", mail->uid); + struct index_mail *imail = (struct index_mail *)mail; + index_mail_cache_add_idx(imail, get_cache_idx(mail), + sha1_r, SHA1_RESULTLEN); + return 1; + } else { + return -1; + } +} + +static bool +get_cached_hdr_sha1(struct mail *mail, buffer_t *cache_buf, + unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN]) +{ + struct index_mail *imail = (struct index_mail *)mail; + + buffer_set_used_size(cache_buf, 0); + if (index_mail_cache_lookup_field(imail, cache_buf, + get_cache_idx(mail)) > 0 && + cache_buf->used == SHA1_RESULTLEN) { + memcpy(sha1_r, cache_buf->data, cache_buf->used); + return TRUE; + } + return FALSE; +} + +static struct mailbox *pop3_mailbox_alloc(struct mail_storage *storage) +{ + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(storage); + struct mail_namespace *ns; + struct mailbox *box; + + ns = mail_namespace_find(storage->user->namespaces, + mstorage->pop3_box_vname); + i_assert(ns != NULL); + box = mailbox_alloc(ns->list, mstorage->pop3_box_vname, + MAILBOX_FLAG_READONLY | MAILBOX_FLAG_POP3_SESSION); + return box; +} + +static int pop3_map_read(struct mail_storage *storage, struct mailbox *pop3_box) +{ + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(storage); + struct mailbox_transaction_context *t; + struct mail_search_args *search_args; + struct mail_search_context *ctx; + struct mail *mail; + struct pop3_uidl_map *map; + const char *uidl; + uoff_t size = UOFF_T_MAX; + int ret = 0; + + if (array_is_created(&mstorage->pop3_uidl_map)) { + /* already read these, just reset the imap_uids */ + array_foreach_modifiable(&mstorage->pop3_uidl_map, map) + map->imap_uid = 0; + return 0; + } + i_array_init(&mstorage->pop3_uidl_map, 128); + + if (mailbox_sync(pop3_box, 0) < 0) { + i_error("pop3_migration: Couldn't sync mailbox %s: %s", + pop3_box->vname, mailbox_get_last_internal_error(pop3_box, NULL)); + return -1; + } + + t = mailbox_transaction_begin(pop3_box, 0, __func__); + search_args = mail_search_build_init(); + mail_search_build_add_all(search_args); + ctx = mailbox_search_init(t, search_args, NULL, + mstorage->skip_size_check ? 0 : + MAIL_FETCH_PHYSICAL_SIZE, NULL); + mail_search_args_unref(&search_args); + + while (mailbox_search_next(ctx, &mail)) { + /* get the size with LIST instead of RETR */ + mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL; + + if (mstorage->skip_size_check) + ; + else if (mail_get_physical_size(mail, &size) < 0) { + i_error("pop3_migration: Failed to get size for msg %u: %s", + mail->seq, + mailbox_get_last_internal_error(pop3_box, NULL)); + ret = -1; + break; + } + mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER; + + if (mail_get_special(mail, MAIL_FETCH_UIDL_BACKEND, &uidl) < 0) { + i_error("pop3_migration: Failed to get UIDL for msg %u: %s", + mail->seq, + mailbox_get_last_internal_error(pop3_box, NULL)); + ret = -1; + break; + } + if (*uidl == '\0') { + i_warning("pop3_migration: UIDL for msg %u is empty", + mail->seq); + continue; + } + + map = array_append_space(&mstorage->pop3_uidl_map); + map->pop3_seq = mail->seq; + map->pop3_uidl = p_strdup(storage->pool, uidl); + map->size = size; + } + + if (mailbox_search_deinit(&ctx) < 0) { + i_error("pop3_migration: Failed to search all POP3 mails: %s", + mailbox_get_last_internal_error(pop3_box, NULL)); + ret = -1; + } + (void)mailbox_transaction_commit(&t); + return ret; +} + +static void +pop3_map_read_cached_hdr_hashes(struct mailbox_transaction_context *t, + struct mail_search_args *search_args, + struct array *msg_map) +{ + struct mail_search_context *ctx; + struct mail *mail; + struct msg_map_common *map; + buffer_t *cache_buf; + + ctx = mailbox_search_init(t, search_args, NULL, 0, NULL); + cache_buf = t_buffer_create(SHA1_RESULTLEN); + + while (mailbox_search_next(ctx, &mail)) { + map = array_idx_modifiable_i(msg_map, mail->seq-1); + + if (get_cached_hdr_sha1(mail, cache_buf, map->hdr_sha1)) + map->hdr_sha1_set = TRUE; + } + + if (mailbox_search_deinit(&ctx) < 0) { + i_warning("pop3_migration: Failed to search all cached POP3 header hashes: %s - ignoring", + mailbox_get_last_internal_error(t->box, NULL)); + } +} + +static void map_remove_found_seqs(struct mail_search_arg *search_arg, + struct array *msg_map, uint32_t seq1) +{ + const struct msg_map_common *map; + uint32_t seq, count = array_count_i(msg_map); + + i_assert(search_arg->type == SEARCH_SEQSET); + + for (seq = seq1; seq <= count; seq++) { + map = array_idx_i(msg_map, seq-1); + if (map->hdr_sha1_set) + seq_range_array_remove(&search_arg->value.seqset, seq); + } +} + +static int +map_read_hdr_hashes(struct mailbox *box, struct array *msg_map, uint32_t seq1) +{ + struct mailbox_transaction_context *t; + struct mail_search_args *search_args; + struct mail_search_context *ctx; + struct mail *mail; + struct msg_map_common *map; + int ret = 0; + + t = mailbox_transaction_begin(box, 0, __func__); + /* get all the cached hashes */ + search_args = mail_search_build_init(); + mail_search_build_add_seqset(search_args, seq1, array_count_i(msg_map)); + pop3_map_read_cached_hdr_hashes(t, search_args, msg_map); + /* read all the non-cached hashes. doing this in two passes allows + us to set wanted_fields=MAIL_FETCH_STREAM_HEADER, which allows + prefetching to work without downloading all the headers even + for mails that already are cached. */ + map_remove_found_seqs(search_args->args, msg_map, seq1); + ctx = mailbox_search_init(t, search_args, NULL, + MAIL_FETCH_STREAM_HEADER, NULL); + mail_search_args_unref(&search_args); + + while (mailbox_search_next(ctx, &mail)) { + map = array_idx_modifiable_i(msg_map, mail->seq-1); + + if ((ret = get_hdr_sha1(mail, map->hdr_sha1)) < 0) { + ret = -1; + break; + } + if (ret > 0) + map->hdr_sha1_set = TRUE; + } + + if (mailbox_search_deinit(&ctx) < 0) { + i_error("pop3_migration: Failed to search all mail headers: %s", + mailbox_get_last_internal_error(box, NULL)); + ret = -1; + } + (void)mailbox_transaction_commit(&t); + return ret < 0 ? -1 : 0; +} + +static int +pop3_map_read_hdr_hashes(struct mail_storage *storage, struct mailbox *pop3_box, + unsigned first_seq) +{ + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(storage); + + if (mstorage->pop3_all_hdr_sha1_set) + return 0; + if (mstorage->all_mailboxes) { + /* we may be matching against multiple mailboxes. + read all the hashes only once. */ + first_seq = 1; + } + + if (map_read_hdr_hashes(pop3_box, &mstorage->pop3_uidl_map.arr, + first_seq) < 0) + return -1; + + if (first_seq == 1) + mstorage->pop3_all_hdr_sha1_set = TRUE; + return 0; +} + +static int imap_map_read(struct mailbox *box) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box); + struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box); + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(box->storage); + const unsigned int uidl_cache_idx = + ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx; + struct mailbox_status status; + struct mailbox_transaction_context *t; + struct mail_search_args *search_args; + struct mail_search_context *ctx; + struct mail *mail; + struct imap_msg_map *map; + uoff_t psize = UOFF_T_MAX; + string_t *uidl; + int ret = 0; + + mailbox_get_open_status(box, STATUS_MESSAGES, &status); + + i_assert(!array_is_created(&mbox->imap_msg_map)); + p_array_init(&mbox->imap_msg_map, box->pool, status.messages); + + t = mailbox_transaction_begin(box, 0, __func__); + search_args = mail_search_build_init(); + mail_search_build_add_all(search_args); + ctx = mailbox_search_init(t, search_args, NULL, + mstorage->skip_size_check ? 0 : + MAIL_FETCH_PHYSICAL_SIZE, NULL); + mail_search_args_unref(&search_args); + + uidl = t_str_new(64); + while (mailbox_search_next(ctx, &mail)) { + if (mstorage->skip_size_check) + ; + else if (mail_get_physical_size(mail, &psize) < 0) { + i_error("pop3_migration: Failed to get psize for imap uid %u: %s", + mail->uid, + mailbox_get_last_internal_error(box, NULL)); + ret = -1; + break; + } + + if (!mstorage->skip_uidl_cache) { + str_truncate(uidl, 0); + (void)mail_cache_lookup_field(mail->transaction->cache_view, + uidl, mail->seq, uidl_cache_idx); + } + + map = array_append_space(&mbox->imap_msg_map); + map->uid = mail->uid; + map->psize = psize; + map->pop3_uidl = p_strdup_empty(box->pool, str_c(uidl)); + } + + if (mailbox_search_deinit(&ctx) < 0) { + i_error("pop3_migration: Failed to search all IMAP mails: %s", + mailbox_get_last_internal_error(box, NULL)); + ret = -1; + } + (void)mailbox_transaction_commit(&t); + return ret; +} + +static int imap_map_read_hdr_hashes(struct mailbox *box) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box); + + return map_read_hdr_hashes(box, &mbox->imap_msg_map.arr, + mbox->first_unfound_idx+1); +} + +static void pop3_uidl_assign_cached(struct mailbox *box) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box); + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(box->storage); + struct pop3_uidl_map *pop3_map; + struct imap_msg_map *imap_map; + unsigned int imap_idx, pop3_idx, pop3_count, imap_count; + int ret; + + if (mstorage->skip_uidl_cache) + return; + + array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_uidl_cmp); + array_sort(&mbox->imap_msg_map, imap_msg_map_uidl_cmp); + + pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count); + imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count); + + /* see if we can match the messages using sizes */ + for (imap_idx = pop3_idx = 0; imap_idx < imap_count; imap_idx++) { + if (imap_map[imap_idx].pop3_uidl == NULL) + continue; + + ret = 1; + for (; pop3_idx < pop3_count; pop3_idx++) { + ret = strcmp(imap_map[imap_idx].pop3_uidl, + pop3_map[pop3_idx].pop3_uidl); + if (ret >= 0) + break; + } + if (ret == 0) { + imap_map[imap_idx].pop3_seq = + pop3_map[pop3_idx].pop3_seq; + pop3_map[pop3_idx].imap_uid = imap_map[imap_idx].uid; + } + } +} + +static bool pop3_uidl_assign_by_size(struct mailbox *box) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box); + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(box->storage); + struct pop3_uidl_map *pop3_map; + struct imap_msg_map *imap_map; + unsigned int i, pop3_count, imap_count, count; + unsigned int size_match = 0, uidl_match = 0; + + pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count); + imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count); + count = I_MIN(pop3_count, imap_count); + + /* see if we can match the messages using sizes */ + for (i = 0; i < count; i++) { + if (imap_map[i].pop3_uidl != NULL) { + /* some of the UIDLs were already found cached. */ + if (strcmp(pop3_map[i].pop3_uidl, imap_map[i].pop3_uidl) == 0) { + uidl_match++; + continue; + } + /* mismatch - can't trust the sizes */ + break; + } + + if (pop3_map[i].size != imap_map[i].psize || + mstorage->skip_size_check) + break; + if (i+1 < count && pop3_map[i].size == pop3_map[i+1].size) { + /* two messages with same size, don't trust them */ + break; + } + + size_match++; + pop3_map[i].imap_uid = imap_map[i].uid; + imap_map[i].pop3_uidl = pop3_map[i].pop3_uidl; + imap_map[i].pop3_seq = pop3_map[i].pop3_seq; + } + mbox->first_unfound_idx = i; + e_debug(box->event, "pop3_migration: cached uidls=%u, size matches=%u, total=%u", + uidl_match, size_match, count); + return i == count && imap_count == pop3_count; +} + +static int +pop3_uidl_assign_by_hdr_hash(struct mailbox *box, struct mailbox *pop3_box) +{ + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(box->storage); + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box); + struct pop3_uidl_map *pop3_map; + struct imap_msg_map *imap_map; + unsigned int pop3_idx, imap_idx, pop3_count, imap_count; + unsigned int first_seq, missing_uids_count; + uint32_t first_missing_idx = 0, first_missing_seq = (uint32_t)-1; + int ret; + + first_seq = mbox->first_unfound_idx+1; + if (pop3_map_read_hdr_hashes(box->storage, pop3_box, first_seq) < 0 || + imap_map_read_hdr_hashes(box) < 0) + return -1; + + array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_hdr_cmp); + array_sort(&mbox->imap_msg_map, imap_msg_map_hdr_cmp); + + pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count); + imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count); + + pop3_idx = imap_idx = 0; + while (pop3_idx < pop3_count && imap_idx < imap_count) { + if (!pop3_map[pop3_idx].common.hdr_sha1_set || + pop3_map[pop3_idx].imap_uid != 0) { + pop3_idx++; + continue; + } + if (!imap_map[imap_idx].common.hdr_sha1_set || + imap_map[imap_idx].pop3_uidl != NULL) { + imap_idx++; + continue; + } + ret = memcmp(pop3_map[pop3_idx].common.hdr_sha1, + imap_map[imap_idx].common.hdr_sha1, + sizeof(pop3_map[pop3_idx].common.hdr_sha1)); + if (ret < 0) + pop3_idx++; + else if (ret > 0) + imap_idx++; + else { + pop3_map[pop3_idx].imap_uid = imap_map[imap_idx].uid; + imap_map[imap_idx].pop3_uidl = + pop3_map[pop3_idx].pop3_uidl; + imap_map[imap_idx].pop3_seq = + pop3_map[pop3_idx].pop3_seq; + } + } + missing_uids_count = 0; + for (pop3_idx = 0; pop3_idx < pop3_count; pop3_idx++) { + if (pop3_map[pop3_idx].imap_uid != 0) { + /* matched */ + } else if (!pop3_map[pop3_idx].common.hdr_sha1_set) { + /* we treated this mail as expunged - ignore */ + } else { + uint32_t seq = pop3_map[pop3_idx].pop3_seq; + if (first_missing_seq > seq) { + first_missing_seq = seq; + first_missing_idx = pop3_idx; + } + missing_uids_count++; + } + } + if (missing_uids_count > 0 && !mstorage->all_mailboxes) { + string_t *str = t_str_new(128); + bool all_imap_mails_found = FALSE; + + str_printfa(str, "pop3_migration: %u POP3 messages have no " + "matching IMAP messages (first POP3 msg %u UIDL %s)", + missing_uids_count, first_missing_seq, + pop3_map[first_missing_idx].pop3_uidl); + if (imap_count + missing_uids_count == pop3_count) { + str_append(str, " - all IMAP messages were found " + "(POP3 contains more than IMAP INBOX - you may want to set pop3_migration_all_mailboxes=yes)"); + all_imap_mails_found = TRUE; + } + if (all_imap_mails_found && mstorage->ignore_extra_uidls) { + /* pop3 had more mails than imap. maybe it was just + that a new mail was just delivered. */ + } else if (!mstorage->ignore_missing_uidls) { + str_append(str, " - set pop3_migration_ignore_missing_uidls=yes"); + if (all_imap_mails_found) + str_append(str, " or pop3_migration_ignore_extra_uidls=yes"); + i_error("%s to continue anyway", str_c(str)); + return -1; + } + i_warning("%s", str_c(str)); + } else + e_debug(box->event, "pop3_migration: %u mails matched by headers", pop3_count); + array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_pop3_seq_cmp); + array_sort(&mbox->imap_msg_map, imap_msg_map_uid_cmp); + return 0; +} + +static void imap_uidls_add_to_cache(struct mailbox *box) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box); + struct mailbox_transaction_context *t; + struct mail *mail; + struct index_mail *imail; + struct imap_msg_map *imap_map; + unsigned int i, count; + unsigned int field_idx; + + t = mailbox_transaction_begin(box, 0, __func__); + mail = mail_alloc(t, 0, NULL); + imail = INDEX_MAIL(mail); + field_idx = imail->ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx; + + imap_map = array_get_modifiable(&mbox->imap_msg_map, &count); + for (i = 0; i < count; i++) { + if (imap_map[i].pop3_uidl == NULL) + continue; + + if (!mail_set_uid(mail, imap_map[i].uid)) + i_unreached(); + if (mail_cache_field_can_add(t->cache_trans, mail->seq, field_idx)) { + index_mail_cache_add_idx(imail, field_idx, + imap_map[i].pop3_uidl, strlen(imap_map[i].pop3_uidl)+1); + } + } + mail_free(&mail); + (void)mailbox_transaction_commit(&t); +} + +static int pop3_migration_uidl_sync(struct mailbox *box) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box); + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(box->storage); + struct mailbox *pop3_box; + + pop3_box = pop3_mailbox_alloc(box->storage); + /* the POP3 server isn't connected to yet. handle all IMAP traffic + first before connecting, so POP3 server won't disconnect us due to + idling. */ + if (imap_map_read(box) < 0 || + pop3_map_read(box->storage, pop3_box) < 0) { + mailbox_free(&pop3_box); + return -1; + } + + pop3_uidl_assign_cached(box); + + array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_pop3_seq_cmp); + array_sort(&mbox->imap_msg_map, imap_msg_map_uid_cmp); + + if (!pop3_uidl_assign_by_size(box)) { + /* everything wasn't assigned, figure out the rest with + header hashes */ + if (pop3_uidl_assign_by_hdr_hash(box, pop3_box) < 0) { + mailbox_free(&pop3_box); + return -1; + } + } + + if (!mstorage->skip_uidl_cache) + imap_uidls_add_to_cache(box); + + mbox->uidl_synced = TRUE; + mailbox_free(&pop3_box); + return 0; +} + +static int pop3_migration_uidl_sync_if_needed(struct mailbox *box) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box); + int ret = 0; + + if (mbox->uidl_synced) + return 0; + + if (mbox->uidl_sync_failed) + ret = -1; + else { + struct event_reason *reason = + event_reason_begin("pop3_migration:uidl_sync"); + ret = pop3_migration_uidl_sync(box); + event_reason_end(&reason); + } + if (ret < 0) { + mbox->uidl_sync_failed = TRUE; + mail_storage_set_error(box->storage, MAIL_ERROR_TEMP, + "POP3 UIDLs couldn't be synced"); + return -1; + } + return 0; +} + +static int +pop3_migration_get_special(struct mail *_mail, enum mail_fetch_field field, + const char **value_r) +{ + struct mail_private *mail = (struct mail_private *)_mail; + union mail_module_context *mmail = POP3_MIGRATION_MAIL_CONTEXT(mail); + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(_mail->box); + struct imap_msg_map map_key, *map; + + if (field == MAIL_FETCH_UIDL_BACKEND || + field == MAIL_FETCH_POP3_ORDER) { + if (pop3_migration_uidl_sync_if_needed(_mail->box) < 0) + return -1; + + i_zero(&map_key); + map_key.uid = _mail->uid; + map = array_bsearch(&mbox->imap_msg_map, &map_key, + imap_msg_map_uid_cmp); + if (map != NULL && map->pop3_uidl != NULL) { + if (field == MAIL_FETCH_UIDL_BACKEND) + *value_r = map->pop3_uidl; + else + *value_r = t_strdup_printf("%u", map->pop3_seq); + return 0; + } + /* not found from POP3 server, fallback to default */ + } + return mmail->super.get_special(_mail, field, value_r); +} + +static void pop3_migration_mail_allocated(struct mail *_mail) +{ + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT(_mail->box->storage); + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + union mail_module_context *mmail; + struct mail_namespace *ns; + + if (mstorage == NULL || + (!mstorage->all_mailboxes && !_mail->box->inbox_user)) { + /* assigns UIDLs only for INBOX */ + return; + } + + ns = mail_namespace_find(_mail->box->storage->user->namespaces, + mstorage->pop3_box_vname); + if (ns == mailbox_get_namespace(_mail->box)) { + /* we're accessing the pop3-migration namespace itself */ + return; + } + + mmail = p_new(mail->pool, union mail_module_context, 1); + mmail->super = *v; + mail->vlast = &mmail->super; + + v->get_special = pop3_migration_get_special; + MODULE_CONTEXT_SET_SELF(mail, pop3_migration_mail_module, mmail); +} + +static struct mail_search_context * +pop3_migration_mailbox_search_init(struct mailbox_transaction_context *t, + struct mail_search_args *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(t->box); + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(t->box->storage); + + if ((wanted_fields & (MAIL_FETCH_UIDL_BACKEND | + MAIL_FETCH_POP3_ORDER)) != 0 && + (mstorage->all_mailboxes || t->box->inbox_user)) { + /* Start POP3 UIDL syncing before the search, so we'll do it + before we start sending any FETCH BODY[]s to IMAP. It + shouldn't matter much, except this works around a bug in + Yahoo IMAP where it sometimes breaks its state when doing + a FETCH BODY[] followed by FETCH BODY[HEADER].. */ + (void)pop3_migration_uidl_sync_if_needed(t->box); + } + + return mbox->module_ctx.super.search_init(t, args, sort_program, + wanted_fields, wanted_headers); +} + +static void pop3_migration_mailbox_allocated(struct mailbox *box) +{ + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT(box->storage); + struct mailbox_vfuncs *v = box->vlast; + struct pop3_migration_mailbox *mbox; + + if (mstorage == NULL) + return; + + mbox = p_new(box->pool, struct pop3_migration_mailbox, 1); + mbox->module_ctx.super = *v; + box->vlast = &mbox->module_ctx.super; + + v->search_init = pop3_migration_mailbox_search_init; + + MODULE_CONTEXT_SET(box, pop3_migration_storage_module, mbox); +} + +static void pop3_migration_mail_storage_destroy(struct mail_storage *storage) +{ + struct pop3_migration_mail_storage *mstorage = + POP3_MIGRATION_CONTEXT_REQUIRE(storage); + + if (array_is_created(&mstorage->pop3_uidl_map)) + array_free(&mstorage->pop3_uidl_map); + + mstorage->module_ctx.super.destroy(storage); +} + +static void pop3_migration_mail_storage_created(struct mail_storage *storage) +{ + struct pop3_migration_mail_storage *mstorage; + struct mail_storage_vfuncs *v = storage->vlast; + const char *pop3_box_vname; + + pop3_box_vname = mail_user_plugin_getenv(storage->user, + "pop3_migration_mailbox"); + if (pop3_box_vname == NULL) { + e_debug(storage->user->event, "pop3_migration: No pop3_migration_mailbox setting - disabled"); + return; + } + + mstorage = p_new(storage->pool, struct pop3_migration_mail_storage, 1); + mstorage->module_ctx.super = *v; + storage->vlast = &mstorage->module_ctx.super; + v->destroy = pop3_migration_mail_storage_destroy; + + mstorage->pop3_box_vname = p_strdup(storage->pool, pop3_box_vname); + mstorage->all_mailboxes = + mail_user_plugin_getenv_bool(storage->user, + "pop3_migration_all_mailboxes"); + mstorage->ignore_missing_uidls = + mail_user_plugin_getenv_bool(storage->user, + "pop3_migration_ignore_missing_uidls"); + mstorage->ignore_extra_uidls = + mail_user_plugin_getenv_bool(storage->user, + "pop3_migration_ignore_extra_uidls"); + mstorage->skip_size_check = + mail_user_plugin_getenv_bool(storage->user, + "pop3_migration_skip_size_check"); + mstorage->skip_uidl_cache = + mail_user_plugin_getenv_bool(storage->user, + "pop3_migration_skip_uidl_cache"); + + MODULE_CONTEXT_SET(storage, pop3_migration_storage_module, mstorage); +} + +static struct mail_storage_hooks pop3_migration_mail_storage_hooks = { + .mail_allocated = pop3_migration_mail_allocated, + .mailbox_allocated = pop3_migration_mailbox_allocated, + .mail_storage_created = pop3_migration_mail_storage_created +}; + +void pop3_migration_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &pop3_migration_mail_storage_hooks); +} + +void pop3_migration_plugin_deinit(void) +{ + mail_storage_hooks_remove(&pop3_migration_mail_storage_hooks); +} diff --git a/src/plugins/pop3-migration/pop3-migration-plugin.h b/src/plugins/pop3-migration/pop3-migration-plugin.h new file mode 100644 index 0000000..1dc84e0 --- /dev/null +++ b/src/plugins/pop3-migration/pop3-migration-plugin.h @@ -0,0 +1,13 @@ +#ifndef POP3_MIGRATION_PLUGIN_H +#define POP3_MIGRATION_PLUGIN_H + +struct module; + +void pop3_migration_plugin_init(struct module *module); +void pop3_migration_plugin_deinit(void); + +int pop3_migration_get_hdr_sha1(uint32_t mail_seq, struct istream *input, + unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN], + bool *have_eoh_r); + +#endif diff --git a/src/plugins/pop3-migration/test-pop3-migration-plugin.c b/src/plugins/pop3-migration/test-pop3-migration-plugin.c new file mode 100644 index 0000000..f8e52d8 --- /dev/null +++ b/src/plugins/pop3-migration/test-pop3-migration-plugin.c @@ -0,0 +1,63 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "sha1.h" +#include "hex-binary.h" +#include "istream.h" +#include "test-common.h" +#include "pop3-migration-plugin.h" + +static void test_pop3_migration_get_hdr_sha1(void) +{ + static const struct { + const char *input; + const char *sha1; + bool have_eoh; + } tests[] = { + { "", "da39a3ee5e6b4b0d3255bfef95601890afd80709", FALSE }, + { "\n", "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc", TRUE }, + { "a: \r\n", "a3871371f2d468493005286282ae10549dab2c57", FALSE }, + { "a: b\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b\r\n\r\n", "938b96404495cced816e3a4f6031734eab4e71b3", TRUE }, + { "a: b\r\n\r\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b\r\n\r\r\nc: d\r\n\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", TRUE }, + { "a: b\r\n \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b\r\n \r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b\r\n\t\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b\t\t\t\t\r\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + { "a: b\nfoo\n", "44ef6a20971148dd54a161f79814e22e2d098ddb", FALSE }, + + { "a: b\nc: d\n", "4dbea2c1bdd1323e15931382c1835200d9286230", FALSE }, + { "a:b\nc:d\n", "4dbea2c1bdd1323e15931382c1835200d9286230", FALSE }, + { "a: b\nfoo\nc: d\n", "4dbea2c1bdd1323e15931382c1835200d9286230", FALSE }, + }; + struct istream *input; + unsigned char digest[SHA1_RESULTLEN]; + unsigned int i; + bool have_eoh; + + test_begin("pop3 migration get hdr sha1"); + + for (i = 0; i < N_ELEMENTS(tests); i++) { + input = i_stream_create_from_data(tests[i].input, + strlen(tests[i].input)); + test_assert_idx(pop3_migration_get_hdr_sha1(1, input, digest, &have_eoh) == 0, i); + test_assert_idx(strcasecmp(binary_to_hex(digest, sizeof(digest)), tests[i].sha1) == 0, i); + test_assert_idx(tests[i].have_eoh == have_eoh, i); + i_stream_unref(&input); + } + + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_pop3_migration_get_hdr_sha1, + NULL + }; + return test_run(test_functions); +} diff --git a/src/plugins/push-notification/Makefile.am b/src/plugins/push-notification/Makefile.am new file mode 100644 index 0000000..c065e38 --- /dev/null +++ b/src/plugins/push-notification/Makefile.am @@ -0,0 +1,83 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/plugins/notify + +NOPLUGIN_LDFLAGS = +lib20_push_notification_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = lib20_push_notification_plugin.la + +if DOVECOT_PLUGIN_DEPS +notify_deps = ../notify/lib15_notify_plugin.la +endif + +lib20_push_notification_plugin_la_LIBADD = \ + $(notify_deps) + +lib20_push_notification_plugin_la_SOURCES = \ + push-notification-driver-dlog.c \ + push-notification-driver-ox.c \ + push-notification-drivers.c \ + push-notification-event-flagsclear.c \ + push-notification-event-flagsset.c \ + push-notification-event-mailboxcreate.c \ + push-notification-event-mailboxdelete.c \ + push-notification-event-mailboxrename.c \ + push-notification-event-mailboxsubscribe.c \ + push-notification-event-mailboxunsubscribe.c \ + push-notification-event-messageappend.c \ + push-notification-event-messageexpunge.c \ + push-notification-event-messagenew.c \ + push-notification-event-messageread.c \ + push-notification-event-messagetrash.c \ + push-notification-event-message-common.c \ + push-notification-events.c \ + push-notification-events-rfc5423.c \ + push-notification-plugin.c \ + push-notification-triggers.c \ + push-notification-txn-mbox.c \ + push-notification-txn-msg.c + +headers = \ + push-notification-drivers.h \ + push-notification-event-flagsclear.h \ + push-notification-event-flagsset.h \ + push-notification-event-mailboxcreate.h \ + push-notification-event-mailboxdelete.h \ + push-notification-event-mailboxrename.h \ + push-notification-event-mailboxsubscribe.h \ + push-notification-event-mailboxunsubscribe.h \ + push-notification-event-message-common.h \ + push-notification-event-messageappend.h \ + push-notification-event-messageexpunge.h \ + push-notification-event-messagenew.h \ + push-notification-event-messageread.h \ + push-notification-event-messagetrash.h \ + push-notification-events.h \ + push-notification-events-rfc5423.h \ + push-notification-plugin.h \ + push-notification-triggers.h \ + push-notification-txn-mbox.h \ + push-notification-txn-msg.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +if HAVE_LUA +lib22_push_notification_lua_plugin_la_CFLAGS = $(AM_CPPFLAGS) \ + -I$(top_srcdir)/src/lib-lua \ + -I$(top_srcdir)/src/plugins/mail-lua \ + $(LUA_CFLAGS) +lib22_push_notification_lua_plugin_la_LDFLAGS = module -avoid-version +module_LTLIBRARIES += \ + lib22_push_notification_lua_plugin.la +lib22_push_notification_lua_plugin_la_LIBADD = $(notify_deps) $(LUA_LIBS) +lib22_push_notification_lua_plugin_la_SOURCES = \ + push-notification-driver-lua.c +endif diff --git a/src/plugins/push-notification/Makefile.in b/src/plugins/push-notification/Makefile.in new file mode 100644 index 0000000..f785877 --- /dev/null +++ b/src/plugins/push-notification/Makefile.in @@ -0,0 +1,1037 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +@HAVE_LUA_TRUE@am__append_1 = \ +@HAVE_LUA_TRUE@ lib22_push_notification_lua_plugin.la + +subdir = src/plugins/push-notification +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" \ + "$(DESTDIR)$(pkginc_libdir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_push_notification_plugin_la_DEPENDENCIES = $(notify_deps) +am_lib20_push_notification_plugin_la_OBJECTS = \ + push-notification-driver-dlog.lo \ + push-notification-driver-ox.lo push-notification-drivers.lo \ + push-notification-event-flagsclear.lo \ + push-notification-event-flagsset.lo \ + push-notification-event-mailboxcreate.lo \ + push-notification-event-mailboxdelete.lo \ + push-notification-event-mailboxrename.lo \ + push-notification-event-mailboxsubscribe.lo \ + push-notification-event-mailboxunsubscribe.lo \ + push-notification-event-messageappend.lo \ + push-notification-event-messageexpunge.lo \ + push-notification-event-messagenew.lo \ + push-notification-event-messageread.lo \ + push-notification-event-messagetrash.lo \ + push-notification-event-message-common.lo \ + push-notification-events.lo \ + push-notification-events-rfc5423.lo \ + push-notification-plugin.lo push-notification-triggers.lo \ + push-notification-txn-mbox.lo push-notification-txn-msg.lo +lib20_push_notification_plugin_la_OBJECTS = \ + $(am_lib20_push_notification_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_push_notification_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(lib20_push_notification_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +am__DEPENDENCIES_1 = +@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_DEPENDENCIES = \ +@HAVE_LUA_TRUE@ $(notify_deps) $(am__DEPENDENCIES_1) +am__lib22_push_notification_lua_plugin_la_SOURCES_DIST = \ + push-notification-driver-lua.c +@HAVE_LUA_TRUE@am_lib22_push_notification_lua_plugin_la_OBJECTS = lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo +lib22_push_notification_lua_plugin_la_OBJECTS = \ + $(am_lib22_push_notification_lua_plugin_la_OBJECTS) +lib22_push_notification_lua_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(lib22_push_notification_lua_plugin_la_CFLAGS) \ + $(CFLAGS) $(lib22_push_notification_lua_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +@HAVE_LUA_TRUE@am_lib22_push_notification_lua_plugin_la_rpath = \ +@HAVE_LUA_TRUE@ -rpath $(moduledir) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo \ + ./$(DEPDIR)/push-notification-driver-dlog.Plo \ + ./$(DEPDIR)/push-notification-driver-ox.Plo \ + ./$(DEPDIR)/push-notification-drivers.Plo \ + ./$(DEPDIR)/push-notification-event-flagsclear.Plo \ + ./$(DEPDIR)/push-notification-event-flagsset.Plo \ + ./$(DEPDIR)/push-notification-event-mailboxcreate.Plo \ + ./$(DEPDIR)/push-notification-event-mailboxdelete.Plo \ + ./$(DEPDIR)/push-notification-event-mailboxrename.Plo \ + ./$(DEPDIR)/push-notification-event-mailboxsubscribe.Plo \ + ./$(DEPDIR)/push-notification-event-mailboxunsubscribe.Plo \ + ./$(DEPDIR)/push-notification-event-message-common.Plo \ + ./$(DEPDIR)/push-notification-event-messageappend.Plo \ + ./$(DEPDIR)/push-notification-event-messageexpunge.Plo \ + ./$(DEPDIR)/push-notification-event-messagenew.Plo \ + ./$(DEPDIR)/push-notification-event-messageread.Plo \ + ./$(DEPDIR)/push-notification-event-messagetrash.Plo \ + ./$(DEPDIR)/push-notification-events-rfc5423.Plo \ + ./$(DEPDIR)/push-notification-events.Plo \ + ./$(DEPDIR)/push-notification-plugin.Plo \ + ./$(DEPDIR)/push-notification-triggers.Plo \ + ./$(DEPDIR)/push-notification-txn-mbox.Plo \ + ./$(DEPDIR)/push-notification-txn-msg.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_push_notification_plugin_la_SOURCES) \ + $(lib22_push_notification_lua_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_push_notification_plugin_la_SOURCES) \ + $(am__lib22_push_notification_lua_plugin_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/plugins/notify + +lib20_push_notification_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = lib20_push_notification_plugin.la $(am__append_1) +@DOVECOT_PLUGIN_DEPS_TRUE@notify_deps = ../notify/lib15_notify_plugin.la +lib20_push_notification_plugin_la_LIBADD = \ + $(notify_deps) + +lib20_push_notification_plugin_la_SOURCES = \ + push-notification-driver-dlog.c \ + push-notification-driver-ox.c \ + push-notification-drivers.c \ + push-notification-event-flagsclear.c \ + push-notification-event-flagsset.c \ + push-notification-event-mailboxcreate.c \ + push-notification-event-mailboxdelete.c \ + push-notification-event-mailboxrename.c \ + push-notification-event-mailboxsubscribe.c \ + push-notification-event-mailboxunsubscribe.c \ + push-notification-event-messageappend.c \ + push-notification-event-messageexpunge.c \ + push-notification-event-messagenew.c \ + push-notification-event-messageread.c \ + push-notification-event-messagetrash.c \ + push-notification-event-message-common.c \ + push-notification-events.c \ + push-notification-events-rfc5423.c \ + push-notification-plugin.c \ + push-notification-triggers.c \ + push-notification-txn-mbox.c \ + push-notification-txn-msg.c + +headers = \ + push-notification-drivers.h \ + push-notification-event-flagsclear.h \ + push-notification-event-flagsset.h \ + push-notification-event-mailboxcreate.h \ + push-notification-event-mailboxdelete.h \ + push-notification-event-mailboxrename.h \ + push-notification-event-mailboxsubscribe.h \ + push-notification-event-mailboxunsubscribe.h \ + push-notification-event-message-common.h \ + push-notification-event-messageappend.h \ + push-notification-event-messageexpunge.h \ + push-notification-event-messagenew.h \ + push-notification-event-messageread.h \ + push-notification-event-messagetrash.h \ + push-notification-events.h \ + push-notification-events-rfc5423.h \ + push-notification-plugin.h \ + push-notification-triggers.h \ + push-notification-txn-mbox.h \ + push-notification-txn-msg.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_CFLAGS = $(AM_CPPFLAGS) \ +@HAVE_LUA_TRUE@ -I$(top_srcdir)/src/lib-lua \ +@HAVE_LUA_TRUE@ -I$(top_srcdir)/src/plugins/mail-lua \ +@HAVE_LUA_TRUE@ $(LUA_CFLAGS) + +@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_LDFLAGS = module -avoid-version +@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_LIBADD = $(notify_deps) $(LUA_LIBS) +@HAVE_LUA_TRUE@lib22_push_notification_lua_plugin_la_SOURCES = \ +@HAVE_LUA_TRUE@ push-notification-driver-lua.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/push-notification/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/push-notification/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_push_notification_plugin.la: $(lib20_push_notification_plugin_la_OBJECTS) $(lib20_push_notification_plugin_la_DEPENDENCIES) $(EXTRA_lib20_push_notification_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_push_notification_plugin_la_LINK) -rpath $(moduledir) $(lib20_push_notification_plugin_la_OBJECTS) $(lib20_push_notification_plugin_la_LIBADD) $(LIBS) + +lib22_push_notification_lua_plugin.la: $(lib22_push_notification_lua_plugin_la_OBJECTS) $(lib22_push_notification_lua_plugin_la_DEPENDENCIES) $(EXTRA_lib22_push_notification_lua_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib22_push_notification_lua_plugin_la_LINK) $(am_lib22_push_notification_lua_plugin_la_rpath) $(lib22_push_notification_lua_plugin_la_OBJECTS) $(lib22_push_notification_lua_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-driver-dlog.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-driver-ox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-drivers.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-flagsclear.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-flagsset.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxcreate.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxdelete.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxrename.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxsubscribe.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-mailboxunsubscribe.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-message-common.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messageappend.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messageexpunge.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messagenew.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messageread.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-event-messagetrash.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-events-rfc5423.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-events.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-triggers.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-txn-mbox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/push-notification-txn-msg.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo: push-notification-driver-lua.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib22_push_notification_lua_plugin_la_CFLAGS) $(CFLAGS) -MT lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo -MD -MP -MF $(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Tpo -c -o lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo `test -f 'push-notification-driver-lua.c' || echo '$(srcdir)/'`push-notification-driver-lua.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Tpo $(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='push-notification-driver-lua.c' object='lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib22_push_notification_lua_plugin_la_CFLAGS) $(CFLAGS) -c -o lib22_push_notification_lua_plugin_la-push-notification-driver-lua.lo `test -f 'push-notification-driver-lua.c' || echo '$(srcdir)/'`push-notification-driver-lua.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo + -rm -f ./$(DEPDIR)/push-notification-driver-dlog.Plo + -rm -f ./$(DEPDIR)/push-notification-driver-ox.Plo + -rm -f ./$(DEPDIR)/push-notification-drivers.Plo + -rm -f ./$(DEPDIR)/push-notification-event-flagsclear.Plo + -rm -f ./$(DEPDIR)/push-notification-event-flagsset.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxcreate.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxdelete.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxrename.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxsubscribe.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxunsubscribe.Plo + -rm -f ./$(DEPDIR)/push-notification-event-message-common.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messageappend.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messageexpunge.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messagenew.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messageread.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messagetrash.Plo + -rm -f ./$(DEPDIR)/push-notification-events-rfc5423.Plo + -rm -f ./$(DEPDIR)/push-notification-events.Plo + -rm -f ./$(DEPDIR)/push-notification-plugin.Plo + -rm -f ./$(DEPDIR)/push-notification-triggers.Plo + -rm -f ./$(DEPDIR)/push-notification-txn-mbox.Plo + -rm -f ./$(DEPDIR)/push-notification-txn-msg.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/lib22_push_notification_lua_plugin_la-push-notification-driver-lua.Plo + -rm -f ./$(DEPDIR)/push-notification-driver-dlog.Plo + -rm -f ./$(DEPDIR)/push-notification-driver-ox.Plo + -rm -f ./$(DEPDIR)/push-notification-drivers.Plo + -rm -f ./$(DEPDIR)/push-notification-event-flagsclear.Plo + -rm -f ./$(DEPDIR)/push-notification-event-flagsset.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxcreate.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxdelete.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxrename.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxsubscribe.Plo + -rm -f ./$(DEPDIR)/push-notification-event-mailboxunsubscribe.Plo + -rm -f ./$(DEPDIR)/push-notification-event-message-common.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messageappend.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messageexpunge.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messagenew.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messageread.Plo + -rm -f ./$(DEPDIR)/push-notification-event-messagetrash.Plo + -rm -f ./$(DEPDIR)/push-notification-events-rfc5423.Plo + -rm -f ./$(DEPDIR)/push-notification-events.Plo + -rm -f ./$(DEPDIR)/push-notification-plugin.Plo + -rm -f ./$(DEPDIR)/push-notification-triggers.Plo + -rm -f ./$(DEPDIR)/push-notification-txn-mbox.Plo + -rm -f ./$(DEPDIR)/push-notification-txn-msg.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/push-notification/push-notification-driver-dlog.c b/src/plugins/push-notification/push-notification-driver-dlog.c new file mode 100644 index 0000000..e0cf790 --- /dev/null +++ b/src/plugins/push-notification/push-notification-driver-dlog.c @@ -0,0 +1,114 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-txn-mbox.h" +#include "push-notification-txn-msg.h" + +static int +push_notification_driver_dlog_init( + struct push_notification_driver_config *config, + struct mail_user *user ATTR_UNUSED, pool_t pool ATTR_UNUSED, + void **context ATTR_UNUSED, const char **error_r ATTR_UNUSED) +{ + i_debug("Called init push_notification plugin hook."); + + if (config->raw_config != NULL) { + i_debug("Config string for dlog push_notification driver: %s", + config->raw_config); + } + + return 0; +} + +static bool +push_notification_driver_dlog_begin_txn( + struct push_notification_driver_txn *dtxn) +{ + const struct push_notification_event *event; + + i_debug("Called begin_txn push_notification plugin hook."); + + array_foreach_elem(&push_notification_events, event) + push_notification_event_init(dtxn, event->name, NULL); + return TRUE; +} + +static void +push_notification_driver_dlog_process_mbox( + struct push_notification_driver_txn *dtxn ATTR_UNUSED, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_txn_event *event; + + i_debug("Called process_mbox push_notification plugin hook."); + + i_debug("Mailbox data: Mailbox [%s]", mbox->mailbox); + + if (array_is_created(&mbox->eventdata)) { + array_foreach_elem(&mbox->eventdata, event) { + if (event->event->event->mbox.debug_mbox != NULL) + event->event->event->mbox.debug_mbox(event); + } + } +} + +static void +push_notification_driver_dlog_process_msg( + struct push_notification_driver_txn *dtxn ATTR_UNUSED, + struct push_notification_txn_msg *msg) +{ + struct push_notification_txn_event *event; + + i_debug("Called process_msg push_notification plugin hook."); + + i_debug("Message data: Mailbox [%s], UID [%u], UIDVALIDITY [%u]", + msg->mailbox, msg->uid, msg->uid_validity); + + if (array_is_created(&msg->eventdata)) { + array_foreach_elem(&msg->eventdata, event) { + if (event->event->event->msg.debug_msg != NULL) + event->event->event->msg.debug_msg(event); + } + } +} + +static void +push_notification_driver_dlog_end_txn( + struct push_notification_driver_txn *dtxn ATTR_UNUSED, + bool success ATTR_UNUSED) +{ + i_debug("Called end_txn push_notification plugin hook."); +} + +static void +push_notification_driver_dlog_deinit( + struct push_notification_driver_user *duser ATTR_UNUSED) +{ + i_debug("Called deinit push_notification plugin hook."); +} + +static void push_notification_driver_dlog_cleanup(void) +{ + i_debug("Called cleanup push_notification plugin hook."); +} + +/* Driver definition */ + +extern struct push_notification_driver push_notification_driver_dlog; + +struct push_notification_driver push_notification_driver_dlog = { + .name = "dlog", + .v = { + .init = push_notification_driver_dlog_init, + .begin_txn = push_notification_driver_dlog_begin_txn, + .process_mbox = push_notification_driver_dlog_process_mbox, + .process_msg = push_notification_driver_dlog_process_msg, + .end_txn = push_notification_driver_dlog_end_txn, + .deinit = push_notification_driver_dlog_deinit, + .cleanup = push_notification_driver_dlog_cleanup + } +}; diff --git a/src/plugins/push-notification/push-notification-driver-lua.c b/src/plugins/push-notification/push-notification-driver-lua.c new file mode 100644 index 0000000..e1178fa --- /dev/null +++ b/src/plugins/push-notification/push-notification-driver-lua.c @@ -0,0 +1,663 @@ +/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "str.h" +#include "hash.h" +#include "dlua-script.h" +#include "dlua-script-private.h" + +#include "mail-storage.h" +#include "mail-user.h" +#include "mail-lua-plugin.h" +#include "mail-storage-lua.h" + +#include "push-notification-plugin.h" +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-message-common.h" +#include "push-notification-txn-mbox.h" +#include "push-notification-txn-msg.h" + +#include "push-notification-event-flagsclear.h" +#include "push-notification-event-flagsset.h" +#include "push-notification-event-mailboxcreate.h" +#include "push-notification-event-mailboxdelete.h" +#include "push-notification-event-mailboxrename.h" +#include "push-notification-event-mailboxsubscribe.h" +#include "push-notification-event-mailboxunsubscribe.h" +#include "push-notification-event-messageappend.h" +#include "push-notification-event-message-common.h" +#include "push-notification-event-messageexpunge.h" +#include "push-notification-event-messagenew.h" +#include "push-notification-event-messageread.h" +#include "push-notification-event-messagetrash.h" + +#define DLUA_LOG_USERENV_KEY "push_notification_lua_script_file" + +#define DLUA_FN_BEGIN_TXN "dovecot_lua_notify_begin_txn" +#define DLUA_FN_EVENT_PREFIX "dovecot_lua_notify_event" +#define DLUA_FN_END_TXN "dovecot_lua_notify_end_txn" + +#define DLUA_CALL_FINISHED "push_notification_lua_call_finished" + +struct dlua_push_notification_context { + struct dlua_script *script; + struct event *event; + bool debug; + + struct push_notification_event_messagenew_config config_mn; + struct push_notification_event_messageappend_config config_ma; + struct push_notification_event_flagsclear_config config_fc; + struct push_notification_event_flagsset_config config_fs; +}; + +struct dlua_push_notification_txn_context { + int tx_ref; +}; + +#define DLUA_DEFAULT_EVENTS (\ + PUSH_NOTIFICATION_MESSAGE_HDR_FROM | \ + PUSH_NOTIFICATION_MESSAGE_HDR_TO | \ + PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT | \ + PUSH_NOTIFICATION_MESSAGE_HDR_DATE | \ + PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET | \ + PUSH_NOTIFICATION_MESSAGE_FLAGS | \ + PUSH_NOTIFICATION_MESSAGE_KEYWORDS | \ + PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID) + +static const char *push_notification_driver_lua_to_fn(const char *evname); + +static int +push_notification_driver_lua_init( + struct push_notification_driver_config *config, struct mail_user *user, + pool_t pool, void **context, const char **error_r) +{ + struct dlua_push_notification_context *ctx; + const char *tmp, *file; + struct event *event = event_create(user->event); + event_add_category(event, push_notification_get_event_category()); + event_set_append_log_prefix(event, "lua: "); + + if ((tmp = mail_user_plugin_getenv(user, DLUA_LOG_USERENV_KEY)) == NULL) + tmp = hash_table_lookup(config->config, (const char *)"file"); + + if (tmp == NULL) { + struct dlua_script *script; + /* If there is a script loaded, use the same context */ + if (mail_lua_plugin_get_script(user, &script)) { + dlua_script_ref(script); + ctx = p_new( + pool, struct dlua_push_notification_context, 1); + ctx->script = script; + ctx->event = event; + *context = ctx; + return 0; + } + + event_unref(&event); + *error_r = "No file in config and no " + DLUA_LOG_USERENV_KEY " set"; + return -1; + } + file = tmp; + + ctx = p_new(pool, struct dlua_push_notification_context, 1); + ctx->event = event; + + e_debug(ctx->event, "Loading %s", file); + + if (dlua_script_create_file(file, &ctx->script, event, error_r) < 0) { + /* There is a T_POP after this, which will break errors */ + event_unref(&event); + *error_r = p_strdup(pool, *error_r); + return -1; + } + + /* Register dovecot helpers */ + dlua_dovecot_register(ctx->script); + dlua_register_mail_storage(ctx->script); + + e_debug(ctx->event, "Calling script_init"); + + /* Initialize script */ + if (dlua_script_init(ctx->script, error_r) < 0) { + *error_r = p_strdup(pool, *error_r); + event_unref(&event); + dlua_script_unref(&ctx->script); + return -1; + } + + *context = ctx; + return 0; +} + +static bool +push_notification_driver_lua_init_events( + struct push_notification_driver_txn *dtxn) +{ + struct dlua_push_notification_context *ctx = dtxn->duser->context; + const struct push_notification_event *event; + ctx->config_mn.flags = DLUA_DEFAULT_EVENTS; + ctx->config_ma.flags = DLUA_DEFAULT_EVENTS; + ctx->config_fc.store_old = TRUE; + bool found_one = FALSE; + + /* Register *all* events that are present in Lua */ + array_foreach_elem(push_notification_get_events(), event) { + const char *name = event->name; + const char *fn = push_notification_driver_lua_to_fn(name); + if (!dlua_script_has_function(ctx->script, fn)) + continue; + + found_one = TRUE; + + e_debug(ctx->event, "Found %s, handling %s event", fn, name); + + if (strcmp(name, "MessageNew") == 0) { + push_notification_event_init(dtxn, name, + &ctx->config_mn); + } else if (strcmp(name, "MessageAppend") == 0) { + push_notification_event_init(dtxn, name, + &ctx->config_ma); + } else if (strcmp(name, "FlagsSet") == 0) { + push_notification_event_init(dtxn, name, + &ctx->config_fs); + } else if (strcmp(name, "FlagsClear") == 0) { + push_notification_event_init(dtxn, name, + &ctx->config_fc); + } else if (event->init.default_config != NULL) { + void *config = event->init.default_config(); + push_notification_event_init(dtxn, name, config); + } else { + push_notification_event_init(dtxn, name, NULL); + } + } + + return found_one; +} + +static bool +push_notification_driver_lua_begin_txn( + struct push_notification_driver_txn *dtxn) +{ + struct mail_user *user = dtxn->ptxn->muser; + struct dlua_push_notification_context *ctx = dtxn->duser->context; + struct event *event = event_create(ctx->event); + const char *error; + + event_set_name(event, DLUA_CALL_FINISHED); + event_add_str(event, "function_name", DLUA_FN_BEGIN_TXN); + + if (!dlua_script_has_function(ctx->script, DLUA_FN_BEGIN_TXN)) { + event_add_str(event, "error", + "Missing function " DLUA_FN_BEGIN_TXN); + e_error(event, "Missing function " DLUA_FN_BEGIN_TXN); + event_unref(&event); + return FALSE; + } + + if (!push_notification_driver_lua_init_events(dtxn)) { + e_debug(event, "No event handlers found in script"); + event_unref(&event); + return FALSE; + } + + e_debug(ctx->event, "Calling " DLUA_FN_BEGIN_TXN "(%s)", + user->username); + + /* Push mail user as argument */ + dlua_push_mail_user(ctx->script->L, user); + if (dlua_pcall(ctx->script->L, DLUA_FN_BEGIN_TXN, 1, 1, &error) < 0) { + event_add_str(event, "error", error); + e_error(event, "%s", error); + return FALSE; + } + + e_debug(event, "Called " DLUA_FN_BEGIN_TXN); + event_unref(&event); + + /* Store the result */ + struct dlua_push_notification_txn_context *tctx = + p_new(dtxn->ptxn->pool, + struct dlua_push_notification_txn_context, 1); + + tctx->tx_ref = luaL_ref(ctx->script->L, LUA_REGISTRYINDEX); + dtxn->context = tctx; + mail_user_ref(user); + + return TRUE; +} + +/* This function only works here, it converts MessageType to event_message_type + */ +static const char *push_notification_driver_lua_to_fn(const char *evname) +{ + /* Camelcase to event_event_name (most events have two underscores) */ + string_t *fn = t_str_new(strlen(evname) + + strlen(DLUA_FN_EVENT_PREFIX) + 2); + str_append(fn, DLUA_FN_EVENT_PREFIX); + + for(; *evname != '\0'; evname++) { + if (*evname >= 'A' && *evname <= 'Z') { + str_append_c(fn, '_'); + str_append_c(fn, (*evname) - 'A' + 'a'); + } else { + str_append_c(fn, *evname); + } + } + + return str_c(fn); +} + +/* Pushes lua list of flags */ +static void dlua_push_flags(struct dlua_script *script, enum mail_flags flags) +{ + lua_newtable(script->L); + int idx = 1; + + if ((flags & MAIL_ANSWERED) != 0) { + lua_pushliteral(script->L, "\\Answered"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_FLAGGED) != 0) { + lua_pushliteral(script->L, "\\Flagged"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_DELETED) != 0) { + lua_pushliteral(script->L, "\\Deleted"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_SEEN) != 0) { + lua_pushliteral(script->L, "\\Seen"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_DRAFT) != 0) { + lua_pushliteral(script->L, "\\Draft"); + lua_rawseti(script->L, -2, idx++); + } + if ((flags & MAIL_RECENT) != 0) { + lua_pushliteral(script->L, "\\Recent"); + lua_rawseti(script->L, -2, idx++); + } +} + +static void +dlua_push_keywords(struct dlua_script *script, const char *const *keywords, + unsigned int count) +{ + lua_newtable(script->L); + if (keywords == NULL) + return; + for (unsigned int idx = 0; idx < count; idx++) { + lua_pushstring(script->L, keywords[idx]); + lua_rawseti(script->L, -2, idx+1); + } +} + +static void +push_notification_lua_push_flagsclear( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + /* Push cleared flags */ + unsigned int size = 0; + struct push_notification_event_flagsclear_data *data = event->data; + + dlua_push_flags(script, data->flags_clear); + lua_setfield(script->L, -2, "flags"); + dlua_push_flags(script, data->flags_old); + lua_setfield(script->L, -2, "flags_old"); + + if (array_is_created(&data->keywords_clear)) { + const char *const *kw = array_get(&data->keywords_clear, &size); + dlua_push_keywords(script, kw, size); + lua_setfield(script->L, -2, "keywords"); + } + + if (array_is_created(&data->keywords_old)) { + const char *const *kw = array_get(&data->keywords_old, &size); + dlua_push_keywords(script, kw, size); + lua_setfield(script->L, -2, "keywords_old"); + } +} + +static void +push_notification_lua_push_flagsset( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + /* push set flags */ + unsigned int size = 0; + struct push_notification_event_flagsset_data *data = event->data; + + dlua_push_flags(script, data->flags_set); + lua_setfield(script->L, -2, "flags"); + + if (array_is_created(&data->keywords_set)) { + const char *const *kw = array_get(&data->keywords_set, &size); + dlua_push_keywords(script, kw, size); + lua_setfield(script->L, -2, "keywords"); + } +} + +static void +push_notification_lua_push_mailboxrename( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + struct push_notification_event_mailboxrename_data *data = event->data; + + lua_pushstring(script->L, data->old_mbox); + lua_setfield(script->L, -2, "mailbox_old"); +} + +#define push_notification_lua_push_string(L, value) \ + lua_pushstring((L), (value) == NULL ? "" : (value)) + +static void +push_notification_lua_push_message_ext( + const struct push_notification_message_ext *ext, + struct dlua_script *script) +{ + push_notification_lua_push_string(script->L, ext->from_address); + lua_setfield(script->L, -2, "from_address"); + push_notification_lua_push_string(script->L, ext->from_display_name_utf8); + lua_setfield(script->L, -2, "from_display_name"); + + push_notification_lua_push_string(script->L, ext->to_address); + lua_setfield(script->L, -2, "to_address"); + push_notification_lua_push_string(script->L, ext->to_display_name_utf8); + lua_setfield(script->L, -2, "to_display_name"); + + lua_pushstring(script->L, ext->subject_utf8); + lua_setfield(script->L, -2, "subject"); +} + +static void +push_notification_lua_push_messageappend( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + struct push_notification_event_messageappend_data *data = event->data; + + lua_pushnumber(script->L, data->date); + lua_setfield(script->L, -2, "date"); + + lua_pushnumber(script->L, data->date_tz); + lua_setfield(script->L, -2, "tz"); + + push_notification_lua_push_string(script->L, data->from); + lua_setfield(script->L, -2, "from"); + + push_notification_lua_push_string(script->L, data->to); + lua_setfield(script->L, -2, "to"); + + lua_pushstring(script->L, data->snippet); + lua_setfield(script->L, -2, "snippet"); + + dlua_push_flags(script, data->flags); + lua_setfield(script->L, -2, "flags"); + + dlua_push_keywords(script, data->keywords, + str_array_length(data->keywords)); + lua_setfield(script->L, -2, "keywords"); + + lua_pushstring(script->L, data->message_id); + lua_setfield(script->L, -2, "message_id"); + + push_notification_lua_push_message_ext(&data->ext, script); +} + +static void +push_notification_lua_push_messagenew( + const struct push_notification_txn_event *event, + struct dlua_script *script) +{ + struct push_notification_event_messagenew_data *data = event->data; + + lua_pushnumber(script->L, data->date); + lua_setfield(script->L, -2, "date"); + + lua_pushnumber(script->L, data->date_tz); + lua_setfield(script->L, -2, "tz"); + + push_notification_lua_push_string(script->L, data->from); + lua_setfield(script->L, -2, "from"); + + push_notification_lua_push_string(script->L, data->to); + lua_setfield(script->L, -2, "to"); + + lua_pushstring(script->L, data->snippet); + lua_setfield(script->L, -2, "snippet"); + + dlua_push_flags(script, data->flags); + lua_setfield(script->L, -2, "flags"); + + dlua_push_keywords(script, data->keywords, + str_array_length(data->keywords)); + lua_setfield(script->L, -2, "keywords"); + + lua_pushstring(script->L, data->message_id); + lua_setfield(script->L, -2, "message_id"); + + push_notification_lua_push_message_ext(&data->ext, script); +} + +/* Events that need special treatment */ +static struct push_notification_event_to_lua { + const char *event_name; + void (*push)(const struct push_notification_txn_event *event, + struct dlua_script *script); +} event_to_push_table[] = { + { + .event_name = "FlagsClear", + .push = push_notification_lua_push_flagsclear + }, + { + .event_name = "FlagsSet", + .push = push_notification_lua_push_flagsset + }, + { + .event_name = "MailboxRename", + .push = push_notification_lua_push_mailboxrename + }, + { + .event_name = "MessageAppend", + .push = push_notification_lua_push_messageappend + }, + { + .event_name = "MessageNew", + .push = push_notification_lua_push_messagenew + }, +}; + +static void +push_notification_driver_lua_push_event( + const struct push_notification_txn_event *event, + struct dlua_push_notification_context *ctx) +{ + struct dlua_script *script = ctx->script; + const char *name = event->event->event->name; + + /* Create a table */ + lua_newtable(script->L); + + /* Event name */ + lua_pushstring(script->L, name); + lua_setfield(script->L, -2, "name"); + + for(size_t i = 0; i < N_ELEMENTS(event_to_push_table); i++) + if (strcmp(event_to_push_table[i].event_name, name) == 0) + event_to_push_table[i].push(event, script); +} + +static void +push_notification_driver_lua_call( + struct dlua_push_notification_context *ctx, + struct dlua_push_notification_txn_context *tctx, + const struct push_notification_txn_event *event, + const struct push_notification_txn_mbox *mbox, + struct push_notification_txn_msg *msg) +{ + const char *error; + const char *fn = + push_notification_driver_lua_to_fn(event->event->event->name); + struct event *e = event_create(ctx->event); + event_set_name(e, DLUA_CALL_FINISHED); + event_add_str(e, "event_name", event->event->event->name); + event_add_str(e, "function_name", fn); + + /* Push context */ + lua_rawgeti(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref); + + /* Push event + common fields */ + push_notification_driver_lua_push_event(event, ctx); + + if (mbox != NULL) { + lua_pushstring(ctx->script->L, mbox->mailbox); + lua_setfield(ctx->script->L, -2, "mailbox"); + e_debug(ctx->event, + "Calling %s(ctx, event[name=%s,mailbox=%s])", + fn, event->event->event->name, mbox->mailbox); + event_add_str(e, "mailbox", mbox->mailbox); + } else if (msg != NULL) { + lua_pushstring(ctx->script->L, msg->mailbox); + lua_setfield(ctx->script->L, -2, "mailbox"); + lua_pushnumber(ctx->script->L, msg->uid); + lua_setfield(ctx->script->L, -2, "uid"); + lua_pushnumber(ctx->script->L, msg->uid_validity); + lua_setfield(ctx->script->L, -2, "uid_validity"); + e_debug(ctx->event, + "Calling %s(ctx, event[name=%s,mailbox=%s,uid=%u])", + fn, event->event->event->name, msg->mailbox, msg->uid); + event_add_str(e, "mailbox", msg->mailbox); + event_add_int(e, "uid", msg->uid); + } else + i_unreached(); + + /* Perform call */ + if (dlua_pcall(ctx->script->L, fn, 2, 0, &error) < 0) { + event_add_str(e, "error", error); + e_error(e, "%s", error); + } else { + e_debug(e, "Called %s", fn); + } + event_unref(&e); +} + +static void +push_notification_driver_lua_process_mbox( + struct push_notification_driver_txn *dtxn, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_txn_event *event; + struct dlua_push_notification_context *ctx = dtxn->duser->context; + struct dlua_push_notification_txn_context *tctx = dtxn->context; + + if (array_is_created(&mbox->eventdata)) { + array_foreach_elem(&mbox->eventdata, event) { + push_notification_driver_lua_call(ctx, tctx, + event, mbox, NULL); + } + } +} + +static void +push_notification_driver_lua_process_msg( + struct push_notification_driver_txn *dtxn, + struct push_notification_txn_msg *msg) +{ + struct push_notification_txn_event *event; + struct dlua_push_notification_context *ctx = dtxn->duser->context; + struct dlua_push_notification_txn_context *tctx = dtxn->context; + + if (array_is_created(&msg->eventdata)) { + array_foreach_elem(&msg->eventdata, event) { + push_notification_driver_lua_call(ctx, tctx, + event, NULL, msg); + } + } +} + +static void +push_notification_driver_lua_end_txn(struct push_notification_driver_txn *dtxn, + bool success) +{ + /* Call end txn */ + const char *error; + struct dlua_push_notification_context *ctx = dtxn->duser->context; + struct dlua_push_notification_txn_context *tctx = dtxn->context; + struct mail_user *user = dtxn->ptxn->muser; + struct event *event = event_create(ctx->event); + event_set_name(event, DLUA_CALL_FINISHED); + event_add_str(event, "function_name", DLUA_FN_END_TXN); + + if (!dlua_script_has_function(ctx->script, DLUA_FN_END_TXN)) { + e_error(event, "Missing function " DLUA_FN_END_TXN); + } else { + e_debug(ctx->event, "Calling " DLUA_FN_END_TXN); + lua_rawgeti(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref); + lua_pushboolean(ctx->script->L, success); + if (dlua_pcall(ctx->script->L, DLUA_FN_END_TXN, 2, 0, &error) < 0) { + event_add_str(event, "error", error); + e_error(event, "%s", error); + } else { + e_debug(event, "Called " DLUA_FN_END_TXN); + } + } + + event_unref(&event); + /* Release context */ + luaL_unref(ctx->script->L, LUA_REGISTRYINDEX, tctx->tx_ref); + /* Call gc here */ + (void)lua_gc(ctx->script->L, LUA_GCCOLLECT, 1); + mail_user_unref(&user); +} + +static void +push_notification_driver_lua_deinit(struct push_notification_driver_user *duser) +{ + /* Call lua deinit */ + struct dlua_push_notification_context *ctx = duser->context; + dlua_script_unref(&ctx->script); + event_unref(&ctx->event); +} + +static void push_notification_driver_lua_cleanup(void) +{ + /* noop */ +} + +/* Driver definition */ + +struct push_notification_driver push_notification_driver_lua = { + .name = "lua", + .v = { + .init = push_notification_driver_lua_init, + .begin_txn = push_notification_driver_lua_begin_txn, + .process_mbox = push_notification_driver_lua_process_mbox, + .process_msg = push_notification_driver_lua_process_msg, + .end_txn = push_notification_driver_lua_end_txn, + .deinit = push_notification_driver_lua_deinit, + .cleanup = push_notification_driver_lua_cleanup, + }, +}; + +void push_notification_lua_plugin_init(struct module *module); +void push_notification_lua_plugin_deinit(void); + +void push_notification_lua_plugin_init(struct module *module ATTR_UNUSED) +{ + push_notification_driver_register(&push_notification_driver_lua); +} + +void push_notification_lua_plugin_deinit(void) +{ + push_notification_driver_unregister(&push_notification_driver_lua); +} + +const char *push_notification_lua_plugin_version = DOVECOT_ABI_VERSION; +const char *push_notification_lua_plugin_dependencies[] = + { "push_notification", "mail_lua", NULL}; diff --git a/src/plugins/push-notification/push-notification-driver-ox.c b/src/plugins/push-notification/push-notification-driver-ox.c new file mode 100644 index 0000000..728cce9 --- /dev/null +++ b/src/plugins/push-notification/push-notification-driver-ox.c @@ -0,0 +1,470 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "hash.h" +#include "http-client.h" +#include "http-url.h" +#include "ioloop.h" +#include "istream.h" +#include "settings-parser.h" +#include "json-parser.h" +#include "mailbox-attribute.h" +#include "mail-storage-private.h" +#include "str.h" +#include "strescape.h" +#include "iostream-ssl.h" + +#include "push-notification-plugin.h" +#include "push-notification-drivers.h" +#include "push-notification-event-messagenew.h" +#include "push-notification-events.h" +#include "push-notification-txn-msg.h" + +#define OX_METADATA_KEY \ + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER \ + "vendor/vendor.dovecot/http-notify" + +/* Default values. */ +static const char *const default_events[] = { "MessageNew", NULL }; +static const char *const default_mboxes[] = { "INBOX", NULL }; +#define DEFAULT_CACHE_LIFETIME_SECS 60 +#define DEFAULT_TIMEOUT_MSECS 2000 +#define DEFAULT_RETRY_COUNT 1 + +/* This is data that is shared by all plugin users. */ +struct push_notification_driver_ox_global { + struct http_client *http_client; + int refcount; +}; +static struct push_notification_driver_ox_global *ox_global = NULL; + +/* This is data specific to an OX driver. */ +struct push_notification_driver_ox_config { + struct http_url *http_url; + struct event *event; + unsigned int cached_ox_metadata_lifetime_secs; + bool use_unsafe_username; + unsigned int http_max_retries; + unsigned int http_timeout_msecs; + + char *cached_ox_metadata; + time_t cached_ox_metadata_timestamp; +}; + +/* This is data specific to an OX driver transaction. */ +struct push_notification_driver_ox_txn { + const char *unsafe_user; +}; + +static void +push_notification_driver_ox_init_global( + struct mail_user *user, + struct push_notification_driver_ox_config *config) +{ + struct http_client_settings http_set; + struct ssl_iostream_settings ssl_set; + + if (ox_global->http_client == NULL) { + /* This is going to use the first user's settings, but these are + unlikely to change between users so it shouldn't matter much. + */ + i_zero(&http_set); + http_set.debug = user->mail_debug; + http_set.max_attempts = config->http_max_retries+1; + http_set.request_timeout_msecs = config->http_timeout_msecs; + http_set.event_parent = user->event; + mail_user_init_ssl_client_settings(user, &ssl_set); + http_set.ssl = &ssl_set; + + ox_global->http_client = http_client_init(&http_set); + } +} + +static int +push_notification_driver_ox_init(struct push_notification_driver_config *config, + struct mail_user *user, pool_t pool, + void **context, const char **error_r) +{ + struct push_notification_driver_ox_config *dconfig; + const char *error, *tmp; + + /* Valid config keys: cache_lifetime, url */ + tmp = hash_table_lookup(config->config, (const char *)"url"); + if (tmp == NULL) { + *error_r = "Driver requires the url parameter"; + return -1; + } + + dconfig = p_new(pool, struct push_notification_driver_ox_config, 1); + dconfig->event = event_create(user->event); + event_add_category(dconfig->event, &event_category_push_notification); + event_set_append_log_prefix(dconfig->event, "push-notification-ox: "); + + if (http_url_parse(tmp, NULL, HTTP_URL_ALLOW_USERINFO_PART, pool, + &dconfig->http_url, &error) < 0) { + event_unref(&dconfig->event); + *error_r = t_strdup_printf("Failed to parse OX REST URL %s: %s", + tmp, error); + return -1; + } + dconfig->use_unsafe_username = + hash_table_lookup(config->config, + (const char *)"user_from_metadata") != NULL; + + e_debug(dconfig->event, "Using URL %s", tmp); + + tmp = hash_table_lookup(config->config, (const char *)"cache_lifetime"); + if (tmp == NULL) { + dconfig->cached_ox_metadata_lifetime_secs = + DEFAULT_CACHE_LIFETIME_SECS; + } else if (settings_get_time( + tmp, &dconfig->cached_ox_metadata_lifetime_secs, &error) < 0) { + event_unref(&dconfig->event); + *error_r = t_strdup_printf( + "Failed to parse OX cache_lifetime %s: %s", tmp, error); + return -1; + } + + tmp = hash_table_lookup(config->config, (const char *)"max_retries"); + if ((tmp == NULL) || + (str_to_uint(tmp, &dconfig->http_max_retries) < 0)) { + dconfig->http_max_retries = DEFAULT_RETRY_COUNT; + } + tmp = hash_table_lookup(config->config, (const char *)"timeout_msecs"); + if ((tmp == NULL) || + (str_to_uint(tmp, &dconfig->http_timeout_msecs) < 0)) { + dconfig->http_timeout_msecs = DEFAULT_TIMEOUT_MSECS; + } + + e_debug(dconfig->event, "Using cache lifetime: %u", + dconfig->cached_ox_metadata_lifetime_secs); + + if (ox_global == NULL) { + ox_global = i_new(struct push_notification_driver_ox_global, 1); + ox_global->refcount = 0; + } + + ++ox_global->refcount; + *context = dconfig; + + return 0; +} + +static const char * +push_notification_driver_ox_get_metadata( + struct push_notification_driver_txn *dtxn) +{ + struct push_notification_driver_ox_config *dconfig = + dtxn->duser->context; + struct mail_attribute_value attr; + struct mailbox *inbox; + struct mail_namespace *ns; + bool success = FALSE, use_existing_txn = FALSE; + int ret; + + if ((dconfig->cached_ox_metadata != NULL) && + ((dconfig->cached_ox_metadata_timestamp + + (time_t)dconfig->cached_ox_metadata_lifetime_secs) > + ioloop_time)) { + return dconfig->cached_ox_metadata; + } + + /* Get canonical INBOX, where private server-level metadata is stored. + * See imap/cmd-getmetadata.c */ + if ((dtxn->ptxn->t != NULL) && dtxn->ptxn->mbox->inbox_user) { + inbox = dtxn->ptxn->mbox; + use_existing_txn = TRUE; + } else { + ns = mail_namespace_find_inbox(dtxn->ptxn->muser->namespaces); + inbox = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY); + } + + ret = mailbox_attribute_get(inbox, MAIL_ATTRIBUTE_TYPE_PRIVATE, + OX_METADATA_KEY, &attr); + if (ret < 0) { + e_error(dconfig->event, + "Skipped because unable to get attribute: %s", + mailbox_get_last_internal_error(inbox, NULL)); + } else if (ret == 0) { + e_debug(dconfig->event, + "Skipped because not active " + "(/private/"OX_METADATA_KEY" METADATA not set)"); + } else { + success = TRUE; + } + + if (!use_existing_txn) + mailbox_free(&inbox); + if (!success) + return NULL; + + i_free(dconfig->cached_ox_metadata); + dconfig->cached_ox_metadata = i_strdup(attr.value); + dconfig->cached_ox_metadata_timestamp = ioloop_time; + + return dconfig->cached_ox_metadata; +} + +static bool +push_notification_driver_ox_begin_txn(struct push_notification_driver_txn *dtxn) +{ + const char *const *args; + struct push_notification_event_messagenew_config *config; + const char *key, *mbox_curr, *md_value, *value; + bool mbox_found = FALSE; + struct push_notification_driver_ox_txn *txn; + struct push_notification_driver_ox_config *dconfig = + dtxn->duser->context; + + md_value = push_notification_driver_ox_get_metadata(dtxn); + if (md_value == NULL) + return FALSE; + + /* Unused keys: events, expire, folder */ + /* TODO: To be implemented later(?) */ + const char *const *events = default_events; + time_t expire = INT_MAX; + const char *const *mboxes = default_mboxes; + + if (expire < ioloop_time) { + e_debug(dconfig->event, "Skipped due to expiration (%ld < %ld)", + (long)expire, (long)ioloop_time); + return FALSE; + } + + mbox_curr = mailbox_get_vname(dtxn->ptxn->mbox); + for (; *mboxes != NULL; mboxes++) { + if (strcmp(mbox_curr, *mboxes) == 0) { + mbox_found = TRUE; + break; + } + } + + if (mbox_found == FALSE) { + e_debug(dconfig->event, + "Skipped because %s is not a watched mailbox", + mbox_curr); + return FALSE; + } + + txn = p_new(dtxn->ptxn->pool, + struct push_notification_driver_ox_txn, 1); + + /* Valid keys: user */ + args = t_strsplit_tabescaped(md_value); + for (; *args != NULL; args++) { + key = *args; + + value = strchr(key, '='); + if (value != NULL) { + key = t_strdup_until(key, value++); + if (strcmp(key, "user") == 0) { + txn->unsafe_user = + p_strdup(dtxn->ptxn->pool, value); + } + } + } + + if (txn->unsafe_user == NULL) { + e_error(dconfig->event, "No user provided in config"); + return FALSE; + } + + e_debug(dconfig->event, "User (%s)", txn->unsafe_user); + + for (; *events != NULL; events++) { + if (strcmp(*events, "MessageNew") == 0) { + config = p_new( + dtxn->ptxn->pool, + struct push_notification_event_messagenew_config, 1); + config->flags = PUSH_NOTIFICATION_MESSAGE_HDR_FROM | + PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT | + PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET; + push_notification_event_init( + dtxn, "MessageNew", config); + e_debug(dconfig->event, "Handling MessageNew event"); + } + } + + dtxn->context = txn; + + return TRUE; +} + +static void +push_notification_driver_ox_http_callback( + const struct http_response *response, + struct push_notification_driver_ox_config *dconfig) +{ + switch (response->status / 100) { + case 2: + // Success. + e_debug(dconfig->event, "Notification sent successfully: %s", + http_response_get_message(response)); + break; + + default: + // Error. + e_error(dconfig->event, "Error when sending notification: %s", + http_response_get_message(response)); + break; + } +} + +/* Callback needed for i_stream_add_destroy_callback() in + push_notification_driver_ox_process_msg. */ +static void str_free_i(string_t *str) +{ + str_free(&str); +} + +static int +push_notification_driver_ox_get_mailbox_status( + struct push_notification_driver_txn *dtxn, + struct mailbox_status *r_box_status) +{ + struct push_notification_driver_ox_config *dconfig = + dtxn->duser->context; + /* The already opened mailbox. We cannot use or sync it, because we are + within a save transaction. */ + struct mailbox *mbox = dtxn->ptxn->mbox; + struct mailbox *box; + int ret; + + /* Open and sync new instance of the same mailbox to get most recent + status */ + box = mailbox_alloc(mailbox_get_namespace(mbox)->list, + mailbox_get_name(mbox), MAILBOX_FLAG_READONLY); + if (mailbox_sync(box, 0) < 0) { + e_error(dconfig->event, "mailbox_sync(%s) failed: %s", + mailbox_get_vname(mbox), + mailbox_get_last_internal_error(box, NULL)); + ret = -1; + } else { + /* only 'unseen' is needed at the moment */ + mailbox_get_open_status(box, STATUS_UNSEEN, r_box_status); + e_debug(dconfig->event, + "Got status of mailbox '%s': (unseen: %u)", + mailbox_get_vname(box), r_box_status->unseen); + ret = 0; + } + + mailbox_free(&box); + return ret; +} + + +static void +push_notification_driver_ox_process_msg( + struct push_notification_driver_txn *dtxn, + struct push_notification_txn_msg *msg) +{ + struct push_notification_driver_ox_config *dconfig = + (struct push_notification_driver_ox_config *) + dtxn->duser->context; + struct http_client_request *http_req; + struct push_notification_event_messagenew_data *messagenew; + struct istream *payload; + string_t *str; + struct push_notification_driver_ox_txn *txn = + (struct push_notification_driver_ox_txn *)dtxn->context; + struct mail_user *user = dtxn->ptxn->muser; + struct mailbox_status box_status; + bool status_success = TRUE; + + if (push_notification_driver_ox_get_mailbox_status( + dtxn, &box_status) < 0) { + status_success = FALSE; + } + + messagenew = push_notification_txn_msg_get_eventdata(msg, "MessageNew"); + if (messagenew == NULL) + return; + + push_notification_driver_ox_init_global(user, dconfig); + + http_req = http_client_request_url( + ox_global->http_client, "PUT", dconfig->http_url, + push_notification_driver_ox_http_callback, dconfig); + http_client_request_set_event(http_req, dtxn->ptxn->event); + http_client_request_add_header(http_req, "Content-Type", + "application/json; charset=utf-8"); + + str = str_new(default_pool, 256); + str_append(str, "{\"user\":\""); + json_append_escaped(str, dconfig->use_unsafe_username ? + txn->unsafe_user : user->username); + str_append(str, "\",\"event\":\"messageNew\",\"folder\":\""); + json_append_escaped(str, msg->mailbox); + str_printfa(str, "\",\"imap-uidvalidity\":%u,\"imap-uid\":%u", + msg->uid_validity, msg->uid); + if (messagenew->from != NULL) { + str_append(str, ",\"from\":\""); + json_append_escaped(str, messagenew->from); + str_append(str, "\""); + } + if (messagenew->subject != NULL) { + str_append(str, ",\"subject\":\""); + json_append_escaped(str, messagenew->subject); + str_append(str, "\""); + } + if (messagenew->snippet != NULL) { + str_append(str, ",\"snippet\":\""); + json_append_escaped(str, messagenew->snippet); + str_append(str, "\""); + } + if (status_success) { + str_printfa(str, ",\"unseen\":%u", box_status.unseen); + } + str_append(str, "}"); + + e_debug(dconfig->event, "Sending notification: %s", str_c(str)); + + payload = i_stream_create_from_data(str_data(str), str_len(str)); + i_stream_add_destroy_callback(payload, str_free_i, str); + http_client_request_set_payload(http_req, payload, FALSE); + + http_client_request_submit(http_req); + i_stream_unref(&payload); +} + +static void +push_notification_driver_ox_deinit( + struct push_notification_driver_user *duser ATTR_UNUSED) +{ + struct push_notification_driver_ox_config *dconfig = duser->context; + + i_free(dconfig->cached_ox_metadata); + if (ox_global != NULL) { + if (ox_global->http_client != NULL) + http_client_wait(ox_global->http_client); + i_assert(ox_global->refcount > 0); + --ox_global->refcount; + } + event_unref(&dconfig->event); +} + +static void push_notification_driver_ox_cleanup(void) +{ + if ((ox_global != NULL) && (ox_global->refcount <= 0)) { + if (ox_global->http_client != NULL) { + http_client_deinit(&ox_global->http_client); + } + i_free_and_null(ox_global); + } +} + +/* Driver definition */ + +extern struct push_notification_driver push_notification_driver_ox; + +struct push_notification_driver push_notification_driver_ox = { + .name = "ox", + .v = { + .init = push_notification_driver_ox_init, + .begin_txn = push_notification_driver_ox_begin_txn, + .process_msg = push_notification_driver_ox_process_msg, + .deinit = push_notification_driver_ox_deinit, + .cleanup = push_notification_driver_ox_cleanup, + }, +}; diff --git a/src/plugins/push-notification/push-notification-drivers.c b/src/plugins/push-notification/push-notification-drivers.c new file mode 100644 index 0000000..67295d1 --- /dev/null +++ b/src/plugins/push-notification/push-notification-drivers.c @@ -0,0 +1,181 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "mail-user.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" + +static ARRAY(const struct push_notification_driver *) push_notification_drivers; + +static bool +push_notification_driver_find(const char *name, unsigned int *idx_r) +{ + unsigned int count, i; + const struct push_notification_driver *const *drivers; + + drivers = array_get(&push_notification_drivers, &count); + for (i = 0; i < count; i++) { + if (strcasecmp(drivers[i]->name, name) == 0) { + *idx_r = i; + return TRUE; + } + } + + return FALSE; +} + +static const struct push_notification_driver * +push_notification_driver_find_class(const char *driver) +{ + unsigned int idx; + + if (!push_notification_driver_find(driver, &idx)) + return NULL; + + return array_idx_elem(&push_notification_drivers, idx); +} + +static struct push_notification_driver_config * +push_notification_driver_parse_config(const char *p) +{ + const char **args, *key, *p2, *value; + struct push_notification_driver_config *config; + + config = t_new(struct push_notification_driver_config, 1); + config->raw_config = p; + + hash_table_create(&config->config, unsafe_data_stack_pool, 0, + str_hash, strcmp); + + if (p == NULL) + return config; + + args = t_strsplit_spaces(p, " "); + + for (; *args != NULL; args++) { + p2 = strchr(*args, '='); + if (p2 != NULL) { + key = t_strdup_until(*args, p2); + value = t_strdup(p2 + 1); + } else { + key = *args; + value = ""; + } + hash_table_update(config->config, key, value); + } + + return config; +} + +int push_notification_driver_init( + struct mail_user *user, const char *config_in, pool_t pool, + struct push_notification_driver_user **duser_r) +{ + void *context = NULL; + const struct push_notification_driver *driver; + const char *driver_name, *error_r, *p; + struct push_notification_driver_user *duser; + int ret; + + /* <driver>[:<driver config>] */ + p = strchr(config_in, ':'); + if (p == NULL) + driver_name = config_in; + else + driver_name = t_strdup_until(config_in, p); + + driver = push_notification_driver_find_class(driver_name); + if (driver == NULL) { + i_error("Unknown push notification driver: %s", driver_name); + return -1; + } + + if (driver->v.init != NULL) { + T_BEGIN { + struct push_notification_driver_config *config; + + config = push_notification_driver_parse_config( + (p == NULL) ? p : p + 1); + ret = driver->v.init(config, user, pool, + &context, &error_r); + if (ret < 0) + i_error("%s: %s", driver_name, error_r); + hash_table_destroy(&config->config); + } T_END; + + if (ret < 0) + return -1; + } + + duser = p_new(pool, struct push_notification_driver_user, 1); + duser->context = context; + duser->driver = driver; + + *duser_r = duser; + + return 0; +} + +void push_notification_driver_cleanup_all(void) +{ + const struct push_notification_driver *driver; + + /* Loop through driver list and perform global cleanup tasks. We may not + have used all drivers in this plugin/worker, but the cleanup hooks + are designed to ignore these unused drivers. */ + array_foreach_elem(&push_notification_drivers, driver) { + if (driver->v.cleanup != NULL) + driver->v.cleanup(); + } +} + +void ATTR_FORMAT(3, 4) +push_notification_driver_debug(const char *label, struct mail_user *user, + const char *fmt, ...) +{ + va_list args; + + T_BEGIN { + va_start(args, fmt); + e_debug(user->event, "%s%s", label, + t_strdup_vprintf(fmt, args)); + va_end(args); + } T_END; +} + +void push_notification_driver_register( + const struct push_notification_driver *driver) +{ + unsigned int idx; + + if (!array_is_created(&push_notification_drivers)) + i_array_init(&push_notification_drivers, 4); + + if (push_notification_driver_find(driver->name, &idx)) { + i_panic("push_notification_driver_register(%s): " + "duplicate driver", driver->name); + } + + array_push_back(&push_notification_drivers, &driver); +} + +void push_notification_driver_unregister( + const struct push_notification_driver *driver) +{ + unsigned int idx; + + if (!push_notification_driver_find(driver->name, &idx)) { + i_panic("push_notification_driver_register(%s): " + "unknown driver", driver->name); + } + + if (array_is_created(&push_notification_drivers)) { + array_delete(&push_notification_drivers, idx, 1); + + if (array_is_empty(&push_notification_drivers)) + array_free(&push_notification_drivers); + } +} diff --git a/src/plugins/push-notification/push-notification-drivers.h b/src/plugins/push-notification/push-notification-drivers.h new file mode 100644 index 0000000..07b570f --- /dev/null +++ b/src/plugins/push-notification/push-notification-drivers.h @@ -0,0 +1,123 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_DRIVERS_H +#define PUSH_NOTIFICATION_DRIVERS_H + +#include "mail-user.h" +#include "push-notification-triggers.h" + +struct mail_user; +struct push_notification_driver_config; +struct push_notification_driver_txn; +struct push_notification_driver_user; +struct push_notification_txn_mbox; +struct push_notification_txn_msg; + +HASH_TABLE_DEFINE_TYPE(push_notification_config, const char *, const char *); +HASH_TABLE_DEFINE_TYPE(push_notification_msgs, void *, + struct push_notification_txn_msg *); + +struct push_notification_driver_vfuncs { + /* Init driver. Config (from plugin configuration) is parsed once (no + user variable substitutions). Return 0 on success, or -1 if this + driver should be disabled (or on error). */ + int (*init)(struct push_notification_driver_config *config, + struct mail_user *user, pool_t pool, void **context, + const char **error_r); + /* Called at the beginning of a notification transaction. Return TRUE on + success, or FALSE if this driver should be ignored for this + transaction. */ + bool (*begin_txn)(struct push_notification_driver_txn *dtxn); + /* Called once for every mailbox processed. */ + void (*process_mbox)(struct push_notification_driver_txn *dtxn, + struct push_notification_txn_mbox *mbox); + /* Called once for every message processed. */ + void (*process_msg)(struct push_notification_driver_txn *dtxn, + struct push_notification_txn_msg *msg); + /* Called at the end of a successful notification transaction. */ + void (*end_txn)(struct push_notification_driver_txn *dtxn, + bool success); + /* Called when plugin is deinitialized. */ + void (*deinit)(struct push_notification_driver_user *duser); + /* Called to cleanup any global resources used in plugin. */ + void (*cleanup)(void); +}; + +struct push_notification_driver { + const char *name; + struct push_notification_driver_vfuncs v; +}; + +struct push_notification_driver_config { + HASH_TABLE_TYPE(push_notification_config) config; + const char *raw_config; +}; + +struct push_notification_driver_user { + const struct push_notification_driver *driver; + void *context; +}; + +struct push_notification_driver_txn { + const struct push_notification_driver_user *duser; + struct push_notification_txn *ptxn; + + /* Transaction context. */ + void *context; +}; + +struct push_notification_driver_list { + ARRAY(struct push_notification_driver_user *) drivers; +}; + +struct push_notification_user { + union mail_user_module_context module_ctx; + struct push_notification_driver_list *driverlist; +}; + +struct push_notification_trigger_ctx { + const char *name; + void *context; +}; + +struct push_notification_txn { + pool_t pool; + + struct mailbox *mbox; + struct mail_user *muser; + struct push_notification_user *puser; + bool initialized; + + enum push_notification_event_trigger trigger; + struct push_notification_trigger_ctx *trigger_ctx; + ARRAY(struct push_notification_driver_txn *) drivers; + ARRAY(struct push_notification_event_config *) events; + + struct event *event; + + /* Used with mailbox events. */ + struct push_notification_txn_mbox *mbox_txn; + + /* Used with mailbox events. */ + HASH_TABLE_TYPE(push_notification_msgs) messages; + + /* Private (used with message events). */ + struct mailbox_transaction_context *t; +}; + + +int push_notification_driver_init( + struct mail_user *user, const char *config_in, pool_t pool, + struct push_notification_driver_user **duser_r); +void push_notification_driver_cleanup_all(void); + +void ATTR_FORMAT(3, 4) +push_notification_driver_debug(const char *label, struct mail_user *user, + const char *fmt, ...); + +void push_notification_driver_register( + const struct push_notification_driver *driver); +void push_notification_driver_unregister( + const struct push_notification_driver *driver); + +#endif diff --git a/src/plugins/push-notification/push-notification-event-flagsclear.c b/src/plugins/push-notification/push-notification-event-flagsclear.c new file mode 100644 index 0000000..2ce0c3f --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-flagsclear.c @@ -0,0 +1,170 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "mail-storage.h" +#include "mail-types.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-flagsclear.h" +#include "push-notification-txn-msg.h" + +#define EVENT_NAME "FlagsClear" + +static struct push_notification_event_flagsclear_config default_config; + + +static void *push_notification_event_flagsclear_default_config(void) +{ + i_zero(&default_config); + + return &default_config; +} + +static void +push_notification_event_flagsclear_debug_msg( + struct push_notification_txn_event *event) +{ + struct push_notification_event_flagsclear_data *data = event->data; + const char *keyword; + + if ((data->flags_clear & MAIL_ANSWERED) != 0) + i_debug("%s: Answered flag cleared", EVENT_NAME); + if ((data->flags_clear & MAIL_FLAGGED) != 0) + i_debug("%s: Flagged flag cleared", EVENT_NAME); + if ((data->flags_clear & MAIL_DELETED) != 0) + i_debug("%s: Deleted flag cleared", EVENT_NAME); + if ((data->flags_clear & MAIL_SEEN) != 0) + i_debug("%s: Seen flag cleared", EVENT_NAME); + if ((data->flags_clear & MAIL_DRAFT) != 0) + i_debug("%s: Draft flag cleared", EVENT_NAME); + + array_foreach_elem(&data->keywords_clear, keyword) + i_debug("%s: Keyword clear [%s]", EVENT_NAME, keyword); +} + +static struct push_notification_event_flagsclear_data * +push_notification_event_flagsclear_get_data( + struct push_notification_txn *ptxn, + struct push_notification_txn_msg *msg, + struct push_notification_event_config *ec) +{ + struct push_notification_event_flagsclear_config *config = + (struct push_notification_event_flagsclear_config *)ec->config; + struct push_notification_event_flagsclear_data *data; + + data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME); + if (data == NULL) { + data = p_new(ptxn->pool, + struct push_notification_event_flagsclear_data, 1); + data->flags_clear = 0; + data->flags_old = 0; + p_array_init(&data->keywords_clear, ptxn->pool, 4); + if (config->store_old == TRUE) + p_array_init(&data->keywords_old, ptxn->pool, 4); + + push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data); + } + + return data; +} + +static void +push_notification_event_flagsclear_flags_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, struct mail *mail, + enum mail_flags old_flags) +{ + struct push_notification_event_flagsclear_config *config = + (struct push_notification_event_flagsclear_config *)ec->config; + struct push_notification_event_flagsclear_data *data; + enum mail_flags flag_check_always[] = { + MAIL_ANSWERED, + MAIL_DELETED, + MAIL_DRAFT, + MAIL_FLAGGED, + MAIL_SEEN + }; + enum mail_flags flags; + unsigned int i; + + data = push_notification_event_flagsclear_get_data(ptxn, msg, ec); + flags = mail_get_flags(mail); + + for (i = 0; i < N_ELEMENTS(flag_check_always); i++) { + if ((flags & flag_check_always[i]) == 0 && + (old_flags & flag_check_always[i]) != 0) + data->flags_clear |= flag_check_always[i]; + } + + if (config->store_old == TRUE) + data->flags_old = old_flags; +} + +static void +push_notification_event_flagsclear_keywords_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, struct mail *mail, + const char *const *old_keywords) +{ + struct push_notification_event_flagsclear_config *config = + (struct push_notification_event_flagsclear_config *)ec->config; + struct push_notification_event_flagsclear_data *data; + const char *const *keywords, *const *kp, *ok; + + data = push_notification_event_flagsclear_get_data(ptxn, msg, ec); + keywords = mail_get_keywords(mail); + + for (; *old_keywords != NULL; old_keywords++) { + for (kp = keywords; *kp != NULL; kp++) { + if (strcmp(*old_keywords, *kp) == 0) + break; + } + + if (*kp == NULL) { + ok = p_strdup(ptxn->pool, *old_keywords); + array_push_back(&data->keywords_clear, &ok); + } + + if (config->store_old == TRUE) { + ok = p_strdup(ptxn->pool, *old_keywords); + array_push_back(&data->keywords_old, &ok); + } + } +} + +static void +push_notification_event_flagsclear_free_msg( + struct push_notification_txn_event *event) +{ + struct push_notification_event_flagsclear_data *data = event->data; + + if (array_is_created(&data->keywords_clear)) + array_free(&data->keywords_clear); + if (array_is_created(&data->keywords_old)) + array_free(&data->keywords_old); +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_flagsclear; + +struct push_notification_event push_notification_event_flagsclear = { + .name = EVENT_NAME, + .init = { + .default_config = + push_notification_event_flagsclear_default_config, + }, + .msg = { + .debug_msg = push_notification_event_flagsclear_debug_msg, + .free_msg = push_notification_event_flagsclear_free_msg, + }, + .msg_triggers = { + .flagchange = push_notification_event_flagsclear_flags_event, + .keywordchange = + push_notification_event_flagsclear_keywords_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-flagsclear.h b/src/plugins/push-notification/push-notification-event-flagsclear.h new file mode 100644 index 0000000..5412335 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-flagsclear.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_FLAGSCLEAR_H +#define PUSH_NOTIFICATION_EVENT_FLAGSCLEAR_H + +#include "mail-types.h" + +struct push_notification_event_flagsclear_config { + /* Store the old flags/keywords? */ + bool store_old; +}; + +struct push_notification_event_flagsclear_data { + enum mail_flags flags_clear; + ARRAY_TYPE(keywords) keywords_clear; + + enum mail_flags flags_old; + ARRAY_TYPE(keywords) keywords_old; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-flagsset.c b/src/plugins/push-notification/push-notification-event-flagsset.c new file mode 100644 index 0000000..c596ced --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-flagsset.c @@ -0,0 +1,167 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "mail-storage.h" +#include "mail-types.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-flagsset.h" +#include "push-notification-txn-msg.h" + + +#define EVENT_NAME "FlagsSet" + +static struct push_notification_event_flagsset_config default_config; + + +static void *push_notification_event_flagsset_default_config(void) +{ + i_zero(&default_config); + + return &default_config; +} + +static void +push_notification_event_flagsset_debug_msg( + struct push_notification_txn_event *event) +{ + struct push_notification_event_flagsset_data *data = event->data; + const char *keyword; + + if ((data->flags_set & MAIL_ANSWERED) != 0) + i_debug("%s: Answered flag set", EVENT_NAME); + if ((data->flags_set & MAIL_FLAGGED) != 0) + i_debug("%s: Flagged flag set", EVENT_NAME); + if ((data->flags_set & MAIL_DELETED) != 0) + i_debug("%s: Deleted flag set", EVENT_NAME); + if ((data->flags_set & MAIL_SEEN) != 0) + i_debug("%s: Seen flag set", EVENT_NAME); + if ((data->flags_set & MAIL_DRAFT) != 0) + i_debug("%s: Draft flag set", EVENT_NAME); + + array_foreach_elem(&data->keywords_set, keyword) + i_debug("%s: Keyword set [%s]", EVENT_NAME, keyword); +} + +static struct push_notification_event_flagsset_data * +push_notification_event_flagsset_get_data( + struct push_notification_txn *ptxn, + struct push_notification_txn_msg *msg, + struct push_notification_event_config *ec) +{ + struct push_notification_event_flagsset_data *data; + + data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME); + if (data == NULL) { + data = p_new(ptxn->pool, + struct push_notification_event_flagsset_data, 1); + data->flags_set = 0; + p_array_init(&data->keywords_set, ptxn->pool, 4); + + push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data); + } + + return data; +} + +static void +push_notification_event_flagsset_flags_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, struct mail *mail, + enum mail_flags old_flags) +{ + struct push_notification_event_flagsset_config *config = + (struct push_notification_event_flagsset_config *)ec->config; + struct push_notification_event_flagsset_data *data; + enum mail_flags flag_check_always[] = { + MAIL_ANSWERED, + MAIL_DRAFT, + MAIL_FLAGGED + }; + enum mail_flags flags, flags_set = 0; + unsigned int i; + + flags = mail_get_flags(mail); + + for (i = 0; i < N_ELEMENTS(flag_check_always); i++) { + if ((flags & flag_check_always[i]) != 0 && + (old_flags & flag_check_always[i]) == 0) + flags_set |= flag_check_always[i]; + } + + if (!config->hide_deleted && (flags & MAIL_DELETED) != 0 && + (old_flags & MAIL_DELETED) == 0) { + flags_set |= MAIL_DELETED; + } + + if (!config->hide_seen && (flags & MAIL_SEEN) != 0 && + (old_flags & MAIL_SEEN) == 0) { + flags_set |= MAIL_SEEN; + } + + /* Only create data element if at least one flag was set. */ + if (flags_set != 0) { + data = push_notification_event_flagsset_get_data(ptxn, msg, ec); + data->flags_set |= flags_set; + } +} + +static void +push_notification_event_flagsset_keywords_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, struct mail *mail, + const char *const *old_keywords) +{ + struct push_notification_event_flagsset_data *data; + const char *k, *const *keywords, *const *op; + + data = push_notification_event_flagsset_get_data(ptxn, msg, ec); + keywords = mail_get_keywords(mail); + + for (; *keywords != NULL; keywords++) { + for (op = old_keywords; *op != NULL; op++) { + if (strcmp(*keywords, *op) == 0) + break; + } + + if (*op == NULL) { + k = p_strdup(ptxn->pool, *keywords); + array_push_back(&data->keywords_set, &k); + } + } +} + +static void +push_notification_event_flagsset_free_msg( + struct push_notification_txn_event *event) +{ + struct push_notification_event_flagsset_data *data = event->data; + + if (array_is_created(&data->keywords_set)) + array_free(&data->keywords_set); +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_flagsset; + +struct push_notification_event push_notification_event_flagsset = { + .name = EVENT_NAME, + .init = { + .default_config = + push_notification_event_flagsset_default_config, + }, + .msg = { + .debug_msg = push_notification_event_flagsset_debug_msg, + .free_msg = push_notification_event_flagsset_free_msg, + }, + .msg_triggers = { + .flagchange = push_notification_event_flagsset_flags_event, + .keywordchange = + push_notification_event_flagsset_keywords_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-flagsset.h b/src/plugins/push-notification/push-notification-event-flagsset.h new file mode 100644 index 0000000..4830260 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-flagsset.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_FLAGSSET_H +#define PUSH_NOTIFICATION_EVENT_FLAGSSET_H + +#include "mail-types.h" + +struct push_notification_event_flagsset_config { + /* RFC 5423[4.2] - allow configuration whether FlagsSet event returns + Deleted and/or Seen flags, since these flags are also settable via + MessageRead/MessageTrash events. By default, include them here. */ + bool hide_deleted; + bool hide_seen; +}; + +struct push_notification_event_flagsset_data { + enum mail_flags flags_set; + ARRAY_TYPE(keywords) keywords_set; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-mailboxcreate.c b/src/plugins/push-notification/push-notification-event-mailboxcreate.c new file mode 100644 index 0000000..87e46c1 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxcreate.c @@ -0,0 +1,56 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-mailboxcreate.h" +#include "push-notification-txn-mbox.h" + +#define EVENT_NAME "MailboxCreate" + +static void +push_notification_event_mailboxcreate_debug_mbox( + struct push_notification_txn_event *event ATTR_UNUSED) +{ + i_debug("%s: Mailbox was created", EVENT_NAME); +} + +static void +push_notification_event_mailboxcreate_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_event_mailboxcreate_data *data; + struct mailbox_status status; + + if (mailbox_get_status(ptxn->mbox, STATUS_UIDVALIDITY, &status) < 0) { + i_error(EVENT_NAME + "Failed to get created mailbox '%s' uidvalidity: %s", + mailbox_get_vname(ptxn->mbox), + mailbox_get_last_internal_error(ptxn->mbox, NULL)); + status.uidvalidity = 0; + } + + data = p_new(ptxn->pool, + struct push_notification_event_mailboxcreate_data, 1); + data->uid_validity = status.uidvalidity; + + push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data); +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_mailboxcreate; + +struct push_notification_event push_notification_event_mailboxcreate = { + .name = EVENT_NAME, + .mbox = { + .debug_mbox = push_notification_event_mailboxcreate_debug_mbox, + }, + .mbox_triggers = { + .create = push_notification_event_mailboxcreate_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-mailboxcreate.h b/src/plugins/push-notification/push-notification-event-mailboxcreate.h new file mode 100644 index 0000000..9a5e1bf --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxcreate.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXCREATE_H +#define PUSH_NOTIFICATION_EVENT_MAILBOXCREATE_H + +struct push_notification_event_mailboxcreate_data { + /* RFC 5423 [4.4]: UIDVALIDITY required for create event. */ + uint32_t uid_validity; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-mailboxdelete.c b/src/plugins/push-notification/push-notification-event-mailboxdelete.c new file mode 100644 index 0000000..8e65f3c --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxdelete.c @@ -0,0 +1,46 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-mailboxdelete.h" +#include "push-notification-txn-mbox.h" + +#define EVENT_NAME "MailboxDelete" + +static void +push_notification_event_mailboxdelete_debug_mbox( + struct push_notification_txn_event *event ATTR_UNUSED) +{ + i_debug("%s: Mailbox was deleted", EVENT_NAME); +} + +static void +push_notification_event_mailboxdelete_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_event_mailboxdelete_data *data; + + data = p_new(ptxn->pool, + struct push_notification_event_mailboxdelete_data, 1); + data->deleted = TRUE; + + push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data); +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_mailboxdelete; + +struct push_notification_event push_notification_event_mailboxdelete = { + .name = EVENT_NAME, + .mbox = { + .debug_mbox = push_notification_event_mailboxdelete_debug_mbox, + }, + .mbox_triggers = { + .delete = push_notification_event_mailboxdelete_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-mailboxdelete.h b/src/plugins/push-notification/push-notification-event-mailboxdelete.h new file mode 100644 index 0000000..ac36747 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxdelete.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXDELETE_H +#define PUSH_NOTIFICATION_EVENT_MAILBOXDELETE_H + +struct push_notification_event_mailboxdelete_data { + /* Can only be TRUE. */ + bool deleted; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-mailboxrename.c b/src/plugins/push-notification/push-notification-event-mailboxrename.c new file mode 100644 index 0000000..e4c2cae --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxrename.c @@ -0,0 +1,50 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-mailboxrename.h" +#include "push-notification-txn-mbox.h" + +#define EVENT_NAME "MailboxRename" + +static void +push_notification_event_mailboxrename_debug_mbox( + struct push_notification_txn_event *event) +{ + struct push_notification_event_mailboxrename_data *data = event->data; + + i_debug("%s: Mailbox was renamed (old name: %s)", + EVENT_NAME, data->old_mbox); +} + +static void +push_notification_event_mailboxrename_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox, struct mailbox *old) +{ + struct push_notification_event_mailboxrename_data *data; + + data = p_new(ptxn->pool, + struct push_notification_event_mailboxrename_data, 1); + data->old_mbox = mailbox_get_vname(old); + + push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data); +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_mailboxrename; + +struct push_notification_event push_notification_event_mailboxrename = { + .name = EVENT_NAME, + .mbox = { + .debug_mbox = push_notification_event_mailboxrename_debug_mbox, + }, + .mbox_triggers = { + .rename = push_notification_event_mailboxrename_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-mailboxrename.h b/src/plugins/push-notification/push-notification-event-mailboxrename.h new file mode 100644 index 0000000..66e21f8 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxrename.h @@ -0,0 +1,11 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXRENAME_H +#define PUSH_NOTIFICATION_EVENT_MAILBOXRENAME_H + +struct push_notification_event_mailboxrename_data { + const char *old_mbox; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-mailboxsubscribe.c b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.c new file mode 100644 index 0000000..0bcd101 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.c @@ -0,0 +1,48 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-mailboxsubscribe.h" +#include "push-notification-txn-mbox.h" + +#define EVENT_NAME "MailboxSubscribe" + +static void +push_notification_event_mailboxsubscribe_debug_mbox( + struct push_notification_txn_event *event ATTR_UNUSED) +{ + i_debug("%s: Mailbox was subscribed to", EVENT_NAME); +} + +static void +push_notification_event_mailboxsubscribe_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_event_mailboxsubscribe_data *data; + + data = p_new(ptxn->pool, + struct push_notification_event_mailboxsubscribe_data, 1); + data->subscribe = TRUE; + + push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data); +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_mailboxsubscribe; + +struct push_notification_event push_notification_event_mailboxsubscribe = { + .name = EVENT_NAME, + .mbox = { + .debug_mbox = + push_notification_event_mailboxsubscribe_debug_mbox, + }, + .mbox_triggers = { + .subscribe = push_notification_event_mailboxsubscribe_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-mailboxsubscribe.h b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.h new file mode 100644 index 0000000..d278482 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXSUBSCRIBE_H +#define PUSH_NOTIFICATION_EVENT_MAILBOXSUBSCRIBE_H + +struct push_notification_event_mailboxsubscribe_data { + /* Can only be TRUE. */ + bool subscribe; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c new file mode 100644 index 0000000..b8d078f --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c @@ -0,0 +1,48 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-mailboxunsubscribe.h" +#include "push-notification-txn-mbox.h" + +#define EVENT_NAME "MailboxUnsubscribe" + +static void +push_notification_event_mailboxunsubscribe_debug_mbox( + struct push_notification_txn_event *event ATTR_UNUSED) +{ + i_debug("%s: Mailbox was subscribed to", EVENT_NAME); +} + +static void +push_notification_event_mailboxunsubscribe_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_event_mailboxunsubscribe_data *data; + + data = p_new(ptxn->pool, + struct push_notification_event_mailboxunsubscribe_data, 1); + data->subscribe = FALSE; + + push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data); +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_mailboxunsubscribe; + +struct push_notification_event push_notification_event_mailboxunsubscribe = { + .name = EVENT_NAME, + .mbox = { + .debug_mbox = + push_notification_event_mailboxunsubscribe_debug_mbox, + }, + .mbox_triggers = { + .unsubscribe = push_notification_event_mailboxunsubscribe_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h new file mode 100644 index 0000000..c519da2 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXUNSUBSCRIBE_H +#define PUSH_NOTIFICATION_EVENT_MAILBOXUNSUBSCRIBE_H + +struct push_notification_event_mailboxunsubscribe_data { + /* Can only be FALSE. */ + bool subscribe; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-message-common.c b/src/plugins/push-notification/push-notification-event-message-common.c new file mode 100644 index 0000000..7965039 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-message-common.c @@ -0,0 +1,117 @@ +/* Copyright (c) 2015-2019 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "message-address.h" +#include "message-header-decode.h" +#include "mail-storage.h" +#include "push-notification-event-message-common.h" + +static void +decode_address_header(pool_t pool, const char *hdr, + const char **address_r, const char **name_r) +{ + struct message_address *addr; + const char *display_name; + + if (hdr == NULL) + return; + + addr = message_address_parse(pool_datastack_create(), + (const unsigned char *)hdr, strlen(hdr), 1, 0); + if (addr == NULL) + return; + + display_name = addr->name; + if (addr->domain == NULL) { + /* group */ + display_name = addr->mailbox; + } else if (addr->domain[0] != '\0') + *address_r = p_strdup_printf(pool, "%s@%s", addr->mailbox, + addr->domain); + else if (addr->mailbox != NULL && addr->mailbox[0] != '\0') + *address_r = p_strdup(pool, addr->mailbox); + + if (display_name != NULL && display_name[0] != '\0') { + string_t *name_utf8 = t_str_new(128); + + message_header_decode_utf8((const unsigned char *)display_name, + strlen(display_name), name_utf8, NULL); + *name_r = p_strdup(pool, str_c(name_utf8)); + } +} + +void push_notification_message_fill( + struct mail *mail, pool_t pool, + enum push_notification_event_message_flags event_flags, + const char **from, const char **to, const char **subject, time_t *date, + int *date_tz, const char **message_id, enum mail_flags *flags, + bool *flags_set, const char *const **keywords, const char **snippet, + struct push_notification_message_ext *ext) +{ + const char *value; + time_t tmp_date; + int tmp_tz; + + if ((*from == NULL) && + (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_FROM) != 0 && + (mail_get_first_header(mail, "From", &value) >= 0)) { + *from = p_strdup(pool, value); + decode_address_header(pool, value, &ext->from_address, + &ext->from_display_name_utf8); + } + + if ((*to == NULL) && + (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_TO) != 0 && + (mail_get_first_header(mail, "To", &value) >= 0)) { + *to = p_strdup(pool, value); + decode_address_header(pool, value, &ext->to_address, + &ext->to_display_name_utf8); + } + + if ((*subject == NULL) && + (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT) != 0 && + (mail_get_first_header(mail, "Subject", &value) >= 0)) { + string_t *subject_utf8 = t_str_new(128); + + *subject = p_strdup(pool, value); + if (value != NULL) { + message_header_decode_utf8( + (const unsigned char *)value, + strlen(value), subject_utf8, NULL); + ext->subject_utf8 = p_strdup(pool, str_c(subject_utf8)); + } + } + + if ((*date == -1) && + (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_DATE) != 0 && + (mail_get_date(mail, &tmp_date, &tmp_tz) >= 0)) { + *date = tmp_date; + *date_tz = tmp_tz; + } + + if ((*message_id == NULL) && + (event_flags & PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID) != 0 && + (mail_get_first_header(mail, "Message-ID", &value) >= 0)) { + *message_id = p_strdup(pool, value); + } + + if (!*flags_set && + (event_flags & PUSH_NOTIFICATION_MESSAGE_FLAGS) != 0) { + *flags = mail_get_flags(mail); + *flags_set = TRUE; + } + + if ((*keywords == NULL) && + (event_flags & PUSH_NOTIFICATION_MESSAGE_KEYWORDS) != 0) { + *keywords = p_strarray_dup(pool, mail_get_keywords(mail)); + } + + if ((*snippet == NULL) && + (event_flags & PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET) != 0 && + (mail_get_special(mail, MAIL_FETCH_BODY_SNIPPET, &value) >= 0)) { + /* [0] contains the snippet algorithm, skip over it */ + i_assert(value[0] != '\0'); + *snippet = p_strdup(pool, value + 1); + } +} diff --git a/src/plugins/push-notification/push-notification-event-message-common.h b/src/plugins/push-notification/push-notification-event-message-common.h new file mode 100644 index 0000000..48c37de --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-message-common.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MESSAGE_COMMON_H +#define PUSH_NOTIFICATION_EVENT_MESSAGE_COMMON_H + +enum push_notification_event_message_flags { + /* Header: From */ + PUSH_NOTIFICATION_MESSAGE_HDR_FROM = 0x01, + /* Header: To */ + PUSH_NOTIFICATION_MESSAGE_HDR_TO = 0x02, + /* Header: Subject */ + PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT = 0x04, + /* Header: Date */ + PUSH_NOTIFICATION_MESSAGE_HDR_DATE = 0x08, + /* Body: Snippet */ + PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET = 0x10, + /* Meta: Flags */ + PUSH_NOTIFICATION_MESSAGE_FLAGS = 0x20, + /* Meta: Keywords */ + PUSH_NOTIFICATION_MESSAGE_KEYWORDS = 0x40, + /* Header: Message-ID */ + PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID = 0x80, +}; + +struct push_notification_message_ext { + const char *from_address, *from_display_name_utf8; + const char *to_address, *to_display_name_utf8; + const char *subject_utf8; +}; + +void push_notification_message_fill( + struct mail *mail, pool_t pool, + enum push_notification_event_message_flags event_flags, + const char **from, const char **to, const char **subject, time_t *date, + int *date_tz, const char **message_id, enum mail_flags *flags, + bool *flags_set, const char *const **keywords, const char **snippet, + struct push_notification_message_ext *ext); + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-messageappend.c b/src/plugins/push-notification/push-notification-event-messageappend.c new file mode 100644 index 0000000..b6e0318 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messageappend.c @@ -0,0 +1,99 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "istream.h" +#include "iso8601-date.h" +#include "mail-storage.h" + +#include <time.h> + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-message-common.h" +#include "push-notification-event-messageappend.h" +#include "push-notification-txn-msg.h" + +#define EVENT_NAME "MessageAppend" + +static struct push_notification_event_messageappend_config default_config; + +static void *push_notification_event_messageappend_default_config(void) +{ + i_zero(&default_config); + + return &default_config; +} + +static void +push_notification_event_messageappend_debug_msg( + struct push_notification_txn_event *event) +{ + struct push_notification_event_messageappend_data *data = event->data; + struct tm *tm; + + if (data->date != -1) { + tm = gmtime(&data->date); + i_debug("%s: Date [%s]", EVENT_NAME, + iso8601_date_create_tm(tm, data->date_tz)); + } + + if (data->from != NULL) + i_debug("%s: From [%s]", EVENT_NAME, data->from); + if (data->snippet != NULL) + i_debug("%s: Snippet [%s]", EVENT_NAME, data->snippet); + if (data->subject != NULL) + i_debug("%s: Subject [%s]", EVENT_NAME, data->subject); + if (data->to != NULL) + i_debug("%s: To [%s]", EVENT_NAME, data->to); +} + +static void +push_notification_event_messageappend_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, struct mail *mail) +{ + struct push_notification_event_messageappend_config *config = + (struct push_notification_event_messageappend_config *) + ec->config; + struct push_notification_event_messageappend_data *data; + + if (config->flags == 0) + return; + + data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME); + if (data == NULL) { + data = p_new(ptxn->pool, + struct push_notification_event_messageappend_data, 1); + data->date = -1; + push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data); + } + + push_notification_message_fill(mail, ptxn->pool, config->flags, + &data->from, &data->to, &data->subject, + &data->date, &data->date_tz, + &data->message_id, + &data->flags, &data->flags_set, + &data->keywords, + &data->snippet, &data->ext); +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_messageappend; + +struct push_notification_event push_notification_event_messageappend = { + .name = EVENT_NAME, + .init = { + .default_config = + push_notification_event_messageappend_default_config, + }, + .msg = { + .debug_msg = push_notification_event_messageappend_debug_msg, + }, + .msg_triggers = { + .append = push_notification_event_messageappend_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-messageappend.h b/src/plugins/push-notification/push-notification-event-messageappend.h new file mode 100644 index 0000000..511fc6b --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messageappend.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEAPPEND_H +#define PUSH_NOTIFICATION_EVENT_MESSAGEAPPEND_H + +struct push_notification_event_messageappend_config { + enum push_notification_event_message_flags flags; +}; + +struct push_notification_event_messageappend_data { + const char *from; + const char *to; + const char *subject; + const char *snippet; + /* PUSH_NOTIFICATION_MESSAGE_HDR_DATE */ + time_t date; + int date_tz; + /* PUSH_NOTIFICATION_MESSAGE_FLAGS */ + bool flags_set; + enum mail_flags flags; + /* PUSH_NOTIFICATION_MESSAGE_KEYWORDS */ + const char *const *keywords; + /* PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID */ + const char *message_id; + + struct push_notification_message_ext ext; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-messageexpunge.c b/src/plugins/push-notification/push-notification-event-messageexpunge.c new file mode 100644 index 0000000..cfa301e --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messageexpunge.c @@ -0,0 +1,53 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage.h" +#include "mail-types.h" + +#include "push-notification-drivers.h" +#include "push-notification-event-messageexpunge.h" +#include "push-notification-events.h" +#include "push-notification-txn-msg.h" + +#define EVENT_NAME "MessageExpunge" + +static void +push_notification_event_messageexpunge_debug_msg( + struct push_notification_txn_event *event) +{ + struct push_notification_event_messageexpunge_data *data = event->data; + + if (data != NULL) + i_debug("%s: Message was expunged", EVENT_NAME); +} + +static void +push_notification_event_messageexpunge_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg) +{ + struct push_notification_event_messageexpunge_data *data; + + data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME); + if (data == NULL) { + data = p_new(ptxn->pool, + struct push_notification_event_messageexpunge_data, 1); + data->expunge = TRUE; + push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data); + } +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_messageexpunge; + +struct push_notification_event push_notification_event_messageexpunge = { + .name = EVENT_NAME, + .msg = { + .debug_msg = push_notification_event_messageexpunge_debug_msg, + }, + .msg_triggers = { + .expunge = push_notification_event_messageexpunge_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-messageexpunge.h b/src/plugins/push-notification/push-notification-event-messageexpunge.h new file mode 100644 index 0000000..bc20f73 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messageexpunge.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEEXPUNGE_H +#define PUSH_NOTIFICATION_EVENT_MESSAGEEXPUNGE_H + +struct push_notification_event_messageexpunge_data { + /* Can only be TRUE. */ + bool expunge; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-messagenew.c b/src/plugins/push-notification/push-notification-event-messagenew.c new file mode 100644 index 0000000..930c0ee --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messagenew.c @@ -0,0 +1,97 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "iso8601-date.h" +#include "istream.h" +#include "mail-storage.h" + +#include <time.h> + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-message-common.h" +#include "push-notification-event-messagenew.h" +#include "push-notification-txn-msg.h" + +#define EVENT_NAME "MessageNew" + +static struct push_notification_event_messagenew_config default_config; + +static void *push_notification_event_messagenew_default_config(void) +{ + i_zero(&default_config); + + return &default_config; +} + +static void +push_notification_event_messagenew_debug_msg( + struct push_notification_txn_event *event) +{ + struct push_notification_event_messagenew_data *data = event->data; + struct tm *tm; + + if (data->date != -1) { + tm = gmtime(&data->date); + i_debug("%s: Date [%s]", EVENT_NAME, + iso8601_date_create_tm(tm, data->date_tz)); + } + + if (data->from != NULL) + i_debug("%s: From [%s]", EVENT_NAME, data->from); + if (data->snippet != NULL) + i_debug("%s: Snippet [%s]", EVENT_NAME, data->snippet); + if (data->subject != NULL) + i_debug("%s: Subject [%s]", EVENT_NAME, data->subject); + if (data->to != NULL) + i_debug("%s: To [%s]", EVENT_NAME, data->to); +} + +static void +push_notification_event_messagenew_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, struct mail *mail) +{ + struct push_notification_event_messagenew_config *config = + (struct push_notification_event_messagenew_config *)ec->config; + struct push_notification_event_messagenew_data *data; + + if (config->flags == 0) + return; + + data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME); + if (data == NULL) { + data = p_new(ptxn->pool, + struct push_notification_event_messagenew_data, 1); + data->date = -1; + + push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data); + } + + push_notification_message_fill(mail, ptxn->pool, config->flags, + &data->from, &data->to, &data->subject, + &data->date, &data->date_tz, + &data->message_id, + &data->flags, &data->flags_set, + &data->keywords, + &data->snippet, &data->ext); +} + +/* Event definition */ + +struct push_notification_event push_notification_event_messagenew = { + .name = EVENT_NAME, + .init = { + .default_config = + push_notification_event_messagenew_default_config, + }, + .msg = { + .debug_msg = push_notification_event_messagenew_debug_msg, + }, + .msg_triggers = { + .save = push_notification_event_messagenew_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-messagenew.h b/src/plugins/push-notification/push-notification-event-messagenew.h new file mode 100644 index 0000000..a939668 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messagenew.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MESSAGENEW_H +#define PUSH_NOTIFICATION_EVENT_MESSAGENEW_H + +#include "push-notification-event-message-common.h" + +struct push_notification_event_messagenew_config { + enum push_notification_event_message_flags flags; +}; + +struct push_notification_event_messagenew_data { + /* PUSH_NOTIFICATION_MESSAGE_HDR_FROM */ + const char *from; + /* PUSH_NOTIFICATION_MESSAGE_HDR_TO */ + const char *to; + /* PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT */ + const char *subject; + /* PUSH_NOTIFICATION_MESSAGE_HDR_DATE */ + time_t date; + int date_tz; + /* PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET */ + const char *snippet; + /* PUSH_NOTIFICATION_MESSAGE_FLAGS */ + bool flags_set; + enum mail_flags flags; + /* PUSH_NOTIFICATION_MESSAGE_KEYWORDS */ + const char *const *keywords; + /* PUSH_NOTIFICATION_MESSAGE_HDR_MESSAGE_ID */ + const char *message_id; + + struct push_notification_message_ext ext; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-event-messageread.c b/src/plugins/push-notification/push-notification-event-messageread.c new file mode 100644 index 0000000..8f0d709 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messageread.c @@ -0,0 +1,58 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage.h" +#include "mail-types.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-messageread.h" +#include "push-notification-txn-msg.h" + +#define EVENT_NAME "MessageRead" + +static void +push_notification_event_messageread_debug_msg( + struct push_notification_txn_event *event ATTR_UNUSED) +{ + i_debug("%s: Message was flagged as seen", EVENT_NAME); +} + +static void +push_notification_event_messageread_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, struct mail *mail, + enum mail_flags old_flags) +{ + struct push_notification_event_messageread_data *data; + enum mail_flags flags; + + /* If data struct exists, that means the read flag was changed. */ + data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME); + if ((data == NULL) && (old_flags & MAIL_SEEN) == 0) { + flags = mail_get_flags(mail); + if ((flags & MAIL_SEEN) != 0) { + data = p_new( + ptxn->pool, + struct push_notification_event_messageread_data, 1); + data->read = TRUE; + push_notification_txn_msg_set_eventdata( + ptxn, msg, ec, data); + } + } +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_messageread; + +struct push_notification_event push_notification_event_messageread = { + .name = EVENT_NAME, + .msg = { + .debug_msg = push_notification_event_messageread_debug_msg, + }, + .msg_triggers = { + .flagchange = push_notification_event_messageread_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-messageread.h b/src/plugins/push-notification/push-notification-event-messageread.h new file mode 100644 index 0000000..6eb5c18 --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messageread.h @@ -0,0 +1,11 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEREAD_H +#define PUSH_NOTIFICATION_EVENT_MESSAGEREAD_H + +struct push_notification_event_messageread_data { + /* Can only be TRUE. */ + bool read; +}; + +#endif diff --git a/src/plugins/push-notification/push-notification-event-messagetrash.c b/src/plugins/push-notification/push-notification-event-messagetrash.c new file mode 100644 index 0000000..2284b6f --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messagetrash.c @@ -0,0 +1,58 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage.h" +#include "mail-types.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-event-messagetrash.h" +#include "push-notification-txn-msg.h" + +#define EVENT_NAME "MessageTrash" + +static void +push_notification_event_messagetrash_debug_msg( + struct push_notification_txn_event *event ATTR_UNUSED) +{ + i_debug("%s: Message was marked as deleted", EVENT_NAME); +} + +static void +push_notification_event_messagetrash_event( + struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, struct mail *mail, + enum mail_flags old_flags) +{ + struct push_notification_event_messagetrash_data *data; + enum mail_flags flags; + + /* If data struct exists, that means the deleted flag was changed. */ + data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME); + if ((data == NULL) && (old_flags & MAIL_DELETED) == 0) { + flags = mail_get_flags(mail); + if ((flags & MAIL_DELETED) != 0) { + data = p_new( + ptxn->pool, + struct push_notification_event_messagetrash_data, 1); + data->trash = TRUE; + push_notification_txn_msg_set_eventdata( + ptxn, msg, ec, data); + } + } +} + +/* Event definition */ + +extern struct push_notification_event push_notification_event_messagetrash; + +struct push_notification_event push_notification_event_messagetrash = { + .name = EVENT_NAME, + .msg = { + .debug_msg = push_notification_event_messagetrash_debug_msg, + }, + .msg_triggers = { + .flagchange = push_notification_event_messagetrash_event, + }, +}; diff --git a/src/plugins/push-notification/push-notification-event-messagetrash.h b/src/plugins/push-notification/push-notification-event-messagetrash.h new file mode 100644 index 0000000..712732f --- /dev/null +++ b/src/plugins/push-notification/push-notification-event-messagetrash.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENT_MESSAGETRASH_H +#define PUSH_NOTIFICATION_EVENT_MESSAGETRASH_H + +struct push_notification_event_messagetrash_data { + /* Can only be TRUE. */ + bool trash; +}; + +#endif + diff --git a/src/plugins/push-notification/push-notification-events-rfc5423.c b/src/plugins/push-notification/push-notification-events-rfc5423.c new file mode 100644 index 0000000..10dd0c7 --- /dev/null +++ b/src/plugins/push-notification/push-notification-events-rfc5423.c @@ -0,0 +1,59 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#include "push-notification-events.h" +#include "push-notification-events-rfc5423.h" + +/* These are the RFC 5423 Mail Store Events currently handled within the core + push-notification code. + + @todo: These events are not currently handled: + - Login + - Logout + - QuotaExceed + - Quota Within + */ +extern struct push_notification_event push_notification_event_flagsclear; +extern struct push_notification_event push_notification_event_flagsset; +extern struct push_notification_event push_notification_event_mailboxcreate; +extern struct push_notification_event push_notification_event_mailboxdelete; +extern struct push_notification_event push_notification_event_mailboxrename; +extern struct push_notification_event push_notification_event_mailboxsubscribe; +extern struct push_notification_event push_notification_event_mailboxunsubscribe; +extern struct push_notification_event push_notification_event_messageappend; +extern struct push_notification_event push_notification_event_messageexpunge; +extern struct push_notification_event push_notification_event_messagenew; +extern struct push_notification_event push_notification_event_messageread; +extern struct push_notification_event push_notification_event_messagetrash; + +static struct push_notification_event *rfc5423_events[] = { + &push_notification_event_flagsclear, + &push_notification_event_flagsset, + &push_notification_event_mailboxcreate, + &push_notification_event_mailboxdelete, + &push_notification_event_mailboxrename, + &push_notification_event_mailboxsubscribe, + &push_notification_event_mailboxunsubscribe, + &push_notification_event_messageappend, + &push_notification_event_messageexpunge, + &push_notification_event_messagenew, + &push_notification_event_messageread, + &push_notification_event_messagetrash +}; + +void push_notification_event_register_rfc5423_events(void) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(rfc5423_events); i++) + push_notification_event_register(rfc5423_events[i]); +} + +void push_notification_event_unregister_rfc5423_events(void) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(rfc5423_events); i++) + push_notification_event_unregister(rfc5423_events[i]); +} diff --git a/src/plugins/push-notification/push-notification-events-rfc5423.h b/src/plugins/push-notification/push-notification-events-rfc5423.h new file mode 100644 index 0000000..71f7009 --- /dev/null +++ b/src/plugins/push-notification/push-notification-events-rfc5423.h @@ -0,0 +1,10 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENTS_RFC5423_H +#define PUSH_NOTIFICATION_EVENTS_RFC5423_H + +void push_notification_event_register_rfc5423_events(void); +void push_notification_event_unregister_rfc5423_events(void); + +#endif + diff --git a/src/plugins/push-notification/push-notification-events.c b/src/plugins/push-notification/push-notification-events.c new file mode 100644 index 0000000..a71e8a6 --- /dev/null +++ b/src/plugins/push-notification/push-notification-events.c @@ -0,0 +1,100 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" + +ARRAY_TYPE(push_notification_event) push_notification_events; + +ARRAY_TYPE(push_notification_event) *push_notification_get_events(void) +{ + return &push_notification_events; +} + +static bool +push_notification_event_find(const char *name, unsigned int *idx_r) +{ + unsigned int count, i; + const struct push_notification_event *const *events; + + events = array_get(&push_notification_events, &count); + for (i = 0; i < count; i++) { + if (strcasecmp(events[i]->name, name) == 0) { + *idx_r = i; + return TRUE; + } + } + + return FALSE; +} + +static const struct push_notification_event * +push_notification_event_find_class(const char *driver) +{ + unsigned int idx; + + if (!push_notification_event_find(driver, &idx)) + return NULL; + + return array_idx_elem(&push_notification_events, idx); +} + +void push_notification_event_init(struct push_notification_driver_txn *dtxn, + const char *event_name, void *config) +{ + const struct push_notification_event *event; + struct push_notification_event_config *ec; + + if (!array_is_created(&dtxn->ptxn->events)) + p_array_init(&dtxn->ptxn->events, dtxn->ptxn->pool, 4); + + event = push_notification_event_find_class(event_name); + if (event != NULL) { + if ((config == NULL) && (event->init.default_config != NULL)) { + config = event->init.default_config(); + } + + ec = p_new(dtxn->ptxn->pool, + struct push_notification_event_config, 1); + ec->config = config; + ec->event = event; + + array_push_back(&dtxn->ptxn->events, &ec); + } +} + +void push_notification_event_register( + const struct push_notification_event *event) +{ + unsigned int idx; + + if (!array_is_created(&push_notification_events)) + i_array_init(&push_notification_events, 16); + + if (push_notification_event_find(event->name, &idx)) { + i_panic("push_notification_event_register(%s): duplicate event", + event->name); + } + + array_push_back(&push_notification_events, &event); +} + +void push_notification_event_unregister( + const struct push_notification_event *event) +{ + unsigned int idx; + + if (!push_notification_event_find(event->name, &idx)) { + i_panic("push_notification_event_register(%s): unknown event", + event->name); + } + + if (array_is_created(&push_notification_events)) { + array_delete(&push_notification_events, idx, 1); + + if (array_is_empty(&push_notification_events)) + array_free(&push_notification_events); + } +} diff --git a/src/plugins/push-notification/push-notification-events.h b/src/plugins/push-notification/push-notification-events.h new file mode 100644 index 0000000..f20075f --- /dev/null +++ b/src/plugins/push-notification/push-notification-events.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_EVENTS_H +#define PUSH_NOTIFICATION_EVENTS_H + +#include "mail-types.h" + +struct mail; +struct mailbox; +struct push_notification_event_config; +struct push_notification_driver_txn; +struct push_notification_txn; +struct push_notification_txn_event; +struct push_notification_txn_mbox; +struct push_notification_txn_msg; + +struct push_notification_event_vfuncs_init { + /* Return the default config for an event (or NULL if config is + required). */ + void *(*default_config)(void); +}; + +struct push_notification_event_vfuncs_mbox { + /* Output debug information about a message event. */ + void (*debug_mbox)(struct push_notification_txn_event *event); + /* Called when message data is about to be free'd. */ + void (*free_mbox)(struct push_notification_txn_event *event); +}; + +struct push_notification_event_vfuncs_mbox_triggers { + /* Mailbox event: create mailbox. */ + void (*create)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox); + /* Mailbox event: delete mailbox. */ + void (*delete)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox); + /* Mailbox event: rename mailbox. */ + void (*rename)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox, + struct mailbox *old); + /* Mailbox event: subscribe mailbox. */ + void (*subscribe)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox); + /* Mailbox event: unsubscribe mailbox. */ + void (*unsubscribe)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_mbox *mbox); +}; + +struct push_notification_event_vfuncs_msg { + /* Output debug information about a message event. */ + void (*debug_msg)(struct push_notification_txn_event *event); + /* Called when message data is about to be free'd. */ + void (*free_msg)(struct push_notification_txn_event *event); +}; + +struct push_notification_event_vfuncs_msg_triggers { + /* Message event: save message (from MTA). */ + void (*save)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, + struct mail *mail); + /* Message event: append message (from MUA). */ + void (*append)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, + struct mail *mail); + /* Message event: expunge message. */ + void (*expunge)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg); + /* Message event: flag change. */ + void (*flagchange)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, + struct mail *mail, enum mail_flags old_flags); + /* Message event: keyword change. */ + void (*keywordchange)(struct push_notification_txn *ptxn, + struct push_notification_event_config *ec, + struct push_notification_txn_msg *msg, + struct mail *mail, + const char *const *old_keywords); +}; + +struct push_notification_event_config { + const struct push_notification_event *event; + void *config; +}; + +struct push_notification_event { + const char *name; + struct push_notification_event_vfuncs_init init; + struct push_notification_event_vfuncs_mbox mbox; + struct push_notification_event_vfuncs_mbox_triggers mbox_triggers; + struct push_notification_event_vfuncs_msg msg; + struct push_notification_event_vfuncs_msg_triggers msg_triggers; +}; + +struct push_notification_txn_event { + struct push_notification_event_config *event; + void *data; +}; + +ARRAY_DEFINE_TYPE(push_notification_event, + const struct push_notification_event *); +extern ARRAY_TYPE(push_notification_event) push_notification_events; + +ARRAY_TYPE(push_notification_event) *push_notification_get_events(void); + + +void push_notification_event_init(struct push_notification_driver_txn *dtxn, + const char *event_name, void *config); + +void push_notification_event_register( + const struct push_notification_event *event); +void push_notification_event_unregister( + const struct push_notification_event *event); + +#endif + diff --git a/src/plugins/push-notification/push-notification-plugin.c b/src/plugins/push-notification/push-notification-plugin.c new file mode 100644 index 0000000..22c2fb0 --- /dev/null +++ b/src/plugins/push-notification/push-notification-plugin.c @@ -0,0 +1,390 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "mail-storage-private.h" +#include "notify-plugin.h" +#include "str.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-events-rfc5423.h" +#include "push-notification-plugin.h" +#include "push-notification-triggers.h" +#include "push-notification-txn-mbox.h" +#include "push-notification-txn-msg.h" + +#define PUSH_NOTIFICATION_CONFIG "push_notification_driver" +#define PUSH_NOTIFICATION_CONFIG_OLD "push_notification_backend" +#define PUSH_NOTIFICATION_EVENT_FINISHED "push_notification_finished" + +#define PUSH_NOTIFICATION_USER_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, push_notification_user_module) +static MODULE_CONTEXT_DEFINE_INIT(push_notification_user_module, + &mail_user_module_register); +static struct ioloop *main_ioloop; + +struct event_category event_category_push_notification = { + .name = "push-notification", +}; + +struct event_category *push_notification_get_event_category(void) +{ + return &event_category_push_notification; +} + +struct push_notification_event *push_notification_get_event_messagenew(void) +{ + return &push_notification_event_messagenew; +} + +static void +push_notification_transaction_init(struct push_notification_txn *ptxn) +{ + struct push_notification_driver_txn *dtxn; + struct push_notification_driver_user *duser; + struct mail_storage *storage; + + if (ptxn->initialized) + return; + + ptxn->initialized = TRUE; + + storage = mailbox_get_storage(ptxn->mbox); + if (storage->user->autocreated && + (strcmp(storage->name, "raw") == 0)) { + /* No notifications for autocreated raw users */ + return; + } + + array_foreach_elem(&ptxn->puser->driverlist->drivers, duser) { + dtxn = p_new(ptxn->pool, struct push_notification_driver_txn, 1); + dtxn->duser = duser; + dtxn->ptxn = ptxn; + + if ((dtxn->duser->driver->v.begin_txn == NULL) || + dtxn->duser->driver->v.begin_txn(dtxn)) { + array_push_back(&ptxn->drivers, &dtxn); + } + } +} + +static struct push_notification_txn * +push_notification_transaction_create(struct mailbox *box, + struct mailbox_transaction_context *t) +{ + pool_t pool; + struct push_notification_txn *ptxn; + struct mail_storage *storage; + + pool = pool_alloconly_create("push notification transaction", 2048); + + ptxn = p_new(pool, struct push_notification_txn, 1); + ptxn->mbox = box; + storage = mailbox_get_storage(box); + ptxn->muser = mail_storage_get_user(storage); + ptxn->pool = pool; + ptxn->puser = PUSH_NOTIFICATION_USER_CONTEXT(ptxn->muser); + ptxn->t = t; + ptxn->trigger = PUSH_NOTIFICATION_EVENT_TRIGGER_NONE; + ptxn->event = event_create(ptxn->muser->event); + event_add_category(ptxn->event, &event_category_push_notification); + event_set_append_log_prefix(ptxn->event, "push-notification: "); + p_array_init(&ptxn->drivers, pool, 4); + + return ptxn; +} + +static void +push_notification_transaction_end(struct push_notification_txn *ptxn, + bool success) +{ + struct push_notification_driver_txn *dtxn; + + if (ptxn->initialized) { + array_foreach_elem(&ptxn->drivers, dtxn) { + if (dtxn->duser->driver->v.end_txn != NULL) + dtxn->duser->driver->v.end_txn(dtxn, success); + } + } + + if (success && ptxn->trigger != 0) { + struct event_passthrough *e = event_create_passthrough(ptxn->event)-> + set_name(PUSH_NOTIFICATION_EVENT_FINISHED); + /* Emit event */ + e_debug(e->event(), "Push notification transaction completed"); + } + + event_unref(&ptxn->event); + pool_unref(&ptxn->pool); +} + +static void +push_notification_transaction_commit( + void *txn, struct mail_transaction_commit_changes *changes) +{ + struct push_notification_txn *ptxn = (struct push_notification_txn *)txn; + struct ioloop *prev_ioloop = current_ioloop; + + /* Make sure we're not in just any random ioloop, which could get + destroyed soon. This way the push-notification drivers can do async + operations that finish in the main ioloop. */ + io_loop_set_current(main_ioloop); + if (changes == NULL) + push_notification_txn_mbox_end(ptxn); + else + push_notification_txn_msg_end(ptxn, changes); + + push_notification_transaction_end(ptxn, TRUE); + io_loop_set_current(prev_ioloop); +} + +static void push_notification_mailbox_create(struct mailbox *box) +{ + struct push_notification_txn *ptxn; + + ptxn = push_notification_transaction_create(box, NULL); + push_notification_transaction_init(ptxn); + push_notification_trigger_mbox_create(ptxn, box, NULL); + push_notification_transaction_commit(ptxn, NULL); +} + +static void +push_notification_mailbox_delete(void *txn ATTR_UNUSED, struct mailbox *box) +{ + struct push_notification_txn *ptxn; + + ptxn = push_notification_transaction_create(box, NULL); + push_notification_transaction_init(ptxn); + push_notification_trigger_mbox_delete(ptxn, box, NULL); + push_notification_transaction_commit(ptxn, NULL); +} + +static void +push_notification_mailbox_rename(struct mailbox *src, struct mailbox *dest) +{ + struct push_notification_txn *ptxn; + + ptxn = push_notification_transaction_create(dest, NULL); + push_notification_transaction_init(ptxn); + push_notification_trigger_mbox_rename(ptxn, src, dest, NULL); + push_notification_transaction_commit(ptxn, NULL); +} + +static void +push_notification_mailbox_subscribe(struct mailbox *box, bool subscribed) +{ + struct push_notification_txn *ptxn; + + ptxn = push_notification_transaction_create(box, NULL); + push_notification_transaction_init(ptxn); + push_notification_trigger_mbox_subscribe(ptxn, box, subscribed, NULL); + push_notification_transaction_commit(ptxn, NULL); +} + +static void push_notification_mail_save(void *txn, struct mail *mail) +{ + struct push_notification_txn *ptxn = txn; + + push_notification_transaction_init(ptxn); + + /* POST_SESSION means MTA delivery. */ + if ((mail->box->flags & MAILBOX_FLAG_POST_SESSION) != 0) + push_notification_trigger_msg_save_new(ptxn, mail, NULL); + else + push_notification_trigger_msg_save_append(ptxn, mail, NULL); +} + +static void +push_notification_mail_copy(void *txn, struct mail *src ATTR_UNUSED, + struct mail *dest) +{ + push_notification_mail_save(txn, dest); +} + +static void push_notification_mail_expunge(void *txn, struct mail *mail) +{ + struct push_notification_txn *ptxn = txn; + + push_notification_transaction_init(ptxn); + push_notification_trigger_msg_save_expunge(txn, mail, NULL); +} + +static void +push_notification_mail_update_flags(void *txn, struct mail *mail, + enum mail_flags old_flags) +{ + struct push_notification_txn *ptxn = txn; + + push_notification_transaction_init(ptxn); + push_notification_trigger_msg_flag_change(txn, mail, NULL, old_flags); +} + +static void +push_notification_mail_update_keywords(void *txn, struct mail *mail, + const char *const *old_keywords) +{ + struct push_notification_txn *ptxn = txn; + + push_notification_transaction_init(ptxn); + push_notification_trigger_msg_keyword_change( + txn, mail, NULL, old_keywords); +} + +static void * +push_notification_transaction_begin(struct mailbox_transaction_context *t) +{ + return push_notification_transaction_create( + mailbox_transaction_get_mailbox(t), t); +} + +static void push_notification_transaction_rollback(void *txn) +{ + struct push_notification_txn *ptxn = txn; + + push_notification_transaction_end(ptxn, FALSE); +} + +static void +push_notification_config_init(const char *config_name, struct mail_user *user, + struct push_notification_driver_list *dlist) +{ + struct push_notification_driver_user *duser; + const char *env; + unsigned int i; + string_t *root_name; + + root_name = t_str_new(32); + str_append(root_name, config_name); + + for (i = 2;; i++) { + env = mail_user_plugin_getenv(user, str_c(root_name)); + if ((env == NULL) || (*env == '\0')) + break; + + if (push_notification_driver_init( + user, env, user->pool, &duser) < 0) + break; + + /* Add driver. */ + array_push_back(&dlist->drivers, &duser); + + str_truncate(root_name, strlen(config_name)); + str_printfa(root_name, "%d", i); + } +} + +static struct push_notification_driver_list * +push_notification_driver_list_init(struct mail_user *user) +{ + struct push_notification_driver_list *dlist; + + dlist = p_new(user->pool, struct push_notification_driver_list, 1); + p_array_init(&dlist->drivers, user->pool, 4); + + push_notification_config_init(PUSH_NOTIFICATION_CONFIG, user, dlist); + + if (array_is_empty(&dlist->drivers)) { + /* Support old configuration (it was available at time initial + OX driver was first released). */ + push_notification_config_init(PUSH_NOTIFICATION_CONFIG_OLD, + user, dlist); + } + return dlist; +} + +static void push_notification_user_deinit(struct mail_user *user) +{ + struct push_notification_user *puser = + PUSH_NOTIFICATION_USER_CONTEXT(user); + struct push_notification_driver_list *dlist = puser->driverlist; + struct push_notification_driver_user *duser; + struct ioloop *prev_ioloop = current_ioloop; + + /* Make sure we're in the main ioloop, so if the deinit/cleanup moves + any I/Os or timeouts they won't get moved to some temporary ioloop. + */ + io_loop_set_current(main_ioloop); + + array_foreach_elem(&dlist->drivers, duser) { + if (duser->driver->v.deinit != NULL) + duser->driver->v.deinit(duser); + if (duser->driver->v.cleanup != NULL) + duser->driver->v.cleanup(); + } + io_loop_set_current(prev_ioloop); + + puser->module_ctx.super.deinit(user); +} + +static void push_notification_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct push_notification_user *puser; + + puser = p_new(user->pool, struct push_notification_user, 1); + puser->module_ctx.super = *v; + user->vlast = &puser->module_ctx.super; + v->deinit = push_notification_user_deinit; + puser->driverlist = push_notification_driver_list_init(user); + + MODULE_CONTEXT_SET(user, push_notification_user_module, puser); +} + +/* Plugin interface. */ + +const char *push_notification_plugin_version = DOVECOT_ABI_VERSION; +const char *push_notification_plugin_dependencies[] = { "notify", NULL }; + +extern struct push_notification_driver push_notification_driver_dlog; +extern struct push_notification_driver push_notification_driver_ox; + +static struct notify_context *push_notification_ctx; + +static const struct notify_vfuncs push_notification_vfuncs = { + /* Mailbox Events */ + .mailbox_create = push_notification_mailbox_create, + .mailbox_delete_commit = push_notification_mailbox_delete, + .mailbox_rename = push_notification_mailbox_rename, + .mailbox_set_subscribed = push_notification_mailbox_subscribe, + + /* Mail Events */ + .mail_copy = push_notification_mail_copy, + .mail_save = push_notification_mail_save, + .mail_expunge = push_notification_mail_expunge, + .mail_update_flags = push_notification_mail_update_flags, + .mail_update_keywords = push_notification_mail_update_keywords, + .mail_transaction_begin = push_notification_transaction_begin, + .mail_transaction_commit = push_notification_transaction_commit, + .mail_transaction_rollback = push_notification_transaction_rollback, +}; + +static struct mail_storage_hooks push_notification_storage_hooks = { + .mail_user_created = push_notification_user_created, +}; + +void push_notification_plugin_init(struct module *module) +{ + push_notification_ctx = notify_register(&push_notification_vfuncs); + mail_storage_hooks_add(module, &push_notification_storage_hooks); + + push_notification_driver_register(&push_notification_driver_dlog); + push_notification_driver_register(&push_notification_driver_ox); + + push_notification_event_register_rfc5423_events(); + main_ioloop = current_ioloop; + i_assert(main_ioloop != NULL); +} + +void push_notification_plugin_deinit(void) +{ + push_notification_driver_unregister(&push_notification_driver_dlog); + push_notification_driver_unregister(&push_notification_driver_ox); + + push_notification_event_unregister_rfc5423_events(); + mail_storage_hooks_remove(&push_notification_storage_hooks); + notify_unregister(push_notification_ctx); +} diff --git a/src/plugins/push-notification/push-notification-plugin.h b/src/plugins/push-notification/push-notification-plugin.h new file mode 100644 index 0000000..7db5397 --- /dev/null +++ b/src/plugins/push-notification/push-notification-plugin.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_PLUGIN_H +#define PUSH_NOTIFICATION_PLUGIN_H + +extern const char *push_notification_plugin_dependencies[]; +extern struct event_category event_category_push_notification; +extern struct push_notification_event push_notification_event_messagenew; + +struct module; + +struct event_category *push_notification_get_event_category(void); +struct push_notification_event *push_notification_get_event_messagenew(void); + +void push_notification_plugin_init(struct module *module); +void push_notification_plugin_deinit(void); + +#endif diff --git a/src/plugins/push-notification/push-notification-triggers.c b/src/plugins/push-notification/push-notification-triggers.c new file mode 100644 index 0000000..3882982 --- /dev/null +++ b/src/plugins/push-notification/push-notification-triggers.c @@ -0,0 +1,215 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" + +#include "mail-storage.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-triggers.h" +#include "push-notification-txn-mbox.h" +#include "push-notification-txn-msg.h" + +static void +push_notification_trigger_mbox_common( + struct push_notification_txn *txn, struct mailbox *box, + struct push_notification_txn_mbox **mbox, + enum push_notification_event_trigger trigger) +{ + if (*mbox == NULL) { + *mbox = push_notification_txn_mbox_create(txn, box); + } + + txn->trigger |= trigger; + event_add_str(txn->event, "mailbox", mailbox_get_vname(box)); +} + +void push_notification_trigger_mbox_create( + struct push_notification_txn *txn, struct mailbox *box, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_mbox_common( + txn, box, &mbox, PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_CREATE); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (ec->event->mbox_triggers.create != NULL) + ec->event->mbox_triggers.create(txn, ec, mbox); + } + } +} + +void push_notification_trigger_mbox_delete( + struct push_notification_txn *txn, struct mailbox *box, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_mbox_common( + txn, box, &mbox, PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_DELETE); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (ec->event->mbox_triggers.delete != NULL) + ec->event->mbox_triggers.delete(txn, ec, mbox); + } + } +} + +void push_notification_trigger_mbox_rename( + struct push_notification_txn *txn, + struct mailbox *src, struct mailbox *dest, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_mbox_common( + txn, dest, &mbox, PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_RENAME); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (ec->event->mbox_triggers.rename != NULL) { + ec->event->mbox_triggers.rename( + txn, ec, mbox, src); + } + } + } +} + +void push_notification_trigger_mbox_subscribe( + struct push_notification_txn *txn, struct mailbox *box, bool subscribed, + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_mbox_common( + txn, box, &mbox, + PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_SUBSCRIBE); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (subscribed == TRUE) { + if (ec->event->mbox_triggers.subscribe != NULL) { + ec->event->mbox_triggers.subscribe( + txn, ec, mbox); + } + } else { + if (ec->event->mbox_triggers.unsubscribe != NULL) { + ec->event->mbox_triggers.unsubscribe( + txn, ec, mbox); + } + } + } + } +} + +static void +push_notification_trigger_msg_common( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg **msg, + enum push_notification_event_trigger trigger) +{ + if (*msg == NULL) + *msg = push_notification_txn_msg_create(txn, mail); + + txn->trigger |= trigger; +} + +void push_notification_trigger_msg_save_new( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_msg_common( + txn, mail, &msg, PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_NEW); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (ec->event->msg_triggers.save != NULL) { + ec->event->msg_triggers.save( + txn, ec, msg, mail); + } + } + } +} + +void push_notification_trigger_msg_save_append( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_msg_common( + txn, mail, &msg, + PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_APPEND); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (ec->event->msg_triggers.append != NULL) { + ec->event->msg_triggers.append( + txn, ec, msg, mail); + } + } + } +} + +void push_notification_trigger_msg_save_expunge( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_msg_common( + txn, mail, &msg, PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_EXPUNGE); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (ec->event->msg_triggers.expunge != NULL) + ec->event->msg_triggers.expunge(txn, ec, msg); + } + } +} + +void push_notification_trigger_msg_flag_change( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg, enum mail_flags old_flags) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_msg_common( + txn, mail, &msg, + PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_FLAGCHANGE); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (ec->event->msg_triggers.flagchange != NULL) { + ec->event->msg_triggers.flagchange( + txn, ec, msg, mail, old_flags); + } + } + } +} + +void push_notification_trigger_msg_keyword_change( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg, const char *const *old_keywords) +{ + struct push_notification_event_config *ec; + + push_notification_trigger_msg_common( + txn, mail, &msg, + PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_KEYWORDCHANGE); + + if (array_is_created(&txn->events)) { + array_foreach_elem(&txn->events, ec) { + if (ec->event->msg_triggers.keywordchange != NULL) { + ec->event->msg_triggers.keywordchange( + txn, ec, msg, mail, old_keywords); + } + } + } +} diff --git a/src/plugins/push-notification/push-notification-triggers.h b/src/plugins/push-notification/push-notification-triggers.h new file mode 100644 index 0000000..b29bc47 --- /dev/null +++ b/src/plugins/push-notification/push-notification-triggers.h @@ -0,0 +1,64 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_TRIGGERS_H +#define PUSH_NOTIFICATION_TRIGGERS_H + +#include "mail-types.h" + +struct mail; +struct mailbox; +struct push_notification_txn; +struct push_notification_txn_mbox; +struct push_notification_txn_msg; + +enum push_notification_event_trigger { + PUSH_NOTIFICATION_EVENT_TRIGGER_NONE, + + /* Mailbox actions */ + PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_CREATE = 0x001, + PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_DELETE = 0x002, + PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_RENAME = 0x004, + PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_SUBSCRIBE = 0x008, + + /* Message actions */ + PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_NEW = 0x010, + PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_APPEND = 0x020, + PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_EXPUNGE = 0x040, + PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_FLAGCHANGE = 0x080, + PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_KEYWORDCHANGE = 0x100, +}; + +/* Mailbox actions. */ +void push_notification_trigger_mbox_create( + struct push_notification_txn *txn, struct mailbox *box, + struct push_notification_txn_mbox *mbox); +void push_notification_trigger_mbox_delete( + struct push_notification_txn *txn, struct mailbox *box, + struct push_notification_txn_mbox *mbox); +void push_notification_trigger_mbox_rename( + struct push_notification_txn *txn, + struct mailbox *src, struct mailbox *dest, + struct push_notification_txn_mbox *mbox); +void push_notification_trigger_mbox_subscribe( + struct push_notification_txn *txn, struct mailbox *box, bool subscribed, + struct push_notification_txn_mbox *mbox); + +/* Message actions. */ +void push_notification_trigger_msg_save_new( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg); +void push_notification_trigger_msg_save_append( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg); +void push_notification_trigger_msg_save_expunge( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg); +void push_notification_trigger_msg_flag_change( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg, enum mail_flags old_flags); +void push_notification_trigger_msg_keyword_change( + struct push_notification_txn *txn, struct mail *mail, + struct push_notification_txn_msg *msg, const char *const *old_keywords); + +#endif + diff --git a/src/plugins/push-notification/push-notification-txn-mbox.c b/src/plugins/push-notification/push-notification-txn-mbox.c new file mode 100644 index 0000000..4401819 --- /dev/null +++ b/src/plugins/push-notification/push-notification-txn-mbox.c @@ -0,0 +1,90 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "hash.h" +#include "mail-storage-private.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-txn-mbox.h" + +struct push_notification_txn_mbox * +push_notification_txn_mbox_create(struct push_notification_txn *txn, + struct mailbox *box) +{ + if (txn->mbox_txn == NULL) { + txn->mbox_txn = p_new(txn->pool, + struct push_notification_txn_mbox, 1); + txn->mbox_txn->mailbox = mailbox_get_vname(box); + } + + return txn->mbox_txn; +} + +void push_notification_txn_mbox_end(struct push_notification_txn *ptxn) +{ + struct push_notification_driver_txn **dtxn; + + if (ptxn->mbox_txn != NULL) { + array_foreach_modifiable(&ptxn->drivers, dtxn) { + if ((*dtxn)->duser->driver->v.process_mbox != NULL) { + (*dtxn)->duser->driver->v.process_mbox( + *dtxn, ptxn->mbox_txn); + } + } + + push_notification_txn_mbox_deinit_eventdata(ptxn->mbox_txn); + } +} + +void * +push_notification_txn_mbox_get_eventdata( + struct push_notification_txn_mbox *mbox, const char *event_name) +{ + struct push_notification_txn_event **mevent; + + if (array_is_created(&mbox->eventdata)) { + array_foreach_modifiable(&mbox->eventdata, mevent) { + if (strcmp((*mevent)->event->event->name, + event_name) == 0) { + return (*mevent)->data; + } + } + } + + return NULL; +} + +void push_notification_txn_mbox_set_eventdata( + struct push_notification_txn *txn, + struct push_notification_txn_mbox *mbox, + struct push_notification_event_config *event, void *data) +{ + struct push_notification_txn_event *mevent; + + if (!array_is_created(&mbox->eventdata)) { + p_array_init(&mbox->eventdata, txn->pool, 4); + } + + mevent = p_new(txn->pool, struct push_notification_txn_event, 1); + mevent->data = data; + mevent->event = event; + + array_push_back(&mbox->eventdata, &mevent); +} + +void push_notification_txn_mbox_deinit_eventdata( + struct push_notification_txn_mbox *mbox) +{ + struct push_notification_txn_event **mevent; + + if (array_is_created(&mbox->eventdata)) { + array_foreach_modifiable(&mbox->eventdata, mevent) { + if (((*mevent)->data != NULL) && + ((*mevent)->event->event->mbox.free_mbox != NULL)) { + (*mevent)->event->event->mbox.free_mbox( + *mevent); + } + } + } +} diff --git a/src/plugins/push-notification/push-notification-txn-mbox.h b/src/plugins/push-notification/push-notification-txn-mbox.h new file mode 100644 index 0000000..c7a7e55 --- /dev/null +++ b/src/plugins/push-notification/push-notification-txn-mbox.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_TXN_MBOX_H +#define PUSH_NOTIFICATION_TXN_MBOX_H + +struct push_notification_txn_event; + +struct push_notification_txn_mbox { + const char *mailbox; + + ARRAY(struct push_notification_txn_event *) eventdata; +}; + +struct push_notification_txn_mbox * +push_notification_txn_mbox_create(struct push_notification_txn *txn, + struct mailbox *box); +void push_notification_txn_mbox_end(struct push_notification_txn *ptxn); + +void * +push_notification_txn_mbox_get_eventdata( + struct push_notification_txn_mbox *mbox, const char *event_name); +void push_notification_txn_mbox_set_eventdata( + struct push_notification_txn *txn, + struct push_notification_txn_mbox *mbox, + struct push_notification_event_config *event, void *data); +void push_notification_txn_mbox_deinit_eventdata( + struct push_notification_txn_mbox *mbox); + +#endif diff --git a/src/plugins/push-notification/push-notification-txn-msg.c b/src/plugins/push-notification/push-notification-txn-msg.c new file mode 100644 index 0000000..ff37bff --- /dev/null +++ b/src/plugins/push-notification/push-notification-txn-msg.c @@ -0,0 +1,139 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "hash.h" +#include "mail-storage-private.h" + +#include "push-notification-drivers.h" +#include "push-notification-events.h" +#include "push-notification-txn-msg.h" + + +struct push_notification_txn_msg * +push_notification_txn_msg_create(struct push_notification_txn *txn, + struct mail *mail) +{ + struct push_notification_txn_msg *msg = NULL; + + if (hash_table_is_created(txn->messages)) { + msg = hash_table_lookup(txn->messages, + POINTER_CAST(mail->seq)); + } else { + hash_table_create_direct(&txn->messages, txn->pool, 4); + } + + if (msg == NULL) { + msg = p_new(txn->pool, struct push_notification_txn_msg, 1); + msg->mailbox = mailbox_get_vname(mail->box); + /* Save sequence number - used to determine UID later. */ + if (mail->uid == 0) + msg->save_idx = txn->t->save_count; + else + msg->save_idx = UINT_MAX; + msg->uid = mail->uid; + + hash_table_insert(txn->messages, POINTER_CAST(mail->seq), + msg); + } + + return msg; +} + +void push_notification_txn_msg_end( + struct push_notification_txn *ptxn, + struct mail_transaction_commit_changes *changes) +{ + struct hash_iterate_context *hiter; + void *key; + struct push_notification_driver_txn **dtxn; + struct seq_range_iter siter; + struct mailbox_status status; + uint32_t uid, uid_validity; + struct push_notification_txn_msg *value; + + if (!hash_table_is_created(ptxn->messages)) { + return; + } + + hiter = hash_table_iterate_init(ptxn->messages); + seq_range_array_iter_init(&siter, &changes->saved_uids); + + /* uid_validity is only set in changes if message is new. */ + if (changes->uid_validity == 0) { + mailbox_get_open_status(ptxn->mbox, STATUS_UIDVALIDITY, &status); + uid_validity = status.uidvalidity; + } else { + uid_validity = changes->uid_validity; + } + + while (hash_table_iterate(hiter, ptxn->messages, &key, &value)) { + if (value->uid == 0) { + if (seq_range_array_iter_nth(&siter, value->save_idx, &uid)) { + value->uid = uid; + } + } else + i_assert(value->save_idx == UINT_MAX); + value->uid_validity = uid_validity; + + array_foreach_modifiable(&ptxn->drivers, dtxn) { + if ((*dtxn)->duser->driver->v.process_msg != NULL) { + (*dtxn)->duser->driver->v.process_msg(*dtxn, value); + } + } + + push_notification_txn_msg_deinit_eventdata(value); + } + + hash_table_iterate_deinit(&hiter); + hash_table_destroy(&ptxn->messages); +} + +void * +push_notification_txn_msg_get_eventdata(struct push_notification_txn_msg *msg, + const char *event_name) +{ + struct push_notification_txn_event **mevent; + + if (array_is_created(&msg->eventdata)) { + array_foreach_modifiable(&msg->eventdata, mevent) { + if (strcmp((*mevent)->event->event->name, event_name) == 0) { + return (*mevent)->data; + } + } + } + + return NULL; +} + +void push_notification_txn_msg_set_eventdata( + struct push_notification_txn *txn, + struct push_notification_txn_msg *msg, + struct push_notification_event_config *event, void *data) +{ + struct push_notification_txn_event *mevent; + + if (!array_is_created(&msg->eventdata)) { + p_array_init(&msg->eventdata, txn->pool, 4); + } + + mevent = p_new(txn->pool, struct push_notification_txn_event, 1); + mevent->data = data; + mevent->event = event; + + array_push_back(&msg->eventdata, &mevent); +} + +void push_notification_txn_msg_deinit_eventdata( + struct push_notification_txn_msg *msg) +{ + struct push_notification_txn_event **mevent; + + if (array_is_created(&msg->eventdata)) { + array_foreach_modifiable(&msg->eventdata, mevent) { + if (((*mevent)->data != NULL) && + ((*mevent)->event->event->msg.free_msg != NULL)) { + (*mevent)->event->event->msg.free_msg(*mevent); + } + } + } +} diff --git a/src/plugins/push-notification/push-notification-txn-msg.h b/src/plugins/push-notification/push-notification-txn-msg.h new file mode 100644 index 0000000..777f115 --- /dev/null +++ b/src/plugins/push-notification/push-notification-txn-msg.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#ifndef PUSH_NOTIFICATION_TXN_MSG_H +#define PUSH_NOTIFICATION_TXN_MSG_H + +struct mail_transaction_commit_changes; +struct push_notification_event_config; +struct push_notification_txn; +struct push_notification_txn_event; + +struct push_notification_txn_msg { + const char *mailbox; + uint32_t uid; + uint32_t uid_validity; + + ARRAY(struct push_notification_txn_event *) eventdata; + + /* Private */ + unsigned int save_idx; +}; + +struct push_notification_txn_msg * +push_notification_txn_msg_create(struct push_notification_txn *txn, + struct mail *mail); +void push_notification_txn_msg_end( + struct push_notification_txn *ptxn, + struct mail_transaction_commit_changes *changes); + +void * +push_notification_txn_msg_get_eventdata(struct push_notification_txn_msg *msg, + const char *event_name); +void push_notification_txn_msg_set_eventdata( + struct push_notification_txn *txn, + struct push_notification_txn_msg *msg, + struct push_notification_event_config *event, void *data); +void push_notification_txn_msg_deinit_eventdata( + struct push_notification_txn_msg *msg); + +#endif diff --git a/src/plugins/quota-clone/Makefile.am b/src/plugins/quota-clone/Makefile.am new file mode 100644 index 0000000..ed4ba68 --- /dev/null +++ b/src/plugins/quota-clone/Makefile.am @@ -0,0 +1,19 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/quota + +NOPLUGIN_LDFLAGS = +lib20_quota_clone_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_quota_clone_plugin.la + +lib20_quota_clone_plugin_la_SOURCES = \ + quota-clone-plugin.c + +noinst_HEADERS = \ + quota-clone-plugin.h diff --git a/src/plugins/quota-clone/Makefile.in b/src/plugins/quota-clone/Makefile.in new file mode 100644 index 0000000..3f6157b --- /dev/null +++ b/src/plugins/quota-clone/Makefile.in @@ -0,0 +1,822 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/quota-clone +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_quota_clone_plugin_la_LIBADD = +am_lib20_quota_clone_plugin_la_OBJECTS = quota-clone-plugin.lo +lib20_quota_clone_plugin_la_OBJECTS = \ + $(am_lib20_quota_clone_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_quota_clone_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_quota_clone_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/quota-clone-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_quota_clone_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_quota_clone_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/quota + +lib20_quota_clone_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_quota_clone_plugin.la + +lib20_quota_clone_plugin_la_SOURCES = \ + quota-clone-plugin.c + +noinst_HEADERS = \ + quota-clone-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/quota-clone/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/quota-clone/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_quota_clone_plugin.la: $(lib20_quota_clone_plugin_la_OBJECTS) $(lib20_quota_clone_plugin_la_DEPENDENCIES) $(EXTRA_lib20_quota_clone_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_quota_clone_plugin_la_LINK) -rpath $(moduledir) $(lib20_quota_clone_plugin_la_OBJECTS) $(lib20_quota_clone_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-clone-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/quota-clone-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/quota-clone-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/quota-clone/quota-clone-plugin.c b/src/plugins/quota-clone/quota-clone-plugin.c new file mode 100644 index 0000000..5f5efa4 --- /dev/null +++ b/src/plugins/quota-clone/quota-clone-plugin.c @@ -0,0 +1,308 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "module-context.h" +#include "ioloop.h" +#include "dict.h" +#include "mail-storage-private.h" +#include "quota.h" +#include "quota-clone-plugin.h" + +/* If mailbox is kept open for this many milliseconds after quota update, + flush quota-clone. */ +#define QUOTA_CLONE_FLUSH_DELAY_MSECS (10*1000) + +#define DICT_QUOTA_CLONE_PATH DICT_PATH_PRIVATE"quota/" +#define DICT_QUOTA_CLONE_BYTES_PATH DICT_QUOTA_CLONE_PATH"storage" +#define DICT_QUOTA_CLONE_COUNT_PATH DICT_QUOTA_CLONE_PATH"messages" + +#define QUOTA_CLONE_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, quota_clone_user_module) +#define QUOTA_CLONE_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, quota_clone_user_module) +#define QUOTA_CLONE_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, quota_clone_storage_module) + +static MODULE_CONTEXT_DEFINE_INIT(quota_clone_user_module, + &mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(quota_clone_storage_module, + &mail_storage_module_register); + +struct quota_clone_user { + union mail_user_module_context module_ctx; + struct dict *dict; + struct timeout *to_quota_flush; + bool quota_changed; + bool quota_flushing; +}; + +static void +quota_clone_dict_commit(const struct dict_commit_result *result, + struct quota_clone_user *quser) +{ + switch (result->ret) { + case DICT_COMMIT_RET_OK: + case DICT_COMMIT_RET_NOTFOUND: + if (!quser->quota_changed) + timeout_remove(&quser->to_quota_flush); + break; + case DICT_COMMIT_RET_FAILED: + quser->quota_changed = TRUE; + i_error("quota_clone_dict: Failed to write value: %s", + result->error); + break; + case DICT_COMMIT_RET_WRITE_UNCERTAIN: + quser->quota_changed = TRUE; + i_error("quota_clone_dict: Write was unconfirmed (timeout or disconnect): %s", + result->error); + break; + } + + quser->quota_flushing = FALSE; +} + +static bool quota_clone_flush_real(struct mail_user *user) +{ + struct quota_clone_user *quser = + QUOTA_CLONE_USER_CONTEXT_REQUIRE(user); + struct dict_transaction_context *trans; + struct quota_root_iter *iter; + struct quota_root *root; + uint64_t bytes_value, count_value, limit; + const char *error; + enum quota_get_result bytes_res, count_res; + + /* we'll clone the first quota root */ + iter = quota_root_iter_init_user(user); + root = quota_root_iter_next(iter); + quota_root_iter_deinit(&iter); + if (root == NULL) { + /* no quota roots defined - ignore */ + quser->quota_changed = FALSE; + return TRUE; + } + + /* get new values first */ + bytes_res = quota_get_resource(root, "", QUOTA_NAME_STORAGE_BYTES, + &bytes_value, &limit, &error); + if (bytes_res == QUOTA_GET_RESULT_INTERNAL_ERROR) { + i_error("quota_clone_plugin: " + "Failed to get quota resource "QUOTA_NAME_STORAGE_BYTES": %s", + error); + return TRUE; + } + count_res = quota_get_resource(root, "", QUOTA_NAME_MESSAGES, + &count_value, &limit, &error); + if (count_res == QUOTA_GET_RESULT_INTERNAL_ERROR) { + i_error("quota_clone_plugin: " + "Failed to get quota resource "QUOTA_NAME_MESSAGES": %s", + error); + return TRUE; + } + if (bytes_res == QUOTA_GET_RESULT_UNKNOWN_RESOURCE && + count_res == QUOTA_GET_RESULT_UNKNOWN_RESOURCE) { + /* quota resources don't exist - no point in updating it */ + return TRUE; + } + if (bytes_res == QUOTA_GET_RESULT_BACKGROUND_CALC && + count_res == QUOTA_GET_RESULT_BACKGROUND_CALC) { + /* Blocked by an ongoing quota calculation - try again later */ + quser->quota_flushing = FALSE; + return FALSE; + } + + /* Then update the resources that exist. The resources' existence can't + change unless the quota backend is changed, so we don't worry about + the special case of lookup changing from + RESULT_LIMITED/RESULT_UNLIMITED to RESULT_UNKNOWN_RESOURCE, which + leaves the old value unchanged. */ + const struct dict_op_settings *set = mail_user_get_dict_op_settings(user); + trans = dict_transaction_begin(quser->dict, set); + if (bytes_res == QUOTA_GET_RESULT_LIMITED || + bytes_res == QUOTA_GET_RESULT_UNLIMITED) { + dict_set(trans, DICT_QUOTA_CLONE_BYTES_PATH, + t_strdup_printf("%"PRIu64, bytes_value)); + } + if (count_res == QUOTA_GET_RESULT_LIMITED || + count_res == QUOTA_GET_RESULT_UNLIMITED) { + dict_set(trans, DICT_QUOTA_CLONE_COUNT_PATH, + t_strdup_printf("%"PRIu64, count_value)); + } + quser->quota_changed = FALSE; + dict_transaction_commit_async(&trans, quota_clone_dict_commit, quser); + return FALSE; +} + +static void quota_clone_flush(struct mail_user *user) +{ + struct quota_clone_user *quser = + QUOTA_CLONE_USER_CONTEXT_REQUIRE(user); + + if (quser->quota_changed) { + i_assert(quser->to_quota_flush != NULL); + if (quser->quota_flushing) { + /* async quota commit is running in background. timeout is still + active, so another update will be done later. */ + } else { + quser->quota_flushing = TRUE; + /* Returns TRUE if flushing action is complete. */ + if (quota_clone_flush_real(user)) { + quser->quota_flushing = FALSE; + timeout_remove(&quser->to_quota_flush); + } + } + } else { + timeout_remove(&quser->to_quota_flush); + } +} + +static struct mail_user *quota_mailbox_get_user(struct mailbox *box) +{ + struct mail_namespace *ns = mailbox_list_get_namespace(box->list); + return ns->owner != NULL ? ns->owner : ns->user; +} + +static void quota_clone_changed(struct mailbox *box) +{ + struct mail_user *user = quota_mailbox_get_user(box); + struct quota_clone_user *quser = + QUOTA_CLONE_USER_CONTEXT_REQUIRE(user); + + quser->quota_changed = TRUE; + if (quser->to_quota_flush == NULL) { + quser->to_quota_flush = timeout_add(QUOTA_CLONE_FLUSH_DELAY_MSECS, + quota_clone_flush, user); + } +} + +static int quota_clone_save_finish(struct mail_save_context *ctx) +{ + union mailbox_module_context *qbox = + QUOTA_CLONE_CONTEXT(ctx->transaction->box); + + quota_clone_changed(ctx->transaction->box); + return qbox->super.save_finish(ctx); +} + +static int +quota_clone_copy(struct mail_save_context *ctx, struct mail *mail) +{ + union mailbox_module_context *qbox = + QUOTA_CLONE_CONTEXT(ctx->transaction->box); + + quota_clone_changed(ctx->transaction->box); + return qbox->super.copy(ctx, mail); +} + +static void +quota_clone_mailbox_sync_notify(struct mailbox *box, uint32_t uid, + enum mailbox_sync_type sync_type) +{ + union mailbox_module_context *qbox = QUOTA_CLONE_CONTEXT(box); + + if (qbox->super.sync_notify != NULL) + qbox->super.sync_notify(box, uid, sync_type); + + if (sync_type == MAILBOX_SYNC_TYPE_EXPUNGE) + quota_clone_changed(box); +} + +static void quota_clone_mailbox_allocated(struct mailbox *box) +{ + struct quota_clone_user *quser = + QUOTA_CLONE_USER_CONTEXT(box->storage->user); + struct mailbox_vfuncs *v = box->vlast; + union mailbox_module_context *qbox; + + if (quser == NULL) + return; + + qbox = p_new(box->pool, union mailbox_module_context, 1); + qbox->super = *v; + box->vlast = &qbox->super; + + v->save_finish = quota_clone_save_finish; + v->copy = quota_clone_copy; + v->sync_notify = quota_clone_mailbox_sync_notify; + MODULE_CONTEXT_SET_SELF(box, quota_clone_storage_module, qbox); +} + +static void quota_clone_mail_user_deinit_pre(struct mail_user *user) +{ + struct quota_clone_user *quser = QUOTA_CLONE_USER_CONTEXT_REQUIRE(user); + + dict_wait(quser->dict); + /* Check once more if quota needs to be updated. This needs to be done + in deinit_pre(), because at deinit() the quota is already + deinitialized. */ + if (quser->to_quota_flush != NULL) { + i_assert(!quser->quota_flushing); + quota_clone_flush(user); + dict_wait(quser->dict); + /* If dict update fails or background calculation is running, + the timeout is still set. Just forget about it. */ + timeout_remove(&quser->to_quota_flush); + } + quser->module_ctx.super.deinit_pre(user); +} + +static void quota_clone_mail_user_deinit(struct mail_user *user) +{ + struct quota_clone_user *quser = QUOTA_CLONE_USER_CONTEXT_REQUIRE(user); + + /* wait once more, just in case something changed quota during + deinit_pre() */ + dict_wait(quser->dict); + i_assert(quser->to_quota_flush == NULL); + dict_deinit(&quser->dict); + quser->module_ctx.super.deinit(user); +} + +static void quota_clone_mail_user_created(struct mail_user *user) +{ + struct quota_clone_user *quser; + struct mail_user_vfuncs *v = user->vlast; + struct dict_settings dict_set; + struct dict *dict; + const char *uri, *error; + + uri = mail_user_plugin_getenv(user, "quota_clone_dict"); + if (uri == NULL || uri[0] == '\0') { + e_debug(user->event, "The quota_clone_dict setting is missing from configuration"); + return; + } + + i_zero(&dict_set); + dict_set.base_dir = user->set->base_dir; + dict_set.event_parent = user->event; + if (dict_init(uri, &dict_set, &dict, &error) < 0) { + i_error("quota_clone_dict: Failed to initialize '%s': %s", + uri, error); + return; + } + + quser = p_new(user->pool, struct quota_clone_user, 1); + quser->module_ctx.super = *v; + user->vlast = &quser->module_ctx.super; + v->deinit_pre = quota_clone_mail_user_deinit_pre; + v->deinit = quota_clone_mail_user_deinit; + quser->dict = dict; + MODULE_CONTEXT_SET(user, quota_clone_user_module, quser); +} + +static struct mail_storage_hooks quota_clone_mail_storage_hooks = { + .mailbox_allocated = quota_clone_mailbox_allocated, + .mail_user_created = quota_clone_mail_user_created +}; + +void quota_clone_plugin_init(struct module *module ATTR_UNUSED) +{ + mail_storage_hooks_add(module, "a_clone_mail_storage_hooks); +} + +void quota_clone_plugin_deinit(void) +{ + mail_storage_hooks_remove("a_clone_mail_storage_hooks); +} + +const char *quota_clone_plugin_dependencies[] = { "quota", NULL }; diff --git a/src/plugins/quota-clone/quota-clone-plugin.h b/src/plugins/quota-clone/quota-clone-plugin.h new file mode 100644 index 0000000..9ae1d8f --- /dev/null +++ b/src/plugins/quota-clone/quota-clone-plugin.h @@ -0,0 +1,7 @@ +#ifndef QUOTA_CLONE_PLUGIN_H +#define QUOTA_CLONE_PLUGIN_H + +void quota_clone_plugin_init(struct module *module); +void quota_clone_plugin_deinit(void); + +#endif diff --git a/src/plugins/quota/Makefile.am b/src/plugins/quota/Makefile.am new file mode 100644 index 0000000..e8bad8c --- /dev/null +++ b/src/plugins/quota/Makefile.am @@ -0,0 +1,144 @@ +doveadm_moduledir = $(moduledir)/doveadm + +pkglibexecdir = $(libexecdir)/dovecot +pkglibexec_PROGRAMS = quota-status + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-smtp \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-imap-client \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/imapc \ + -I$(top_srcdir)/src/lib-storage/index/maildir \ + -I$(top_srcdir)/src/lib-program-client \ + -I$(top_srcdir)/src/doveadm \ + $(LIBTIRPC_CFLAGS) + +NOPLUGIN_LDFLAGS = +lib10_doveadm_quota_plugin_la_LDFLAGS = -module -avoid-version +lib10_quota_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib10_quota_plugin.la + +quota_dist_sources = \ + quota.c \ + quota-count.c \ + quota-fs.c \ + quota-dict.c \ + quota-dirsize.c \ + quota-imapc.c \ + quota-maildir.c \ + quota-plugin.c \ + quota-storage.c \ + quota-util.c + +quota_common_objects = \ + quota.lo \ + quota-count.lo \ + quota-fs.lo \ + quota-dict.lo \ + quota-dirsize.lo \ + quota-imapc.lo \ + quota-maildir.lo \ + quota-plugin.lo \ + quota-storage.lo \ + quota-util.lo \ + $(RQUOTA_XDR_LO) + +lib10_quota_plugin_la_SOURCES = $(quota_dist_sources) +nodist_lib10_quota_plugin_la_SOURCES = $(RQUOTA_XDR) +lib10_quota_plugin_la_LIBADD = $(QUOTA_LIBS) + +doveadm_module_LTLIBRARIES = \ + lib10_doveadm_quota_plugin.la + +lib10_doveadm_quota_plugin_la_SOURCES = \ + doveadm-quota.c + +quota_status_SOURCES = \ + quota-status.c \ + quota-status-settings.c + +quota_status_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) +quota_status_LDADD = \ + $(quota_common_objects) \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) \ + $(QUOTA_LIBS) \ + $(BINARY_LDFLAGS) +quota_status_DEPENDENCIES = \ + $(quota_common_objects) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +if HAVE_RQUOTA +RQUOTA_XDR = rquota_xdr.c +RQUOTA_XDR_LO = rquota_xdr.lo +#RQUOTA_X = /usr/include/rpcsvc/rquota.x +RQUOTA_X = $(srcdir)/rquota.x +rquota_xdr.c: Makefile rquota.h + if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \ + cp $(RQUOTA_X) $(top_builddir)/src/plugins/quota/; \ + fi; \ + (echo '#include "lib.h"'; \ + echo '#undef FALSE'; \ + echo '#undef TRUE'; \ + echo '#include <rpc/rpc.h>'; \ + $(RPCGEN) -c $(top_builddir)/src/plugins/quota/rquota.x | \ + sed -e 's/IXDR_PUT/(void)IXDR_PUT/g' \ + -e 's,!xdr_,0 == xdr_,' \ + -e 's,/usr/include/rpcsvc/rquota.h,rquota.h,' \ + -e 's/int32_t \*buf/int32_t *buf ATTR_UNUSED/' \ + -e 's/^static char rcsid.*//' ) > rquota_xdr.c + +rquota.h: Makefile $(RQUOTA_X) + $(RPCGEN) -h $(RQUOTA_X) > rquota.h + +quota-fs.lo: rquota.h + +endif + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = \ + quota.h \ + quota-fs.h \ + quota-plugin.h \ + quota-private.h +noinst_HEADERS = \ + quota-status-settings.h + +EXTRA_DIST = rquota.x + +clean-generic: + if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \ + rm -f $(top_builddir)/src/plugins/quota/rquota.x; \ + fi; \ + rm -f rquota_xdr.c rquota.h + +test_programs = \ + test-quota-util +noinst_PROGRAMS = $(test_programs) + +test_libs = \ + ../../lib-test/libtest.la \ + ../../lib/liblib.la +test_deps = $(noinst_LTLIBRARIES) $(test_libs) + +test_quota_util_SOURCES = test-quota-util.c +test_quota_util_LDADD = quota-util.lo $(test_libs) +test_quota_util_DEPENDENCIES = quota-util.lo $(test_deps) + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/plugins/quota/Makefile.in b/src/plugins/quota/Makefile.in new file mode 100644 index 0000000..a8a824f --- /dev/null +++ b/src/plugins/quota/Makefile.in @@ -0,0 +1,1179 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +pkglibexec_PROGRAMS = quota-status$(EXEEXT) +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/plugins/quota +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(pkginc_lib_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-quota-util$(EXEEXT) +am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \ + "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" \ + "$(DESTDIR)$(pkginc_libdir)" +PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES) +lib10_doveadm_quota_plugin_la_LIBADD = +am_lib10_doveadm_quota_plugin_la_OBJECTS = doveadm-quota.lo +lib10_doveadm_quota_plugin_la_OBJECTS = \ + $(am_lib10_doveadm_quota_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib10_doveadm_quota_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(lib10_doveadm_quota_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +am__DEPENDENCIES_1 = +lib10_quota_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1) +am__objects_1 = quota.lo quota-count.lo quota-fs.lo quota-dict.lo \ + quota-dirsize.lo quota-imapc.lo quota-maildir.lo \ + quota-plugin.lo quota-storage.lo quota-util.lo +am_lib10_quota_plugin_la_OBJECTS = $(am__objects_1) +@HAVE_RQUOTA_TRUE@am__objects_2 = rquota_xdr.lo +nodist_lib10_quota_plugin_la_OBJECTS = $(am__objects_2) +lib10_quota_plugin_la_OBJECTS = $(am_lib10_quota_plugin_la_OBJECTS) \ + $(nodist_lib10_quota_plugin_la_OBJECTS) +lib10_quota_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib10_quota_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_quota_status_OBJECTS = quota_status-quota-status.$(OBJEXT) \ + quota_status-quota-status-settings.$(OBJEXT) +quota_status_OBJECTS = $(am_quota_status_OBJECTS) +am_test_quota_util_OBJECTS = test-quota-util.$(OBJEXT) +test_quota_util_OBJECTS = $(am_test_quota_util_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/doveadm-quota.Plo \ + ./$(DEPDIR)/quota-count.Plo ./$(DEPDIR)/quota-dict.Plo \ + ./$(DEPDIR)/quota-dirsize.Plo ./$(DEPDIR)/quota-fs.Plo \ + ./$(DEPDIR)/quota-imapc.Plo ./$(DEPDIR)/quota-maildir.Plo \ + ./$(DEPDIR)/quota-plugin.Plo ./$(DEPDIR)/quota-storage.Plo \ + ./$(DEPDIR)/quota-util.Plo ./$(DEPDIR)/quota.Plo \ + ./$(DEPDIR)/quota_status-quota-status-settings.Po \ + ./$(DEPDIR)/quota_status-quota-status.Po \ + ./$(DEPDIR)/rquota_xdr.Plo ./$(DEPDIR)/test-quota-util.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib10_doveadm_quota_plugin_la_SOURCES) \ + $(lib10_quota_plugin_la_SOURCES) \ + $(nodist_lib10_quota_plugin_la_SOURCES) \ + $(quota_status_SOURCES) $(test_quota_util_SOURCES) +DIST_SOURCES = $(lib10_doveadm_quota_plugin_la_SOURCES) \ + $(lib10_quota_plugin_la_SOURCES) $(quota_status_SOURCES) \ + $(test_quota_util_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +pkglibexecdir = $(libexecdir)/dovecot +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +doveadm_moduledir = $(moduledir)/doveadm +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-smtp \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-imap-client \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/imapc \ + -I$(top_srcdir)/src/lib-storage/index/maildir \ + -I$(top_srcdir)/src/lib-program-client \ + -I$(top_srcdir)/src/doveadm \ + $(LIBTIRPC_CFLAGS) + +lib10_doveadm_quota_plugin_la_LDFLAGS = -module -avoid-version +lib10_quota_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib10_quota_plugin.la + +quota_dist_sources = \ + quota.c \ + quota-count.c \ + quota-fs.c \ + quota-dict.c \ + quota-dirsize.c \ + quota-imapc.c \ + quota-maildir.c \ + quota-plugin.c \ + quota-storage.c \ + quota-util.c + +quota_common_objects = \ + quota.lo \ + quota-count.lo \ + quota-fs.lo \ + quota-dict.lo \ + quota-dirsize.lo \ + quota-imapc.lo \ + quota-maildir.lo \ + quota-plugin.lo \ + quota-storage.lo \ + quota-util.lo \ + $(RQUOTA_XDR_LO) + +lib10_quota_plugin_la_SOURCES = $(quota_dist_sources) +nodist_lib10_quota_plugin_la_SOURCES = $(RQUOTA_XDR) +lib10_quota_plugin_la_LIBADD = $(QUOTA_LIBS) +doveadm_module_LTLIBRARIES = \ + lib10_doveadm_quota_plugin.la + +lib10_doveadm_quota_plugin_la_SOURCES = \ + doveadm-quota.c + +quota_status_SOURCES = \ + quota-status.c \ + quota-status-settings.c + +quota_status_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) +quota_status_LDADD = \ + $(quota_common_objects) \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) \ + $(QUOTA_LIBS) \ + $(BINARY_LDFLAGS) + +quota_status_DEPENDENCIES = \ + $(quota_common_objects) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +@HAVE_RQUOTA_TRUE@RQUOTA_XDR = rquota_xdr.c +@HAVE_RQUOTA_TRUE@RQUOTA_XDR_LO = rquota_xdr.lo +#RQUOTA_X = /usr/include/rpcsvc/rquota.x +@HAVE_RQUOTA_TRUE@RQUOTA_X = $(srcdir)/rquota.x +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = \ + quota.h \ + quota-fs.h \ + quota-plugin.h \ + quota-private.h + +noinst_HEADERS = \ + quota-status-settings.h + +EXTRA_DIST = rquota.x +test_programs = \ + test-quota-util + +test_libs = \ + ../../lib-test/libtest.la \ + ../../lib/liblib.la + +test_deps = $(noinst_LTLIBRARIES) $(test_libs) +test_quota_util_SOURCES = test-quota-util.c +test_quota_util_LDADD = quota-util.lo $(test_libs) +test_quota_util_DEPENDENCIES = quota-util.lo $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/quota/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/quota/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-pkglibexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files + +clean-pkglibexecPROGRAMS: + @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \ + } + +uninstall-doveadm_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \ + done + +clean-doveadm_moduleLTLIBRARIES: + -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES) + @list='$(doveadm_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib10_doveadm_quota_plugin.la: $(lib10_doveadm_quota_plugin_la_OBJECTS) $(lib10_doveadm_quota_plugin_la_DEPENDENCIES) $(EXTRA_lib10_doveadm_quota_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib10_doveadm_quota_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib10_doveadm_quota_plugin_la_OBJECTS) $(lib10_doveadm_quota_plugin_la_LIBADD) $(LIBS) + +lib10_quota_plugin.la: $(lib10_quota_plugin_la_OBJECTS) $(lib10_quota_plugin_la_DEPENDENCIES) $(EXTRA_lib10_quota_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib10_quota_plugin_la_LINK) -rpath $(moduledir) $(lib10_quota_plugin_la_OBJECTS) $(lib10_quota_plugin_la_LIBADD) $(LIBS) + +quota-status$(EXEEXT): $(quota_status_OBJECTS) $(quota_status_DEPENDENCIES) $(EXTRA_quota_status_DEPENDENCIES) + @rm -f quota-status$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(quota_status_OBJECTS) $(quota_status_LDADD) $(LIBS) + +test-quota-util$(EXEEXT): $(test_quota_util_OBJECTS) $(test_quota_util_DEPENDENCIES) $(EXTRA_test_quota_util_DEPENDENCIES) + @rm -f test-quota-util$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_quota_util_OBJECTS) $(test_quota_util_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-quota.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-count.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-dict.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-dirsize.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-fs.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-imapc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-maildir.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota_status-quota-status-settings.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota_status-quota-status.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rquota_xdr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-quota-util.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +quota_status-quota-status.o: quota-status.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status.o -MD -MP -MF $(DEPDIR)/quota_status-quota-status.Tpo -c -o quota_status-quota-status.o `test -f 'quota-status.c' || echo '$(srcdir)/'`quota-status.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status.Tpo $(DEPDIR)/quota_status-quota-status.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status.c' object='quota_status-quota-status.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status.o `test -f 'quota-status.c' || echo '$(srcdir)/'`quota-status.c + +quota_status-quota-status.obj: quota-status.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status.obj -MD -MP -MF $(DEPDIR)/quota_status-quota-status.Tpo -c -o quota_status-quota-status.obj `if test -f 'quota-status.c'; then $(CYGPATH_W) 'quota-status.c'; else $(CYGPATH_W) '$(srcdir)/quota-status.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status.Tpo $(DEPDIR)/quota_status-quota-status.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status.c' object='quota_status-quota-status.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status.obj `if test -f 'quota-status.c'; then $(CYGPATH_W) 'quota-status.c'; else $(CYGPATH_W) '$(srcdir)/quota-status.c'; fi` + +quota_status-quota-status-settings.o: quota-status-settings.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status-settings.o -MD -MP -MF $(DEPDIR)/quota_status-quota-status-settings.Tpo -c -o quota_status-quota-status-settings.o `test -f 'quota-status-settings.c' || echo '$(srcdir)/'`quota-status-settings.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status-settings.Tpo $(DEPDIR)/quota_status-quota-status-settings.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status-settings.c' object='quota_status-quota-status-settings.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status-settings.o `test -f 'quota-status-settings.c' || echo '$(srcdir)/'`quota-status-settings.c + +quota_status-quota-status-settings.obj: quota-status-settings.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status-settings.obj -MD -MP -MF $(DEPDIR)/quota_status-quota-status-settings.Tpo -c -o quota_status-quota-status-settings.obj `if test -f 'quota-status-settings.c'; then $(CYGPATH_W) 'quota-status-settings.c'; else $(CYGPATH_W) '$(srcdir)/quota-status-settings.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status-settings.Tpo $(DEPDIR)/quota_status-quota-status-settings.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status-settings.c' object='quota_status-quota-status-settings.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status-settings.obj `if test -f 'quota-status-settings.c'; then $(CYGPATH_W) 'quota-status-settings.c'; else $(CYGPATH_W) '$(srcdir)/quota-status-settings.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-noinstPROGRAMS \ + clean-pkglibexecPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/doveadm-quota.Plo + -rm -f ./$(DEPDIR)/quota-count.Plo + -rm -f ./$(DEPDIR)/quota-dict.Plo + -rm -f ./$(DEPDIR)/quota-dirsize.Plo + -rm -f ./$(DEPDIR)/quota-fs.Plo + -rm -f ./$(DEPDIR)/quota-imapc.Plo + -rm -f ./$(DEPDIR)/quota-maildir.Plo + -rm -f ./$(DEPDIR)/quota-plugin.Plo + -rm -f ./$(DEPDIR)/quota-storage.Plo + -rm -f ./$(DEPDIR)/quota-util.Plo + -rm -f ./$(DEPDIR)/quota.Plo + -rm -f ./$(DEPDIR)/quota_status-quota-status-settings.Po + -rm -f ./$(DEPDIR)/quota_status-quota-status.Po + -rm -f ./$(DEPDIR)/rquota_xdr.Plo + -rm -f ./$(DEPDIR)/test-quota-util.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-doveadm_moduleLTLIBRARIES \ + install-moduleLTLIBRARIES install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-pkglibexecPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/doveadm-quota.Plo + -rm -f ./$(DEPDIR)/quota-count.Plo + -rm -f ./$(DEPDIR)/quota-dict.Plo + -rm -f ./$(DEPDIR)/quota-dirsize.Plo + -rm -f ./$(DEPDIR)/quota-fs.Plo + -rm -f ./$(DEPDIR)/quota-imapc.Plo + -rm -f ./$(DEPDIR)/quota-maildir.Plo + -rm -f ./$(DEPDIR)/quota-plugin.Plo + -rm -f ./$(DEPDIR)/quota-storage.Plo + -rm -f ./$(DEPDIR)/quota-util.Plo + -rm -f ./$(DEPDIR)/quota.Plo + -rm -f ./$(DEPDIR)/quota_status-quota-status-settings.Po + -rm -f ./$(DEPDIR)/quota_status-quota-status.Po + -rm -f ./$(DEPDIR)/rquota_xdr.Plo + -rm -f ./$(DEPDIR)/test-quota-util.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \ + uninstall-pkglibexecPROGRAMS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-doveadm_moduleLTLIBRARIES \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + clean-noinstPROGRAMS clean-pkglibexecPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-doveadm_moduleLTLIBRARIES install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-pkglibexecPROGRAMS \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-doveadm_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \ + uninstall-pkglibexecPROGRAMS + +.PRECIOUS: Makefile + +@HAVE_RQUOTA_TRUE@rquota_xdr.c: Makefile rquota.h +@HAVE_RQUOTA_TRUE@ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \ +@HAVE_RQUOTA_TRUE@ cp $(RQUOTA_X) $(top_builddir)/src/plugins/quota/; \ +@HAVE_RQUOTA_TRUE@ fi; \ +@HAVE_RQUOTA_TRUE@ (echo '#include "lib.h"'; \ +@HAVE_RQUOTA_TRUE@ echo '#undef FALSE'; \ +@HAVE_RQUOTA_TRUE@ echo '#undef TRUE'; \ +@HAVE_RQUOTA_TRUE@ echo '#include <rpc/rpc.h>'; \ +@HAVE_RQUOTA_TRUE@ $(RPCGEN) -c $(top_builddir)/src/plugins/quota/rquota.x | \ +@HAVE_RQUOTA_TRUE@ sed -e 's/IXDR_PUT/(void)IXDR_PUT/g' \ +@HAVE_RQUOTA_TRUE@ -e 's,!xdr_,0 == xdr_,' \ +@HAVE_RQUOTA_TRUE@ -e 's,/usr/include/rpcsvc/rquota.h,rquota.h,' \ +@HAVE_RQUOTA_TRUE@ -e 's/int32_t \*buf/int32_t *buf ATTR_UNUSED/' \ +@HAVE_RQUOTA_TRUE@ -e 's/^static char rcsid.*//' ) > rquota_xdr.c + +@HAVE_RQUOTA_TRUE@rquota.h: Makefile $(RQUOTA_X) +@HAVE_RQUOTA_TRUE@ $(RPCGEN) -h $(RQUOTA_X) > rquota.h + +@HAVE_RQUOTA_TRUE@quota-fs.lo: rquota.h + +clean-generic: + if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \ + rm -f $(top_builddir)/src/plugins/quota/rquota.x; \ + fi; \ + rm -f rquota_xdr.c rquota.h + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/quota/doveadm-quota.c b/src/plugins/quota/doveadm-quota.c new file mode 100644 index 0000000..8a42b22 --- /dev/null +++ b/src/plugins/quota/doveadm-quota.c @@ -0,0 +1,165 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "module-dir.h" +#include "quota-plugin.h" +#include "quota-private.h" +#include "doveadm-print.h" +#include "doveadm-mail.h" + +const char *doveadm_quota_plugin_version = DOVECOT_ABI_VERSION; + +void doveadm_quota_plugin_init(struct module *module); +void doveadm_quota_plugin_deinit(void); + +static int cmd_quota_get_root(struct quota_root *root) +{ + const char *const *res; + const char *error; + uint64_t value, limit; + enum quota_get_result qret; + int ret = 0; + + res = quota_root_get_resources(root); + for (; *res != NULL; res++) { + qret = quota_get_resource(root, "", *res, &value, &limit, &error); + doveadm_print(root->set->name); + doveadm_print(*res); + if (qret == QUOTA_GET_RESULT_LIMITED) { + doveadm_print_num(value); + doveadm_print_num(limit); + if (limit > 0) + doveadm_print_num(value*100 / limit); + else + doveadm_print("0"); + } else if (qret == QUOTA_GET_RESULT_UNLIMITED) { + doveadm_print_num(value); + doveadm_print("-"); + doveadm_print("0"); + } else { + i_error("Failed to get quota resource %s: %s", + *res, error); + doveadm_print("error"); + doveadm_print("error"); + doveadm_print("error"); + ret = -1; + } + } + return ret; +} + +static int +cmd_quota_get_run(struct doveadm_mail_cmd_context *ctx, + struct mail_user *user) +{ + struct quota_user *quser = QUOTA_USER_CONTEXT(user); + struct quota_root *const *root; + + if (quser == NULL) { + i_error("Quota not enabled"); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + + int ret = 0; + array_foreach(&quser->quota->roots, root) + if (cmd_quota_get_root(*root) < 0) + ret = -1; + if (ret < 0) + doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP); + return ret; +} + +static void cmd_quota_get_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + const char *const args[] ATTR_UNUSED) +{ + doveadm_print_header("root", "Quota name", 0); + doveadm_print_header("type", "Type", 0); + doveadm_print_header("value", "Value", + DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY); + doveadm_print_header("limit", "Limit", + DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY); + doveadm_print_header("percent", "%", + DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY); +} + +static struct doveadm_mail_cmd_context * +cmd_quota_get_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_quota_get_run; + ctx->v.init = cmd_quota_get_init; + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + return ctx; +} + +static int +cmd_quota_recalc_run(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, + struct mail_user *user) +{ + struct quota_user *quser = QUOTA_USER_CONTEXT(user); + struct quota_root *const *root; + struct quota_transaction_context trans; + + if (quser == NULL) { + i_error("Quota not enabled"); + doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND); + return -1; + } + + i_zero(&trans); + trans.quota = quser->quota; + trans.recalculate = QUOTA_RECALCULATE_FORCED; + + array_foreach(&quser->quota->roots, root) { + const char *error; + if ((*root)->backend.v.update(*root, &trans, &error) < 0) + i_error("Recalculating quota failed: %s", error); + if ((*root)->backend.v.flush != NULL) + (*root)->backend.v.flush(*root); + } + return 0; +} + +static struct doveadm_mail_cmd_context * +cmd_quota_recalc_alloc(void) +{ + struct doveadm_mail_cmd_context *ctx; + + ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); + ctx->v.run = cmd_quota_recalc_run; + return ctx; +} + +static struct doveadm_cmd_ver2 quota_commands[] = { + { + .name = "quota get", + .usage = "", + .mail_cmd = cmd_quota_get_alloc, +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAMS_END + }, + { + .name = "quota recalc", + .usage = "", + .mail_cmd = cmd_quota_recalc_alloc, +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_MAIL_COMMON +DOVEADM_CMD_PARAMS_END + } +}; + +void doveadm_quota_plugin_init(struct module *module ATTR_UNUSED) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(quota_commands); i++) + doveadm_cmd_register_ver2("a_commands[i]); +} + +void doveadm_quota_plugin_deinit(void) +{ +} diff --git a/src/plugins/quota/quota-count.c b/src/plugins/quota/quota-count.c new file mode 100644 index 0000000..00e25e6 --- /dev/null +++ b/src/plugins/quota/quota-count.c @@ -0,0 +1,400 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "mailbox-list-iter.h" +#include "quota-private.h" + +struct count_quota_root { + struct quota_root root; + + struct timeval cache_timeval; + uint64_t cached_bytes, cached_count; +}; + +struct quota_mailbox_iter { + struct quota_root *root; + struct mail_namespace *ns; + unsigned int ns_idx; + struct mailbox_list_iterate_context *iter; + struct mailbox_info info; + const char *error; +}; + +extern struct quota_backend quota_backend_count; + +static int +quota_count_mailbox(struct quota_root *root, struct mail_namespace *ns, + const char *vname, uint64_t *bytes, uint64_t *count, + enum quota_get_result *error_result_r, + const char **error_r) +{ + struct quota_rule *rule; + struct mailbox *box; + struct mailbox_metadata metadata; + struct mailbox_status status; + enum mail_error error; + const char *errstr; + int ret; + + rule = quota_root_rule_find(root->set, vname); + if (rule != NULL && rule->ignore) { + /* mailbox not included in quota */ + return 0; + } + + box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_READONLY); + if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0) { + /* quota doesn't exist for this mailbox/storage */ + ret = 0; + } else if (mailbox_get_metadata(box, root->quota->set->vsizes ? + MAILBOX_METADATA_VIRTUAL_SIZE : + MAILBOX_METADATA_PHYSICAL_SIZE, + &metadata) < 0 || + mailbox_get_status(box, STATUS_MESSAGES, &status) < 0) { + errstr = mailbox_get_last_internal_error(box, &error); + if (error == MAIL_ERROR_TEMP) { + *error_r = t_strdup_printf( + "Couldn't get size of mailbox %s: %s", + vname, errstr); + *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR; + ret = -1; + } else if (error == MAIL_ERROR_INUSE) { + /* started on background. don't log an error. */ + *error_r = t_strdup_printf( + "Ongoing quota calculation blocked getting size of %s: %s", + vname, errstr); + *error_result_r = QUOTA_GET_RESULT_BACKGROUND_CALC; + ret = -1; + } else { + /* non-temporary error, e.g. ACLs denied access. */ + ret = 0; + } + } else { + ret = 0; + *bytes += root->quota->set->vsizes ? + metadata.virtual_size : metadata.physical_size; + *count += status.messages; + } + mailbox_free(&box); + return ret; +} + +static struct quota_mailbox_iter * +quota_mailbox_iter_begin(struct quota_root *root) +{ + struct quota_mailbox_iter *iter; + + iter = i_new(struct quota_mailbox_iter, 1); + iter->root = root; + iter->error = ""; + return iter; +} + +static int +quota_mailbox_iter_deinit(struct quota_mailbox_iter **_iter, + const char **error_r) +{ + struct quota_mailbox_iter *iter = *_iter; + int ret = *iter->error != '\0' ? -1 : 0; + + *_iter = NULL; + + const char *error2 = ""; + if (iter->iter != NULL) { + if (mailbox_list_iter_deinit(&iter->iter) < 0) { + error2 = t_strdup_printf( + "Listing namespace '%s' failed: %s", + iter->ns->prefix, + mailbox_list_get_last_internal_error(iter->ns->list, NULL)); + ret = -1; + } + } + if (ret < 0) { + const char *separator = + *iter->error != '\0' && *error2 != '\0' ? " and " : ""; + *error_r = t_strdup_printf("%s%s%s", + iter->error, separator, error2); + } + i_free(iter); + return ret; +} + +static const struct mailbox_info * +quota_mailbox_iter_next(struct quota_mailbox_iter *iter) +{ + struct mail_namespace *const *namespaces; + const struct mailbox_info *info; + unsigned int count; + + if (iter->iter == NULL) { + namespaces = array_get(&iter->root->quota->namespaces, &count); + do { + if (iter->ns_idx >= count) + return NULL; + + iter->ns = namespaces[iter->ns_idx++]; + } while (!quota_root_is_namespace_visible(iter->root, iter->ns)); + iter->iter = mailbox_list_iter_init(iter->ns->list, "*", + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS | + MAILBOX_LIST_ITER_NO_AUTO_BOXES); + } + while ((info = mailbox_list_iter_next(iter->iter)) != NULL) { + if ((info->flags & (MAILBOX_NONEXISTENT | + MAILBOX_NOSELECT)) == 0) + return info; + } + if (mailbox_list_iter_deinit(&iter->iter) < 0) { + iter->error = t_strdup_printf( + "Listing namespace '%s' failed: %s", + iter->ns->prefix, + mailbox_list_get_last_internal_error(iter->ns->list, NULL)); + } + if (iter->ns->prefix_len > 0 && + (iter->ns->prefix_len != 6 || + strncasecmp(iter->ns->prefix, "INBOX", 5) != 0)) { + /* if the namespace prefix itself exists, count it also */ + iter->info.ns = iter->ns; + iter->info.vname = t_strndup(iter->ns->prefix, + iter->ns->prefix_len-1); + return &iter->info; + } + /* try the next namespace */ + return quota_mailbox_iter_next(iter); +} + +int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r, + enum quota_get_result *error_result_r, const char **error_r) +{ + struct quota_mailbox_iter *iter; + const struct mailbox_info *info; + const char *error1 = "", *error2 = ""; + int ret = 1; + + *bytes_r = *count_r = 0; + if (root->recounting) + return 0; + root->recounting = TRUE; + + struct event_reason *reason = event_reason_begin("quota:count"); + + iter = quota_mailbox_iter_begin(root); + while ((info = quota_mailbox_iter_next(iter)) != NULL) { + if (quota_count_mailbox(root, info->ns, info->vname, + bytes_r, count_r, error_result_r, + &error1) < 0) { + ret = -1; + break; + } + } + if (quota_mailbox_iter_deinit(&iter, &error2) < 0) { + *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR; + ret = -1; + } + if (ret < 0) { + const char *separator = + *error1 != '\0' && *error2 != '\0' ? " and " : ""; + *error_r = t_strconcat(error1, separator, error2, NULL); + } + event_reason_end(&reason); + root->recounting = FALSE; + return ret; +} + +static enum quota_get_result +quota_count_cached(struct count_quota_root *root, + uint64_t *bytes_r, uint64_t *count_r, + const char **error_r) +{ + int ret; + + if (root->cache_timeval.tv_usec == ioloop_timeval.tv_usec && + root->cache_timeval.tv_sec == ioloop_timeval.tv_sec && + ioloop_timeval.tv_sec != 0) { + *bytes_r = root->cached_bytes; + *count_r = root->cached_count; + return QUOTA_GET_RESULT_LIMITED; + } + + enum quota_get_result error_res; + ret = quota_count(&root->root, bytes_r, count_r, &error_res, error_r); + if (ret < 0) { + return error_res; + } else if (ret > 0) { + root->cache_timeval = ioloop_timeval; + root->cached_bytes = *bytes_r; + root->cached_count = *count_r; + } + return QUOTA_GET_RESULT_LIMITED; +} + +static struct quota_root *count_quota_alloc(void) +{ + struct count_quota_root *root; + + root = i_new(struct count_quota_root, 1); + return &root->root; +} + +static int count_quota_init(struct quota_root *root, const char *args, + const char **error_r) +{ + if (!root->quota->set->vsizes) { + *error_r = "quota count backend requires quota_vsizes=yes"; + return -1; + } + event_set_append_log_prefix(root->backend.event, "quota-count: "); + + root->auto_updating = TRUE; + return quota_root_default_init(root, args, error_r); +} + +static void count_quota_deinit(struct quota_root *_root) +{ + i_free(_root); +} + +static const char *const * +count_quota_root_get_resources(struct quota_root *root ATTR_UNUSED) +{ + static const char *resources[] = { + QUOTA_NAME_STORAGE_KILOBYTES, QUOTA_NAME_MESSAGES, NULL + }; + return resources; +} + +static enum quota_get_result +count_quota_get_resource(struct quota_root *_root, + const char *name, uint64_t *value_r, + const char **error_r) +{ + struct count_quota_root *root = (struct count_quota_root *)_root; + uint64_t bytes, count; + enum quota_get_result ret; + + ret = quota_count_cached(root, &bytes, &count, error_r); + if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) + return ret; + + if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) + *value_r = bytes; + else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) + *value_r = count; + else { + *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; + return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; + } + return QUOTA_GET_RESULT_LIMITED; +} + +static int quota_count_recalculate_box(struct mailbox *box, + const char **error_r) +{ + struct mail_index_transaction *trans; + struct mailbox_metadata metadata; + struct mailbox_index_vsize vsize_hdr; + const char *errstr; + enum mail_error error; + + if (mailbox_open(box) < 0) { + errstr = mailbox_get_last_internal_error(box, &error); + if (error != MAIL_ERROR_TEMP) { + /* non-temporary error, e.g. ACLs denied access. */ + return 0; + } + *error_r = t_strdup_printf( + "Couldn't open mailbox %s: %s", box->vname, errstr); + return -1; + } + + /* reset the vsize header first */ + trans = mail_index_transaction_begin(box->view, + MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL); + i_zero(&vsize_hdr); + mail_index_update_header_ext(trans, box->vsize_hdr_ext_id, + 0, &vsize_hdr, sizeof(vsize_hdr)); + if (mail_index_transaction_commit(&trans) < 0) { + *error_r = t_strdup_printf( + "Couldn't commit mail index transaction for %s: %s", + box->vname, + mail_index_get_error_message(box->view->index)); + return -1; + } + /* getting the vsize now forces its recalculation */ + if (mailbox_get_metadata(box, MAILBOX_METADATA_VIRTUAL_SIZE, + &metadata) < 0) { + *error_r = t_strdup_printf( + "Couldn't get mailbox %s vsize: %s", box->vname, + mailbox_get_last_internal_error(box, NULL)); + return -1; + } + /* call sync to write the change to mailbox list index */ + if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) < 0) { + *error_r = t_strdup_printf( + "Couldn't sync mailbox %s: %s", box->vname, + mailbox_get_last_internal_error(box, NULL)); + return -1; + } + return 0; +} + +static int quota_count_recalculate(struct quota_root *root, + const char **error_r) +{ + struct event_reason *reason; + struct quota_mailbox_iter *iter; + const struct mailbox_info *info; + struct mailbox *box; + int ret = 0; + const char *error1 = "", *error2 = ""; + + reason = event_reason_begin("quota:recalculate"); + + iter = quota_mailbox_iter_begin(root); + while ((info = quota_mailbox_iter_next(iter)) != NULL) { + box = mailbox_alloc(info->ns->list, info->vname, 0); + if (quota_count_recalculate_box(box, &error1) < 0) + ret = -1; + mailbox_free(&box); + } + if (quota_mailbox_iter_deinit(&iter, &error2) < 0) + ret = -1; + if (ret < 0) { + const char *separator = + *error1 != '\0' && *error2 != '\0' ? " and " : ""; + *error_r = t_strdup_printf( + "quota-count: recalculate failed: %s%s%s", + error1, separator, error2); + } + event_reason_end(&reason); + return ret; +} + +static int +count_quota_update(struct quota_root *root, + struct quota_transaction_context *ctx, + const char **error_r) +{ + struct count_quota_root *croot = (struct count_quota_root *)root; + + croot->cache_timeval.tv_sec = 0; + if (ctx->recalculate == QUOTA_RECALCULATE_FORCED) { + if (quota_count_recalculate(root, error_r) < 0) + return -1; + } + return 0; +} + +struct quota_backend quota_backend_count = { + .name = "count", + + .v = { + .alloc = count_quota_alloc, + .init = count_quota_init, + .deinit = count_quota_deinit, + .get_resources = count_quota_root_get_resources, + .get_resource = count_quota_get_resource, + .update = count_quota_update, + } +}; diff --git a/src/plugins/quota/quota-dict.c b/src/plugins/quota/quota-dict.c new file mode 100644 index 0000000..02e444a --- /dev/null +++ b/src/plugins/quota/quota-dict.c @@ -0,0 +1,269 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "str.h" +#include "dict.h" +#include "mail-user.h" +#include "mail-namespace.h" +#include "quota-private.h" + + +#define DICT_QUOTA_CURRENT_PATH DICT_PATH_PRIVATE"quota/" +#define DICT_QUOTA_CURRENT_BYTES_PATH DICT_QUOTA_CURRENT_PATH"storage" +#define DICT_QUOTA_CURRENT_COUNT_PATH DICT_QUOTA_CURRENT_PATH"messages" + +struct dict_quota_root { + struct quota_root root; + struct dict *dict; + struct timeout *to_update; + bool disable_unset; +}; + +extern struct quota_backend quota_backend_dict; + +static struct quota_root *dict_quota_alloc(void) +{ + struct dict_quota_root *root; + + root = i_new(struct dict_quota_root, 1); + return &root->root; +} + +static void handle_nounset_param(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + ((struct dict_quota_root *)_root)->disable_unset = TRUE; +} + +static int dict_quota_init(struct quota_root *_root, const char *args, + const char **error_r) +{ + struct dict_quota_root *root = (struct dict_quota_root *)_root; + struct dict_settings set; + const char *username, *p, *error; + + event_set_append_log_prefix(_root->backend.event, "quota-dict: "); + + const struct quota_param_parser dict_params[] = { + {.param_name = "no-unset", .param_handler = handle_nounset_param}, + quota_param_hidden, quota_param_ignoreunlimited, quota_param_noenforcing, quota_param_ns, + {.param_name = NULL} + }; + + p = args == NULL ? NULL : strchr(args, ':'); + if (p == NULL) { + *error_r = "URI missing from parameters"; + return -1; + } + + username = t_strdup_until(args, p); + args = p+1; + + if (quota_parse_parameters(_root, &args, error_r, dict_params, FALSE) < 0) + i_unreached(); + + if (*username == '\0') + username = _root->quota->user->username; + + e_debug(_root->backend.event, "user=%s, uri=%s, noenforcing=%d", + username, args, _root->no_enforcing ? 1 : 0); + + /* FIXME: we should use 64bit integer as datatype instead but before + it can actually be used don't bother */ + i_zero(&set); + set.base_dir = _root->quota->user->set->base_dir; + set.event_parent = _root->quota->user->event; + if (dict_init(args, &set, &root->dict, &error) < 0) { + *error_r = t_strdup_printf("dict_init(%s) failed: %s", args, error); + return -1; + } + return 0; +} + +static void dict_quota_deinit(struct quota_root *_root) +{ + struct dict_quota_root *root = (struct dict_quota_root *)_root; + + i_assert(root->to_update == NULL); + + if (root->dict != NULL) { + dict_wait(root->dict); + dict_deinit(&root->dict); + } + i_free(root); +} + +static const char *const * +dict_quota_root_get_resources(struct quota_root *root ATTR_UNUSED) +{ + static const char *resources[] = { + QUOTA_NAME_STORAGE_KILOBYTES, QUOTA_NAME_MESSAGES, NULL + }; + + return resources; +} + +static enum quota_get_result +dict_quota_count(struct dict_quota_root *root, + bool want_bytes, uint64_t *value_r, + const char **error_r) +{ + struct dict_transaction_context *dt; + struct event_reason *reason; + uint64_t bytes, count; + enum quota_get_result error_res; + const struct dict_op_settings *set; + + reason = event_reason_begin("quota:recalculate"); + int ret = quota_count(&root->root, &bytes, &count, &error_res, error_r); + event_reason_end(&reason); + if (ret < 0) + return error_res; + + set = mail_user_get_dict_op_settings(root->root.quota->user); + dt = dict_transaction_begin(root->dict, set); + /* these unsets are mainly necessary for pgsql, because its + trigger otherwise increases quota without deleting it. + but some people with other databases want to store the + quota usage among other data in the same row, which + shouldn't be deleted. */ + if (!root->disable_unset) { + dict_unset(dt, DICT_QUOTA_CURRENT_BYTES_PATH); + dict_unset(dt, DICT_QUOTA_CURRENT_COUNT_PATH); + } + dict_set(dt, DICT_QUOTA_CURRENT_BYTES_PATH, dec2str(bytes)); + dict_set(dt, DICT_QUOTA_CURRENT_COUNT_PATH, dec2str(count)); + + e_debug(root->root.backend.event, "Quota recalculated: " + "count=%"PRIu64" bytes=%"PRIu64, count, bytes); + + dict_transaction_commit_async_nocallback(&dt); + *value_r = want_bytes ? bytes : count; + return QUOTA_GET_RESULT_LIMITED; +} + +static enum quota_get_result +dict_quota_get_resource(struct quota_root *_root, + const char *name, uint64_t *value_r, + const char **error_r) +{ + struct dict_quota_root *root = (struct dict_quota_root *)_root; + bool want_bytes; + int ret; + const struct dict_op_settings *set; + + if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) + want_bytes = TRUE; + else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) + want_bytes = FALSE; + else { + *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; + return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; + } + + set = mail_user_get_dict_op_settings(root->root.quota->user); + const char *key, *value, *error; + key = want_bytes ? DICT_QUOTA_CURRENT_BYTES_PATH : + DICT_QUOTA_CURRENT_COUNT_PATH; + ret = dict_lookup(root->dict, set, unsafe_data_stack_pool, + key, &value, &error); + if (ret < 0) { + *error_r = t_strdup_printf( + "dict_lookup(%s) failed: %s", key, error); + *value_r = 0; + return QUOTA_GET_RESULT_INTERNAL_ERROR; + } + + intmax_t tmp; + /* recalculate quota if it's negative or if it wasn't found */ + if (ret == 0 || str_to_intmax(value, &tmp) < 0) + tmp = -1; + if (tmp >= 0) + *value_r = tmp; + else + return dict_quota_count(root, want_bytes, value_r, error_r); + return QUOTA_GET_RESULT_LIMITED; +} + +static void dict_quota_recalc_timeout(struct dict_quota_root *root) +{ + uint64_t value; + const char *error; + + timeout_remove(&root->to_update); + if (dict_quota_count(root, TRUE, &value, &error) + <= QUOTA_GET_RESULT_INTERNAL_ERROR) + e_error(root->root.backend.event, + "Recalculation failed: %s", error); +} + +static void dict_quota_update_callback(const struct dict_commit_result *result, + struct dict_quota_root *root) +{ + if (result->ret == 0) { + /* row doesn't exist, need to recalculate it */ + if (root->to_update == NULL) + root->to_update = timeout_add_short(0, dict_quota_recalc_timeout, root); + } else if (result->ret < 0) { + e_error(root->root.backend.event, + "Quota update failed: %s " + "- Quota is now desynced", result->error); + } +} + +static int +dict_quota_update(struct quota_root *_root, + struct quota_transaction_context *ctx, + const char **error_r) +{ + struct dict_quota_root *root = (struct dict_quota_root *) _root; + struct dict_transaction_context *dt; + uint64_t value; + const struct dict_op_settings *set; + + if (ctx->recalculate != QUOTA_RECALCULATE_DONT) { + if (dict_quota_count(root, TRUE, &value, error_r) + <= QUOTA_GET_RESULT_INTERNAL_ERROR) + return -1; + } else { + set = mail_user_get_dict_op_settings(root->root.quota->user); + dt = dict_transaction_begin(root->dict, set); + if (ctx->bytes_used != 0) { + dict_atomic_inc(dt, DICT_QUOTA_CURRENT_BYTES_PATH, + ctx->bytes_used); + } + if (ctx->count_used != 0) { + dict_atomic_inc(dt, DICT_QUOTA_CURRENT_COUNT_PATH, + ctx->count_used); + } + dict_transaction_no_slowness_warning(dt); + dict_transaction_commit_async(&dt, dict_quota_update_callback, + root); + } + return 0; +} + +static void dict_quota_flush(struct quota_root *_root) +{ + struct dict_quota_root *root = (struct dict_quota_root *)_root; + + dict_wait(root->dict); + if (root->to_update != NULL) { + dict_quota_recalc_timeout(root); + dict_wait(root->dict); + } +} + +struct quota_backend quota_backend_dict = { + .name = "dict", + + .v = { + .alloc = dict_quota_alloc, + .init = dict_quota_init, + .deinit = dict_quota_deinit, + .get_resources = dict_quota_root_get_resources, + .get_resource = dict_quota_get_resource, + .update = dict_quota_update, + .flush = dict_quota_flush, + } +}; diff --git a/src/plugins/quota/quota-dirsize.c b/src/plugins/quota/quota-dirsize.c new file mode 100644 index 0000000..a8305d8 --- /dev/null +++ b/src/plugins/quota/quota-dirsize.c @@ -0,0 +1,232 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +/* Quota reporting based on simply summing sizes of all files in mailbox + together. */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "quota-private.h" + +#include <unistd.h> +#include <dirent.h> +#include <sys/stat.h> + +struct quota_count_path { + const char *path; + bool is_file; +}; +ARRAY_DEFINE_TYPE(quota_count_path, struct quota_count_path); + +extern struct quota_backend quota_backend_dirsize; + +static struct quota_root *dirsize_quota_alloc(void) +{ + return i_new(struct quota_root, 1); +} + +static int dirsize_quota_init(struct quota_root *root, const char *args, + const char **error_r) +{ + root->auto_updating = TRUE; + event_set_append_log_prefix(root->backend.event, "quota-dirsize: "); + return quota_root_default_init(root, args, error_r); +} + +static void dirsize_quota_deinit(struct quota_root *_root) +{ + i_free(_root); +} + +static const char *const * +dirsize_quota_root_get_resources(struct quota_root *root ATTR_UNUSED) +{ + static const char *resources[] = { QUOTA_NAME_STORAGE_KILOBYTES, NULL }; + + return resources; +} + +static int get_dir_usage(const char *dir, uint64_t *value, + const char **error_r) +{ + DIR *dirp; + string_t *path; + struct dirent *d; + struct stat st; + unsigned int path_pos; + int ret; + + dirp = opendir(dir); + if (dirp == NULL) { + if (errno == ENOENT) + return 0; + + *error_r = t_strdup_printf("opendir(%s) failed: %m", dir); + return -1; + } + + path = t_str_new(128); + str_append(path, dir); + str_append_c(path, '/'); + path_pos = str_len(path); + + ret = 0; + while ((d = readdir(dirp)) != NULL) { + if (d->d_name[0] == '.' && + (d->d_name[1] == '\0' || + (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { + /* skip . and .. */ + continue; + } + + str_truncate(path, path_pos); + str_append(path, d->d_name); + + if (lstat(str_c(path), &st) < 0) { + if (errno == ENOENT) + continue; + + *error_r = t_strdup_printf("lstat(%s) failed: %m", dir); + ret = -1; + break; + } else if (S_ISDIR(st.st_mode)) { + if (get_dir_usage(str_c(path), value, error_r) < 0) { + ret = -1; + break; + } + } else { + *value += st.st_size; + } + } + + (void)closedir(dirp); + return ret; +} + +static int get_usage(const char *path, bool is_file, uint64_t *value_r, + const char **error_r) +{ + struct stat st; + + if (is_file) { + if (lstat(path, &st) < 0) { + if (errno == ENOENT) + return 0; + + *error_r = t_strdup_printf("lstat(%s) failed: %m", path); + return -1; + } + *value_r += st.st_size; + } else { + if (get_dir_usage(path, value_r, error_r) < 0) + return -1; + } + return 0; +} + +static void quota_count_path_add(ARRAY_TYPE(quota_count_path) *paths, + const char *path, bool is_file) +{ + struct quota_count_path *count_path; + unsigned int i, count; + size_t path_len; + + path_len = strlen(path); + count_path = array_get_modifiable(paths, &count); + for (i = 0; i < count; ) { + if (strncmp(count_path[i].path, path, + strlen(count_path[i].path)) == 0) { + /* this path has already been counted */ + return; + } + if (strncmp(count_path[i].path, path, path_len) == 0 && + count_path[i].path[path_len] == '/') { + /* the new path contains the existing path. + drop it and see if there are more to drop. */ + array_delete(paths, i, 1); + count_path = array_get_modifiable(paths, &count); + } else { + i++; + } + } + + count_path = array_append_space(paths); + count_path->path = t_strdup(path); + count_path->is_file = is_file; +} + +static int +get_quota_root_usage(struct quota_root *root, uint64_t *value_r, + const char **error_r) +{ + struct mail_namespace *const *namespaces; + ARRAY_TYPE(quota_count_path) paths; + const struct quota_count_path *count_paths; + unsigned int i, count; + const char *path; + bool is_file; + + t_array_init(&paths, 8); + namespaces = array_get(&root->quota->namespaces, &count); + for (i = 0; i < count; i++) { + if (!quota_root_is_namespace_visible(root, namespaces[i])) + continue; + + is_file = mail_storage_is_mailbox_file(namespaces[i]->storage); + if (mailbox_list_get_root_path(namespaces[i]->list, + MAILBOX_LIST_PATH_TYPE_DIR, &path)) + quota_count_path_add(&paths, path, FALSE); + + /* INBOX may be in different path. */ + if (mailbox_list_get_path(namespaces[i]->list, "INBOX", + MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) > 0) + quota_count_path_add(&paths, path, is_file); + } + + /* now sum up the found paths */ + *value_r = 0; + count_paths = array_get(&paths, &count); + for (i = 0; i < count; i++) { + if (get_usage(count_paths[i].path, count_paths[i].is_file, + value_r, error_r) < 0) + return -1; + } + return 0; +} + +static enum quota_get_result +dirsize_quota_get_resource(struct quota_root *_root, const char *name, + uint64_t *value_r, const char **error_r) +{ + int ret; + + if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0) { + *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; + return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; + } + + ret = get_quota_root_usage(_root, value_r, error_r); + + return ret < 0 ? QUOTA_GET_RESULT_INTERNAL_ERROR : QUOTA_GET_RESULT_LIMITED; +} + +static int +dirsize_quota_update(struct quota_root *root ATTR_UNUSED, + struct quota_transaction_context *ctx ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ + return 0; +} + +struct quota_backend quota_backend_dirsize = { + .name = "dirsize", + + .v = { + .alloc = dirsize_quota_alloc, + .init = dirsize_quota_init, + .deinit = dirsize_quota_deinit, + .get_resources = dirsize_quota_root_get_resources, + .get_resource = dirsize_quota_get_resource, + .update = dirsize_quota_update, + } +}; diff --git a/src/plugins/quota/quota-fs.c b/src/plugins/quota/quota-fs.c new file mode 100644 index 0000000..57620c1 --- /dev/null +++ b/src/plugins/quota/quota-fs.c @@ -0,0 +1,970 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +/* Only for reporting filesystem quota */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hostpid.h" +#include "mountpoint.h" +#include "quota-private.h" +#include "quota-fs.h" + +#ifdef HAVE_FS_QUOTA + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> +#ifdef HAVE_LINUX_DQBLK_XFS_H +# include <linux/dqblk_xfs.h> +# define HAVE_XFS_QUOTA +#elif defined (HAVE_XFS_XQM_H) +# include <xfs/xqm.h> /* CentOS 4.x at least uses this */ +# define HAVE_XFS_QUOTA +#endif + +#ifdef HAVE_RQUOTA +# include "rquota.h" +# define RQUOTA_GETQUOTA_TIMEOUT_SECS 10 +#endif + +#ifndef DEV_BSIZE +# ifdef DQBSIZE +# define DEV_BSIZE DQBSIZE /* AIX */ +# else +# define DEV_BSIZE 512 +# endif +#endif + +#ifdef HAVE_STRUCT_DQBLK_CURSPACE +# define dqb_curblocks dqb_curspace +#endif + +/* Very old sys/quota.h doesn't define _LINUX_QUOTA_VERSION at all, which means + it supports only v1 quota. However, new sys/quota.h (glibc 2.25) removes + support for v1 entirely and again it doesn't define it. I guess we can just + assume v2 now, and if someone still wants v1 support they can add + -D_LINUX_QUOTA_VERSION=1 to CFLAGS. */ +#ifndef _LINUX_QUOTA_VERSION +# define _LINUX_QUOTA_VERSION 2 +#endif + +#define mount_type_is_nfs(mount) \ + (strcmp((mount)->type, "nfs") == 0 || \ + strcmp((mount)->type, "nfs4") == 0) + +struct fs_quota_mountpoint { + int refcount; + + char *mount_path; + char *device_path; + char *type; + unsigned int block_size; + +#ifdef FS_QUOTA_SOLARIS + int fd; + char *path; +#endif +}; + +struct fs_quota_root { + struct quota_root root; + char *storage_mount_path; + + uid_t uid; + gid_t gid; + struct fs_quota_mountpoint *mount; + + bool inode_per_mail:1; + bool user_disabled:1; + bool group_disabled:1; +#ifdef FS_QUOTA_NETBSD + struct quotahandle *qh; +#endif +}; + +extern struct quota_backend quota_backend_fs; + +static struct quota_root *fs_quota_alloc(void) +{ + struct fs_quota_root *root; + + root = i_new(struct fs_quota_root, 1); + root->uid = geteuid(); + root->gid = getegid(); + + return &root->root; +} + +static void handle_user_param(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + ((struct fs_quota_root *)_root)->group_disabled = TRUE; +} + +static void handle_group_param(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + ((struct fs_quota_root *)_root)->user_disabled = TRUE; +} + +static void handle_inode_param(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + ((struct fs_quota_root *)_root)->inode_per_mail = TRUE; +} + +static void handle_mount_param(struct quota_root *_root, const char *param_value) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + i_free(root->storage_mount_path); + root->storage_mount_path = i_strdup(param_value); +} + +static int fs_quota_init(struct quota_root *_root, const char *args, + const char **error_r) +{ + const struct quota_param_parser fs_params[] = { + {.param_name = "user", .param_handler = handle_user_param}, + {.param_name = "group", .param_handler = handle_group_param}, + {.param_name = "mount=", .param_handler = handle_mount_param}, + {.param_name = "inode_per_mail", .param_handler = handle_inode_param}, + quota_param_hidden, quota_param_noenforcing, quota_param_ns, + {.param_name = NULL} + }; + + event_set_append_log_prefix(_root->backend.event, "quota-fs: "); + + if (quota_parse_parameters(_root, &args, error_r, fs_params, TRUE) < 0) + return -1; + _root->auto_updating = TRUE; + return 0; +} + +static void fs_quota_mountpoint_free(struct fs_quota_mountpoint *mount) +{ + if (--mount->refcount > 0) + return; + +#ifdef FS_QUOTA_SOLARIS + i_close_fd_path(&mount->fd, mount->path); + i_free(mount->path); +#endif + + i_free(mount->device_path); + i_free(mount->mount_path); + i_free(mount->type); + i_free(mount); +} + +static void fs_quota_deinit(struct quota_root *_root) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + + if (root->mount != NULL) + fs_quota_mountpoint_free(root->mount); + i_free(root->storage_mount_path); + i_free(root); +} + +static struct fs_quota_mountpoint *fs_quota_mountpoint_get(const char *dir) +{ + struct fs_quota_mountpoint *mount; + struct mountpoint point; + int ret; + + ret = mountpoint_get(dir, default_pool, &point); + if (ret <= 0) + return NULL; + + mount = i_new(struct fs_quota_mountpoint, 1); + mount->refcount = 1; + mount->device_path = point.device_path; + mount->mount_path = point.mount_path; + mount->type = point.type; + mount->block_size = point.block_size; +#ifdef FS_QUOTA_SOLARIS + mount->fd = -1; +#endif + + if (mount_type_is_nfs(mount)) { + if (strchr(mount->device_path, ':') == NULL) { + e_error(quota_backend_fs.event, + "%s is not a valid NFS device path", + mount->device_path); + fs_quota_mountpoint_free(mount); + return NULL; + } + } + return mount; +} + +#define QUOTA_ROOT_MATCH(root, mount) \ + ((root)->root.backend.name == quota_backend_fs.name && \ + ((root)->storage_mount_path == NULL || \ + strcmp((root)->storage_mount_path, (mount)->mount_path) == 0)) + +static struct fs_quota_root * +fs_quota_root_find_mountpoint(struct quota *quota, + const struct fs_quota_mountpoint *mount) +{ + struct quota_root *const *roots; + struct fs_quota_root *empty = NULL; + unsigned int i, count; + + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) { + struct fs_quota_root *root = (struct fs_quota_root *)roots[i]; + if (QUOTA_ROOT_MATCH(root, mount)) { + if (root->mount == NULL) + empty = root; + else if (strcmp(root->mount->mount_path, + mount->mount_path) == 0) + return root; + } + } + return empty; +} + +static void +fs_quota_mount_init(struct fs_quota_root *root, + struct fs_quota_mountpoint *mount, const char *dir) +{ + struct quota_root *const *roots; + unsigned int i, count; + +#ifdef FS_QUOTA_SOLARIS +#ifdef HAVE_RQUOTA + if (mount_type_is_nfs(mount)) { + /* using rquota for this mount */ + } else +#endif + if (mount->path == NULL) { + mount->path = i_strconcat(mount->mount_path, "/quotas", NULL); + mount->fd = open(mount->path, O_RDONLY); + if (mount->fd == -1 && errno != ENOENT) + e_error(root->root.backend.event, + "open(%s) failed: %m", mount->path); + } +#endif + root->mount = mount; + + e_debug(root->root.backend.event, "fs quota add mailbox dir = %s", dir); + e_debug(root->root.backend.event, "fs quota block device = %s", mount->device_path); + e_debug(root->root.backend.event, "fs quota mount point = %s", mount->mount_path); + e_debug(root->root.backend.event, "fs quota mount type = %s", mount->type); + + /* if there are more unused quota roots, copy this mount to them */ + roots = array_get(&root->root.quota->roots, &count); + for (i = 0; i < count; i++) { + root = (struct fs_quota_root *)roots[i]; + if (QUOTA_ROOT_MATCH(root, mount) && root->mount == NULL) { + mount->refcount++; + root->mount = mount; + } + } +} + +static void fs_quota_add_missing_mounts(struct quota *quota) +{ + struct fs_quota_mountpoint *mount; + struct quota_root *const *roots; + unsigned int i, count; + + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) { + struct fs_quota_root *root = (struct fs_quota_root *)roots[i]; + + if (root->root.backend.name != quota_backend_fs.name || + root->storage_mount_path == NULL || root->mount != NULL) + continue; + + mount = fs_quota_mountpoint_get(root->storage_mount_path); + if (mount != NULL) { + fs_quota_mount_init(root, mount, + root->storage_mount_path); + } + } +} + +static void fs_quota_namespace_added(struct quota *quota, + struct mail_namespace *ns) +{ + struct fs_quota_mountpoint *mount; + struct fs_quota_root *root; + const char *dir; + + if (!mailbox_list_get_root_path(ns->list, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &dir)) + mount = NULL; + else + mount = fs_quota_mountpoint_get(dir); + if (mount != NULL) { + root = fs_quota_root_find_mountpoint(quota, mount); + if (root != NULL && root->mount == NULL) + fs_quota_mount_init(root, mount, dir); + else + fs_quota_mountpoint_free(mount); + } + + /* we would actually want to do this only once after all quota roots + are created, but there's no way to do this right now */ + fs_quota_add_missing_mounts(quota); +} + +static const char *const * +fs_quota_root_get_resources(struct quota_root *_root) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + static const char *resources_kb[] = { + QUOTA_NAME_STORAGE_KILOBYTES, + NULL + }; + static const char *resources_kb_messages[] = { + QUOTA_NAME_STORAGE_KILOBYTES, + QUOTA_NAME_MESSAGES, + NULL + }; + + return root->inode_per_mail ? resources_kb_messages : resources_kb; +} + +#if defined(FS_QUOTA_LINUX) || defined(FS_QUOTA_BSDAIX) || \ + defined(FS_QUOTA_NETBSD) || defined(HAVE_RQUOTA) +static void fs_quota_root_disable(struct fs_quota_root *root, bool group) +{ + if (group) + root->group_disabled = TRUE; + else + root->user_disabled = TRUE; +} +#endif + +#ifdef HAVE_RQUOTA +static void +rquota_get_result(const rquota *rq, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r) +{ + /* use soft limits if they exist, fallback to hard limits */ + + /* convert the results from blocks to bytes */ + *bytes_value_r = (uint64_t)rq->rq_curblocks * + (uint64_t)rq->rq_bsize; + if (rq->rq_bsoftlimit != 0) { + *bytes_limit_r = (uint64_t)rq->rq_bsoftlimit * + (uint64_t)rq->rq_bsize; + } else { + *bytes_limit_r = (uint64_t)rq->rq_bhardlimit * + (uint64_t)rq->rq_bsize; + } + + *count_value_r = rq->rq_curfiles; + if (rq->rq_fsoftlimit != 0) + *count_limit_r = rq->rq_fsoftlimit; + else + *count_limit_r = rq->rq_fhardlimit; +} + +static int +do_rquota_user(struct fs_quota_root *root, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct getquota_rslt result; + struct getquota_args args; + struct timeval timeout; + enum clnt_stat call_status; + CLIENT *cl; + struct fs_quota_mountpoint *mount = root->mount; + const char *host; + char *path; + + path = strchr(mount->device_path, ':'); + i_assert(path != NULL); + + host = t_strdup_until(mount->device_path, path); + path++; + + /* For NFSv4, we send the filesystem path without initial /. Server + prepends proper NFS pseudoroot automatically and uses this for + detection of NFSv4 mounts. */ + if (strcmp(root->mount->type, "nfs4") == 0) { + while (*path == '/') + path++; + } + + e_debug(root->root.backend.event, "host=%s, path=%s, uid=%s", + host, path, dec2str(root->uid)); + + /* clnt_create() polls for a while to establish a connection */ + cl = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp"); + if (cl == NULL) { + *error_r = t_strdup_printf( + "could not contact RPC service on %s", host); + return -1; + } + + /* Establish some RPC credentials */ + auth_destroy(cl->cl_auth); + cl->cl_auth = authunix_create_default(); + + /* make the rquota call on the remote host */ + args.gqa_pathp = path; + args.gqa_uid = root->uid; + + timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS; + timeout.tv_usec = 0; + call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA, + (xdrproc_t)xdr_getquota_args, (char *)&args, + (xdrproc_t)xdr_getquota_rslt, (char *)&result, + timeout); + + /* the result has been deserialized, let the client go */ + auth_destroy(cl->cl_auth); + clnt_destroy(cl); + + if (call_status != RPC_SUCCESS) { + const char *rpc_error_msg = clnt_sperrno(call_status); + + *error_r = t_strdup_printf( + "remote rquota call failed: %s", + rpc_error_msg); + return -1; + } + + switch (result.status) { + case Q_OK: { + rquota_get_result(&result.getquota_rslt_u.gqr_rquota, + bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r); + e_debug(root->root.backend.event, "uid=%s, bytes=%"PRIu64"/%"PRIu64" " + "files=%"PRIu64"/%"PRIu64, + dec2str(root->uid), + *bytes_value_r, *bytes_limit_r, + *count_value_r, *count_limit_r); + return 1; + } + case Q_NOQUOTA: + e_debug(root->root.backend.event, "uid=%s, limit=unlimited", + dec2str(root->uid)); + fs_quota_root_disable(root, FALSE); + return 0; + case Q_EPERM: + *error_r = "permission denied to rquota service"; + return -1; + default: + *error_r = t_strdup_printf( + "unrecognized status code (%d) from rquota service", + result.status); + return -1; + } +} + +static int +do_rquota_group(struct fs_quota_root *root ATTR_UNUSED, + uint64_t *bytes_value_r ATTR_UNUSED, + uint64_t *bytes_limit_r ATTR_UNUSED, + uint64_t *count_value_r ATTR_UNUSED, + uint64_t *count_limit_r ATTR_UNUSED, + const char **error_r) +{ +#if defined(EXT_RQUOTAVERS) && defined(GRPQUOTA) + struct getquota_rslt result; + ext_getquota_args args; + struct timeval timeout; + enum clnt_stat call_status; + CLIENT *cl; + struct fs_quota_mountpoint *mount = root->mount; + const char *host; + char *path; + + path = strchr(mount->device_path, ':'); + i_assert(path != NULL); + + host = t_strdup_until(mount->device_path, path); + path++; + + e_debug(root->root.backend.event, "host=%s, path=%s, gid=%s", + host, path, dec2str(root->gid)); + + /* clnt_create() polls for a while to establish a connection */ + cl = clnt_create(host, RQUOTAPROG, EXT_RQUOTAVERS, "udp"); + if (cl == NULL) { + *error_r = t_strdup_printf( + "could not contact RPC service on %s (group)", host); + return -1; + } + + /* Establish some RPC credentials */ + auth_destroy(cl->cl_auth); + cl->cl_auth = authunix_create_default(); + + /* make the rquota call on the remote host */ + args.gqa_pathp = path; + args.gqa_id = root->gid; + args.gqa_type = GRPQUOTA; + timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS; + timeout.tv_usec = 0; + + call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA, + (xdrproc_t)xdr_ext_getquota_args, (char *)&args, + (xdrproc_t)xdr_getquota_rslt, (char *)&result, + timeout); + + /* the result has been deserialized, let the client go */ + auth_destroy(cl->cl_auth); + clnt_destroy(cl); + + if (call_status != RPC_SUCCESS) { + const char *rpc_error_msg = clnt_sperrno(call_status); + + *error_r = t_strdup_printf( + "remote ext rquota call failed: %s", rpc_error_msg); + return -1; + } + + switch (result.status) { + case Q_OK: { + rquota_get_result(&result.getquota_rslt_u.gqr_rquota, + bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r); + e_debug(root->root.backend.event, "gid=%s, bytes=%"PRIu64"/%"PRIu64" " + "files=%"PRIu64"/%"PRIu64, + dec2str(root->gid), + *bytes_value_r, *bytes_limit_r, + *count_value_r, *count_limit_r); + return 1; + } + case Q_NOQUOTA: + e_debug(root->root.backend.event, "gid=%s, limit=unlimited", + dec2str(root->gid)); + fs_quota_root_disable(root, TRUE); + return 0; + case Q_EPERM: + *error_r = "permission denied to ext rquota service"; + return -1; + default: + *error_r = t_strdup_printf( + "unrecognized status code (%d) from ext rquota service", + result.status); + return -1; + } +#else + *error_r = "rquota not compiled with group support"; + return -1; +#endif +} +#endif + +#ifdef FS_QUOTA_LINUX +static int +fs_quota_get_linux(struct fs_quota_root *root, bool group, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct dqblk dqblk; + int type, id; + + type = group ? GRPQUOTA : USRQUOTA; + id = group ? root->gid : root->uid; + +#ifdef HAVE_XFS_QUOTA + if (strcmp(root->mount->type, "xfs") == 0) { + struct fs_disk_quota xdqblk; + + if (quotactl(QCMD(Q_XGETQUOTA, type), + root->mount->device_path, + id, (caddr_t)&xdqblk) < 0) { + if (errno == ESRCH) { + fs_quota_root_disable(root, group); + return 0; + } + *error_r = t_strdup_printf( + "errno=%d, quotactl(Q_XGETQUOTA, %s) failed: %m", + errno, root->mount->device_path); + return -1; + } + + /* values always returned in 512 byte blocks */ + *bytes_value_r = xdqblk.d_bcount * 512ULL; + *bytes_limit_r = xdqblk.d_blk_softlimit * 512ULL; + if (*bytes_limit_r == 0) { + *bytes_limit_r = xdqblk.d_blk_hardlimit * 512ULL; + } + *count_value_r = xdqblk.d_icount; + *count_limit_r = xdqblk.d_ino_softlimit; + if (*count_limit_r == 0) { + *count_limit_r = xdqblk.d_ino_hardlimit; + } + } else +#endif + { + /* ext2, ext3 */ + if (quotactl(QCMD(Q_GETQUOTA, type), + root->mount->device_path, + id, (caddr_t)&dqblk) < 0) { + if (errno == ESRCH) { + fs_quota_root_disable(root, group); + return 0; + } + *error_r = t_strdup_printf( + "quotactl(Q_GETQUOTA, %s) failed: %m", + root->mount->device_path); + if (errno == EINVAL) { + *error_r = t_strdup_printf("%s, " + "Dovecot was compiled with Linux quota " + "v%d support, try changing it " + "(CPPFLAGS=-D_LINUX_QUOTA_VERSION=%d configure)", + *error_r, + _LINUX_QUOTA_VERSION, + _LINUX_QUOTA_VERSION == 1 ? 2 : 1); + } + return -1; + } + +#if _LINUX_QUOTA_VERSION == 1 + *bytes_value_r = dqblk.dqb_curblocks * 1024ULL; +#else + *bytes_value_r = dqblk.dqb_curblocks; +#endif + *bytes_limit_r = dqblk.dqb_bsoftlimit * 1024ULL; + if (*bytes_limit_r == 0) { + *bytes_limit_r = dqblk.dqb_bhardlimit * 1024ULL; + } + *count_value_r = dqblk.dqb_curinodes; + *count_limit_r = dqblk.dqb_isoftlimit; + if (*count_limit_r == 0) { + *count_limit_r = dqblk.dqb_ihardlimit; + } + } + return 1; +} +#endif + +#ifdef FS_QUOTA_BSDAIX +static int +fs_quota_get_bsdaix(struct fs_quota_root *root, bool group, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct dqblk dqblk; + int type, id; + + type = group ? GRPQUOTA : USRQUOTA; + id = group ? root->gid : root->uid; + + if (quotactl(root->mount->mount_path, QCMD(Q_GETQUOTA, type), + id, (void *)&dqblk) < 0) { + if (errno == ESRCH) { + fs_quota_root_disable(root, group); + return 0; + } + *error_r = t_strdup_printf( + "quotactl(Q_GETQUOTA, %s) failed: %m", + root->mount->mount_path); + return -1; + } + *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * DEV_BSIZE; + *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * DEV_BSIZE; + if (*bytes_limit_r == 0) { + *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * DEV_BSIZE; + } + *count_value_r = dqblk.dqb_curinodes; + *count_limit_r = dqblk.dqb_isoftlimit; + if (*count_limit_r == 0) { + *count_limit_r = dqblk.dqb_ihardlimit; + } + return 1; +} +#endif + +#ifdef FS_QUOTA_NETBSD +static int +fs_quota_get_netbsd(struct fs_quota_root *root, bool group, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct quotakey qk; + struct quotaval qv; + struct quotahandle *qh; + int ret; + + if ((qh = quota_open(root->mount->mount_path)) == NULL) { + *error_r = t_strdup_printf("cannot open quota for %s: %m", + root->mount->mount_path); + fs_quota_root_disable(root, group); + return 0; + } + + qk.qk_idtype = group ? QUOTA_IDTYPE_GROUP : QUOTA_IDTYPE_USER; + qk.qk_id = group ? root->gid : root->uid; + + for (int i = 0; i < 2; i++) { + qk.qk_objtype = i == 0 ? QUOTA_OBJTYPE_BLOCKS : QUOTA_OBJTYPE_FILES; + + if (quota_get(qh, &qk, &qv) != 0) { + if (errno == ESRCH) { + fs_quota_root_disable(root, group); + return 0; + } + *error_r = t_strdup_printf( + "quotactl(Q_GETQUOTA, %s) failed: %m", + root->mount->mount_path); + ret = -1; + break; + } + if (i == 0) { + *bytes_value_r = qv.qv_usage * DEV_BSIZE; + *bytes_limit_r = qv.qv_softlimit * DEV_BSIZE; + } else { + *count_value_r = qv.qv_usage; + *count_limit_r = qv.qv_softlimit; + } + ret = 1; + } + quota_close(qh); + return ret; +} +#endif + +#ifdef FS_QUOTA_HPUX +static int +fs_quota_get_hpux(struct fs_quota_root *root, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct dqblk dqblk; + + if (quotactl(Q_GETQUOTA, root->mount->device_path, + root->uid, &dqblk) < 0) { + if (errno == ESRCH) { + root->user_disabled = TRUE; + return 0; + } + *error_r = t_strdup_printf( + "quotactl(Q_GETQUOTA, %s) failed: %m", + root->mount->device_path); + return -1; + } + + *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * + root->mount->block_size; + *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * + root->mount->block_size; + if (*bytes_limit_r == 0) { + *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * + root->mount->block_size; + } + *count_value_r = dqblk.dqb_curfiles; + *count_limit_r = dqblk.dqb_fsoftlimit; + if (*count_limit_r == 0) { + *count_limit_r = dqblk.dqb_fhardlimit; + } + return 1; +} +#endif + +#ifdef FS_QUOTA_SOLARIS +static int +fs_quota_get_solaris(struct fs_quota_root *root, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct dqblk dqblk; + struct quotctl ctl; + + if (root->mount->fd == -1) + return 0; + + ctl.op = Q_GETQUOTA; + ctl.uid = root->uid; + ctl.addr = (caddr_t)&dqblk; + if (ioctl(root->mount->fd, Q_QUOTACTL, &ctl) < 0) { + *error_r = t_strdup_printf( + "ioctl(%s, Q_QUOTACTL) failed: %m", + root->mount->path); + return -1; + } + *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * DEV_BSIZE; + *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * DEV_BSIZE; + if (*bytes_limit_r == 0) { + *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * DEV_BSIZE; + } + *count_value_r = dqblk.dqb_curfiles; + *count_limit_r = dqblk.dqb_fsoftlimit; + if (*count_limit_r == 0) { + *count_limit_r = dqblk.dqb_fhardlimit; + } + return 1; +} +#endif + +static int +fs_quota_get_resources(struct fs_quota_root *root, bool group, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + if (group) { + if (root->group_disabled) + return 0; + } else { + if (root->user_disabled) + return 0; + } +#ifdef FS_QUOTA_LINUX + return fs_quota_get_linux(root, group, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#elif defined (FS_QUOTA_NETBSD) + return fs_quota_get_netbsd(root, group, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#elif defined (FS_QUOTA_BSDAIX) + return fs_quota_get_bsdaix(root, group, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#else + if (group) { + /* not supported */ + return 0; + } +#ifdef FS_QUOTA_HPUX + return fs_quota_get_hpux(root, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#else + return fs_quota_get_solaris(root, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#endif +#endif +} + +static bool fs_quota_match_box(struct quota_root *_root, struct mailbox *box) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + struct stat mst, rst; + const char *mailbox_path; + bool match; + + if (root->storage_mount_path == NULL) + return TRUE; + + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &mailbox_path) <= 0) + return FALSE; + if (stat(mailbox_path, &mst) < 0) { + if (errno != ENOENT) + e_error(_root->backend.event, + "stat(%s) failed: %m", mailbox_path); + return FALSE; + } + if (stat(root->storage_mount_path, &rst) < 0) { + e_debug(_root->backend.event, "stat(%s) failed: %m", + root->storage_mount_path); + return FALSE; + } + match = CMP_DEV_T(mst.st_dev, rst.st_dev); + e_debug(_root->backend.event, "box=%s mount=%s match=%s", mailbox_path, + root->storage_mount_path, match ? "yes" : "no"); + return match; +} + +static enum quota_get_result +fs_quota_get_resource(struct quota_root *_root, const char *name, + uint64_t *value_r, const char **error_r) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + uint64_t bytes_value, count_value; + uint64_t bytes_limit = 0, count_limit = 0; + int ret; + + *value_r = 0; + + if (root->mount == NULL) { + if (root->storage_mount_path != NULL) + *error_r = t_strdup_printf( + "Mount point unknown for path %s", + root->storage_mount_path); + else + *error_r = "Mount point unknown"; + return QUOTA_GET_RESULT_INTERNAL_ERROR; + } + if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0 && + strcasecmp(name, QUOTA_NAME_MESSAGES) != 0) { + *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; + return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; + } + +#ifdef HAVE_RQUOTA + if (mount_type_is_nfs(root->mount)) { + ret = root->user_disabled ? 0 : + do_rquota_user(root, &bytes_value, &bytes_limit, + &count_value, &count_limit, error_r); + if (ret == 0 && !root->group_disabled) + ret = do_rquota_group(root, &bytes_value, + &bytes_limit, &count_value, + &count_limit, error_r); + } else +#endif + { + ret = fs_quota_get_resources(root, FALSE, &bytes_value, + &bytes_limit, &count_value, + &count_limit, error_r); + if (ret == 0) { + /* fallback to group quota */ + ret = fs_quota_get_resources(root, TRUE, &bytes_value, + &bytes_limit, &count_value, + &count_limit, error_r); + } + } + if (ret < 0) + return QUOTA_GET_RESULT_INTERNAL_ERROR; + else if (ret == 0) + return QUOTA_GET_RESULT_LIMITED; + + if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) + *value_r = bytes_value; + else + *value_r = count_value; + if (_root->bytes_limit != (int64_t)bytes_limit || + _root->count_limit != (int64_t)count_limit) { + /* update limit */ + _root->bytes_limit = bytes_limit; + _root->count_limit = count_limit; + + /* limits have changed, so we'll need to recalculate + relative quota rules */ + quota_root_recalculate_relative_rules(_root->set, bytes_limit, count_limit); + } + return QUOTA_GET_RESULT_LIMITED; +} + +static int +fs_quota_update(struct quota_root *root ATTR_UNUSED, + struct quota_transaction_context *ctx ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ + return 0; +} + +struct quota_backend quota_backend_fs = { + .name = "fs", + + .v = { + .alloc = fs_quota_alloc, + .init = fs_quota_init, + .deinit = fs_quota_deinit, + + .namespace_added = fs_quota_namespace_added, + + .get_resources = fs_quota_root_get_resources, + .get_resource = fs_quota_get_resource, + .update = fs_quota_update, + + .match_box = fs_quota_match_box, + } +}; + +#endif diff --git a/src/plugins/quota/quota-fs.h b/src/plugins/quota/quota-fs.h new file mode 100644 index 0000000..8508cdf --- /dev/null +++ b/src/plugins/quota/quota-fs.h @@ -0,0 +1,51 @@ +#ifndef QUOTA_FS_H +#define QUOTA_FS_H + +#if defined (HAVE_STRUCT_DQBLK_CURBLOCKS) || \ + defined (HAVE_STRUCT_DQBLK_CURSPACE) +# define HAVE_FS_QUOTA +#endif + +#ifdef HAVE_QUOTA_OPEN +/* absolute path to avoid confusion with ./quota.h */ +# include "/usr/include/quota.h" /* NetBSD with libquota */ +#endif + +#ifdef HAVE_SYS_QUOTA_H +# include <sys/quota.h> /* Linux, HP-UX */ +#elif defined(HAVE_SYS_FS_UFS_QUOTA_H) +# include <sys/fs/ufs_quota.h> /* Solaris */ +#elif defined(HAVE_UFS_UFS_QUOTA_H) +# include <ufs/ufs/quota.h> /* BSDs */ +#elif defined(HAVE_JFS_QUOTA_H) +# include <jfs/quota.h> /* AIX */ +# ifdef HAVE_SYS_FS_QUOTA_COMMON_H +# include <sys/fs/quota_common.h> /* quotactl() */ +# endif +#else +# undef HAVE_FS_QUOTA +#endif + +#ifdef HAVE_QUOTACTL +# ifdef HAVE_SYS_QUOTA_H +# ifndef _HPUX_SOURCE +# define FS_QUOTA_LINUX +# else +# define FS_QUOTA_HPUX +# endif +# else +# define FS_QUOTA_BSDAIX +# endif +#elif defined (HAVE_Q_QUOTACTL) +# define FS_QUOTA_SOLARIS +#else +# undef HAVE_FS_QUOTA +#endif + +#ifdef HAVE_QUOTA_OPEN /* NetBSD with libquota */ +# define FS_QUOTA_NETBSD +# define HAVE_FS_QUOTA +# undef FS_QUOTA_LINUX /* obtained because we also have <sys/quota.h> */ +#endif + +#endif diff --git a/src/plugins/quota/quota-imapc.c b/src/plugins/quota/quota-imapc.c new file mode 100644 index 0000000..d5931db --- /dev/null +++ b/src/plugins/quota/quota-imapc.c @@ -0,0 +1,494 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "imap-arg.h" +#include "imapc-storage.h" +#include "mailbox-list-private.h" +#include "quota-private.h" + +struct imapc_quota_refresh_root { + const char *name; + unsigned int order; + + uint64_t bytes_cur, count_cur; + uint64_t bytes_limit, count_limit; +}; + +struct imapc_quota_refresh { + pool_t pool; + const char *box_name; + ARRAY(struct imapc_quota_refresh_root) roots; +}; + +struct imapc_quota_root { + struct quota_root root; + const char *box_name, *root_name; + + struct mail_namespace *imapc_ns; + struct imapc_storage_client *client; + bool initialized; + + uint64_t bytes_last, count_last; + + struct timeval last_refresh; + struct imapc_quota_refresh refresh; +}; + +extern struct quota_backend quota_backend_imapc; + +static struct quota_root *imapc_quota_alloc(void) +{ + struct imapc_quota_root *root; + + root = i_new(struct imapc_quota_root, 1); + return &root->root; +} + +static void handle_box_param(struct quota_root *_root, const char *param_value) +{ + ((struct imapc_quota_root *)_root)->box_name = p_strdup(_root->pool, param_value); +} + +static void handle_root_param(struct quota_root *_root, const char *param_value) +{ + ((struct imapc_quota_root *)_root)->root_name = p_strdup(_root->pool, param_value); +} + +static int imapc_quota_init(struct quota_root *_root, const char *args, + const char **error_r) +{ + struct imapc_quota_root *root = (struct imapc_quota_root *)_root; + const struct quota_param_parser imapc_params[] = { + {.param_name = "box=", .param_handler = handle_box_param}, + {.param_name = "root=", .param_handler = handle_root_param}, + quota_param_ns, + {.param_name = NULL} + }; + + _root->auto_updating = TRUE; + event_set_append_log_prefix(root->root.backend.event, "quota-imapc: "); + + if (quota_parse_parameters(_root, &args, error_r, imapc_params, TRUE) < 0) + return -1; + + if (root->box_name == NULL && root->root_name == NULL) + root->box_name = "INBOX"; + /* we'll never try to enforce the quota - it's just a lot of + unnecessary remote GETQUOTA calls. */ + _root->no_enforcing = TRUE; + return 0; +} + +static void imapc_quota_deinit(struct quota_root *_root) +{ + i_free(_root); +} + +static void +imapc_quota_root_namespace_added(struct quota_root *_root, + struct mail_namespace *ns) +{ + struct imapc_quota_root *root = (struct imapc_quota_root *)_root; + + if (root->imapc_ns == NULL) + root->imapc_ns = ns; +} + +static struct imapc_quota_refresh * +imapc_quota_root_refresh_find(struct imapc_storage_client *client) +{ + struct imapc_storage *storage = client->_storage; + struct quota *quota; + struct quota_root *const *rootp; + + i_assert(storage != NULL); + quota = quota_get_mail_user_quota(storage->storage.user); + i_assert(quota != NULL); + + /* find the quota root that is being refreshed */ + array_foreach("a->roots, rootp) { + if ((*rootp)->backend.name == quota_backend_imapc.name) { + struct imapc_quota_root *root = + (struct imapc_quota_root *)*rootp; + + if (root->refresh.pool != NULL) + return &root->refresh; + } + } + return NULL; +} + +static struct imapc_quota_refresh_root * +imapc_quota_refresh_root_get(struct imapc_quota_refresh *refresh, + const char *root_name) +{ + struct imapc_quota_refresh_root *refresh_root; + + array_foreach_modifiable(&refresh->roots, refresh_root) { + if (strcmp(refresh_root->name, root_name) == 0) + return refresh_root; + } + + refresh_root = array_append_space(&refresh->roots); + refresh_root->order = UINT_MAX; + refresh_root->name = p_strdup(refresh->pool, root_name); + refresh_root->bytes_limit = (uint64_t)-1; + refresh_root->count_limit = (uint64_t)-1; + return refresh_root; +} + +static void imapc_untagged_quotaroot(const struct imapc_untagged_reply *reply, + struct imapc_storage_client *client) +{ + struct imapc_quota_refresh *refresh; + struct imapc_quota_refresh_root *refresh_root; + const char *mailbox_name, *root_name; + unsigned int i; + + if (!imap_arg_get_astring(&reply->args[0], &mailbox_name)) + return; + + if ((refresh = imapc_quota_root_refresh_find(client)) == NULL || + refresh->box_name == NULL || + strcmp(refresh->box_name, mailbox_name) != 0) { + /* unsolicited QUOTAROOT reply - ignore */ + return; + } + if (array_count(&refresh->roots) > 0) { + /* duplicate QUOTAROOT reply - ignore */ + return; + } + + i = 1; + while (imap_arg_get_astring(&reply->args[i], &root_name)) { + refresh_root = imapc_quota_refresh_root_get(refresh, root_name); + refresh_root->order = i; + i++; + } +} + +static void imapc_untagged_quota(const struct imapc_untagged_reply *reply, + struct imapc_storage_client *client) +{ + const struct imap_arg *list; + struct imapc_quota_refresh *refresh; + struct imapc_quota_refresh_root *refresh_root; + const char *root_name, *resource, *value_str, *limit_str; + uint64_t value, limit; + unsigned int i; + + if (!imap_arg_get_astring(&reply->args[0], &root_name) || + !imap_arg_get_list(&reply->args[1], &list)) + return; + + if ((refresh = imapc_quota_root_refresh_find(client)) == NULL) { + /* unsolicited QUOTA reply - ignore */ + return; + } + refresh_root = imapc_quota_refresh_root_get(refresh, root_name); + + for (i = 0; list[i].type != IMAP_ARG_EOL; i += 3) { + if (!imap_arg_get_atom(&list[i], &resource) || + !imap_arg_get_atom(&list[i+1], &value_str) || + !imap_arg_get_atom(&list[i+2], &limit_str) || + /* RFC2087 uses 32bit number, but be ready for future */ + str_to_uint64(value_str, &value) < 0 || + str_to_uint64(limit_str, &limit) < 0) + return; + + if (strcasecmp(resource, QUOTA_NAME_STORAGE_KILOBYTES) == 0) { + refresh_root->bytes_cur = value * 1024; + refresh_root->bytes_limit = limit * 1024; + } else if (strcasecmp(resource, QUOTA_NAME_MESSAGES) == 0) { + refresh_root->count_cur = value; + refresh_root->count_limit = limit; + } + } +} + +static bool imapc_quota_client_init(struct imapc_quota_root *root) +{ + struct mailbox_list *list; + struct mail_storage *storage; + + if (root->initialized) + return root->client != NULL; + root->initialized = TRUE; + + list = root->imapc_ns->list; + if (mailbox_list_get_storage(&list, "", &storage) == 0 && + strcmp(storage->name, IMAPC_STORAGE_NAME) != 0) { + /* non-imapc namespace, skip */ + if ((storage->class_flags & + MAIL_STORAGE_CLASS_FLAG_NOQUOTA) == 0) { + e_warning(root->root.backend.event, + "Namespace '%s' is not imapc, " + "skipping for imapc quota", + root->imapc_ns->prefix); + } + return FALSE; + } + root->client = ((struct imapc_storage *)storage)->client; + + imapc_storage_client_register_untagged(root->client, "QUOTAROOT", + imapc_untagged_quotaroot); + imapc_storage_client_register_untagged(root->client, "QUOTA", + imapc_untagged_quota); + return TRUE; +} + +static void imapc_quota_refresh_init(struct imapc_quota_refresh *refresh) +{ + i_assert(refresh->pool == NULL); + + refresh->pool = pool_alloconly_create("imapc quota refresh", 256); + p_array_init(&refresh->roots, refresh->pool, 4); +} + +static void +imapc_quota_refresh_update(struct quota *quota, + struct imapc_quota_refresh *refresh) +{ + struct quota_root *const *rootp; + const struct imapc_quota_refresh_root *refresh_root; + + if (array_count(&refresh->roots) == 0) { + e_error(quota_backend_imapc.event, + "imapc didn't return any QUOTA results"); + return; + } + /* use the first quota root for everything */ + refresh_root = array_front(&refresh->roots); + + array_foreach("a->roots, rootp) { + if ((*rootp)->backend.name == quota_backend_imapc.name) { + struct imapc_quota_root *root = + (struct imapc_quota_root *)*rootp; + + root->bytes_last = refresh_root->bytes_cur; + root->count_last = refresh_root->count_cur; + + /* If limits are higher than what dovecot can handle + consider them unlimited. */ + if (refresh_root->bytes_limit > INT64_MAX) + root->root.bytes_limit = 0; + else + root->root.bytes_limit = refresh_root->bytes_limit; + if (refresh_root->count_limit > INT64_MAX) + root->root.count_limit = 0; + else + root->root.count_limit = refresh_root->count_limit; + } + } +} + +static void +imapc_quota_refresh_deinit(struct quota *quota, + struct imapc_quota_refresh *refresh, bool success) +{ + if (success) + imapc_quota_refresh_update(quota, refresh); + pool_unref(&refresh->pool); + i_zero(refresh); +} + +static int +imapc_quota_refresh_root_order_cmp(const struct imapc_quota_refresh_root *root1, + const struct imapc_quota_refresh_root *root2) +{ + if (root1->order < root2->order) + return -1; + else if (root1->order > root2->order) + return 1; + else + return 0; +} + +static int imapc_quota_refresh_mailbox(struct imapc_quota_root *root, + const char **error_r) +{ + struct imapc_simple_context sctx; + struct imapc_command *cmd; + + i_assert(root->box_name != NULL); + + /* ask quotas for the configured mailbox */ + imapc_quota_refresh_init(&root->refresh); + root->refresh.box_name = root->box_name; + + imapc_simple_context_init(&sctx, root->client); + cmd = imapc_client_cmd(root->client->client, + imapc_simple_callback, &sctx); + imapc_command_sendf(cmd, "GETQUOTAROOT %s", root->box_name); + imapc_simple_run(&sctx, &cmd); + + /* if there are multiple quota roots, use the first one returned by + the QUOTAROOT */ + array_sort(&root->refresh.roots, imapc_quota_refresh_root_order_cmp); + imapc_quota_refresh_deinit(root->root.quota, &root->refresh, + sctx.ret == 0); + if (sctx.ret < 0) + *error_r = t_strdup_printf( + "GETQUOTAROOT %s failed: %s", + root->box_name, + mail_storage_get_last_internal_error( + &root->client->_storage->storage, NULL)); + + return sctx.ret; +} + +static int imapc_quota_refresh_root(struct imapc_quota_root *root, + const char **error_r) +{ + struct imapc_simple_context sctx; + struct imapc_command *cmd; + + i_assert(root->root_name != NULL); + + /* ask quotas for the configured quota root */ + imapc_quota_refresh_init(&root->refresh); + + imapc_simple_context_init(&sctx, root->client); + cmd = imapc_client_cmd(root->client->client, + imapc_simple_callback, &sctx); + imapc_command_sendf(cmd, "GETQUOTA %s", root->root_name); + imapc_simple_run(&sctx, &cmd); + + /* there shouldn't be more than one QUOTA reply, but ignore anyway + anything we didn't expect. */ + while (array_count(&root->refresh.roots) > 0) { + const struct imapc_quota_refresh_root *refresh_root = + array_front(&root->refresh.roots); + if (strcmp(refresh_root->name, root->root_name) == 0) + break; + array_pop_front(&root->refresh.roots); + } + imapc_quota_refresh_deinit(root->root.quota, &root->refresh, + sctx.ret == 0); + if (sctx.ret < 0) + *error_r = t_strdup_printf( + "GETQUOTA %s failed: %s", + root->root_name, + mail_storage_get_last_internal_error( + &root->client->_storage->storage, NULL)); + return sctx.ret; +} + +static int imapc_quota_refresh(struct imapc_quota_root *root, + const char **error_r) +{ + enum imapc_capability capa; + int ret; + + if (root->imapc_ns == NULL) { + /* imapc namespace is missing - disable this quota backend */ + return 0; + } + if (root->last_refresh.tv_sec == ioloop_timeval.tv_sec && + root->last_refresh.tv_usec == ioloop_timeval.tv_usec) + return 0; + if (!imapc_quota_client_init(root)) + return 0; + + if (imapc_client_get_capabilities(root->client->client, &capa) < 0) { + *error_r = "Failed to get server capabilities"; + return -1; + } + if ((capa & IMAPC_CAPABILITY_QUOTA) == 0) { + /* no QUOTA capability - disable quota */ + e_warning(root->root.backend.event, + "Remote IMAP server doesn't support QUOTA - disabling"); + root->client = NULL; + return 0; + } + + if (root->root_name == NULL) + ret = imapc_quota_refresh_mailbox(root, error_r); + else + ret = imapc_quota_refresh_root(root, error_r); + + /* set the last_refresh only after the refresh, because it changes + ioloop_timeval. */ + root->last_refresh = ioloop_timeval; + return ret; +} + +static int imapc_quota_init_limits(struct quota_root *_root, + const char **error_r) +{ + struct imapc_quota_root *root = (struct imapc_quota_root *)_root; + + return imapc_quota_refresh(root, error_r); +} + +static void +imapc_quota_namespace_added(struct quota *quota, struct mail_namespace *ns) +{ + struct quota_root **roots; + unsigned int i, count; + + roots = array_get_modifiable("a->roots, &count); + for (i = 0; i < count; i++) { + if (roots[i]->backend.name == quota_backend_imapc.name && + ((roots[i]->ns_prefix == NULL && + ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) || + roots[i]->ns == ns)) + imapc_quota_root_namespace_added(roots[i], ns); + } +} + +static const char *const * +imapc_quota_root_get_resources(struct quota_root *root ATTR_UNUSED) +{ + static const char *resources_both[] = { + QUOTA_NAME_STORAGE_KILOBYTES, + QUOTA_NAME_MESSAGES, + NULL + }; + return resources_both; +} + +static enum quota_get_result +imapc_quota_get_resource(struct quota_root *_root, const char *name, + uint64_t *value_r, const char **error_r) +{ + struct imapc_quota_root *root = (struct imapc_quota_root *)_root; + + if (imapc_quota_refresh(root, error_r) < 0) + return QUOTA_GET_RESULT_INTERNAL_ERROR; + + if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) + *value_r = root->bytes_last; + else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) + *value_r = root->count_last; + else { + *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; + return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; + } + return QUOTA_GET_RESULT_LIMITED; +} + +static int +imapc_quota_update(struct quota_root *root ATTR_UNUSED, + struct quota_transaction_context *ctx ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ + return 0; +} + +struct quota_backend quota_backend_imapc = { + .name = "imapc", + + .v = { + .alloc = imapc_quota_alloc, + .init = imapc_quota_init, + .deinit = imapc_quota_deinit, + .init_limits = imapc_quota_init_limits, + .namespace_added = imapc_quota_namespace_added, + .get_resources = imapc_quota_root_get_resources, + .get_resource = imapc_quota_get_resource, + .update = imapc_quota_update, + } +}; diff --git a/src/plugins/quota/quota-maildir.c b/src/plugins/quota/quota-maildir.c new file mode 100644 index 0000000..f4fd3a7 --- /dev/null +++ b/src/plugins/quota/quota-maildir.c @@ -0,0 +1,953 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "nfs-workarounds.h" +#include "safe-mkstemp.h" +#include "mkdir-parents.h" +#include "read-full.h" +#include "write-full.h" +#include "str.h" +#include "maildir-storage.h" +#include "mailbox-list-private.h" +#include "quota-private.h" + +#include <stdio.h> +#include <dirent.h> +#include <sys/stat.h> + +#define MAILDIRSIZE_FILENAME "maildirsize" +#define MAILDIRSIZE_STALE_SECS (60*15) + +struct maildir_quota_root { + struct quota_root root; + + struct mail_namespace *maildirsize_ns; + const char *maildirsize_path; + + uint64_t total_bytes; + uint64_t total_count; + + int fd; + time_t recalc_last_stamp; + off_t last_size; + + bool limits_initialized:1; +}; + +struct maildir_list_context { + struct mailbox_list *list; + struct maildir_quota_root *root; + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + + string_t *path; + int state; +}; + +extern struct quota_backend quota_backend_maildir; + +static struct dotlock_settings dotlock_settings = { + .timeout = 0, + .stale_timeout = 30 +}; + +static int maildir_sum_dir(const char *dir, uint64_t *total_bytes, + uint64_t *total_count, const char **error_r) +{ + DIR *dirp; + struct dirent *dp; + string_t *path; + const char *p; + size_t len; + uoff_t num; + int ret = 0; + + dirp = opendir(dir); + if (dirp == NULL) { + if (errno == ENOENT || errno == ESTALE) + return 0; + *error_r = t_strdup_printf("opendir(%s) failed: %m", dir); + return -1; + } + + path = t_str_new(256); + str_append(path, dir); + str_append_c(path, '/'); + + len = str_len(path); + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && + (dp->d_name[1] == '\0' || dp->d_name[1] == '.')) + continue; + + p = strstr(dp->d_name, ",S="); + num = UOFF_T_MAX; + if (p != NULL) { + /* ,S=nnnn[:,] */ + p += 3; + for (num = 0; *p >= '0' && *p <= '9'; p++) + num = num * 10 + (*p - '0'); + + if (*p != ':' && *p != '\0' && *p != ',') { + /* not in expected format, fallback to stat() */ + num = UOFF_T_MAX; + } else { + *total_bytes += num; + *total_count += 1; + } + } + if (num == UOFF_T_MAX) { + struct stat st; + + str_truncate(path, len); + str_append(path, dp->d_name); + if (stat(str_c(path), &st) == 0) { + *total_bytes += st.st_size; + *total_count += 1; + } else if (errno != ENOENT && errno != ESTALE) { + *error_r = t_strdup_printf( + "stat(%s) failed: %m", str_c(path)); + ret = -1; + } + } + } + + if (closedir(dirp) < 0) { + *error_r = t_strdup_printf("closedir(%s) failed: %m", dir); + return -1; + } + return ret; +} + +static struct maildir_list_context * +maildir_list_init(struct maildir_quota_root *root, struct mailbox_list *list) +{ + struct maildir_list_context *ctx; + + ctx = i_new(struct maildir_list_context, 1); + ctx->root = root; + ctx->path = str_new(default_pool, 512); + ctx->list = list; + ctx->iter = mailbox_list_iter_init(list, "*", + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + return ctx; +} + +static bool maildir_set_next_path(struct maildir_list_context *ctx) +{ + const char *path, *storage_name; + + str_truncate(ctx->path, 0); + + storage_name = mailbox_list_get_storage_name( + ctx->info->ns->list, ctx->info->vname); + if (mailbox_list_get_path(ctx->list, storage_name, + MAILBOX_LIST_PATH_TYPE_MAILBOX, + &path) > 0) { + str_append(ctx->path, path); + str_append(ctx->path, ctx->state == 0 ? + "/new" : "/cur"); + } + + return str_len(ctx->path) > 0; +} + +static const char * +maildir_list_next(struct maildir_list_context *ctx, time_t *mtime_r) +{ + struct quota_rule *rule; + struct stat st; + + for (;;) { + if (ctx->state == 0) { + ctx->info = mailbox_list_iter_next(ctx->iter); + if (ctx->info == NULL) + return NULL; + + rule = quota_root_rule_find(ctx->root->root.set, + ctx->info->vname); + if (rule != NULL && rule->ignore) { + /* mailbox not included in quota */ + continue; + } + } + + if (!maildir_set_next_path(ctx)) { + ctx->state = 0; + continue; + } + + if (++ctx->state == 2) + ctx->state = 0; + + if (stat(str_c(ctx->path), &st) == 0) + break; + /* ignore if the directory got lost, stale or if it was + actually a file and not a directory */ + if (errno != ENOENT && errno != ESTALE && errno != ENOTDIR) { + e_error(ctx->root->root.backend.event, + "stat(%s) failed: %m", str_c(ctx->path)); + ctx->state = 0; + } + } + + *mtime_r = st.st_mtime; + return str_c(ctx->path); +} + +static int maildir_list_deinit(struct maildir_list_context *ctx, + const char **error_r) +{ + int ret = mailbox_list_iter_deinit(&ctx->iter); + if (ret < 0) + *error_r = t_strdup_printf( + "Listing mailboxes failed: %s", + mailbox_list_get_last_internal_error(ctx->list, NULL)); + + str_free(&ctx->path); + i_free(ctx); + return ret; +} + +static int +maildirs_check_have_changed(struct maildir_quota_root *root, + struct mail_namespace *ns, time_t latest_mtime, + const char **error_r) +{ + struct maildir_list_context *ctx; + time_t mtime; + int ret = 0; + + ctx = maildir_list_init(root, ns->list); + while (maildir_list_next(ctx, &mtime) != NULL) { + if (mtime > latest_mtime) { + ret = 1; + break; + } + } + if (maildir_list_deinit(ctx, error_r) < 0) + return -1; + return ret; +} + +static int maildirsize_write(struct maildir_quota_root *root, const char *path) +{ + const struct mail_storage_settings *set = + root->maildirsize_ns->mail_set; + struct quota_root *_root = &root->root; + struct mail_namespace *const *namespaces; + unsigned int i, count; + struct mailbox_permissions perm; + const char *p, *dir; + string_t *str, *temp_path; + int fd; + + i_assert(root->fd == -1); + + /* figure out what permissions we should use for maildirsize. + use the inbox namespace's permissions if possible. */ + perm.file_create_mode = 0600; perm.dir_create_mode = 0700; + perm.file_create_gid = (gid_t)-1; + perm.file_create_gid_origin = "default"; + namespaces = array_get(&root->root.quota->namespaces, &count); + i_assert(count > 0); + for (i = 0; i < count; i++) { + if ((namespaces[i]->flags & NAMESPACE_FLAG_INBOX_USER) == 0) + continue; + + mailbox_list_get_root_permissions(namespaces[i]->list, + &perm); + break; + } + + dotlock_settings.use_excl_lock = set->dotlock_use_excl; + dotlock_settings.nfs_flush = set->mail_nfs_storage; + + temp_path = t_str_new(128); + str_append(temp_path, path); + fd = safe_mkstemp_hostpid_group(temp_path, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + if (fd == -1 && errno == ENOENT) { + /* the control directory doesn't exist yet? create it */ + p = strrchr(path, '/'); + dir = t_strdup_until(path, p); + if (mkdir_parents_chgrp(dir, perm.dir_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin) < 0 && + errno != EEXIST) { + e_error(root->root.backend.event, + "mkdir_parents(%s) failed: %m", dir); + return -1; + } + fd = safe_mkstemp_hostpid_group(temp_path, + perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + } + if (fd == -1) { + e_error(root->root.backend.event, + "safe_mkstemp(%s) failed: %m", path); + return -1; + } + + str = t_str_new(128); + /* if we have no limits, write 0S instead of an empty line */ + if (_root->bytes_limit != 0 || _root->count_limit == 0) { + str_printfa(str, "%"PRId64"S", _root->bytes_limit); + } + if (_root->count_limit != 0) { + if (str_len(str) > 0) + str_append_c(str, ','); + str_printfa(str, "%"PRIu64"C", _root->count_limit); + } + str_printfa(str, "\n%"PRIu64" %"PRIu64"\n", + root->total_bytes, root->total_count); + if (write_full(fd, str_data(str), str_len(str)) < 0) { + e_error(root->root.backend.event, + "write_full(%s) failed: %m", str_c(temp_path)); + i_close_fd(&fd); + i_unlink(str_c(temp_path)); + return -1; + } + i_close_fd(&fd); + + if (rename(str_c(temp_path), path) < 0) { + e_error(root->root.backend.event, + "rename(%s, %s) failed: %m", str_c(temp_path), path); + i_unlink_if_exists(str_c(temp_path)); + return -1; + } + return 0; +} + +static void maildirsize_recalculate_init(struct maildir_quota_root *root) +{ + root->total_bytes = root->total_count = 0; + root->recalc_last_stamp = 0; +} + +static int maildirsize_recalculate_namespace(struct maildir_quota_root *root, + struct mail_namespace *ns, + const char **error_r) +{ + struct maildir_list_context *ctx; + const char *dir; + time_t mtime; + int ret = 0; + + ctx = maildir_list_init(root, ns->list); + while ((dir = maildir_list_next(ctx, &mtime)) != NULL) { + if (mtime > root->recalc_last_stamp) + root->recalc_last_stamp = mtime; + + if (maildir_sum_dir(dir, &root->total_bytes, + &root->total_count, error_r) < 0) + ret = -1; + } + if (maildir_list_deinit(ctx, error_r) < 0) + ret = -1; + + return ret; +} + +static void maildirsize_rebuild_later(struct maildir_quota_root *root) +{ + if (!root->root.set->force_default_rule) { + /* FIXME: can't unlink(), because the limits would be lost. */ + return; + } + + if (unlink(root->maildirsize_path) < 0 && + errno != ENOENT && errno != ESTALE) + e_error(root->root.backend.event, + "unlink(%s) failed: %m", root->maildirsize_path); +} + +static int maildirsize_recalculate_finish(struct maildir_quota_root *root, + int ret, const char **error_r) +{ + if (ret == 0) { + /* maildir didn't change, we can write the maildirsize file */ + if ((ret = maildirsize_write(root, root->maildirsize_path)) < 0) + *error_r = "failed to write maildirsize"; + } + if (ret != 0) + maildirsize_rebuild_later(root); + + return ret; +} + +static int maildirsize_recalculate(struct maildir_quota_root *root, + const char **error_r) +{ + struct mail_namespace *const *namespaces; + struct event_reason *reason; + unsigned int i, count; + int ret = 0; + + reason = event_reason_begin("quota:recalculate"); + maildirsize_recalculate_init(root); + + /* count mails from all namespaces */ + namespaces = array_get(&root->root.quota->namespaces, &count); + for (i = 0; i < count; i++) { + if (!quota_root_is_namespace_visible(&root->root, namespaces[i])) + continue; + + if (maildirsize_recalculate_namespace(root, namespaces[i], error_r) < 0) { + ret = -1; + break; + } + } + + if (ret == 0) { + /* check if any of the directories have changed */ + for (i = 0; i < count; i++) { + if (!quota_root_is_namespace_visible(&root->root, + namespaces[i])) + continue; + + ret = maildirs_check_have_changed(root, namespaces[i], + root->recalc_last_stamp, + error_r); + if (ret != 0) + break; + } + } + + ret = maildirsize_recalculate_finish(root, ret, error_r); + event_reason_end(&reason); + return ret; +} + +static bool +maildir_parse_limit(const char *str, uint64_t *bytes_r, uint64_t *count_r) +{ + const char *const *limit; + unsigned long long value; + const char *pos; + bool ret = TRUE; + + *bytes_r = 0; + *count_r = 0; + + /* 0 values mean unlimited */ + for (limit = t_strsplit(str, ","); *limit != NULL; limit++) { + if (str_parse_ullong(*limit, &value, &pos) < 0) { + ret = FALSE; + continue; + } + if (pos[0] != '\0' && pos[1] == '\0') { + switch (pos[0]) { + case 'C': + if (value != 0) + *count_r = value; + break; + case 'S': + if (value != 0) + *bytes_r = value; + break; + default: + ret = FALSE; + break; + } + } else { + ret = FALSE; + } + } + return ret; +} + +static int maildirsize_parse(struct maildir_quota_root *root, + int fd, const char *const *lines) +{ + struct quota_root *_root = &root->root; + uint64_t message_bytes_limit, message_count_limit; + long long bytes_diff, total_bytes; + int count_diff, total_count; + unsigned int line_count = 0; + + if (*lines == NULL) + return -1; + + /* first line contains the limits */ + (void)maildir_parse_limit(lines[0], &message_bytes_limit, + &message_count_limit); + + /* truncate too high limits to signed 64bit int range */ + if (message_bytes_limit >= (1ULL << 63)) + message_bytes_limit = (1ULL << 63) - 1; + if (message_count_limit >= (1ULL << 63)) + message_count_limit = (1ULL << 63) - 1; + + if (root->root.bytes_limit == (int64_t)message_bytes_limit && + root->root.count_limit == (int64_t)message_count_limit) { + /* limits haven't changed */ + } else if (root->root.set->force_default_rule) { + /* we know the limits and they've changed. + the file must be rewritten. */ + return 0; + } else { + /* we're using limits from the file. */ + root->root.bytes_limit = message_bytes_limit; + root->root.count_limit = message_count_limit; + quota_root_recalculate_relative_rules(root->root.set, + message_bytes_limit, + message_count_limit); + } + + if (*lines == NULL) { + /* no quota lines. rebuild it. */ + return 0; + } + + /* rest of the lines contains <bytes> <count> diffs */ + total_bytes = 0; total_count = 0; + for (lines++; *lines != NULL; lines++, line_count++) { + if (sscanf(*lines, "%lld %d", &bytes_diff, &count_diff) != 2) + return -1; + + total_bytes += bytes_diff; + total_count += count_diff; + } + + if (total_bytes < 0 || total_count < 0) { + /* corrupted */ + return -1; + } + + if ((total_bytes > _root->bytes_limit && _root->bytes_limit != 0) || + (total_count > _root->count_limit && _root->count_limit != 0)) { + /* we're over quota. don't trust these values if the file + contains more than the initial summary line, or if the file + is older than 15 minutes. */ + struct stat st; + + if (line_count > 1) + return 0; + + if (fstat(fd, &st) < 0 || + st.st_mtime < ioloop_time - MAILDIRSIZE_STALE_SECS) + return 0; + } + root->total_bytes = (uint64_t)total_bytes; + root->total_count = (uint64_t)total_count; + return 1; +} + +static int maildirsize_open(struct maildir_quota_root *root, + const char **error_r) +{ + i_close_fd_path(&root->fd, root->maildirsize_path); + + root->fd = nfs_safe_open(root->maildirsize_path, O_RDWR | O_APPEND); + if (root->fd == -1) { + if (errno == ENOENT) + return 0; + *error_r = t_strdup_printf( + "open(%s) failed: %m", root->maildirsize_path); + return -1; + } + return 1; +} + +static bool maildirsize_has_changed(struct maildir_quota_root *root) +{ + struct stat st1, st2; + + if (dotlock_settings.nfs_flush) { + nfs_flush_file_handle_cache(root->maildirsize_path); + nfs_flush_attr_cache_unlocked(root->maildirsize_path); + } + + if (root->fd == -1) + return TRUE; + + if (stat(root->maildirsize_path, &st1) < 0) + return TRUE; + if (fstat(root->fd, &st2) < 0) + return TRUE; + + return root->last_size != st2.st_size || st1.st_ino != st2.st_ino || + !CMP_DEV_T(st1.st_dev, st2.st_dev); +} + +static int maildirsize_read(struct maildir_quota_root *root, bool *retry, + const char **error_r) +{ + char buf[5120+1]; + unsigned int i, size; + bool retry_estale = *retry; + int ret; + + *retry = FALSE; + + if (!maildirsize_has_changed(root)) + return 1; + + if ((ret = maildirsize_open(root, error_r)) <= 0) + return ret; + + /* @UNSAFE */ + size = 0; + while ((ret = read(root->fd, buf + size, sizeof(buf)-1 - size)) != 0) { + if (ret < 0) { + if (errno == ESTALE && retry_estale) { + *retry = TRUE; + break; + } + *error_r = t_strdup_printf( + "read(%s) failed: %m", root->maildirsize_path); + break; + } + size += ret; + if (size >= sizeof(buf)-1) { + /* we'll need to recalculate the quota */ + break; + } + } + + /* try to use the file even if we ran into some error. if we don't have + forced limits, we'll need to read the header to get them */ + root->total_bytes = root->total_count = 0; + root->last_size = size; + + /* skip the last line if there's no LF at the end. Remove the last LF + so we don't get one empty line in the strsplit. */ + while (size > 0 && buf[size-1] != '\n') size--; + if (size > 0) size--; + buf[size] = '\0'; + + if (ret < 0 && size == 0) { + /* the read failed and there's no usable header, fail. */ + i_close_fd(&root->fd); + return -1; + } + + /* If there are any NUL bytes, the file is broken. */ + for (i = 0; i < size; i++) { + if (buf[i] == '\0') + break; + } + + if (i == size && + maildirsize_parse(root, root->fd, t_strsplit(buf, "\n")) > 0 && + ret == 0) + ret = 1; + else { + /* broken file / need recalculation */ + i_close_fd(&root->fd); + ret = 0; + } + return ret; +} + +static bool maildirquota_limits_init(struct maildir_quota_root *root) +{ + struct mailbox_list *list; + struct mail_storage *storage; + const char *control_dir; + + if (root->limits_initialized) + return root->maildirsize_path != NULL; + root->limits_initialized = TRUE; + + if (root->maildirsize_ns == NULL) { + i_assert(root->maildirsize_path == NULL); + return FALSE; + } + + list = root->maildirsize_ns->list; + if (mailbox_list_get_storage(&list, "", &storage) == 0 && + strcmp(storage->name, MAILDIR_STORAGE_NAME) != 0) { + /* non-maildir namespace, skip */ + if ((storage->class_flags & + MAIL_STORAGE_CLASS_FLAG_NOQUOTA) == 0) { + e_warning(root->root.backend.event, + "Namespace '%s' is not Maildir, " + "skipping for Maildir++ quota", + root->maildirsize_ns->prefix); + } + root->maildirsize_path = NULL; + return FALSE; + } + if (root->maildirsize_path == NULL) { + if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_CONTROL, + &control_dir)) + i_unreached(); + root->maildirsize_path = + p_strconcat(root->root.pool, control_dir, + "/"MAILDIRSIZE_FILENAME, NULL); + } + return TRUE; +} + +static int maildirquota_read_limits(struct maildir_quota_root *root, + const char **error_r) +{ + bool retry = TRUE; + int ret, n = 0; + + if (!maildirquota_limits_init(root)) + return 1; + + do { + if (n == NFS_ESTALE_RETRY_COUNT) + retry = FALSE; + ret = maildirsize_read(root, &retry, error_r); + n++; + } while (ret == -1 && retry); + return ret; +} + +static int +maildirquota_refresh(struct maildir_quota_root *root, bool *recalculated_r, + const char **error_r) +{ + int ret; + + *recalculated_r = FALSE; + + ret = maildirquota_read_limits(root, error_r); + if (ret == 0) { + if (root->root.bytes_limit == 0 && + root->root.count_limit == 0 && + root->root.set->default_rule.bytes_limit == 0 && + root->root.set->default_rule.count_limit == 0) { + /* no quota */ + if (!root->root.set->force_default_rule) + return 0; + /* explicitly specified 0 as quota. keep the quota + updated even if it's not enforced. */ + } + + ret = maildirsize_recalculate(root, error_r); + if (ret == 0) + *recalculated_r = TRUE; + } + return ret < 0 ? -1 : 0; +} + +static int maildirsize_update(struct maildir_quota_root *root, + int count_diff, int64_t bytes_diff) +{ + char str[MAX_INT_STRLEN * 2 + 2]; + int ret = 0; + + if (count_diff == 0 && bytes_diff == 0) + return 0; + + /* We rely on O_APPEND working in here. That isn't NFS-safe, but it + isn't necessarily that bad because the file is recreated once in + a while, and sooner if corruption causes calculations to go + over quota. This is also how Maildir++ spec specifies it should be + done.. */ + if (i_snprintf(str, sizeof(str), "%lld %d\n", + (long long)bytes_diff, count_diff) < 0) + i_unreached(); + if (write_full(root->fd, str, strlen(str)) < 0) { + ret = -1; + if (errno == ESTALE) { + /* deleted/replaced already, ignore */ + } else { + e_error(root->root.backend.event, + "write_full(%s) failed: %m", + root->maildirsize_path); + } + } else { + /* close the file to force a flush with NFS */ + if (close(root->fd) < 0) { + ret = -1; + if (errno != ESTALE) + e_error(root->root.backend.event, + "close(%s) failed: %m", root->maildirsize_path); + } + root->fd = -1; + } + return ret; +} + +static struct quota_root *maildir_quota_alloc(void) +{ + struct maildir_quota_root *root; + + root = i_new(struct maildir_quota_root, 1); + root->fd = -1; + return &root->root; +} + +static int maildir_quota_init(struct quota_root *_root, const char *args, + const char **error_r) +{ + event_set_append_log_prefix(_root->backend.event, "quota-maildir: "); + return quota_root_default_init(_root, args, error_r); +} + +static void maildir_quota_deinit(struct quota_root *_root) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + + i_close_fd(&root->fd); + i_free(root); +} + +static bool +maildir_quota_parse_rule(struct quota_root_settings *root_set ATTR_UNUSED, + struct quota_rule *rule, + const char *str, const char **error_r) +{ + uint64_t bytes, count; + + if (strcmp(str, "NOQUOTA") == 0) { + bytes = 0; + count = 0; + } else if (!maildir_parse_limit(str, &bytes, &count)) { + *error_r = t_strdup_printf( + "quota-maildir: Invalid Maildir++ quota rule \"%s\"", + str); + return FALSE; + } + + rule->bytes_limit = bytes; + rule->count_limit = count; + return TRUE; +} + +static int maildir_quota_init_limits(struct quota_root *_root, + const char **error_r) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + const char *error; + + if (maildirquota_read_limits(root, &error) < 0) { + *error_r = t_strdup_printf( + "quota-maildir: Failed to read limits: %s", error); + return -1; + } + return 0; +} + +static void +maildir_quota_root_namespace_added(struct quota_root *_root, + struct mail_namespace *ns) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + + if (root->maildirsize_ns == NULL) + root->maildirsize_ns = ns; +} + +static void +maildir_quota_namespace_added(struct quota *quota, struct mail_namespace *ns) +{ + struct quota_root **roots; + unsigned int i, count; + + roots = array_get_modifiable("a->roots, &count); + for (i = 0; i < count; i++) { + if (roots[i]->backend.name == quota_backend_maildir.name && + ((roots[i]->ns_prefix == NULL && + ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) || + roots[i]->ns == ns)) + maildir_quota_root_namespace_added(roots[i], ns); + } +} + +static const char *const * +maildir_quota_root_get_resources(struct quota_root *root ATTR_UNUSED) +{ + static const char *resources_both[] = { + QUOTA_NAME_STORAGE_KILOBYTES, + QUOTA_NAME_MESSAGES, + NULL + }; + + return resources_both; +} + +static enum quota_get_result +maildir_quota_get_resource(struct quota_root *_root, const char *name, + uint64_t *value_r, const char **error_r) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + bool recalculated; + const char *error; + + if (maildirquota_refresh(root, &recalculated, &error) < 0) { + *error_r = t_strdup_printf("Failed to get %s: %s", name, error); + return QUOTA_GET_RESULT_INTERNAL_ERROR; + } + + if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) { + *value_r = root->total_bytes; + } else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) { + *value_r = root->total_count; + } else { + *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; + return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; + } + return QUOTA_GET_RESULT_LIMITED; +} + +static int +maildir_quota_update(struct quota_root *_root, + struct quota_transaction_context *ctx, + const char **error_r) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + bool recalculated; + const char *error; + + if (!maildirquota_limits_init(root)) { + /* no limits */ + return 0; + } + + /* even though we don't really care about the limits in here ourself, + we do want to make sure the header gets updated if the limits have + changed. also this makes sure the maildirsize file is created if + it doesn't exist. */ + if (maildirquota_refresh(root, &recalculated, &error) < 0) { + *error_r = t_strdup_printf( + "Could not update storage usage data: %s", + error); + return -1; + } + + if (recalculated) { + /* quota was just recalculated and it already contains the changes + we wanted to do. */ + } else if (root->fd == -1) { + if (maildirsize_recalculate(root, &error) < 0) + e_error(root->root.backend.event, "%s", error); + } else if (ctx->recalculate != QUOTA_RECALCULATE_DONT) { + i_close_fd(&root->fd); + if (maildirsize_recalculate(root, &error) < 0) + e_error(root->root.backend.event, "%s", error); + } else if (maildirsize_update(root, ctx->count_used, ctx->bytes_used) < 0) { + i_close_fd(&root->fd); + maildirsize_rebuild_later(root); + } + + return 0; +} + +struct quota_backend quota_backend_maildir = { + .name = "maildir", + + .v = { + .alloc = maildir_quota_alloc, + .init = maildir_quota_init, + .deinit = maildir_quota_deinit, + .parse_rule = maildir_quota_parse_rule, + .init_limits = maildir_quota_init_limits, + .namespace_added = maildir_quota_namespace_added, + .get_resources = maildir_quota_root_get_resources, + .get_resource = maildir_quota_get_resource, + .update = maildir_quota_update, + } +}; diff --git a/src/plugins/quota/quota-plugin.c b/src/plugins/quota/quota-plugin.c new file mode 100644 index 0000000..507a329 --- /dev/null +++ b/src/plugins/quota/quota-plugin.c @@ -0,0 +1,31 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-user.h" +#include "mail-storage-hooks.h" +#include "quota-plugin.h" + +void quota_backends_register(void); +void quota_backends_unregister(void); + +const char *quota_plugin_version = DOVECOT_ABI_VERSION; + +static struct mail_storage_hooks quota_mail_storage_hooks = { + .mail_user_created = quota_mail_user_created, + .mail_namespaces_created = quota_mail_namespaces_created, + .mailbox_list_created = quota_mailbox_list_created, + .mailbox_allocated = quota_mailbox_allocated, + .mail_allocated = quota_mail_allocated +}; + +void quota_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, "a_mail_storage_hooks); + quota_backends_register(); +} + +void quota_plugin_deinit(void) +{ + mail_storage_hooks_remove("a_mail_storage_hooks); + quota_backends_unregister(); +} diff --git a/src/plugins/quota/quota-plugin.h b/src/plugins/quota/quota-plugin.h new file mode 100644 index 0000000..927d428 --- /dev/null +++ b/src/plugins/quota/quota-plugin.h @@ -0,0 +1,36 @@ +#ifndef QUOTA_PLUGIN_H +#define QUOTA_PLUGIN_H + +#include "module-context.h" +#include "mail-user.h" + +struct module; +struct mailbox; +struct mailbox_list; +struct mail; + +#define QUOTA_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, quota_user_module) +#define QUOTA_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, quota_user_module) + +struct quota_user { + union mail_user_module_context module_ctx; + + struct quota *quota; +}; + +struct mail_storage; + +extern MODULE_CONTEXT_DEFINE(quota_user_module, &mail_user_module_register); + +void quota_mail_user_created(struct mail_user *user); +void quota_mailbox_list_created(struct mailbox_list *list); +void quota_mail_namespaces_created(struct mail_namespace *namespaces); +void quota_mailbox_allocated(struct mailbox *box); +void quota_mail_allocated(struct mail *mail); + +void quota_plugin_init(struct module *module); +void quota_plugin_deinit(void); + +#endif diff --git a/src/plugins/quota/quota-private.h b/src/plugins/quota/quota-private.h new file mode 100644 index 0000000..ac091d3 --- /dev/null +++ b/src/plugins/quota/quota-private.h @@ -0,0 +1,230 @@ +#ifndef QUOTA_PRIVATE_H +#define QUOTA_PRIVATE_H + +#include "mail-storage-private.h" +#include "mail-namespace.h" +#include "quota.h" + +/* Modules should use do "my_id = quota_module_id++" and + use quota_module_contexts[id] for their own purposes. */ +extern unsigned int quota_module_id; + +struct quota { + struct mail_user *user; + struct quota_settings *set; + struct event *event; + + ARRAY(struct quota_root *) roots; + ARRAY(struct mail_namespace *) namespaces; + struct mail_namespace *unwanted_ns; +}; + +struct quota_settings { + pool_t pool; + + ARRAY(struct quota_root_settings *) root_sets; + struct event *event; + enum quota_alloc_result (*test_alloc)( + struct quota_transaction_context *ctx, uoff_t size, + const char **error_r); + + uoff_t max_mail_size; + const char *quota_exceeded_msg; + bool debug:1; + bool initialized:1; + bool vsizes:1; +}; + +struct quota_rule { + const char *mailbox_mask; + + int64_t bytes_limit, count_limit; + /* relative to default_rule */ + int bytes_percent, count_percent; + + /* Don't include this mailbox in quota */ + bool ignore:1; +}; + +struct quota_warning_rule { + struct quota_rule rule; + const char *command; + bool reverse:1; +}; + +struct quota_backend_vfuncs { + struct quota_root *(*alloc)(void); + int (*init)(struct quota_root *root, const char *args, + const char **error_r); + void (*deinit)(struct quota_root *root); + + bool (*parse_rule)(struct quota_root_settings *root_set, + struct quota_rule *rule, + const char *str, const char **error_r); + int (*init_limits)(struct quota_root *root, const char **error_r); + + /* called once for each namespace */ + void (*namespace_added)(struct quota *quota, + struct mail_namespace *ns); + + const char *const *(*get_resources)(struct quota_root *root); + /* Backends return success as QUOTA_GET_RESULT_LIMITED, and returning + QUOTA_GET_RESULT_UNLIMITED is prohibited by quota_get_resource(), + which is the only caller of this vfunc. */ + enum quota_get_result (*get_resource)(struct quota_root *root, + const char *name, + uint64_t *value_r, + const char **error_r); + + int (*update)(struct quota_root *root, + struct quota_transaction_context *ctx, + const char **error_r); + bool (*match_box)(struct quota_root *root, struct mailbox *box); + void (*flush)(struct quota_root *root); +}; + +struct quota_backend { + /* quota backends equal if backend1.name == backend2.name */ + const char *name; + struct event *event; + struct quota_backend_vfuncs v; +}; + +struct quota_root_settings { + /* Unique quota root name. */ + const char *name; + /* Name in settings, e.g. "quota", "quota2", .. */ + const char *set_name; + + struct quota_settings *set; + const char *args; + + const struct quota_backend *backend; + struct quota_rule default_rule; + ARRAY(struct quota_rule) rules; + ARRAY(struct quota_warning_rule) warning_rules; + const char *limit_set; + + /* If user is under quota before saving a mail, allow the last mail to + bring the user over quota by this many bytes. */ + uint64_t last_mail_max_extra_bytes; + struct quota_rule grace_rule; + + /* Limits in default_rule override backend's quota limits */ + bool force_default_rule:1; + /* TRUE if any of the warning_rules have reverse==TRUE */ + bool have_reverse_warnings:1; +}; + +struct quota_root { + pool_t pool; + + struct quota_root_settings *set; + struct quota *quota; + struct quota_backend backend; + struct dict *limit_set_dict; + + /* this quota root applies only to this namespace. it may also be + a public namespace without an owner. */ + struct mail_namespace *ns; + /* this is set in quota init(), because namespaces aren't known yet. + when accessing shared users the ns_prefix may be non-NULL but + ns=NULL, so when checking if quota root applies only to a specific + namespace use the ns_prefix!=NULL check. */ + const char *ns_prefix; + + /* initially the same as set->default_rule.*_limit, but some backends + may change these by reading the limits elsewhere (e.g. Maildir++, + FS quota) */ + int64_t bytes_limit, count_limit; + + /* Module-specific contexts. See quota_module_id. */ + ARRAY(void) quota_module_contexts; + + /* don't enforce quota when saving */ + bool no_enforcing:1; + /* quota is automatically updated. update() should be called but the + bytes won't be changed. count is still changed, because it's cheap + to do and it's internally used to figure out whether there have + been some changes and that quota_warnings should be checked. */ + bool auto_updating:1; + /* If user has unlimited quota, disable quota tracking */ + bool disable_unlimited_tracking:1; + /* Set while quota is being recalculated to avoid recursion. */ + bool recounting:1; + /* Quota root is hidden (to e.g. IMAP GETQUOTAROOT) */ + bool hidden:1; + /* Did we already check quota_over_flag correctness? */ + bool quota_over_flag_checked:1; +}; + +struct quota_transaction_context { + union mailbox_transaction_module_context module_ctx; + + struct quota *quota; + struct mailbox *box; + + int64_t bytes_used, count_used; + /* how many bytes/mails can be saved until limit is reached. + (set once, not updated by bytes_used/count_used). + + if last_mail_max_extra_bytes>0, the bytes_ceil is initially + increased by that much, while bytes_ceil2 contains the real ceiling. + after the first allocation is done, bytes_ceil is set to + bytes_ceil2. */ + uint64_t bytes_ceil, bytes_ceil2, count_ceil; + /* How many bytes/mails we are over quota. Like *_ceil, these are set + only once and not updated by bytes_used/count_used. (Either *_ceil + or *_over is always zero.) */ + uint64_t bytes_over, count_over; + + struct mail *tmp_mail; + enum quota_recalculate recalculate; + + bool limits_set:1; + bool failed:1; + bool sync_transaction:1; + /* TRUE if all roots have auto_updating=TRUE */ + bool auto_updating:1; + /* Quota doesn't need to be updated within this transaction. */ + bool no_quota_updates:1; +}; + +/* Register storage to all user's quota roots. */ +void quota_add_user_namespace(struct quota *quota, struct mail_namespace *ns); +void quota_remove_user_namespace(struct mail_namespace *ns); + +int quota_root_default_init(struct quota_root *root, const char *args, + const char **error_r); +struct quota *quota_get_mail_user_quota(struct mail_user *user); + +bool quota_root_is_namespace_visible(struct quota_root *root, + struct mail_namespace *ns); +struct quota_rule * +quota_root_rule_find(struct quota_root_settings *root_set, const char *name); + +void quota_root_recalculate_relative_rules(struct quota_root_settings *root_set, + int64_t bytes_limit, + int64_t count_limit); +/* Returns 1 if values were returned successfully, 0 if we're recursing into + the same function, -1 if error. */ +int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r, + enum quota_get_result *error_result_r, const char **error_r); + +int quota_root_parse_grace(struct quota_root_settings *root_set, + const char *value, const char **error_r); +bool quota_warning_match(const struct quota_warning_rule *w, + uint64_t bytes_before, uint64_t bytes_current, + uint64_t count_before, uint64_t count_current, + const char **reason_r); +bool quota_transaction_is_over(struct quota_transaction_context *ctx, uoff_t size); +int quota_transaction_set_limits(struct quota_transaction_context *ctx, + enum quota_get_result *error_result_r, + const char **error_r); + +void quota_backend_register(const struct quota_backend *backend); +void quota_backend_unregister(const struct quota_backend *backend); + +#define QUOTA_UNKNOWN_RESOURCE_ERROR_STRING "Unknown quota resource" + +#endif diff --git a/src/plugins/quota/quota-status-settings.c b/src/plugins/quota/quota-status-settings.c new file mode 100644 index 0000000..e98cc09 --- /dev/null +++ b/src/plugins/quota/quota-status-settings.c @@ -0,0 +1,37 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "settings-parser.h" +#include "service-settings.h" +#include "mail-storage-settings.h" +#include "quota-status-settings.h" + +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct quota_status_settings) + +static const struct setting_define quota_status_setting_defines[] = { + DEF(STR, recipient_delimiter), + + SETTING_DEFINE_LIST_END +}; + +static const struct quota_status_settings quota_status_default_settings = { + .recipient_delimiter = "+", +}; + +static const struct setting_parser_info *quota_status_setting_dependencies[] = { + NULL +}; + +const struct setting_parser_info quota_status_setting_parser_info = { + .module_name = "mail", + .defines = quota_status_setting_defines, + .defaults = "a_status_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct quota_status_settings), + + .parent_offset = SIZE_MAX, + .dependencies = quota_status_setting_dependencies +}; diff --git a/src/plugins/quota/quota-status-settings.h b/src/plugins/quota/quota-status-settings.h new file mode 100644 index 0000000..e69a0aa --- /dev/null +++ b/src/plugins/quota/quota-status-settings.h @@ -0,0 +1,10 @@ +#ifndef QUOTA_STATUS_SETTINGS_H +#define QUOTA_STATUS_SETTINGS_H 1 + +struct quota_status_settings { + const char *recipient_delimiter; +}; + +extern const struct setting_parser_info quota_status_setting_parser_info; + +#endif diff --git a/src/plugins/quota/quota-status.c b/src/plugins/quota/quota-status.c new file mode 100644 index 0000000..7db0cb4 --- /dev/null +++ b/src/plugins/quota/quota-status.c @@ -0,0 +1,360 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "ostream.h" +#include "connection.h" +#include "restrict-access.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "mail-storage-settings.h" +#include "mail-storage-service.h" +#include "smtp-address.h" +#include "quota-private.h" +#include "quota-plugin.h" +#include "quota-status-settings.h" + +enum quota_protocol { + QUOTA_PROTOCOL_UNKNOWN = 0, + QUOTA_PROTOCOL_POSTFIX +}; + +struct quota_client { + struct connection conn; + + struct event *event; + + char *state; + char *recipient; + uoff_t size; + + bool warned_bad_state:1; +}; + +static struct event_category event_category_quota_status = { + .name = "quota-status" +}; + +static struct quota_status_settings *quota_status_settings; +static pool_t quota_status_pool; +static enum quota_protocol protocol; +static struct mail_storage_service_ctx *storage_service; +static struct connection_list *clients; +static char *nouser_reply; + +static void client_connected(struct master_service_connection *conn) +{ + struct quota_client *client; + + client = i_new(struct quota_client, 1); + + client->event = event_create(NULL); + client->conn.event_parent = client->event; + event_add_category(client->event, &event_category_quota_status); + connection_init_server(clients, &client->conn, + "quota-client", conn->fd, conn->fd); + master_service_client_connection_accept(conn); + + e_debug(client->event, "Client connected"); +} + +static void client_reset(struct quota_client *client) +{ + i_free(client->state); + i_free(client->recipient); +} + +static enum quota_alloc_result +quota_check(struct mail_user *user, uoff_t mail_size, const char **error_r) +{ + struct quota_user *quser = QUOTA_USER_CONTEXT(user); + struct mail_namespace *ns; + struct mailbox *box; + struct quota_transaction_context *ctx; + enum quota_alloc_result ret; + + if (quser == NULL) { + /* no quota for user */ + e_debug(user->event, "User has no quota"); + return QUOTA_ALLOC_RESULT_OK; + } + + ns = mail_namespace_find_inbox(user->namespaces); + box = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_POST_SESSION); + + ctx = quota_transaction_begin(box); + const char *internal_error; + ret = quota_test_alloc(ctx, I_MAX(1, mail_size), &internal_error); + if (ret == QUOTA_ALLOC_RESULT_TEMPFAIL) + e_error(user->event, "quota check failed: %s", internal_error); + *error_r = quota_alloc_result_errstr(ret, ctx); + quota_transaction_rollback(&ctx); + + mailbox_free(&box); + return ret; +} + +static int client_check_mta_state(struct quota_client *client) +{ + if (client->state == NULL || strcasecmp(client->state, "RCPT") == 0) + return 0; + + if (!client->warned_bad_state) { + e_warning(client->event, + "Received policy query from MTA in unexpected state %s " + "(service can only be used for recipient restrictions)", + client->state); + } + client->warned_bad_state = TRUE; + return -1; +} + +static void client_handle_request(struct quota_client *client) +{ + struct mail_storage_service_input input; + struct mail_storage_service_user *service_user; + struct mail_user *user; + struct smtp_address *rcpt; + const char *value = NULL, *error; + const char *detail ATTR_UNUSED; + char delim ATTR_UNUSED; + string_t *resp; + int ret; + + if (client_check_mta_state(client) < 0 || client->recipient == NULL) { + e_debug(client->event, "Response: action=DUNNO"); + o_stream_nsend_str(client->conn.output, "action=DUNNO\n\n"); + return; + } + + if (smtp_address_parse_path(pool_datastack_create(), client->recipient, + SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | + SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | + SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART, + &rcpt, &error) < 0) { + e_error(client->event, + "Client sent invalid recipient address `%s': " + "%s", str_sanitize(client->recipient, 256), error); + e_debug(client->event, "Response: action=DUNNO"); + o_stream_nsend_str(client->conn.output, "action=DUNNO\n\n"); + return; + } + + i_zero(&input); + input.event_parent = client->event; + smtp_address_detail_parse_temp(quota_status_settings->recipient_delimiter, + rcpt, &input.username, &delim, + &detail); + ret = mail_storage_service_lookup_next(storage_service, &input, + &service_user, &user, &error); + restrict_access_allow_coredumps(TRUE); + if (ret == 0) { + e_debug(client->event, "User `%s' not found", input.username); + value = nouser_reply; + } else if (ret > 0) { + enum quota_alloc_result qret = quota_check(user, client->size, + &error); + if (qret == QUOTA_ALLOC_RESULT_OK) { + e_debug(client->event, + "Message is acceptable"); + } else { + e_debug(client->event, + "Quota check failed: %s", error); + } + + switch (qret) { + case QUOTA_ALLOC_RESULT_OK: /* under quota */ + value = mail_user_plugin_getenv(user, + "quota_status_success"); + if (value == NULL) + value = "OK"; + break; + case QUOTA_ALLOC_RESULT_OVER_MAXSIZE: + /* even over maximum quota */ + case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT: + value = mail_user_plugin_getenv(user, + "quota_status_toolarge"); + /* fall through */ + case QUOTA_ALLOC_RESULT_OVER_QUOTA: + if (value == NULL) + value = mail_user_plugin_getenv(user, + "quota_status_overquota"); + if (value == NULL) + value = t_strdup_printf("554 5.2.2 %s", error); + break; + case QUOTA_ALLOC_RESULT_TEMPFAIL: + case QUOTA_ALLOC_RESULT_BACKGROUND_CALC: + ret = -1; + break; + } + value = t_strdup(value); /* user's pool is being freed */ + mail_user_deinit(&user); + mail_storage_service_user_unref(&service_user); + } else { + e_error(client->event, + "Failed to lookup user %s: %s", input.username, error); + error = "Temporary internal error"; + } + + resp = t_str_new(256); + if (ret < 0) { + /* temporary failure */ + str_append(resp, "action=DEFER_IF_PERMIT "); + str_append(resp, error); + } else { + str_append(resp, "action="); + str_append(resp, value); + } + + e_debug(client->event, "Response: %s", str_c(resp)); + str_append(resp, "\n\n"); + o_stream_nsend_str(client->conn.output, str_c(resp)); +} + +static int client_input_line(struct connection *conn, const char *line) +{ + struct quota_client *client = (struct quota_client *)conn; + + e_debug(client->event, "Request: %s", str_sanitize(line, 1024)); + + if (*line == '\0') { + o_stream_cork(conn->output); + client_handle_request(client); + o_stream_uncork(conn->output); + client_reset(client); + return 1; + } + if (str_begins(line, "recipient=")) { + if (client->recipient == NULL) + client->recipient = i_strdup(line + 10); + } else if (str_begins(line, "size=")) { + if (str_to_uoff(line+5, &client->size) < 0) + client->size = 0; + } else if (str_begins(line, "protocol_state=")) { + if (client->state == NULL) + client->state = i_strdup(line + 15); + } + return 1; +} + +static void client_destroy(struct connection *conn) +{ + struct quota_client *client = (struct quota_client *)conn; + + e_debug(client->event, "Client disconnected"); + + connection_deinit(&client->conn); + client_reset(client); + event_unref(&client->event); + i_free(client); + + master_service_client_connection_destroyed(master_service); +} + +static struct connection_settings client_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = FALSE +}; + +static const struct connection_vfuncs client_vfuncs = { + .destroy = client_destroy, + .input_line = client_input_line +}; + +static void main_preinit(void) +{ + restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); + restrict_access_allow_coredumps(TRUE); +} + +static void main_init(void) +{ + static const struct setting_parser_info *set_roots[] = { + "a_status_setting_parser_info, + NULL + }; + struct mail_storage_service_input input; + const struct setting_parser_info *user_info; + const struct setting_parser_context *set_parser; + const struct mail_user_settings *user_set; + const struct quota_status_settings *set; + const char *value, *error; + pool_t pool; + + clients = connection_list_init(&client_set, &client_vfuncs); + storage_service = mail_storage_service_init(master_service, set_roots, + MAIL_STORAGE_SERVICE_FLAG_ALLOW_ROOT | + MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP | + MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP | + MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS | + MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR); + + i_zero(&input); + input.service = "quota-status"; + input.module = "mail"; + input.username = ""; + + quota_status_pool = pool_alloconly_create("quota status settings", 512); + pool = pool_alloconly_create("service all settings", 4096); + if (mail_storage_service_read_settings(storage_service, &input, pool, + &user_info, &set_parser, + &error) < 0) + i_fatal("%s", error); + user_set = master_service_settings_parser_get_others(master_service, + set_parser)[0]; + set = master_service_settings_get_others(master_service)[1]; + + quota_status_settings = settings_dup("a_status_setting_parser_info, set, + quota_status_pool); + value = mail_user_set_plugin_getenv(user_set, "quota_status_nouser"); + nouser_reply = p_strdup(quota_status_pool, + value != NULL ? value : "REJECT Unknown user"); + pool_unref(&pool); +} + +static void main_deinit(void) +{ + pool_unref("a_status_pool); + connection_list_deinit(&clients); + mail_storage_service_deinit(&storage_service); +} + +int main(int argc, char *argv[]) +{ + enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN; + int c; + + protocol = QUOTA_PROTOCOL_UNKNOWN; + master_service = master_service_init("quota-status", service_flags, + &argc, &argv, "p:"); + while ((c = master_getopt(master_service)) > 0) { + switch (c) { + case 'p': + if (strcmp(optarg, "postfix") == 0) + protocol = QUOTA_PROTOCOL_POSTFIX; + else + i_fatal("Unknown -p parameter: '%s'", optarg); + break; + default: + return FATAL_DEFAULT; + } + } + if (protocol == QUOTA_PROTOCOL_UNKNOWN) + i_fatal("Missing -p parameter"); + + master_service_init_log(master_service); + main_preinit(); + + main_init(); + master_service_init_finish(master_service); + master_service_run(master_service, client_connected); + main_deinit(); + master_service_deinit(&master_service); + return 0; +} diff --git a/src/plugins/quota/quota-storage.c b/src/plugins/quota/quota-storage.c new file mode 100644 index 0000000..a1d08ee --- /dev/null +++ b/src/plugins/quota/quota-storage.c @@ -0,0 +1,780 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "mail-search-build.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "maildir-storage.h" +#include "index-mailbox-size.h" +#include "quota-private.h" +#include "quota-plugin.h" + +#include <sys/stat.h> + +#define QUOTA_CONTEXT(obj) \ + MODULE_CONTEXT(obj, quota_storage_module) +#define QUOTA_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, quota_storage_module) +#define QUOTA_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, quota_mail_module) +#define QUOTA_LIST_CONTEXT(obj) \ + MODULE_CONTEXT(obj, quota_mailbox_list_module) + +struct quota_mailbox_list { + union mailbox_list_module_context module_ctx; +}; + +struct quota_mailbox { + union mailbox_module_context module_ctx; + + struct mailbox_transaction_context *expunge_trans; + struct quota_transaction_context *expunge_qt; + ARRAY(uint32_t) expunge_uids; + ARRAY(uoff_t) expunge_sizes; + unsigned int prev_idx; + + bool recalculate:1; + bool sync_transaction_expunge:1; +}; + +struct quota_user_module quota_user_module = + MODULE_CONTEXT_INIT(&mail_user_module_register); + +static MODULE_CONTEXT_DEFINE_INIT(quota_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(quota_mail_module, &mail_module_register); +static MODULE_CONTEXT_DEFINE_INIT(quota_mailbox_list_module, + &mailbox_list_module_register); + +static void quota_set_storage_error(struct quota_transaction_context *qt, + struct mailbox *box, + enum quota_alloc_result res, + const char *internal_err) +{ + const char *errstr = quota_alloc_result_errstr(res, qt); + struct mail_storage *storage = box->storage; + switch (res) { + case QUOTA_ALLOC_RESULT_OVER_MAXSIZE: + mail_storage_set_error(storage, MAIL_ERROR_LIMIT, errstr); + break; + case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT: + case QUOTA_ALLOC_RESULT_OVER_QUOTA: + mail_storage_set_error(storage, MAIL_ERROR_NOQUOTA, errstr); + break; + case QUOTA_ALLOC_RESULT_TEMPFAIL: + case QUOTA_ALLOC_RESULT_BACKGROUND_CALC: + mailbox_set_critical(box, "quota: %s", internal_err); + break; + case QUOTA_ALLOC_RESULT_OK: + i_unreached(); + } +} + +static void quota_mail_expunge(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(_mail->box); + struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(_mail->box->storage->user); + union mail_module_context *qmail = QUOTA_MAIL_CONTEXT(mail); + struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(_mail->transaction); + uoff_t size; + int ret; + + if (qt->auto_updating) { + qmail->super.expunge(_mail); + return; + } + + /* We need to handle the situation where multiple transactions expunged + the mail at the same time. In here we'll just save the message's + physical size and do the quota freeing later when the message was + known to be expunged. */ + if (quser->quota->set->vsizes) + ret = mail_get_virtual_size(_mail, &size); + else + ret = mail_get_physical_size(_mail, &size); + if (ret == 0) { + if (!array_is_created(&qbox->expunge_uids)) { + i_array_init(&qbox->expunge_uids, 64); + i_array_init(&qbox->expunge_sizes, 64); + } + array_push_back(&qbox->expunge_uids, &_mail->uid); + array_push_back(&qbox->expunge_sizes, &size); + if ((_mail->transaction->flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0) { + /* we're running dsync. if this brings the quota below + a negative quota warning, don't execute it, because + it probably was already executed by the replica. */ + qbox->sync_transaction_expunge = TRUE; + } else { + qbox->sync_transaction_expunge = FALSE; + } + } + + qmail->super.expunge(_mail); +} + +static int +quota_get_status(struct mailbox *box, enum mailbox_status_items items, + struct mailbox_status *status_r) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box); + struct quota_transaction_context *qt; + int ret = 0; + + if ((items & STATUS_CHECK_OVER_QUOTA) != 0) { + qt = quota_transaction_begin(box); + const char *error; + enum quota_alloc_result qret = quota_test_alloc(qt, 0, &error); + if (qret != QUOTA_ALLOC_RESULT_OK) { + quota_set_storage_error(qt, box, qret, error); + ret = -1; + } + quota_transaction_rollback(&qt); + + if ((items & ENUM_NEGATE(STATUS_CHECK_OVER_QUOTA)) == 0) { + /* don't bother calling parent, it may unnecessarily + try to open the mailbox */ + return ret < 0 ? -1 : 0; + } + } + + if (qbox->module_ctx.super.get_status(box, items, status_r) < 0) + ret = -1; + return ret < 0 ? -1 : 0; +} + +static struct mailbox_transaction_context * +quota_mailbox_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box); + struct mailbox_transaction_context *t; + struct quota_transaction_context *qt; + + t = qbox->module_ctx.super.transaction_begin(box, flags, reason); + qt = quota_transaction_begin(box); + qt->sync_transaction = (flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0; + + MODULE_CONTEXT_SET(t, quota_storage_module, qt); + return t; +} + +static int +quota_mailbox_transaction_commit(struct mailbox_transaction_context *ctx, + struct mail_transaction_commit_changes *changes_r) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box); + struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(ctx); + + i_assert(qt->tmp_mail == NULL); + + if (qbox->module_ctx.super.transaction_commit(ctx, changes_r) < 0) { + quota_transaction_rollback(&qt); + return -1; + } else { + (void)quota_transaction_commit(&qt); + return 0; + } +} + +static void +quota_mailbox_transaction_rollback(struct mailbox_transaction_context *ctx) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box); + struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(ctx); + + i_assert(qt->tmp_mail == NULL); + + qbox->module_ctx.super.transaction_rollback(ctx); + quota_transaction_rollback(&qt); +} + +void quota_mail_allocated(struct mail *_mail) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT(_mail->box); + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + union mail_module_context *qmail; + + if (qbox == NULL) + return; + + qmail = p_new(mail->pool, union mail_module_context, 1); + qmail->super = *v; + mail->vlast = &qmail->super; + + v->expunge = quota_mail_expunge; + MODULE_CONTEXT_SET_SELF(mail, quota_mail_module, qmail); +} + +static bool +quota_move_requires_check(struct mailbox *dest_box, struct mailbox *src_box) +{ + struct mail_namespace *src_ns = src_box->list->ns; + struct mail_namespace *dest_ns = dest_box->list->ns; + struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(src_ns->user); + struct quota_root *const *rootp; + + array_foreach(&quser->quota->roots, rootp) { + bool have_src_quota, have_dest_quota; + + have_src_quota = quota_root_is_namespace_visible(*rootp, src_ns); + have_dest_quota = quota_root_is_namespace_visible(*rootp, dest_ns); + if (have_src_quota == have_dest_quota) { + /* Both/neither have this quota */ + } else if (have_dest_quota) { + /* Destination mailbox has a quota that doesn't exist + in source. We'll need to check if it's being + exceeded. */ + return TRUE; + } else { + /* Source mailbox has a quota root that doesn't exist + in destination. We're not increasing the source + quota, so ignore it. */ + } + } + return FALSE; +} + +static int quota_check(struct mail_save_context *ctx, struct mailbox *src_box) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t); + enum quota_alloc_result ret; + + i_assert(!ctx->moving || src_box != NULL); + + if (ctx->moving && + !quota_move_requires_check(ctx->transaction->box, src_box)) { + /* the mail is being moved. the quota won't increase (after + the following expunge), so allow this even if user is + currently over quota */ + quota_alloc(qt, ctx->dest_mail); + return 0; + } else if (qt->failed) { + return 0; + } + + const char *error; + ret = quota_try_alloc(qt, ctx->dest_mail, &error); + switch (ret) { + case QUOTA_ALLOC_RESULT_OK: + return 0; + case QUOTA_ALLOC_RESULT_TEMPFAIL: + /* Log the error, but allow saving anyway. */ + e_error(qt->quota->event, + "Failed to check if user is under quota: %s - " + "saving mail anyway", error); + return 0; + case QUOTA_ALLOC_RESULT_BACKGROUND_CALC: + /* Could not determine if there is enough space due to ongoing + background quota calculation, allow saving anyway. */ + e_warning(qt->quota->event, + "Failed to check if user is under quota: %s - " + "saving mail anyway", error); + return 0; + default: + quota_set_storage_error(qt, t->box, ret, error); + return -1; + } +} + +static int +quota_copy(struct mail_save_context *ctx, struct mail *mail) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t); + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(t->box); + + /* we always want to know the mail size */ + mail_add_temp_wanted_fields(ctx->dest_mail, MAIL_FETCH_PHYSICAL_SIZE, NULL); + + /* get quota before copying any mails. this avoids dovecot-vsize.lock + deadlocks with backends that lock mails for expunging/copying. */ + enum quota_get_result error_res; + const char *error; + if (quota_transaction_set_limits(qt, &error_res, &error) < 0) { + if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC) { + e_warning(qt->quota->event, + "%s - copying mail anyway", error); + } else { + e_error(qt->quota->event, + "%s - copying mail anyway", error); + } + } + + if (qbox->module_ctx.super.copy(ctx, mail) < 0) + return -1; + + if (ctx->copying_via_save) { + /* copying used saving internally, we already checked the + quota */ + return 0; + } + return quota_check(ctx, mail->box); +} + +static int +quota_save_begin(struct mail_save_context *ctx, struct istream *input) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t); + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(t->box); + const char *error; + uoff_t size; + + if (!ctx->moving && i_stream_get_size(input, TRUE, &size) > 0 && + !qt->failed) { + /* Input size is known, check for quota immediately. This + check isn't perfect, especially because input stream's + linefeeds may contain CR+LFs while physical message would + only contain LFs. With mbox some headers might be skipped + entirely. + + I think these don't really matter though compared to the + benefit of giving "out of quota" error before sending the + full mail. */ + + enum quota_alloc_result qret = quota_test_alloc(qt, size, &error); + switch (qret) { + case QUOTA_ALLOC_RESULT_OK: + /* Great, there is space. */ + break; + case QUOTA_ALLOC_RESULT_TEMPFAIL: + /* Log the error, but allow saving anyway. */ + e_error(qt->quota->event, + "Failed to check if user is under quota: %s - " + "saving mail anyway", error); + break; + case QUOTA_ALLOC_RESULT_BACKGROUND_CALC: + /* Could not determine if there is enough space due to + * ongoing background quota calculation, allow saving + * anyway. */ + e_warning(qt->quota->event, + "Failed to check if user is under quota: %s - " + "saving mail anyway", error); + break; + default: + quota_set_storage_error(qt, t->box, qret, error); + return -1; + } + } + + /* we always want to know the mail size */ + mail_add_temp_wanted_fields(ctx->dest_mail, MAIL_FETCH_PHYSICAL_SIZE, NULL); + + /* get quota before copying any mails. this avoids dovecot-vsize.lock + deadlocks with backends that lock mails for expunging/copying. */ + enum quota_get_result error_res; + if (quota_transaction_set_limits(qt, &error_res, &error) < 0) { + if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC) + e_warning(qt->quota->event, + "%s - saving mail anyway", error); + else + e_error(qt->quota->event, + "%s - saving mail anyway", error); + } + + return qbox->module_ctx.super.save_begin(ctx, input); +} + +static int quota_save_finish(struct mail_save_context *ctx) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->transaction->box); + struct mailbox *src_box; + + if (qbox->module_ctx.super.save_finish(ctx) < 0) + return -1; + + src_box = ctx->copy_src_mail == NULL ? NULL : ctx->copy_src_mail->box; + return quota_check(ctx, src_box); +} + +static void quota_mailbox_sync_cleanup(struct quota_mailbox *qbox) +{ + if (array_is_created(&qbox->expunge_uids)) { + array_clear(&qbox->expunge_uids); + array_clear(&qbox->expunge_sizes); + } + + if (qbox->expunge_qt != NULL && qbox->expunge_qt->tmp_mail != NULL) { + mail_free(&qbox->expunge_qt->tmp_mail); + (void)mailbox_transaction_commit(&qbox->expunge_trans); + } + qbox->sync_transaction_expunge = FALSE; +} + +static void quota_mailbox_sync_commit(struct quota_mailbox *qbox) +{ + quota_mailbox_sync_cleanup(qbox); + if (qbox->expunge_qt != NULL) + (void)quota_transaction_commit(&qbox->expunge_qt); + qbox->recalculate = FALSE; +} + +static void quota_mailbox_sync_notify(struct mailbox *box, uint32_t uid, + enum mailbox_sync_type sync_type) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box); + struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box); + struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(box->storage->user); + const uint32_t *uids; + const uoff_t *sizep; + unsigned int i, count; + uoff_t size; + + if (qbox->module_ctx.super.sync_notify != NULL) + qbox->module_ctx.super.sync_notify(box, uid, sync_type); + + if (sync_type != MAILBOX_SYNC_TYPE_EXPUNGE || qbox->recalculate || + (box->flags & MAILBOX_FLAG_DELETE_UNSAFE) != 0) { + if (uid == 0) { + /* free the transaction before view syncing begins, + otherwise it'll crash. */ + quota_mailbox_sync_cleanup(qbox); + } + return; + } + + if (qbox->expunge_qt == NULL) { + qbox->expunge_qt = quota_transaction_begin(box); + qbox->expunge_qt->sync_transaction = + qbox->sync_transaction_expunge; + } + if (qbox->expunge_qt->auto_updating) { + /* even though backend doesn't care about size/count changes, + make sure count_used changes so quota_warnings are + executed */ + quota_free_bytes(qbox->expunge_qt, 0); + return; + } + + /* we're in the middle of syncing the mailbox, so it's a bad idea to + try and get the message sizes at this point. Rely on sizes that + we saved earlier, or recalculate the whole quota if we don't know + the size. */ + if (!array_is_created(&qbox->expunge_uids) || + array_is_empty(&qbox->expunge_uids)) { + i = count = 0; + } else { + uids = array_get(&qbox->expunge_uids, &count); + for (i = qbox->prev_idx; i < count; i++) { + if (uids[i] == uid) + break; + } + if (i >= count) { + for (i = 0; i < qbox->prev_idx; i++) { + if (uids[i] == uid) + break; + } + if (i == qbox->prev_idx) + i = count; + } + qbox->prev_idx = i; + } + + if (i != count) { + /* we already know the size */ + sizep = array_idx(&qbox->expunge_sizes, i); + quota_free_bytes(qbox->expunge_qt, *sizep); + /* FIXME: it's not ideal that we do the vsize update here, but + this is the easiest place for it for now.. maybe the mail + size checking code could be moved to lib-storage */ + if (ibox->vsize_update != NULL && quser->quota->set->vsizes) + index_mailbox_vsize_hdr_expunge(ibox->vsize_update, uid, *sizep); + return; + } + + /* try to look up the size. this works only if it's cached. */ + if (qbox->expunge_qt->tmp_mail == NULL) { + /* FIXME: ugly kludge to open the transaction for sync_view. + box->view may not have all the new messages that + sync_notify() notifies about, and those messages would + cause a quota recalculation. */ + struct mail_index_view *box_view = box->view; + if (box->tmp_sync_view != NULL) + box->view = box->tmp_sync_view; + qbox->expunge_trans = mailbox_transaction_begin(box, 0, "quota"); + box->view = box_view; + qbox->expunge_qt->tmp_mail = + mail_alloc(qbox->expunge_trans, + MAIL_FETCH_PHYSICAL_SIZE, NULL); + } + if (!mail_set_uid(qbox->expunge_qt->tmp_mail, uid)) + ; + else if (!quser->quota->set->vsizes) { + if (mail_get_physical_size(qbox->expunge_qt->tmp_mail, &size) == 0) { + quota_free_bytes(qbox->expunge_qt, size); + return; + } + } else if (mail_get_virtual_size(qbox->expunge_qt->tmp_mail, &size) == 0) { + quota_free_bytes(qbox->expunge_qt, size); + if (ibox->vsize_update != NULL) + index_mailbox_vsize_hdr_expunge(ibox->vsize_update, uid, size); + } else { + /* there's no way to get the size. recalculate the quota. */ + quota_recalculate(qbox->expunge_qt, QUOTA_RECALCULATE_MISSING_FREES); + qbox->recalculate = TRUE; + } +} + +static int quota_mailbox_sync_deinit(struct mailbox_sync_context *ctx, + struct mailbox_sync_status *status_r) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box); + int ret; + + ret = qbox->module_ctx.super.sync_deinit(ctx, status_r); + /* update quota only after syncing is finished. the quota commit may + recalculate the quota and cause all mailboxes to be synced, + including the one we're already syncing. */ + quota_mailbox_sync_commit(qbox); + return ret; +} + +static void quota_roots_flush(struct quota *quota) +{ + struct quota_root *const *roots; + unsigned int i, count; + + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) { + if (roots[i]->backend.v.flush != NULL) + roots[i]->backend.v.flush(roots[i]); + } +} + +static void quota_mailbox_close(struct mailbox *box) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box); + struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(box->storage->user); + + /* sync_notify() may be called outside sync_begin()..sync_deinit(). + make sure we apply changes at close time at latest. */ + quota_mailbox_sync_commit(qbox); + + /* make sure quota backend flushes all data. this could also be done + somewhat later, but user.deinit() is too late, since the flushing + can trigger quota recalculation which isn't safe to do anymore + at user.deinit() when most of the loaded plugins have already been + deinitialized. */ + quota_roots_flush(quser->quota); + + qbox->module_ctx.super.close(box); +} + +static void quota_mailbox_free(struct mailbox *box) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box); + + if (array_is_created(&qbox->expunge_uids)) { + array_free(&qbox->expunge_uids); + array_free(&qbox->expunge_sizes); + } + i_assert(qbox->expunge_qt == NULL || + qbox->expunge_qt->tmp_mail == NULL); + + qbox->module_ctx.super.free(box); +} + +void quota_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct quota_mailbox *qbox; + + if (QUOTA_LIST_CONTEXT(box->list) == NULL) + return; + + if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0) + return; + + qbox = p_new(box->pool, struct quota_mailbox, 1); + qbox->module_ctx.super = *v; + box->vlast = &qbox->module_ctx.super; + + v->get_status = quota_get_status; + v->transaction_begin = quota_mailbox_transaction_begin; + v->transaction_commit = quota_mailbox_transaction_commit; + v->transaction_rollback = quota_mailbox_transaction_rollback; + v->save_begin = quota_save_begin; + v->save_finish = quota_save_finish; + v->copy = quota_copy; + v->sync_notify = quota_mailbox_sync_notify; + v->sync_deinit = quota_mailbox_sync_deinit; + v->close = quota_mailbox_close; + v->free = quota_mailbox_free; + MODULE_CONTEXT_SET(box, quota_storage_module, qbox); +} + +static void quota_mailbox_list_deinit(struct mailbox_list *list) +{ + struct quota_mailbox_list *qlist = QUOTA_LIST_CONTEXT(list); + + i_assert(qlist != NULL); + quota_remove_user_namespace(list->ns); + qlist->module_ctx.super.deinit(list); +} + +struct quota *quota_get_mail_user_quota(struct mail_user *user) +{ + struct quota_user *quser = QUOTA_USER_CONTEXT(user); + + return quser == NULL ? NULL : quser->quota; +} + +static void quota_user_deinit(struct mail_user *user) +{ + struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(user); + struct quota_settings *quota_set = quser->quota->set; + + quota_deinit(&quser->quota); + quser->module_ctx.super.deinit(user); + + quota_settings_deinit("a_set); +} + +void quota_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct quota_user *quser; + struct quota_settings *set; + struct quota *quota; + const char *error; + int ret; + + if ((ret = quota_user_read_settings(user, &set, &error)) > 0) { + if (quota_init(set, user, "a, &error) < 0) { + quota_settings_deinit(&set); + ret = -1; + } + } + + if (ret < 0) { + user->error = p_strdup_printf(user->pool, + "Failed to initialize quota: %s", error); + return; + } + if (ret > 0) { + quser = p_new(user->pool, struct quota_user, 1); + quser->module_ctx.super = *v; + user->vlast = &quser->module_ctx.super; + v->deinit = quota_user_deinit; + quser->quota = quota; + + MODULE_CONTEXT_SET(user, quota_user_module, quser); + } else { + e_debug(user->event, "quota: No quota setting - plugin disabled"); + } +} + +static struct quota_root * +quota_find_root_for_ns(struct quota *quota, struct mail_namespace *ns) +{ + struct quota_root *const *roots; + unsigned int i, count; + + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) { + if (roots[i]->ns_prefix != NULL && + strcmp(roots[i]->ns_prefix, ns->prefix) == 0) + return roots[i]; + } + return NULL; +} + +void quota_mailbox_list_created(struct mailbox_list *list) +{ + struct quota_mailbox_list *qlist; + struct quota *quota = NULL; + struct quota_root *root; + struct mail_user *quota_user; + bool add; + + /* see if we have a quota explicitly defined for this namespace */ + quota = quota_get_mail_user_quota(list->ns->user); + if (quota == NULL) + return; + root = quota_find_root_for_ns(quota, list->ns); + if (root != NULL) { + /* explicit quota root */ + root->ns = list->ns; + quota_user = list->ns->user; + } else { + quota_user = list->ns->owner != NULL ? + list->ns->owner : list->ns->user; + } + + if ((list->ns->flags & NAMESPACE_FLAG_NOQUOTA) != 0) + add = FALSE; + else if (list->ns->owner == NULL) { + /* public namespace - add quota only if namespace is + explicitly defined for it */ + add = root != NULL; + } else { + /* for shared namespaces add only if the owner has quota + enabled */ + add = QUOTA_USER_CONTEXT(quota_user) != NULL; + } + + if (add) { + struct mailbox_list_vfuncs *v = list->vlast; + + qlist = p_new(list->pool, struct quota_mailbox_list, 1); + qlist->module_ctx.super = *v; + list->vlast = &qlist->module_ctx.super; + v->deinit = quota_mailbox_list_deinit; + MODULE_CONTEXT_SET(list, quota_mailbox_list_module, qlist); + + quota = quota_get_mail_user_quota(quota_user); + i_assert(quota != NULL); + quota_add_user_namespace(quota, list->ns); + } +} + +static void quota_root_set_namespace(struct quota_root *root, + struct mail_namespace *namespaces) +{ + const struct quota_rule *rule; + const char *name; + struct mail_namespace *ns; + /* silence errors for autocreated (shared) users */ + bool silent_errors = namespaces->user->autocreated; + + if (root->ns_prefix != NULL && root->ns == NULL) { + root->ns = mail_namespace_find_prefix(namespaces, + root->ns_prefix); + if (root->ns == NULL && !silent_errors) { + e_error(root->quota->event, + "Unknown namespace: %s", + root->ns_prefix); + } + } + + array_foreach(&root->set->rules, rule) { + name = rule->mailbox_mask; + ns = mail_namespace_find(namespaces, name); + if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0 && + !silent_errors) + e_error(root->quota->event, + "Unknown namespace: %s", name); + } +} + +void quota_mail_namespaces_created(struct mail_namespace *namespaces) +{ + struct quota *quota; + struct quota_root *const *roots; + unsigned int i, count; + + quota = quota_get_mail_user_quota(namespaces->user); + if (quota == NULL) + return; + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) + quota_root_set_namespace(roots[i], namespaces); + + quota_over_flag_check_startup(quota); +} diff --git a/src/plugins/quota/quota-util.c b/src/plugins/quota/quota-util.c new file mode 100644 index 0000000..95ce369 --- /dev/null +++ b/src/plugins/quota/quota-util.c @@ -0,0 +1,465 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "wildcard-match.h" +#include "quota-private.h" + +#include <ctype.h> + +#define QUOTA_DEFAULT_GRACE "10%" + +#define RULE_NAME_DEFAULT_FORCE "*" +#define RULE_NAME_DEFAULT_NONFORCE "?" + +struct quota_rule * +quota_root_rule_find(struct quota_root_settings *root_set, const char *name) +{ + struct quota_rule *rule; + + array_foreach_modifiable(&root_set->rules, rule) { + if (wildcard_match(name, rule->mailbox_mask)) + return rule; + } + return NULL; +} + +static struct quota_rule * +quota_root_rule_find_exact(struct quota_root_settings *root_set, + const char *name) +{ + struct quota_rule *rule; + + array_foreach_modifiable(&root_set->rules, rule) { + if (strcmp(rule->mailbox_mask, name) == 0) + return rule; + } + return NULL; +} + +static int +quota_rule_parse_percentage(struct quota_root_settings *root_set, + struct quota_rule *rule, + int64_t *limit, const char **error_r) +{ + int64_t percentage = *limit; + + if (percentage <= -100 || percentage >= UINT_MAX) { + *error_r = "Invalid percentage"; + return -1; + } + + if (rule == &root_set->default_rule) { + *error_r = "Default rule can't be a percentage"; + return -1; + } + + if (limit == &rule->bytes_limit) + rule->bytes_percent = percentage; + else if (limit == &rule->count_limit) + rule->count_percent = percentage; + else + i_unreached(); + return 0; +} + +static int quota_limit_parse(struct quota_root_settings *root_set, + struct quota_rule *rule, const char *unit, + uint64_t multiply, int64_t *limit, + const char **error_r) +{ + switch (i_toupper(*unit)) { + case '\0': + /* default */ + break; + case 'B': + multiply = 1; + break; + case 'K': + multiply = 1024; + break; + case 'M': + multiply = 1024*1024; + break; + case 'G': + multiply = 1024*1024*1024; + break; + case 'T': + multiply = 1024ULL*1024*1024*1024; + break; + case '%': + multiply = 0; + if (quota_rule_parse_percentage(root_set, rule, limit, + error_r) < 0) + return -1; + break; + default: + *error_r = t_strdup_printf("Unknown unit: %s", unit); + return -1; + } + *limit *= multiply; + return 0; +} + +static void +quota_rule_recalculate_relative_rules(struct quota_rule *rule, + int64_t bytes_limit, int64_t count_limit) +{ + if (rule->bytes_percent != 0) + rule->bytes_limit = bytes_limit * rule->bytes_percent / 100; + if (rule->count_percent != 0) + rule->count_limit = count_limit * rule->count_percent / 100; +} + +void quota_root_recalculate_relative_rules(struct quota_root_settings *root_set, + int64_t bytes_limit, + int64_t count_limit) +{ + struct quota_rule *rule; + struct quota_warning_rule *warning_rule; + + array_foreach_modifiable(&root_set->rules, rule) { + quota_rule_recalculate_relative_rules(rule, bytes_limit, + count_limit); + } + + array_foreach_modifiable(&root_set->warning_rules, warning_rule) { + quota_rule_recalculate_relative_rules(&warning_rule->rule, + bytes_limit, count_limit); + } + quota_rule_recalculate_relative_rules(&root_set->grace_rule, + bytes_limit, 0); + root_set->last_mail_max_extra_bytes = root_set->grace_rule.bytes_limit; + + if (root_set->set->initialized) { + e_debug(root_set->set->event, + "Quota root %s: Recalculated relative rules with " + "bytes=%lld count=%lld. Now grace=%"PRIu64, root_set->name, + (long long)bytes_limit, (long long)count_limit, + root_set->last_mail_max_extra_bytes); + } +} + +static int +quota_rule_parse_limits(struct quota_root_settings *root_set, + struct quota_rule *rule, const char *limits, + const char *full_rule_def, + bool relative_rule, const char **error_r) +{ + const char **args, *key, *value, *error, *p; + uint64_t multiply; + int64_t *limit; + + args = t_strsplit(limits, ":"); + for (; *args != NULL; args++) { + multiply = 1; + limit = NULL; + + key = *args; + value = strchr(key, '='); + if (value == NULL) + value = ""; + else + key = t_strdup_until(key, value++); + + if (*value == '+') { + if (!relative_rule) { + *error_r = "Rule limit cannot have '+'"; + return -1; + } + value++; + } else if (*value != '-' && relative_rule) { + e_warning(root_set->set->event, "quota root %s rule %s: " + "obsolete configuration for rule '%s' " + "should be changed to '%s=+%s'", + root_set->name, full_rule_def, + *args, key, value); + } + + if (strcmp(key, "storage") == 0) { + multiply = 1024; + limit = &rule->bytes_limit; + if (str_parse_int64(value, limit, &p) < 0) { + *error_r = p_strdup_printf(root_set->set->pool, + "Invalid storage limit: %s", value); + return -1; + } + } else if (strcmp(key, "bytes") == 0) { + limit = &rule->bytes_limit; + if (str_parse_int64(value, limit, &p) < 0) { + *error_r = p_strdup_printf(root_set->set->pool, + "Invalid bytes limit: %s", value); + return -1; + } + } else if (strcmp(key, "messages") == 0) { + limit = &rule->count_limit; + if (str_parse_int64(value, limit, &p) < 0) { + *error_r = p_strdup_printf(root_set->set->pool, + "Invalid bytes messages: %s", value); + return -1; + } + } else { + *error_r = p_strdup_printf(root_set->set->pool, + "Unknown rule limit name: %s", key); + return -1; + } + + if (quota_limit_parse(root_set, rule, p, multiply, + limit, &error) < 0) { + *error_r = p_strdup_printf(root_set->set->pool, + "Invalid rule limit value '%s': %s", + *args, error); + return -1; + } + } + if (!relative_rule) { + if (rule->bytes_limit < 0) { + *error_r = "Bytes limit can't be negative"; + return -1; + } + if (rule->count_limit < 0) { + *error_r = "Count limit can't be negative"; + return -1; + } + } + return 0; +} + +int quota_root_add_rule(struct quota_root_settings *root_set, + const char *rule_def, const char **error_r) +{ + struct quota_rule *rule; + const char *p, *mailbox_mask; + int ret = 0; + + p = strchr(rule_def, ':'); + if (p == NULL) { + *error_r = "Invalid rule"; + return -1; + } + + /* <mailbox mask>:<quota limits> */ + mailbox_mask = t_strdup_until(rule_def, p++); + + rule = quota_root_rule_find_exact(root_set, mailbox_mask); + if (rule == NULL) { + if (strcmp(mailbox_mask, RULE_NAME_DEFAULT_NONFORCE) == 0) + rule = &root_set->default_rule; + else if (strcmp(mailbox_mask, RULE_NAME_DEFAULT_FORCE) == 0) { + rule = &root_set->default_rule; + root_set->force_default_rule = TRUE; + } else { + rule = array_append_space(&root_set->rules); + rule->mailbox_mask = strcasecmp(mailbox_mask, "INBOX") == 0 ? "INBOX" : + p_strdup(root_set->set->pool, mailbox_mask); + } + } + + if (strcmp(p, "ignore") == 0) { + rule->ignore = TRUE; + e_debug(root_set->set->event, + "Quota rule: root=%s mailbox=%s ignored", + root_set->name, mailbox_mask); + return 0; + } + + if (str_begins(p, "backend=")) { + if (root_set->backend->v.parse_rule == NULL) { + *error_r = "backend rule not supported"; + ret = -1; + } else if (!root_set->backend->v.parse_rule(root_set, rule, + p + 8, error_r)) + ret = -1; + } else { + bool relative_rule = rule != &root_set->default_rule; + + if (quota_rule_parse_limits(root_set, rule, p, rule_def, + relative_rule, error_r) < 0) + ret = -1; + } + + quota_root_recalculate_relative_rules(root_set, + root_set->default_rule.bytes_limit, + root_set->default_rule.count_limit); + const char *rule_plus = + rule == &root_set->default_rule ? "" : "+"; + + e_debug(root_set->set->event, "Quota rule: root=%s mailbox=%s " + "bytes=%s%lld%s messages=%s%lld%s", + root_set->name, mailbox_mask, + rule->bytes_limit > 0 ? rule_plus : "", + (long long)rule->bytes_limit, + rule->bytes_percent == 0 ? "" : + t_strdup_printf(" (%u%%)", rule->bytes_percent), + rule->count_limit > 0 ? rule_plus : "", + (long long)rule->count_limit, + rule->count_percent == 0 ? "" : + t_strdup_printf(" (%u%%)", rule->count_percent)); + return ret; +} + +int quota_root_add_warning_rule(struct quota_root_settings *root_set, + const char *rule_def, const char **error_r) +{ + struct quota_warning_rule *warning; + struct quota_rule rule; + const char *p, *q; + int ret; + bool reverse = FALSE; + + p = strchr(rule_def, ' '); + if (p == NULL || p[1] == '\0') { + *error_r = "No command specified"; + return -1; + } + + if (*rule_def == '+') { + /* warn when exceeding quota */ + q = rule_def+1; + } else if (*rule_def == '-') { + /* warn when going below quota */ + q = rule_def+1; + reverse = TRUE; + } else { + /* default: same as '+' */ + q = rule_def; + } + + i_zero(&rule); + ret = quota_rule_parse_limits(root_set, &rule, t_strdup_until(q, p), + rule_def, FALSE, error_r); + if (ret < 0) + return -1; + + warning = array_append_space(&root_set->warning_rules); + warning->command = p_strdup(root_set->set->pool, p+1); + warning->rule = rule; + warning->reverse = reverse; + if (reverse) + root_set->have_reverse_warnings = TRUE; + + quota_root_recalculate_relative_rules(root_set, + root_set->default_rule.bytes_limit, + root_set->default_rule.count_limit); + e_debug(root_set->set->event, "Quota warning: bytes=%"PRId64"%s " + "messages=%"PRId64"%s reverse=%s command=%s", + warning->rule.bytes_limit, + warning->rule.bytes_percent == 0 ? "" : + t_strdup_printf(" (%u%%)", warning->rule.bytes_percent), + warning->rule.count_limit, + warning->rule.count_percent == 0 ? "" : + t_strdup_printf(" (%u%%)", warning->rule.count_percent), + warning->reverse ? "yes" : "no", + warning->command); + return 0; +} + +int quota_root_parse_grace(struct quota_root_settings *root_set, + const char *value, const char **error_r) +{ + const char *p; + + if (value == NULL) { + /* default */ + value = QUOTA_DEFAULT_GRACE; + } + + if (str_parse_int64(value, &root_set->grace_rule.bytes_limit, &p) < 0) + return -1; + if (quota_limit_parse(root_set, &root_set->grace_rule, p, 1, + &root_set->grace_rule.bytes_limit, error_r) < 0) + return -1; + quota_rule_recalculate_relative_rules(&root_set->grace_rule, + root_set->default_rule.bytes_limit, 0); + root_set->last_mail_max_extra_bytes = root_set->grace_rule.bytes_limit; + e_debug(root_set->set->event, "Quota grace: root=%s bytes=%lld%s", + root_set->name, (long long)root_set->grace_rule.bytes_limit, + root_set->grace_rule.bytes_percent == 0 ? "" : + t_strdup_printf(" (%u%%)", root_set->grace_rule.bytes_percent)); + return 0; +} + +bool quota_warning_match(const struct quota_warning_rule *w, + uint64_t bytes_before, uint64_t bytes_current, + uint64_t count_before, uint64_t count_current, + const char **reason_r) +{ +#define QUOTA_EXCEEDED(before, current, limit) \ + ((before) < (uint64_t)(limit) && (current) >= (uint64_t)(limit)) + if (!w->reverse) { + /* over quota (default) */ + if (QUOTA_EXCEEDED(bytes_before, bytes_current, w->rule.bytes_limit)) { + *reason_r = t_strdup_printf("bytes=%"PRIu64" -> %"PRIu64" over limit %"PRId64, + bytes_before, bytes_current, w->rule.bytes_limit); + return TRUE; + } + if (QUOTA_EXCEEDED(count_before, count_current, w->rule.count_limit)) { + *reason_r = t_strdup_printf("count=%"PRIu64" -> %"PRIu64" over limit %"PRId64, + count_before, count_current, w->rule.count_limit); + return TRUE; + } + } else { + if (QUOTA_EXCEEDED(bytes_current, bytes_before, w->rule.bytes_limit)) { + *reason_r = t_strdup_printf("bytes=%"PRIu64" -> %"PRIu64" below limit %"PRId64, + bytes_before, bytes_current, w->rule.bytes_limit); + return TRUE; + } + if (QUOTA_EXCEEDED(count_current, count_before, w->rule.count_limit)) { + *reason_r = t_strdup_printf("count=%"PRIu64" -> %"PRIu64" below limit %"PRId64, + count_before, count_current, w->rule.count_limit); + return TRUE; + } + } + return FALSE; +} + +bool quota_transaction_is_over(struct quota_transaction_context *ctx, + uoff_t size) +{ + if (ctx->count_used < 0) { + /* we've deleted some messages. we should be ok, unless we + were already over quota and still are after these + deletions. */ + const uint64_t count_deleted = (uint64_t)-ctx->count_used; + + if (ctx->count_over > 0) { + if (count_deleted - 1 < ctx->count_over) + return TRUE; + } + } else { + if (ctx->count_ceil < 1 || + ctx->count_ceil - 1 < (uint64_t)ctx->count_used) { + /* count limit reached */ + return TRUE; + } + } + + if (ctx->bytes_used < 0) { + const uint64_t bytes_deleted = (uint64_t)-ctx->bytes_used; + + /* we've deleted some messages. same logic as above. */ + if (ctx->bytes_over > 0) { + if (ctx->bytes_over > bytes_deleted) { + /* even after deletions we're over quota */ + return TRUE; + } + if (size > bytes_deleted - ctx->bytes_over) + return TRUE; + } else { + if (size > bytes_deleted && + size - bytes_deleted < ctx->bytes_ceil) + return TRUE; + } + } else if (size == 0) { + /* we need to explicitly test this case, since the generic + check would fail if user is already over quota */ + if (ctx->bytes_over > 0) + return TRUE; + } else { + if (ctx->bytes_ceil < size || + ctx->bytes_ceil - size < (uint64_t)ctx->bytes_used) { + /* bytes limit reached */ + return TRUE; + } + } + return FALSE; +} diff --git a/src/plugins/quota/quota.c b/src/plugins/quota/quota.c new file mode 100644 index 0000000..3d6d8e5 --- /dev/null +++ b/src/plugins/quota/quota.c @@ -0,0 +1,1543 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "str.h" +#include "ioloop.h" +#include "net.h" +#include "write-full.h" +#include "eacces-error.h" +#include "wildcard-match.h" +#include "dict.h" +#include "mailbox-list-private.h" +#include "quota-private.h" +#include "quota-fs.h" +#include "llist.h" +#include "program-client.h" +#include "settings-parser.h" + +#include <sys/wait.h> + +#define DEFAULT_QUOTA_EXCEEDED_MSG \ + "Quota exceeded (mailbox for user is full)" +#define QUOTA_LIMIT_SET_PATH DICT_PATH_PRIVATE"quota/limit/" + +/* How many seconds after the userdb lookup do we still want to execute the + quota_over_script. This applies to quota_over_flag_lazy_check=yes and also + after unhibernating IMAP connections. */ +#define QUOTA_OVER_FLAG_MAX_DELAY_SECS 10 + +struct quota_root_iter { + struct quota *quota; + struct mailbox *box; + + unsigned int i; +}; + +unsigned int quota_module_id = 0; + +extern struct quota_backend quota_backend_count; +extern struct quota_backend quota_backend_dict; +extern struct quota_backend quota_backend_dirsize; +extern struct quota_backend quota_backend_fs; +extern struct quota_backend quota_backend_imapc; +extern struct quota_backend quota_backend_maildir; + +static const struct quota_backend *quota_internal_backends[] = { +#ifdef HAVE_FS_QUOTA + "a_backend_fs, +#endif + "a_backend_count, + "a_backend_dict, + "a_backend_dirsize, + "a_backend_imapc, + "a_backend_maildir +}; + +static ARRAY(const struct quota_backend*) quota_backends; + +static void hidden_param_handler(struct quota_root *_root, const char *param_value); +static void ignoreunlim_param_handler(struct quota_root *_root, const char *param_value); +static void noenforcing_param_handler(struct quota_root *_root, const char *param_value); +static void ns_param_handler(struct quota_root *_root, const char *param_value); + +struct quota_param_parser quota_param_hidden = {.param_name = "hidden", .param_handler = hidden_param_handler}; +struct quota_param_parser quota_param_ignoreunlimited = {.param_name = "ignoreunlimited", .param_handler = ignoreunlim_param_handler}; +struct quota_param_parser quota_param_noenforcing = {.param_name = "noenforcing", .param_handler = noenforcing_param_handler}; +struct quota_param_parser quota_param_ns = {.param_name = "ns=", .param_handler = ns_param_handler}; + +static enum quota_alloc_result quota_default_test_alloc( + struct quota_transaction_context *ctx, uoff_t size, + const char **error_r); +static void quota_over_flag_check_root(struct quota_root *root); + +static const struct quota_backend *quota_backend_find(const char *name) +{ + const struct quota_backend *const *backend; + + array_foreach("a_backends, backend) { + if (strcmp((*backend)->name, name) == 0) + return *backend; + } + + return NULL; +} + +void quota_backend_register(const struct quota_backend *backend) +{ + i_assert(quota_backend_find(backend->name) == NULL); + array_push_back("a_backends, &backend); +} + +void quota_backend_unregister(const struct quota_backend *backend) +{ + for(unsigned int i = 0; i < array_count("a_backends); i++) { + const struct quota_backend *be = + array_idx_elem("a_backends, i); + if (strcmp(be->name, backend->name) == 0) { + array_delete("a_backends, i, 1); + return; + } + } + + i_unreached(); +} + +void quota_backends_register(void); +void quota_backends_unregister(void); + +void quota_backends_register(void) +{ + i_array_init("a_backends, 8); + array_append("a_backends, quota_internal_backends, + N_ELEMENTS(quota_internal_backends)); +} + +void quota_backends_unregister(void) +{ + for(size_t i = 0; i < N_ELEMENTS(quota_internal_backends); i++) { + quota_backend_unregister(quota_internal_backends[i]); + } + + i_assert(array_count("a_backends) == 0); + array_free("a_backends); + +} + +static int quota_root_add_rules(struct mail_user *user, const char *root_name, + struct quota_root_settings *root_set, + const char **error_r) +{ + const char *rule_name, *rule, *error; + unsigned int i; + + rule_name = t_strconcat(root_name, "_rule", NULL); + for (i = 2;; i++) { + rule = mail_user_plugin_getenv(user, rule_name); + if (rule == NULL) + break; + + if (quota_root_add_rule(root_set, rule, &error) < 0) { + *error_r = t_strdup_printf("Invalid rule %s: %s", + rule, error); + return -1; + } + rule_name = t_strdup_printf("%s_rule%d", root_name, i); + } + return 0; +} + +static int +quota_root_add_warning_rules(struct mail_user *user, const char *root_name, + struct quota_root_settings *root_set, + const char **error_r) +{ + const char *rule_name, *rule, *error; + unsigned int i; + + rule_name = t_strconcat(root_name, "_warning", NULL); + for (i = 2;; i++) { + rule = mail_user_plugin_getenv(user, rule_name); + if (rule == NULL) + break; + + if (quota_root_add_warning_rule(root_set, rule, &error) < 0) { + *error_r = t_strdup_printf("Invalid warning rule: %s", + rule); + return -1; + } + rule_name = t_strdup_printf("%s_warning%d", root_name, i); + } + return 0; +} + +static int +quota_root_parse_set(struct mail_user *user, const char *root_name, + struct quota_root_settings *root_set, + const char **error_r) +{ + const char *name, *value; + + name = t_strconcat(root_name, "_set", NULL); + value = mail_user_plugin_getenv(user, name); + if (value == NULL) + return 0; + + if (!str_begins(value, "dict:")) { + *error_r = t_strdup_printf("%s supports only dict backend", name); + return -1; + } + root_set->limit_set = p_strdup(root_set->set->pool, value+5); + return 0; +} + +static int +quota_root_settings_init(struct quota_settings *quota_set, const char *root_def, + struct quota_root_settings **set_r, + const char **error_r) +{ + struct quota_root_settings *root_set; + const struct quota_backend *backend; + const char *p, *args, *backend_name; + + /* <backend>[:<quota root name>[:<backend args>]] */ + p = strchr(root_def, ':'); + if (p == NULL) { + backend_name = root_def; + args = NULL; + } else { + backend_name = t_strdup_until(root_def, p); + args = p + 1; + } + + backend = quota_backend_find(backend_name); + if (backend == NULL) { + *error_r = t_strdup_printf("Unknown quota backend: %s", + backend_name); + return -1; + } + + root_set = p_new(quota_set->pool, struct quota_root_settings, 1); + root_set->set = quota_set; + root_set->backend = backend; + + if (args != NULL) { + /* save root's name */ + p = strchr(args, ':'); + if (p == NULL) { + root_set->name = p_strdup(quota_set->pool, args); + args = NULL; + } else { + root_set->name = + p_strdup_until(quota_set->pool, args, p); + args = p + 1; + } + } else { + root_set->name = ""; + } + root_set->args = p_strdup(quota_set->pool, args); + + e_debug(quota_set->event, "Quota root: name=%s backend=%s args=%s", + root_set->name, backend_name, args == NULL ? "" : args); + + p_array_init(&root_set->rules, quota_set->pool, 4); + p_array_init(&root_set->warning_rules, quota_set->pool, 4); + array_push_back("a_set->root_sets, &root_set); + *set_r = root_set; + return 0; +} + +static int +quota_root_add(struct quota_settings *quota_set, struct mail_user *user, + const char *env, const char *root_name, const char **error_r) +{ + struct quota_root_settings *root_set; + const char *set_name, *value; + + if (quota_root_settings_init(quota_set, env, &root_set, error_r) < 0) + return -1; + root_set->set_name = p_strdup(quota_set->pool, root_name); + if (quota_root_add_rules(user, root_name, root_set, error_r) < 0) + return -1; + if (quota_root_add_warning_rules(user, root_name, root_set, error_r) < 0) + return -1; + if (quota_root_parse_set(user, root_name, root_set, error_r) < 0) + return -1; + + set_name = t_strconcat(root_name, "_grace", NULL); + value = mail_user_plugin_getenv(user, set_name); + if (quota_root_parse_grace(root_set, value, error_r) < 0) { + *error_r = t_strdup_printf("Invalid %s value '%s': %s", + set_name, value, *error_r); + return -1; + } + return 0; +} + +const char *quota_alloc_result_errstr(enum quota_alloc_result res, + struct quota_transaction_context *qt) +{ + switch (res) { + case QUOTA_ALLOC_RESULT_OK: + return "OK"; + case QUOTA_ALLOC_RESULT_BACKGROUND_CALC: + return "Blocked by an ongoing background quota calculation"; + case QUOTA_ALLOC_RESULT_TEMPFAIL: + return "Internal quota calculation error"; + case QUOTA_ALLOC_RESULT_OVER_MAXSIZE: + return "Mail size is larger than the maximum size allowed by " + "server configuration"; + case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT: + case QUOTA_ALLOC_RESULT_OVER_QUOTA: + return qt->quota->set->quota_exceeded_msg; + } + i_unreached(); +} + +int quota_user_read_settings(struct mail_user *user, + struct quota_settings **set_r, + const char **error_r) +{ + struct quota_settings *quota_set; + char root_name[5 + MAX_INT_STRLEN]; + const char *env, *error; + unsigned int i; + pool_t pool; + + pool = pool_alloconly_create("quota settings", 2048); + quota_set = p_new(pool, struct quota_settings, 1); + quota_set->pool = pool; + quota_set->event = event_create(user->event); + quota_set->test_alloc = quota_default_test_alloc; + quota_set->debug = user->mail_debug; + quota_set->quota_exceeded_msg = + mail_user_plugin_getenv(user, "quota_exceeded_message"); + if (quota_set->quota_exceeded_msg == NULL) + quota_set->quota_exceeded_msg = DEFAULT_QUOTA_EXCEEDED_MSG; + quota_set->vsizes = mail_user_plugin_getenv_bool(user, "quota_vsizes"); + + const char *max_size = mail_user_plugin_getenv(user, + "quota_max_mail_size"); + if (max_size != NULL) { + const char *error = NULL; + if (settings_get_size(max_size, "a_set->max_mail_size, + &error) < 0) { + *error_r = t_strdup_printf("quota_max_mail_size: %s", + error); + return -1; + } + } + + p_array_init("a_set->root_sets, pool, 4); + if (i_strocpy(root_name, "quota", sizeof(root_name)) < 0) + i_unreached(); + for (i = 2;; i++) { + env = mail_user_plugin_getenv(user, root_name); + if (env == NULL || *env == '\0') + break; + + if (quota_root_add(quota_set, user, env, root_name, + &error) < 0) { + *error_r = t_strdup_printf("Invalid quota root %s: %s", + root_name, error); + event_unref("a_set->event); + pool_unref(&pool); + return -1; + } + if (i_snprintf(root_name, sizeof(root_name), "quota%d", i) < 0) + i_unreached(); + } + if (quota_set->max_mail_size == 0 && + array_count("a_set->root_sets) == 0) { + event_unref("a_set->event); + pool_unref(&pool); + return 0; + } + + quota_set->initialized = TRUE; + *set_r = quota_set; + return 1; +} + +void quota_settings_deinit(struct quota_settings **_quota_set) +{ + struct quota_settings *quota_set = *_quota_set; + + *_quota_set = NULL; + + event_unref("a_set->event); + pool_unref("a_set->pool); +} + +static void quota_root_deinit(struct quota_root *root) +{ + pool_t pool = root->pool; + + if (root->limit_set_dict != NULL) + dict_deinit(&root->limit_set_dict); + event_unref(&root->backend.event); + root->backend.v.deinit(root); + pool_unref(&pool); +} + +int quota_root_default_init(struct quota_root *root, const char *args, + const char **error_r) +{ + const struct quota_param_parser default_params[] = { + quota_param_hidden, + quota_param_ignoreunlimited, + quota_param_noenforcing, + quota_param_ns, + {.param_name = NULL} + }; + return quota_parse_parameters(root, &args, error_r, default_params, TRUE); +} + +static int +quota_root_init(struct quota_root_settings *root_set, struct quota *quota, + struct quota_root **root_r, const char **error_r) +{ + struct quota_root *root; + + root = root_set->backend->v.alloc(); + root->pool = pool_alloconly_create("quota root", 512); + root->set = root_set; + root->quota = quota; + root->backend = *root_set->backend; + root->bytes_limit = root_set->default_rule.bytes_limit; + root->count_limit = root_set->default_rule.count_limit; + + array_create(&root->quota_module_contexts, root->pool, + sizeof(void *), 10); + + if (root->backend.v.init != NULL) { + root->backend.event = event_create(quota->event); + event_drop_parent_log_prefixes(root->backend.event, 1); + event_set_forced_debug(root->backend.event, root->quota->set->debug); + + if (root->backend.v.init(root, root_set->args, error_r) < 0) { + *error_r = t_strdup_printf("%s quota init failed: %s", + root->backend.name, *error_r); + + event_unref(&root->backend.event); + return -1; + } + } else { + if (quota_root_default_init(root, root_set->args, error_r) < 0) + return -1; + } + if (root_set->default_rule.bytes_limit == 0 && + root_set->default_rule.count_limit == 0 && + root->disable_unlimited_tracking) { + quota_root_deinit(root); + return 0; + } + *root_r = root; + return 1; +} + +int quota_init(struct quota_settings *quota_set, struct mail_user *user, + struct quota **quota_r, const char **error_r) +{ + struct quota *quota; + struct quota_root *root; + struct quota_root_settings *const *root_sets; + unsigned int i, count; + const char *error; + int ret; + + quota = i_new(struct quota, 1); + quota->event = event_create(user->event); + event_set_forced_debug(quota->event, quota_set->debug); + event_set_append_log_prefix(quota->event, "quota: "); + quota->user = user; + quota->set = quota_set; + i_array_init("a->roots, 8); + + root_sets = array_get("a_set->root_sets, &count); + i_array_init("a->namespaces, count); + for (i = 0; i < count; i++) { + ret = quota_root_init(root_sets[i], quota, &root, &error); + if (ret < 0) { + *error_r = t_strdup_printf("Quota root %s: %s", + root_sets[i]->name, error); + quota_deinit("a); + return -1; + } + if (ret > 0) + array_push_back("a->roots, &root); + } + *quota_r = quota; + return 0; +} + +void quota_deinit(struct quota **_quota) +{ + struct quota *quota = *_quota; + struct quota_root *const *roots; + unsigned int i, count; + + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) + quota_root_deinit(roots[i]); + + /* deinit quota roots before setting quser->quota=NULL */ + *_quota = NULL; + + array_free("a->roots); + array_free("a->namespaces); + event_unref("a->event); + i_free(quota); +} + +static int quota_root_get_rule_limits(struct quota_root *root, + const char *mailbox_name, + uint64_t *bytes_limit_r, + uint64_t *count_limit_r, + bool *ignored_r, + const char **error_r) +{ + struct quota_rule *rule; + int64_t bytes_limit, count_limit; + int ret; + + *ignored_r = FALSE; + + if (!root->set->force_default_rule) { + if (root->backend.v.init_limits != NULL) { + const char *error; + if (root->backend.v.init_limits(root, &error) < 0) { + *error_r = t_strdup_printf( + "Initializing limits failed for quota backend: %s", + error); + return -1; + } + } + } + + bytes_limit = root->bytes_limit; + count_limit = root->count_limit; + + /* if default rule limits are 0, user has unlimited quota. + ignore any specific quota rules */ + if (bytes_limit != 0 || count_limit != 0) { + (void)mail_namespace_find_unalias(root->quota->user->namespaces, + &mailbox_name); + rule = quota_root_rule_find(root->set, mailbox_name); + ret = 1; + } else { + rule = NULL; + ret = 0; + } + + if (rule != NULL) { + if (!rule->ignore) { + bytes_limit += rule->bytes_limit; + count_limit += rule->count_limit; + } else { + bytes_limit = 0; + count_limit = 0; + *ignored_r = TRUE; + } + } + + *bytes_limit_r = bytes_limit <= 0 ? 0 : bytes_limit; + *count_limit_r = count_limit <= 0 ? 0 : count_limit; + return ret; +} + +static bool +quota_is_duplicate_namespace(struct quota *quota, struct mail_namespace *ns) +{ + struct mail_namespace *const *namespaces; + unsigned int i, count; + const char *path, *path2; + + if (!mailbox_list_get_root_path(ns->list, + MAILBOX_LIST_PATH_TYPE_MAILBOX, &path)) + path = NULL; + + namespaces = array_get("a->namespaces, &count); + for (i = 0; i < count; i++) { + /* count namespace aliases only once. don't rely only on + alias_for != NULL, because the alias might have been + explicitly added as the wanted quota namespace. */ + if (ns->alias_for == namespaces[i] || + namespaces[i]->alias_for == ns) + continue; + + if (path != NULL && + mailbox_list_get_root_path(namespaces[i]->list, + MAILBOX_LIST_PATH_TYPE_MAILBOX, &path2) && + strcmp(path, path2) == 0) { + /* duplicate path */ + if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) == 0) + return TRUE; + + /* this is inbox=yes namespace, but the earlier one + that had the same location was inbox=no. we need to + include the INBOX also in quota calculations, so we + can't just ignore this namespace. but since we've + already called backend's namespace_added(), we can't + just remove it either. so just mark the old one as + unwanted namespace. + + an alternative would be to do a bit larger change so + namespaces wouldn't be added until + mail_namespaces_created() hook is called */ + i_assert(quota->unwanted_ns == NULL); + quota->unwanted_ns = namespaces[i]; + return FALSE; + } + } + return FALSE; +} + +void quota_add_user_namespace(struct quota *quota, struct mail_namespace *ns) +{ + struct quota_root *const *roots; + struct quota_backend **backends; + unsigned int i, j, count; + + /* first check if there already exists a namespace with the exact same + path. we don't want to count them twice. */ + if (quota_is_duplicate_namespace(quota, ns)) + return; + + array_push_back("a->namespaces, &ns); + + roots = array_get("a->roots, &count); + /* @UNSAFE: get different backends into one array */ + backends = t_new(struct quota_backend *, count + 1); + for (i = 0; i < count; i++) { + for (j = 0; backends[j] != NULL; j++) { + if (backends[j]->name == roots[i]->backend.name) + break; + } + if (backends[j] == NULL) + backends[j] = &roots[i]->backend; + } + + for (i = 0; backends[i] != NULL; i++) { + if (backends[i]->v.namespace_added != NULL) + backends[i]->v.namespace_added(quota, ns); + } +} + +void quota_remove_user_namespace(struct mail_namespace *ns) +{ + struct quota *quota; + struct mail_namespace *const *namespaces; + unsigned int i, count; + + quota = ns->owner != NULL ? + quota_get_mail_user_quota(ns->owner) : + quota_get_mail_user_quota(ns->user); + if (quota == NULL) { + /* no quota for this namespace */ + return; + } + + namespaces = array_get("a->namespaces, &count); + for (i = 0; i < count; i++) { + if (namespaces[i] == ns) { + array_delete("a->namespaces, i, 1); + break; + } + } +} + +struct quota_root_iter * +quota_root_iter_init_user(struct mail_user *user) +{ + struct quota_root_iter *iter; + + iter = i_new(struct quota_root_iter, 1); + iter->quota = quota_get_mail_user_quota(user); + return iter; +} + +struct quota_root_iter * +quota_root_iter_init(struct mailbox *box) +{ + struct quota_root_iter *iter; + struct mail_user *user; + + user = box->list->ns->owner != NULL ? + box->list->ns->owner : box->list->ns->user; + iter = quota_root_iter_init_user(user); + iter->box = box; + return iter; +} + +bool quota_root_is_namespace_visible(struct quota_root *root, + struct mail_namespace *ns) +{ + struct mailbox_list *list = ns->list; + struct mail_storage *storage; + + /* this check works as long as there is only one storage per list */ + if (mailbox_list_get_storage(&list, "", &storage) == 0 && + (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0) + return FALSE; + if (root->quota->unwanted_ns == ns) + return FALSE; + + if (root->ns_prefix != NULL) { + if (root->ns != ns) + return FALSE; + } else { + if (ns->owner == NULL) + return FALSE; + } + return TRUE; +} + +static bool +quota_root_is_visible(struct quota_root *root, struct mailbox *box) +{ + if (!quota_root_is_namespace_visible(root, box->list->ns)) + return FALSE; + if (array_count(&root->quota->roots) == 1) { + /* a single quota root: don't bother checking further */ + return TRUE; + } + return root->backend.v.match_box == NULL ? TRUE : + root->backend.v.match_box(root, box); +} + +struct quota_root *quota_root_iter_next(struct quota_root_iter *iter) +{ + struct quota_root *const *roots, *root = NULL; + unsigned int count; + + if (iter->quota == NULL) + return NULL; + + roots = array_get(&iter->quota->roots, &count); + if (iter->i >= count) + return NULL; + + for (; iter->i < count; iter->i++) { + if (iter->box != NULL && + !quota_root_is_visible(roots[iter->i], iter->box)) + continue; + + root = roots[iter->i]; + break; + } + + iter->i++; + return root; +} + +void quota_root_iter_deinit(struct quota_root_iter **_iter) +{ + struct quota_root_iter *iter = *_iter; + + *_iter = NULL; + i_free(iter); +} + +struct quota_root *quota_root_lookup(struct mail_user *user, const char *name) +{ + struct quota *quota; + struct quota_root *const *roots; + unsigned int i, count; + + quota = quota_get_mail_user_quota(user); + if (quota == NULL) + return NULL; + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) { + if (strcmp(roots[i]->set->name, name) == 0) + return roots[i]; + } + return NULL; +} + +const char *quota_root_get_name(struct quota_root *root) +{ + return root->set->name; +} + +const char *const *quota_root_get_resources(struct quota_root *root) +{ + /* if we haven't checked the quota_over_flag yet, do it now */ + quota_over_flag_check_root(root); + + return root->backend.v.get_resources(root); +} + +bool quota_root_is_hidden(struct quota_root *root) +{ + return root->hidden; +} + +enum quota_get_result +quota_get_resource(struct quota_root *root, const char *mailbox_name, + const char *name, uint64_t *value_r, uint64_t *limit_r, + const char **error_r) +{ + const char *error; + uint64_t bytes_limit, count_limit; + bool ignored, kilobytes = FALSE; + enum quota_get_result ret; + + *value_r = *limit_r = 0; + + if (strcmp(name, QUOTA_NAME_STORAGE_KILOBYTES) == 0) { + name = QUOTA_NAME_STORAGE_BYTES; + kilobytes = TRUE; + } + + /* Get the value first. This call may also update quota limits if + they're defined externally. */ + ret = root->backend.v.get_resource(root, name, value_r, &error); + if (ret == QUOTA_GET_RESULT_UNLIMITED) + i_panic("Quota backend %s returned QUOTA_GET_RESULT_UNLIMITED " + "while getting resource %s from box %s", + root->backend.name, name, mailbox_name); + else if (ret != QUOTA_GET_RESULT_LIMITED) { + *error_r = t_strdup_printf( + "quota-%s: %s", root->set->backend->name, error); + return ret; + } + + if (quota_root_get_rule_limits(root, mailbox_name, + &bytes_limit, &count_limit, + &ignored, &error) < 0) { + *error_r = t_strdup_printf( + "Failed to get quota root rule limits for mailbox %s: %s", + mailbox_name, error); + return QUOTA_GET_RESULT_INTERNAL_ERROR; + } + + if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) + *limit_r = bytes_limit; + else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) + *limit_r = count_limit; + else + *limit_r = 0; + + if (kilobytes) { + *value_r = (*value_r + 1023) / 1024; + *limit_r = (*limit_r + 1023) / 1024; + } + return *limit_r == 0 ? QUOTA_GET_RESULT_UNLIMITED : QUOTA_GET_RESULT_LIMITED; +} + +int quota_set_resource(struct quota_root *root, const char *name, + uint64_t value, const char **client_error_r) +{ + struct dict_transaction_context *trans; + const char *key, *error; + const struct dict_op_settings *set; + + if (root->set->limit_set == NULL) { + *client_error_r = MAIL_ERRSTR_NO_PERMISSION; + return -1; + } + if (strcasecmp(name, QUOTA_NAME_STORAGE_KILOBYTES) == 0) + key = "storage"; + else if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) + key = "bytes"; + else if (strcasecmp(name, QUOTA_NAME_MESSAGES) == 0) + key = "messages"; + else { + *client_error_r = t_strdup_printf( + "Unsupported resource name: %s", name); + return -1; + } + + if (root->limit_set_dict == NULL) { + struct dict_settings set; + + i_zero(&set); + set.base_dir = root->quota->user->set->base_dir; + set.event_parent = root->quota->user->event; + if (dict_init(root->set->limit_set, &set, + &root->limit_set_dict, &error) < 0) { + e_error(root->quota->event, + "dict_init() failed: %s", error); + *client_error_r = "Internal quota limit update error"; + return -1; + } + } + + set = mail_user_get_dict_op_settings(root->ns->user); + trans = dict_transaction_begin(root->limit_set_dict, set); + key = t_strdup_printf(QUOTA_LIMIT_SET_PATH"%s", key); + dict_set(trans, key, dec2str(value)); + if (dict_transaction_commit(&trans, &error) < 0) { + e_error(root->quota->event, + "dict_transaction_commit() failed: %s", error); + *client_error_r = "Internal quota limit update error"; + return -1; + } + return 0; +} + +struct quota_transaction_context *quota_transaction_begin(struct mailbox *box) +{ + struct quota_transaction_context *ctx; + struct quota_root *const *rootp; + const struct quota_rule *rule; + const char *mailbox_name; + + ctx = i_new(struct quota_transaction_context, 1); + ctx->quota = box->list->ns->owner != NULL ? + quota_get_mail_user_quota(box->list->ns->owner) : + quota_get_mail_user_quota(box->list->ns->user); + i_assert(ctx->quota != NULL); + + ctx->box = box; + ctx->bytes_ceil = (uint64_t)-1; + ctx->bytes_ceil2 = (uint64_t)-1; + ctx->count_ceil = (uint64_t)-1; + + mailbox_name = mailbox_get_vname(box); + (void)mail_namespace_find_unalias(box->storage->user->namespaces, + &mailbox_name); + + ctx->auto_updating = TRUE; + array_foreach(&ctx->quota->roots, rootp) { + if (!quota_root_is_visible(*rootp, ctx->box)) + continue; + + rule = quota_root_rule_find((*rootp)->set, mailbox_name); + if (rule != NULL && rule->ignore) { + /* This mailbox isn't included in quota. This means + it's also not included in quota_warnings, so make + sure it's fully ignored. */ + continue; + } + + /* If there are reverse quota_warnings, we'll need to track + how many bytes were expunged even with auto_updating roots. + (An alternative could be to get the current quota usage + before and after the expunges, but that's more complicated + and probably isn't any better.) */ + if (!(*rootp)->auto_updating || + (*rootp)->set->have_reverse_warnings) + ctx->auto_updating = FALSE; + } + + if (box->storage->user->dsyncing) { + /* ignore quota for dsync */ + ctx->limits_set = TRUE; + } + return ctx; +} + +int quota_transaction_set_limits(struct quota_transaction_context *ctx, + enum quota_get_result *error_result_r, + const char **error_r) +{ + struct quota_root *const *roots; + const char *mailbox_name, *error; + unsigned int i, count; + uint64_t bytes_limit, count_limit, current, limit, diff; + bool use_grace, ignored; + enum quota_get_result ret; + + if (ctx->limits_set) + return 0; + ctx->limits_set = TRUE; + mailbox_name = mailbox_get_vname(ctx->box); + /* use quota_grace only for LDA/LMTP */ + use_grace = (ctx->box->flags & MAILBOX_FLAG_POST_SESSION) != 0; + ctx->no_quota_updates = TRUE; + + /* find the lowest quota limits from all roots and use them */ + roots = array_get(&ctx->quota->roots, &count); + for (i = 0; i < count; i++) { + /* make sure variables get initialized */ + bytes_limit = count_limit = 0; + if (!quota_root_is_visible(roots[i], ctx->box)) + continue; + else if (roots[i]->no_enforcing) { + ignored = FALSE; + } else if (quota_root_get_rule_limits(roots[i], mailbox_name, + &bytes_limit, &count_limit, + &ignored, &error) < 0) { + ctx->failed = TRUE; + *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR; + *error_r = t_strdup_printf( + "Failed to get quota root rule limits for %s: %s", + mailbox_name, error); + return -1; + } + if (!ignored) + ctx->no_quota_updates = FALSE; + + if (bytes_limit > 0) { + ret = quota_get_resource(roots[i], mailbox_name, + QUOTA_NAME_STORAGE_BYTES, + ¤t, &limit, &error); + if (ret == QUOTA_GET_RESULT_LIMITED) { + if (limit <= current) { + /* over quota */ + ctx->bytes_ceil = 0; + ctx->bytes_ceil2 = 0; + diff = current - limit; + if (ctx->bytes_over < diff) + ctx->bytes_over = diff; + } else { + diff = limit - current; + if (ctx->bytes_ceil2 > diff) + ctx->bytes_ceil2 = diff; + diff += !use_grace ? 0 : + roots[i]->set->last_mail_max_extra_bytes; + if (ctx->bytes_ceil > diff) + ctx->bytes_ceil = diff; + } + } else if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) { + ctx->failed = TRUE; + *error_result_r = ret; + *error_r = t_strdup_printf( + "Failed to get quota resource " + QUOTA_NAME_STORAGE_BYTES" for %s: %s", + mailbox_name, error); + return -1; + } + } + + if (count_limit > 0) { + ret = quota_get_resource(roots[i], mailbox_name, + QUOTA_NAME_MESSAGES, + ¤t, &limit, &error); + if (ret == QUOTA_GET_RESULT_LIMITED) { + if (limit <= current) { + /* over quota */ + ctx->count_ceil = 0; + diff = current - limit; + if (ctx->count_over < diff) + ctx->count_over = diff; + } else { + diff = limit - current; + if (ctx->count_ceil > diff) + ctx->count_ceil = diff; + } + } else if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) { + ctx->failed = TRUE; + *error_result_r = ret; + *error_r = t_strdup_printf( + "Failed to get quota resource " + QUOTA_NAME_MESSAGES" for %s: %s", + mailbox_name, error); + return -1; + } + } + } + return 0; +} + +static void quota_warning_execute(struct quota_root *root, const char *cmd, + const char *last_arg, const char *reason) +{ + const char *socket_path, *const *args, *error, *scheme, *ptr; + + struct program_client_settings set = { + .client_connect_timeout_msecs = 1000, + .debug = root->quota->user->mail_debug, + }; + struct program_client *pc; + + restrict_access_init(&set.restrict_set); + + e_debug(root->quota->event, "Executing warning: %s (because %s)", cmd, reason); + + args = t_strsplit_spaces(cmd, " "); + if (last_arg != NULL) { + unsigned int count = str_array_length(args); + const char **new_args = t_new(const char *, count + 2); + + memcpy(new_args, args, sizeof(const char *) * count); + new_args[count] = last_arg; + args = new_args; + } + socket_path = args[0]; + + if ((ptr = strchr(socket_path, ':')) != NULL) { + scheme = t_strcut(socket_path, ':'); + socket_path = ptr+1; + } else { + scheme = "unix"; + } + + if (*socket_path != '/' && + strcmp(scheme, "unix") == 0) + socket_path = + t_strconcat(root->quota->user->set->base_dir, + "/", socket_path, NULL); + + socket_path = t_strdup_printf("%s:%s", scheme, socket_path); + + args++; + + if (program_client_create(socket_path, args, &set, TRUE, + &pc, &error) < 0) { + e_error(root->quota->event, + "program_client_create(%s) failed: %s", socket_path, + error); + return; + } + + (void)program_client_run(pc); + + program_client_destroy(&pc); +} + +static void quota_warnings_execute(struct quota_transaction_context *ctx, + struct quota_root *root) +{ + struct quota_warning_rule *warnings; + unsigned int i, count; + uint64_t bytes_current, bytes_before, bytes_limit; + uint64_t count_current, count_before, count_limit; + const char *reason, *error; + + warnings = array_get_modifiable(&root->set->warning_rules, &count); + if (count == 0) + return; + + if (quota_get_resource(root, "", QUOTA_NAME_STORAGE_BYTES, + &bytes_current, &bytes_limit, &error) == QUOTA_GET_RESULT_INTERNAL_ERROR) { + e_error(root->quota->event, + "Failed to get quota resource "QUOTA_NAME_STORAGE_BYTES + ": %s", error); + return; + } + if (quota_get_resource(root, "", QUOTA_NAME_MESSAGES, + &count_current, &count_limit, &error) == QUOTA_GET_RESULT_INTERNAL_ERROR) { + e_error(root->quota->event, + "Failed to get quota resource "QUOTA_NAME_MESSAGES + ": %s", error); + return; + } + + if (ctx->bytes_used > 0 && bytes_current < (uint64_t)ctx->bytes_used) + bytes_before = 0; + else + bytes_before = (int64_t)bytes_current - ctx->bytes_used; + + if (ctx->count_used > 0 && count_current < (uint64_t)ctx->count_used) + count_before = 0; + else + count_before = (int64_t)count_current - ctx->count_used; + for (i = 0; i < count; i++) { + if (quota_warning_match(&warnings[i], + bytes_before, bytes_current, + count_before, count_current, + &reason)) { + quota_warning_execute(root, warnings[i].command, + NULL, reason); + break; + } + } +} + +int quota_transaction_commit(struct quota_transaction_context **_ctx) +{ + struct quota_transaction_context *ctx = *_ctx; + struct quota_rule *rule; + struct quota_root *const *roots; + unsigned int i, count; + const char *mailbox_name; + int ret = 0; + + *_ctx = NULL; + + if (ctx->failed) + ret = -1; + else if (ctx->bytes_used != 0 || ctx->count_used != 0 || + ctx->recalculate != QUOTA_RECALCULATE_DONT) T_BEGIN { + ARRAY(struct quota_root *) warn_roots; + + mailbox_name = mailbox_get_vname(ctx->box); + (void)mail_namespace_find_unalias( + ctx->box->storage->user->namespaces, &mailbox_name); + + roots = array_get(&ctx->quota->roots, &count); + t_array_init(&warn_roots, count); + for (i = 0; i < count; i++) { + if (!quota_root_is_visible(roots[i], ctx->box)) + continue; + + rule = quota_root_rule_find(roots[i]->set, + mailbox_name); + if (rule != NULL && rule->ignore) { + /* mailbox not included in quota */ + continue; + } + + const char *error; + if (roots[i]->backend.v.update(roots[i], ctx, &error) < 0) { + e_error(ctx->quota->event, + "Failed to update quota for %s: %s", + mailbox_name, error); + ret = -1; + } + else if (!ctx->sync_transaction) + array_push_back(&warn_roots, &roots[i]); + } + /* execute quota warnings after all updates. this makes it + work correctly regardless of whether backend.get_resource() + returns updated values before backend.update() or not. + warnings aren't executed when dsync bring the user over, + because the user probably already got the warning on the + other replica. */ + array_foreach(&warn_roots, roots) + quota_warnings_execute(ctx, *roots); + } T_END; + + i_free(ctx); + return ret; +} + +static bool quota_over_flag_init_root(struct quota_root *root, + const char **quota_over_script_r, + const char **quota_over_flag_r, + bool *status_r) +{ + const char *name, *flag_mask; + + *quota_over_flag_r = NULL; + *status_r = FALSE; + + name = t_strconcat(root->set->set_name, "_over_script", NULL); + *quota_over_script_r = mail_user_plugin_getenv(root->quota->user, name); + if (*quota_over_script_r == NULL) { + e_debug(root->quota->event, "quota_over_flag check: " + "%s unset - skipping", name); + return FALSE; + } + + /* e.g.: quota_over_flag_value=TRUE or quota_over_flag_value=* */ + name = t_strconcat(root->set->set_name, "_over_flag_value", NULL); + flag_mask = mail_user_plugin_getenv(root->quota->user, name); + if (flag_mask == NULL) { + e_debug(root->quota->event, "quota_over_flag check: " + "%s unset - skipping", name); + return FALSE; + } + + /* compare quota_over_flag's value (that comes from userdb) to + quota_over_flag_value and save the result. */ + name = t_strconcat(root->set->set_name, "_over_flag", NULL); + *quota_over_flag_r = mail_user_plugin_getenv(root->quota->user, name); + *status_r = *quota_over_flag_r != NULL && + wildcard_match_icase(*quota_over_flag_r, flag_mask); + return TRUE; +} + +static void quota_over_flag_check_root(struct quota_root *root) +{ + const char *quota_over_script, *quota_over_flag, *error; + const char *const *resources; + unsigned int i; + uint64_t value, limit; + bool cur_overquota = FALSE; + bool quota_over_status; + enum quota_get_result ret; + + if (root->quota_over_flag_checked) + return; + if (root->quota->user->session_create_time + + QUOTA_OVER_FLAG_MAX_DELAY_SECS < ioloop_time) { + /* userdb's quota_over_flag lookup is too old. */ + e_debug(root->quota->event, "quota_over_flag check: " + "Flag lookup time is too old - skipping"); + return; + } + if (root->quota->user->session_restored) { + /* we don't know whether the quota_over_script was executed + before hibernation. just assume that it was, so we don't + unnecessarily call it too often. */ + e_debug(root->quota->event, "quota_over_flag check: " + "Session was already hibernated - skipping"); + return; + } + root->quota_over_flag_checked = TRUE; + if (!quota_over_flag_init_root(root, "a_over_script, + "a_over_flag, "a_over_status)) + return; + + resources = quota_root_get_resources(root); + for (i = 0; resources[i] != NULL; i++) { + ret = quota_get_resource(root, "", resources[i], &value, + &limit, &error); + if (ret == QUOTA_GET_RESULT_INTERNAL_ERROR) { + /* can't reliably verify this */ + e_error(root->quota->event, "Quota %s lookup failed -" + "can't verify quota_over_flag: %s", + resources[i], error); + return; + } + e_debug(root->quota->event, "quota_over_flag check: %s ret=%d" + "value=%"PRIu64" limit=%"PRIu64, resources[i], ret, + value, limit); + if (ret == QUOTA_GET_RESULT_LIMITED && value >= limit) + cur_overquota = TRUE; + } + e_debug(root->quota->event, "quota_over_flag=%d(%s) vs currently overquota=%d", + quota_over_status ? 1 : 0, + quota_over_flag == NULL ? "(null)" : quota_over_flag, + cur_overquota ? 1 : 0); + if (cur_overquota != quota_over_status) { + quota_warning_execute(root, quota_over_script, quota_over_flag, + "quota_over_flag mismatch"); + } +} + +void quota_over_flag_check_startup(struct quota *quota) +{ + struct quota_root *const *roots; + unsigned int i, count; + const char *name; + + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) { + name = t_strconcat(roots[i]->set->set_name, "_over_flag_lazy_check", NULL); + if (!mail_user_plugin_getenv_bool(roots[i]->quota->user, name)) + quota_over_flag_check_root(roots[i]); + } +} + +void quota_transaction_rollback(struct quota_transaction_context **_ctx) +{ + struct quota_transaction_context *ctx = *_ctx; + + *_ctx = NULL; + i_free(ctx); +} + +static int quota_get_mail_size(struct quota_transaction_context *ctx, + struct mail *mail, uoff_t *size_r) +{ + if (ctx->quota->set->vsizes) + return mail_get_virtual_size(mail, size_r); + else + return mail_get_physical_size(mail, size_r); +} + +enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx, + struct mail *mail, const char **error_r) +{ + uoff_t size; + const char *error; + enum quota_get_result error_res; + + if (quota_transaction_set_limits(ctx, &error_res, error_r) < 0) { + if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC) + return QUOTA_ALLOC_RESULT_BACKGROUND_CALC; + return QUOTA_ALLOC_RESULT_TEMPFAIL; + } + + if (ctx->no_quota_updates) + return QUOTA_ALLOC_RESULT_OK; + + if (quota_get_mail_size(ctx, mail, &size) < 0) { + enum mail_error err; + error = mailbox_get_last_internal_error(mail->box, &err); + + if (err == MAIL_ERROR_EXPUNGED) { + /* mail being copied was already expunged. it'll fail, + so just return success for the quota allocated. */ + return QUOTA_ALLOC_RESULT_OK; + } + *error_r = t_strdup_printf( + "Failed to get mail size (box=%s, uid=%u): %s", + mail->box->vname, mail->uid, error); + return QUOTA_ALLOC_RESULT_TEMPFAIL; + } + + enum quota_alloc_result ret = quota_test_alloc(ctx, size, error_r); + if (ret != QUOTA_ALLOC_RESULT_OK) + return ret; + /* with quota_try_alloc() we want to keep track of how many bytes + we've been adding/removing, so disable auto_updating=TRUE + optimization. this of course doesn't work perfectly if + quota_alloc() or quota_free_bytes() was already used within the same + transaction, but that doesn't normally happen. */ + ctx->auto_updating = FALSE; + quota_alloc(ctx, mail); + return QUOTA_ALLOC_RESULT_OK; +} + +enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx, + uoff_t size, const char **error_r) +{ + if (ctx->failed) { + *error_r = "Quota transaction has failed earlier"; + return QUOTA_ALLOC_RESULT_TEMPFAIL; + } + + enum quota_get_result error_res; + if (quota_transaction_set_limits(ctx, &error_res, error_r) < 0) { + if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC) + return QUOTA_ALLOC_RESULT_BACKGROUND_CALC; + return QUOTA_ALLOC_RESULT_TEMPFAIL; + } + + uoff_t max_size = ctx->quota->set->max_mail_size; + if (max_size > 0 && size > max_size) { + *error_r = t_strdup_printf( + "Requested allocation size %"PRIuUOFF_T" exceeds max " + "mail size %"PRIuUOFF_T, size, max_size); + return QUOTA_ALLOC_RESULT_OVER_MAXSIZE; + } + + if (ctx->no_quota_updates) + return QUOTA_ALLOC_RESULT_OK; + /* this is a virtual function mainly for trash plugin and similar, + which may automatically delete mails to stay under quota. */ + return ctx->quota->set->test_alloc(ctx, size, error_r); +} + +static enum quota_alloc_result quota_default_test_alloc( + struct quota_transaction_context *ctx, uoff_t size, + const char **error_r) +{ + struct quota_root *const *roots; + unsigned int i, count; + bool ignore; + int ret; + + if (!quota_transaction_is_over(ctx, size)) + return QUOTA_ALLOC_RESULT_OK; + + /* limit reached. */ + roots = array_get(&ctx->quota->roots, &count); + for (i = 0; i < count; i++) { + uint64_t bytes_limit, count_limit; + + if (!quota_root_is_visible(roots[i], ctx->box) || + roots[i]->no_enforcing) + continue; + + const char *error; + ret = quota_root_get_rule_limits(roots[i], + mailbox_get_vname(ctx->box), + &bytes_limit, &count_limit, + &ignore, &error); + if (ret < 0) { + *error_r = t_strdup_printf( + "Failed to get quota root rule limits: %s", + error); + return QUOTA_ALLOC_RESULT_TEMPFAIL; + } + + /* if size is bigger than any limit, then + it is bigger than the lowest limit */ + if (bytes_limit > 0 && size > bytes_limit) { + *error_r = t_strdup_printf( + "Allocating %"PRIuUOFF_T" bytes would exceed quota limit", + size); + return QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT; + } + } + *error_r = t_strdup_printf( + "Allocating %"PRIuUOFF_T" bytes would exceed quota", size); + return QUOTA_ALLOC_RESULT_OVER_QUOTA; +} + +void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail) +{ + uoff_t size; + + if (!ctx->auto_updating) { + if (quota_get_mail_size(ctx, mail, &size) == 0) + ctx->bytes_used += size; + } + + ctx->bytes_ceil = ctx->bytes_ceil2; + ctx->count_used++; +} + +void quota_free_bytes(struct quota_transaction_context *ctx, + uoff_t physical_size) +{ + i_assert(physical_size <= INT64_MAX); + ctx->bytes_used -= (int64_t)physical_size; + ctx->count_used--; +} + +void quota_recalculate(struct quota_transaction_context *ctx, + enum quota_recalculate recalculate) +{ + ctx->recalculate = recalculate; +} + +static void hidden_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + _root->hidden = TRUE; +} + +static void ignoreunlim_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + _root->disable_unlimited_tracking = TRUE; +} + +static void noenforcing_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + _root->no_enforcing = TRUE; +} + +static void ns_param_handler(struct quota_root *_root, const char *param_value) +{ + _root->ns_prefix = p_strdup(_root->pool, param_value); +} + +int quota_parse_parameters(struct quota_root *root, const char **args, const char **error_r, + const struct quota_param_parser *valid_params, bool fail_on_unknown) +{ + const char *tmp_param_name, *tmp_param_val; + size_t tmp_param_len; + + while (*args != NULL && (*args)[0] != '\0') { + for (; valid_params->param_name != NULL; ++valid_params) { + tmp_param_name = valid_params->param_name; + tmp_param_len = strlen(valid_params->param_name); + i_assert(*args != NULL); + if (strncmp(*args, tmp_param_name, tmp_param_len) == 0) { + tmp_param_val = NULL; + *args += tmp_param_len; + if (tmp_param_name[tmp_param_len - 1] == '=') { + const char *next_colon = strchr(*args, ':'); + tmp_param_val = (next_colon == NULL)? + t_strdup(*args): + t_strdup_until(*args, next_colon); + *args = (next_colon == NULL) ? NULL : next_colon + 1; + } + else if ((*args)[0] == '\0' || + (*args)[0] == ':') { + *args = ((*args)[0] == ':') ? *args + 1 : NULL; + /* in case parameter is a boolean second parameter + * string parameter value will be ignored by param_handler + * we just need some non-NULL value + * to indicate that argument is to be processed */ + tmp_param_val = ""; + } + if (tmp_param_val != NULL) { + valid_params->param_handler(root, tmp_param_val); + break; + } + } + } + if (valid_params->param_name == NULL) { + if (fail_on_unknown) { + *error_r = t_strdup_printf( + "Unknown parameter for backend %s: %s", + root->backend.name, *args); + return -1; + } + else { + break; + } + } + } + return 0; +} diff --git a/src/plugins/quota/quota.h b/src/plugins/quota/quota.h new file mode 100644 index 0000000..8de2d8e --- /dev/null +++ b/src/plugins/quota/quota.h @@ -0,0 +1,147 @@ +#ifndef QUOTA_H +#define QUOTA_H + +struct mail; +struct mailbox; +struct mail_user; + +/* Message storage size kilobytes. */ +#define QUOTA_NAME_STORAGE_KILOBYTES "STORAGE" +/* Message storage size bytes. This is used only internally. */ +#define QUOTA_NAME_STORAGE_BYTES "STORAGE_BYTES" +/* Number of messages. */ +#define QUOTA_NAME_MESSAGES "MESSAGE" + +struct quota; +struct quota_settings; +struct quota_root_settings; +struct quota_root; +struct quota_root_iter; +struct quota_transaction_context; + +struct quota_param_parser { + char *param_name; + void (* param_handler)(struct quota_root *_root, const char *param_value); +}; + +extern struct quota_param_parser quota_param_hidden; +extern struct quota_param_parser quota_param_ignoreunlimited; +extern struct quota_param_parser quota_param_noenforcing; +extern struct quota_param_parser quota_param_ns; + +enum quota_recalculate { + QUOTA_RECALCULATE_DONT = 0, + /* We may want to recalculate quota because we weren't able to call + quota_free*() correctly for all mails. Quota needs to be + recalculated unless the backend does the quota tracking + internally. */ + QUOTA_RECALCULATE_MISSING_FREES, + /* doveadm quota recalc called - make sure the quota is correct */ + QUOTA_RECALCULATE_FORCED +}; + +enum quota_alloc_result { + QUOTA_ALLOC_RESULT_OK, + QUOTA_ALLOC_RESULT_TEMPFAIL, + QUOTA_ALLOC_RESULT_OVER_MAXSIZE, + QUOTA_ALLOC_RESULT_OVER_QUOTA, + /* Mail size is larger than even the maximum allowed quota. */ + QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT, + /* Blocked by ongoing background quota calculation. */ + QUOTA_ALLOC_RESULT_BACKGROUND_CALC, +}; + +/* Anything <= QUOTA_GET_RESULT_INTERNAL_ERROR is an error. */ +enum quota_get_result { + /* Ongoing background quota calculation */ + QUOTA_GET_RESULT_BACKGROUND_CALC, + /* Quota resource name doesn't exist */ + QUOTA_GET_RESULT_UNKNOWN_RESOURCE, + /* Internal error */ + QUOTA_GET_RESULT_INTERNAL_ERROR, + + /* Quota limit exists and was returned successfully */ + QUOTA_GET_RESULT_LIMITED, + /* Quota is unlimited, but its value was returned */ + QUOTA_GET_RESULT_UNLIMITED, +}; + +const char *quota_alloc_result_errstr(enum quota_alloc_result res, + struct quota_transaction_context *qt); + +int quota_user_read_settings(struct mail_user *user, + struct quota_settings **set_r, + const char **error_r); +void quota_settings_deinit(struct quota_settings **quota_set); + +/* Add a new rule too the quota root. Returns 0 if ok, -1 if rule is invalid. */ +int quota_root_add_rule(struct quota_root_settings *root_set, + const char *rule_def, const char **error_r); +/* Add a new warning rule for the quota root. Returns 0 if ok, -1 if rule is + invalid. */ +int quota_root_add_warning_rule(struct quota_root_settings *root_set, + const char *rule_def, const char **error_r); + +/* Initialize quota for the given user. Returns 0 and quota_r on success, + -1 and error_r on failure. */ +int quota_init(struct quota_settings *quota_set, struct mail_user *user, + struct quota **quota_r, const char **error_r); +void quota_deinit(struct quota **quota); + +/* List all visible quota roots. They don't need to be freed. */ +struct quota_root_iter *quota_root_iter_init_user(struct mail_user *user); +struct quota_root_iter *quota_root_iter_init(struct mailbox *box); +struct quota_root *quota_root_iter_next(struct quota_root_iter *iter); +void quota_root_iter_deinit(struct quota_root_iter **iter); + +/* Return quota root or NULL. */ +struct quota_root *quota_root_lookup(struct mail_user *user, const char *name); + +/* Returns name of the quota root. */ +const char *quota_root_get_name(struct quota_root *root); +/* Return a list of all resources set for the quota root. */ +const char *const *quota_root_get_resources(struct quota_root *root); +/* Returns TRUE if quota root is marked as hidden (so it shouldn't be visible + to users via IMAP GETQUOTAROOT command). */ +bool quota_root_is_hidden(struct quota_root *root); + +/* Returns 1 if values were successfully returned, 0 if resource name doesn't + exist or isn't enabled, -1 if error. */ +enum quota_get_result +quota_get_resource(struct quota_root *root, const char *mailbox_name, + const char *name, uint64_t *value_r, uint64_t *limit_r, + const char **error_r); +/* Returns 0 if OK, -1 if error (eg. permission denied, invalid name). */ +int quota_set_resource(struct quota_root *root, const char *name, + uint64_t value, const char **client_error_r); + +/* Start a new quota transaction. */ +struct quota_transaction_context *quota_transaction_begin(struct mailbox *box); +/* Commit quota transaction. Returns 0 if ok, -1 if failed. */ +int quota_transaction_commit(struct quota_transaction_context **ctx); +/* Rollback quota transaction changes. */ +void quota_transaction_rollback(struct quota_transaction_context **ctx); + +/* Allocate from quota if there's space. error_r is set when result is not + * QUOTA_ALLOC_RESULT_OK. */ +enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx, + struct mail *mail, const char **error_r); +/* Like quota_try_alloc(), but don't actually allocate anything. */ +enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx, + uoff_t size, const char **error_r); +/* Update quota by allocating/freeing space used by mail. */ +void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail); +void quota_free_bytes(struct quota_transaction_context *ctx, + uoff_t physical_size); +/* Mark the quota to be recalculated */ +void quota_recalculate(struct quota_transaction_context *ctx, + enum quota_recalculate recalculate); + +/* Execute quota_over_scripts if needed. */ +void quota_over_flag_check_startup(struct quota *quota); + +/* Common quota parameters parsing loop */ +int quota_parse_parameters(struct quota_root *root, const char **args, const char **error_r, + const struct quota_param_parser *valid_params, bool fail_on_unknown); + +#endif diff --git a/src/plugins/quota/rquota.x b/src/plugins/quota/rquota.x new file mode 100644 index 0000000..3cd5c10 --- /dev/null +++ b/src/plugins/quota/rquota.x @@ -0,0 +1,139 @@ +/* @(#)rquota.x 2.1 88/08/01 4.0 RPCSRC */ +/* @(#)rquota.x 1.2 87/09/20 Copyr 1987 Sun Micro */ + +/* + * Remote quota protocol + * Requires unix authentication + */ + +const RQ_PATHLEN = 1024; + +struct sq_dqblk { + unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */ + unsigned int rq_bsoftlimit; /* preferred limit on disk blks */ + unsigned int rq_curblocks; /* current block count */ + unsigned int rq_fhardlimit; /* absolute limit on allocated files */ + unsigned int rq_fsoftlimit; /* preferred file limit */ + unsigned int rq_curfiles; /* current # allocated files */ + unsigned int rq_btimeleft; /* time left for excessive disk use */ + unsigned int rq_ftimeleft; /* time left for excessive files */ +}; + +struct getquota_args { + string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */ + int gqa_uid; /* Inquire about quota for uid */ +}; + +struct setquota_args { + int sqa_qcmd; + string sqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */ + int sqa_id; /* Set quota for uid */ + sq_dqblk sqa_dqblk; +}; + +struct ext_getquota_args { + string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */ + int gqa_type; /* Type of quota info is needed about */ + int gqa_id; /* Inquire about quota for id */ +}; + +struct ext_setquota_args { + int sqa_qcmd; + string sqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */ + int sqa_id; /* Set quota for id */ + int sqa_type; /* Type of quota to set */ + sq_dqblk sqa_dqblk; +}; + +/* + * remote quota structure + */ +struct rquota { + int rq_bsize; /* block size for block counts */ + bool rq_active; /* indicates whether quota is active */ + unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */ + unsigned int rq_bsoftlimit; /* preferred limit on disk blks */ + unsigned int rq_curblocks; /* current block count */ + unsigned int rq_fhardlimit; /* absolute limit on allocated files */ + unsigned int rq_fsoftlimit; /* preferred file limit */ + unsigned int rq_curfiles; /* current # allocated files */ + unsigned int rq_btimeleft; /* time left for excessive disk use */ + unsigned int rq_ftimeleft; /* time left for excessive files */ +}; + +enum qr_status { + Q_OK = 1, /* quota returned */ + Q_NOQUOTA = 2, /* noquota for uid */ + Q_EPERM = 3 /* no permission to access quota */ +}; + +union getquota_rslt switch (qr_status status) { +case Q_OK: + rquota gqr_rquota; /* valid if status == Q_OK */ +case Q_NOQUOTA: + void; +case Q_EPERM: + void; +}; + +union setquota_rslt switch (qr_status status) { +case Q_OK: + rquota sqr_rquota; /* valid if status == Q_OK */ +case Q_NOQUOTA: + void; +case Q_EPERM: + void; +}; + +program RQUOTAPROG { + version RQUOTAVERS { + /* + * Get all quotas + */ + getquota_rslt + RQUOTAPROC_GETQUOTA(getquota_args) = 1; + + /* + * Get active quotas only + */ + getquota_rslt + RQUOTAPROC_GETACTIVEQUOTA(getquota_args) = 2; + + /* + * Set all quotas + */ + setquota_rslt + RQUOTAPROC_SETQUOTA(setquota_args) = 3; + + /* + * Get active quotas only + */ + setquota_rslt + RQUOTAPROC_SETACTIVEQUOTA(setquota_args) = 4; + } = 1; + version EXT_RQUOTAVERS { + /* + * Get all quotas + */ + getquota_rslt + RQUOTAPROC_GETQUOTA(ext_getquota_args) = 1; + + /* + * Get active quotas only + */ + getquota_rslt + RQUOTAPROC_GETACTIVEQUOTA(ext_getquota_args) = 2; + + /* + * Set all quotas + */ + setquota_rslt + RQUOTAPROC_SETQUOTA(ext_setquota_args) = 3; + + /* + * Set active quotas only + */ + setquota_rslt + RQUOTAPROC_SETACTIVEQUOTA(ext_setquota_args) = 4; + } = 2; +} = 100011; diff --git a/src/plugins/quota/test-quota-util.c b/src/plugins/quota/test-quota-util.c new file mode 100644 index 0000000..682cffe --- /dev/null +++ b/src/plugins/quota/test-quota-util.c @@ -0,0 +1,96 @@ +/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "quota-private.h" +#include "test-common.h" + +struct test { + uint64_t limit, initial_size; + int64_t transaction_diff; + uint64_t new_size; + bool is_over; +}; + +static void test_quota_transaction_is_over(void) +{ +#define MAXU64 (uint64_t)-1 +#define MAXS64 9223372036854775807LL +#define MINS64 (-MAXS64 - 1LL) + static const struct test tests[] = { + /* first test only with new_size=1. these are used for both + count and bytes tests: */ + + /* limit, init, diff, new */ + { 1, 0, 0, 1, FALSE }, + { MAXU64, MAXU64, 0, 1, TRUE }, + { MAXU64, MAXU64-1, 0, 1, FALSE }, + { MAXU64, MAXU64-1, 1, 1, TRUE }, + { MAXU64-1, MAXU64-1, 0, 1, TRUE }, + { MAXU64-1, MAXU64-1, -1, 1, FALSE }, + { MAXU64-2, MAXU64-1, -1, 1, TRUE }, + { MAXU64-2, MAXU64-1, -2, 1, FALSE }, + + /* these are for bytes tests: */ + + /* limit, init, diff, new */ + { MAXU64, MAXU64, 0, 0, FALSE }, + { MAXU64, MAXU64-1, 1, 0, FALSE }, + { MAXU64-1, MAXU64, 1, 0, TRUE }, + { MAXU64-1, MAXU64, 0, 0, TRUE }, + { MAXU64-1, MAXU64, -1, 0, FALSE }, + { MAXU64, MAXU64, 0, 1, TRUE }, + { MAXU64, 0, 0, MAXU64, FALSE }, + { MAXU64, 1, 0, MAXU64, TRUE }, + { MAXU64, 0, 1, MAXU64, TRUE }, + { MAXU64-1, 0, 0, MAXU64, TRUE }, + { MAXU64-1, 0, 0, MAXU64-1, FALSE }, + { MAXU64-1, 1, 0, MAXU64-1, TRUE }, + { MAXU64-1, 1, -1, MAXU64-1, FALSE }, + { MAXU64, MAXU64, 0, MAXU64, TRUE }, + }; + struct quota_transaction_context ctx; + unsigned int i; + + test_begin("quota transaction is over (count)"); + for (i = 0; i < N_ELEMENTS(tests); i++) { + if (tests[i].new_size != 1) + continue; + + i_zero(&ctx); + ctx.count_used = tests[i].transaction_diff; + if (tests[i].initial_size > tests[i].limit) + ctx.count_over = tests[i].initial_size - tests[i].limit; + else { + ctx.count_ceil = tests[i].limit - tests[i].initial_size; + i_assert(ctx.count_used < 0 || + (uint64_t)ctx.count_used <= ctx.count_ceil); /* test is broken otherwise */ + } + test_assert_idx(quota_transaction_is_over(&ctx, 0) == tests[i].is_over, i); + } + test_end(); + + test_begin("quota transaction is over (bytes)"); + for (i = 0; i < N_ELEMENTS(tests); i++) { + i_zero(&ctx); + ctx.count_ceil = 1; + ctx.bytes_used = tests[i].transaction_diff; + if (tests[i].initial_size > tests[i].limit) + ctx.bytes_over = tests[i].initial_size - tests[i].limit; + else { + ctx.bytes_ceil = tests[i].limit - tests[i].initial_size; + i_assert(ctx.bytes_used < 0 || + (uint64_t)ctx.bytes_used <= ctx.bytes_ceil); /* test is broken otherwise */ + } + test_assert_idx(quota_transaction_is_over(&ctx, tests[i].new_size) == tests[i].is_over, i); + } + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_quota_transaction_is_over, + NULL + }; + return test_run(test_functions); +} diff --git a/src/plugins/replication/Makefile.am b/src/plugins/replication/Makefile.am new file mode 100644 index 0000000..461935c --- /dev/null +++ b/src/plugins/replication/Makefile.am @@ -0,0 +1,25 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/replication \ + -I$(top_srcdir)/src/plugins/notify + +NOPLUGIN_LDFLAGS = +lib20_replication_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_replication_plugin.la + +if DOVECOT_PLUGIN_DEPS +lib20_replication_plugin_la_LIBADD = \ + ../notify/lib15_notify_plugin.la +endif + +lib20_replication_plugin_la_SOURCES = \ + replication-plugin.c + +noinst_HEADERS = \ + replication-plugin.h diff --git a/src/plugins/replication/Makefile.in b/src/plugins/replication/Makefile.in new file mode 100644 index 0000000..34a0ce3 --- /dev/null +++ b/src/plugins/replication/Makefile.in @@ -0,0 +1,827 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/replication +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +@DOVECOT_PLUGIN_DEPS_TRUE@lib20_replication_plugin_la_DEPENDENCIES = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../notify/lib15_notify_plugin.la +am_lib20_replication_plugin_la_OBJECTS = replication-plugin.lo +lib20_replication_plugin_la_OBJECTS = \ + $(am_lib20_replication_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_replication_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_replication_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/replication-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_replication_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_replication_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/replication \ + -I$(top_srcdir)/src/plugins/notify + +lib20_replication_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_replication_plugin.la + +@DOVECOT_PLUGIN_DEPS_TRUE@lib20_replication_plugin_la_LIBADD = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../notify/lib15_notify_plugin.la + +lib20_replication_plugin_la_SOURCES = \ + replication-plugin.c + +noinst_HEADERS = \ + replication-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/replication/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/replication/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_replication_plugin.la: $(lib20_replication_plugin_la_OBJECTS) $(lib20_replication_plugin_la_DEPENDENCIES) $(EXTRA_lib20_replication_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_replication_plugin_la_LINK) -rpath $(moduledir) $(lib20_replication_plugin_la_OBJECTS) $(lib20_replication_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replication-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/replication-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/replication-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/replication/replication-plugin.c b/src/plugins/replication/replication-plugin.c new file mode 100644 index 0000000..9b4bb08 --- /dev/null +++ b/src/plugins/replication/replication-plugin.c @@ -0,0 +1,404 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strescape.h" +#include "ioloop.h" +#include "net.h" +#include "write-full.h" +#include "mail-user.h" +#include "mail-namespace.h" +#include "mail-storage-private.h" +#include "notify-plugin.h" +#include "replication-common.h" +#include "replication-plugin.h" + + +#define REPLICATION_SOCKET_NAME "replication-notify" +#define REPLICATION_FIFO_NAME "replication-notify-fifo" +#define REPLICATION_NOTIFY_DELAY_MSECS 500 +#define REPLICATION_SYNC_TIMEOUT_SECS 10 + +#define REPLICATION_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, replication_user_module) + +struct replication_user { + union mail_user_module_context module_ctx; + + const char *socket_path; + + struct timeout *to; + enum replication_priority priority; + unsigned int sync_secs; +}; + +struct replication_mail_txn_context { + struct mail_namespace *ns; + bool new_messages; + bool sync_trans; + char *reason; +}; + +static MODULE_CONTEXT_DEFINE_INIT(replication_user_module, + &mail_user_module_register); +static int fifo_fd; +static bool fifo_failed; +static char *fifo_path; + +static int +replication_fifo_notify(struct mail_user *user, + enum replication_priority priority) +{ + string_t *str; + ssize_t ret; + + if (fifo_failed) + return -1; + if (fifo_fd == -1) { + fifo_fd = open(fifo_path, O_WRONLY | O_NONBLOCK); + if (fifo_fd == -1) { + i_error("open(%s) failed: %m", fifo_path); + fifo_failed = TRUE; + return -1; + } + } + /* <username> \t <priority> */ + str = t_str_new(256); + str_append_tabescaped(str, user->username); + str_append_c(str, '\t'); + switch (priority) { + case REPLICATION_PRIORITY_NONE: + case REPLICATION_PRIORITY_SYNC: + i_unreached(); + case REPLICATION_PRIORITY_LOW: + str_append(str, "low"); + break; + case REPLICATION_PRIORITY_HIGH: + str_append(str, "high"); + break; + } + str_append_c(str, '\n'); + ret = write(fifo_fd, str_data(str), str_len(str)); + i_assert(ret != 0); + if (ret != (ssize_t)str_len(str)) { + if (ret > 0) + i_error("write(%s) wrote partial data", fifo_path); + else if (errno == EAGAIN) { + /* busy, try again later */ + return 0; + } else if (errno != EPIPE) { + i_error("write(%s) failed: %m", fifo_path); + } else { + /* server was probably restarted, don't bother logging + this. */ + } + if (close(fifo_fd) < 0) + i_error("close(%s) failed: %m", fifo_path); + fifo_fd = -1; + return -1; + } + return 1; +} + +static void replication_notify_now(struct mail_user *user) +{ + struct replication_user *ruser = REPLICATION_USER_CONTEXT(user); + int ret; + + i_assert(ruser != NULL); + i_assert(ruser->priority != REPLICATION_PRIORITY_NONE); + i_assert(ruser->priority != REPLICATION_PRIORITY_SYNC); + + if ((ret = replication_fifo_notify(user, ruser->priority)) < 0 && + !fifo_failed) { + /* retry once, in case replication server was restarted */ + ret = replication_fifo_notify(user, ruser->priority); + } + if (ret != 0) { + timeout_remove(&ruser->to); + ruser->priority = REPLICATION_PRIORITY_NONE; + } +} + +static int replication_notify_sync(struct mail_user *user) +{ + struct replication_user *ruser = REPLICATION_USER_CONTEXT(user); + string_t *str; + char buf[1024]; + int fd; + ssize_t ret; + bool success = FALSE; + + i_assert(ruser != NULL); + + fd = net_connect_unix(ruser->socket_path); + if (fd == -1) { + i_error("net_connect_unix(%s) failed: %m", ruser->socket_path); + return -1; + } + net_set_nonblock(fd, FALSE); + + /* <username> \t "sync" */ + str = t_str_new(256); + str_append_tabescaped(str, user->username); + str_append(str, "\tsync\n"); + alarm(ruser->sync_secs); + if (write_full(fd, str_data(str), str_len(str)) < 0) { + i_error("write(%s) failed: %m", ruser->socket_path); + } else { + /* + | - */ + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) { + if (errno != EINTR) { + i_error("read(%s) failed: %m", + ruser->socket_path); + } else { + i_warning("replication(%s): Sync failure: " + "Timeout in %u secs", + user->username, ruser->sync_secs); + } + } else if (ret == 0) { + i_error("read(%s) failed: EOF", ruser->socket_path); + } else if (buf[0] == '+') { + /* success */ + success = TRUE; + } else if (buf[0] == '-') { + /* failure */ + if (buf[ret-1] == '\n') ret--; + i_warning("replication(%s): Sync failure: %s", + user->username, t_strndup(buf+1, ret-1)); + i_warning("replication(%s): " + "Remote sent invalid input: %s", + user->username, t_strndup(buf, ret)); + } + } + alarm(0); + if (close(fd) < 0) + i_error("close(%s) failed: %m", ruser->socket_path); + return success ? 0 : -1; +} + +static void replication_notify(struct mail_namespace *ns, + enum replication_priority priority, + const char *event) +{ + struct replication_user *ruser; + + ruser = REPLICATION_USER_CONTEXT(ns->user); + if (ruser == NULL) + return; + + e_debug(ns->user->event, + "replication: Replication requested by '%s', priority=%d", + event, priority); + + if (priority == REPLICATION_PRIORITY_SYNC) { + if (replication_notify_sync(ns->user) == 0) { + timeout_remove(&ruser->to); + ruser->priority = REPLICATION_PRIORITY_NONE; + return; + } + /* sync replication failed, try as "high" via fifo */ + priority = REPLICATION_PRIORITY_HIGH; + } + + if (ruser->priority < priority) + ruser->priority = priority; + if (ruser->to == NULL) { + ruser->to = timeout_add_short(REPLICATION_NOTIFY_DELAY_MSECS, + replication_notify_now, ns->user); + } +} + +static void * +replication_mail_transaction_begin(struct mailbox_transaction_context *t) +{ + struct replication_mail_txn_context *ctx; + + ctx = i_new(struct replication_mail_txn_context, 1); + ctx->ns = mailbox_get_namespace(t->box); + ctx->reason = i_strdup(t->reason); + if ((t->flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0) { + /* Transaction is from dsync. Don't trigger replication back. */ + ctx->sync_trans = TRUE; + } + return ctx; +} + +static void replication_mail_save(void *txn, struct mail *mail ATTR_UNUSED) +{ + struct replication_mail_txn_context *ctx = + (struct replication_mail_txn_context *)txn; + + ctx->new_messages = TRUE; +} + +static void replication_mail_copy(void *txn, struct mail *src, + struct mail *dst) +{ + struct replication_mail_txn_context *ctx = + (struct replication_mail_txn_context *)txn; + + if (src->box->storage != dst->box->storage) { + /* copy between storages, e.g. new mail delivery */ + ctx->new_messages = TRUE; + } else { + /* copy within storage, which isn't as high priority since the + mail already exists. and especially copies to Trash or to + lazy-expunge namespace is pretty low priority. */ + } +} + +static bool +replication_want_sync_changes(const struct mail_transaction_commit_changes *changes) +{ + /* Replication needs to be triggered on all the user-visible changes, + but not e.g. due to writes to cache file. */ + return (changes->changes_mask & + ENUM_NEGATE(MAIL_INDEX_TRANSACTION_CHANGE_OTHERS)) != 0; +} + +static void +replication_mail_transaction_commit(void *txn, + struct mail_transaction_commit_changes *changes) +{ + struct replication_mail_txn_context *ctx = + (struct replication_mail_txn_context *)txn; + struct replication_user *ruser = + REPLICATION_USER_CONTEXT(ctx->ns->user); + enum replication_priority priority; + + if (ruser != NULL && !ctx->sync_trans && + (ctx->new_messages || replication_want_sync_changes(changes))) { + priority = !ctx->new_messages ? REPLICATION_PRIORITY_LOW : + ruser->sync_secs == 0 ? REPLICATION_PRIORITY_HIGH : + REPLICATION_PRIORITY_SYNC; + replication_notify(ctx->ns, priority, ctx->reason); + } + i_free(ctx->reason); + i_free(ctx); +} + +static void replication_mailbox_create(struct mailbox *box) +{ + replication_notify(mailbox_get_namespace(box), + REPLICATION_PRIORITY_LOW, "mailbox create"); +} + +static void +replication_mailbox_delete_commit(void *txn ATTR_UNUSED, + struct mailbox *box) +{ + replication_notify(mailbox_get_namespace(box), + REPLICATION_PRIORITY_LOW, "mailbox delete"); +} + +static void +replication_mailbox_rename(struct mailbox *src ATTR_UNUSED, + struct mailbox *dest) +{ + replication_notify(mailbox_get_namespace(dest), + REPLICATION_PRIORITY_LOW, "mailbox rename"); +} + +static void replication_mailbox_set_subscribed(struct mailbox *box, + bool subscribed ATTR_UNUSED) +{ + replication_notify(mailbox_get_namespace(box), + REPLICATION_PRIORITY_LOW, "mailbox subscribe"); +} + +static void replication_user_deinit(struct mail_user *user) +{ + struct replication_user *ruser = REPLICATION_USER_CONTEXT(user); + + i_assert(ruser != NULL); + + if (ruser->to != NULL) { + replication_notify_now(user); + if (ruser->to != NULL) { + i_warning("%s: Couldn't send final notification " + "due to fifo being busy", fifo_path); + timeout_remove(&ruser->to); + } + } + + ruser->module_ctx.super.deinit(user); +} + +static void replication_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct replication_user *ruser; + const char *value; + + value = mail_user_plugin_getenv(user, "mail_replica"); + if (value == NULL || value[0] == '\0') { + e_debug(user->event, "replication: No mail_replica setting - replication disabled"); + return; + } + + if (user->dsyncing) { + /* we're running dsync, which means that the remote is telling + us about a change. don't trigger a replication back to it */ + e_debug(user->event, "replication: We're running dsync - replication disabled"); + return; + } + + ruser = p_new(user->pool, struct replication_user, 1); + ruser->module_ctx.super = *v; + user->vlast = &ruser->module_ctx.super; + v->deinit = replication_user_deinit; + MODULE_CONTEXT_SET(user, replication_user_module, ruser); + + if (fifo_path == NULL) { + /* we'll assume that all users have the same base_dir. + they really should. */ + fifo_path = i_strconcat(user->set->base_dir, + "/"REPLICATION_FIFO_NAME, NULL); + } + ruser->socket_path = p_strconcat(user->pool, user->set->base_dir, + "/"REPLICATION_SOCKET_NAME, NULL); + value = mail_user_plugin_getenv(user, "replication_sync_timeout"); + if (value != NULL && str_to_uint(value, &ruser->sync_secs) < 0) { + i_error("replication(%s): " + "Invalid replication_sync_timeout value: %s", + user->username, value); + } +} + +static const struct notify_vfuncs replication_vfuncs = { + .mail_transaction_begin = replication_mail_transaction_begin, + .mail_save = replication_mail_save, + .mail_copy = replication_mail_copy, + .mail_transaction_commit = replication_mail_transaction_commit, + .mailbox_create = replication_mailbox_create, + .mailbox_delete_commit = replication_mailbox_delete_commit, + .mailbox_rename = replication_mailbox_rename, + .mailbox_set_subscribed = replication_mailbox_set_subscribed +}; + +static struct notify_context *replication_ctx; + +static struct mail_storage_hooks replication_mail_storage_hooks = { + .mail_user_created = replication_user_created +}; + +void replication_plugin_init(struct module *module) +{ + fifo_fd = -1; + replication_ctx = notify_register(&replication_vfuncs); + mail_storage_hooks_add(module, &replication_mail_storage_hooks); +} + +void replication_plugin_deinit(void) +{ + i_close_fd_path(&fifo_fd, fifo_path); + i_free_and_null(fifo_path); + + mail_storage_hooks_remove(&replication_mail_storage_hooks); + notify_unregister(replication_ctx); +} + +const char *replication_plugin_dependencies[] = { "notify", NULL }; diff --git a/src/plugins/replication/replication-plugin.h b/src/plugins/replication/replication-plugin.h new file mode 100644 index 0000000..7fa344f --- /dev/null +++ b/src/plugins/replication/replication-plugin.h @@ -0,0 +1,9 @@ +#ifndef REPLICATION_PLUGIN_H +#define REPLICATION_PLUGIN_H + +extern const char *replication_plugin_dependencies[]; + +void replication_plugin_init(struct module *module); +void replication_plugin_deinit(void); + +#endif diff --git a/src/plugins/trash/Makefile.am b/src/plugins/trash/Makefile.am new file mode 100644 index 0000000..af15d13 --- /dev/null +++ b/src/plugins/trash/Makefile.am @@ -0,0 +1,23 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/quota + +NOPLUGIN_LDFLAGS = +lib11_trash_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib11_trash_plugin.la + +if DOVECOT_PLUGIN_DEPS +lib11_trash_plugin_la_LIBADD = \ + ../quota/lib10_quota_plugin.la +endif + +lib11_trash_plugin_la_SOURCES = \ + trash-plugin.c + +noinst_HEADERS = \ + trash-plugin.h diff --git a/src/plugins/trash/Makefile.in b/src/plugins/trash/Makefile.in new file mode 100644 index 0000000..8ebd77e --- /dev/null +++ b/src/plugins/trash/Makefile.in @@ -0,0 +1,824 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/trash +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +@DOVECOT_PLUGIN_DEPS_TRUE@lib11_trash_plugin_la_DEPENDENCIES = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../quota/lib10_quota_plugin.la +am_lib11_trash_plugin_la_OBJECTS = trash-plugin.lo +lib11_trash_plugin_la_OBJECTS = $(am_lib11_trash_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib11_trash_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib11_trash_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/trash-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib11_trash_plugin_la_SOURCES) +DIST_SOURCES = $(lib11_trash_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/quota + +lib11_trash_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib11_trash_plugin.la + +@DOVECOT_PLUGIN_DEPS_TRUE@lib11_trash_plugin_la_LIBADD = \ +@DOVECOT_PLUGIN_DEPS_TRUE@ ../quota/lib10_quota_plugin.la + +lib11_trash_plugin_la_SOURCES = \ + trash-plugin.c + +noinst_HEADERS = \ + trash-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/trash/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/trash/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib11_trash_plugin.la: $(lib11_trash_plugin_la_OBJECTS) $(lib11_trash_plugin_la_DEPENDENCIES) $(EXTRA_lib11_trash_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib11_trash_plugin_la_LINK) -rpath $(moduledir) $(lib11_trash_plugin_la_OBJECTS) $(lib11_trash_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trash-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/trash-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/trash-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/trash/trash-plugin.c b/src/plugins/trash/trash-plugin.c new file mode 100644 index 0000000..d918484 --- /dev/null +++ b/src/plugins/trash/trash-plugin.c @@ -0,0 +1,392 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "unichar.h" +#include "istream.h" +#include "mail-namespace.h" +#include "mail-search-build.h" +#include "quota-private.h" +#include "quota-plugin.h" +#include "trash-plugin.h" + +#include <unistd.h> +#include <fcntl.h> + +#define INIT_TRASH_MAILBOX_COUNT 4 +#define MAX_RETRY_COUNT 3 + +#define TRASH_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, trash_user_module) +#define TRASH_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, trash_user_module) + +struct trash_mailbox { + const char *name; + int priority; /* lower number = higher priority */ + + struct mail_namespace *ns; + + /* temporarily set while cleaning: */ + struct mailbox *box; + struct mailbox_transaction_context *trans; + struct mail_search_context *search_ctx; + struct mail *mail; +}; + +struct trash_user { + union mail_user_module_context module_ctx; + + const char *config_file; + /* ordered by priority, highest first */ + ARRAY(struct trash_mailbox) trash_boxes; +}; + +const char *trash_plugin_version = DOVECOT_ABI_VERSION; + +static MODULE_CONTEXT_DEFINE_INIT(trash_user_module, + &mail_user_module_register); +static enum quota_alloc_result (*trash_next_quota_test_alloc)( + struct quota_transaction_context *, uoff_t, + const char **error_r); + +static int trash_clean_mailbox_open(struct trash_mailbox *trash) +{ + struct mail_search_args *search_args; + + trash->box = mailbox_alloc(trash->ns->list, trash->name, 0); + if (mailbox_open(trash->box) < 0) { + mailbox_free(&trash->box); + return 0; + } + + if (mailbox_sync(trash->box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) + return -1; + + trash->trans = mailbox_transaction_begin(trash->box, 0, __func__); + + search_args = mail_search_build_init(); + mail_search_build_add_all(search_args); + trash->search_ctx = mailbox_search_init(trash->trans, + search_args, NULL, + MAIL_FETCH_PHYSICAL_SIZE | + MAIL_FETCH_RECEIVED_DATE, NULL); + mail_search_args_unref(&search_args); + + return mailbox_search_next(trash->search_ctx, &trash->mail) ? 1 : 0; +} + +static int trash_clean_mailbox_get_next(struct trash_mailbox *trash, + time_t *received_time_r) +{ + int ret; + + if (trash->mail == NULL) { + if (trash->box == NULL) + ret = trash_clean_mailbox_open(trash); + else { + ret = mailbox_search_next(trash->search_ctx, + &trash->mail) ? 1 : 0; + } + if (ret <= 0) { + *received_time_r = 0; + return ret; + } + } + + if (mail_get_received_date(trash->mail, received_time_r) < 0) + return -1; + return 1; +} + +static int trash_try_clean_mails(struct quota_transaction_context *ctx, + uint64_t size_needed, + unsigned int count_needed) +{ + struct trash_user *tuser = TRASH_USER_CONTEXT_REQUIRE(ctx->quota->user); + struct trash_mailbox *trashes; + struct event_reason *reason; + unsigned int i, j, count, oldest_idx; + time_t oldest, received = 0; + uint64_t size, size_expunged = 0; + unsigned int expunged_count = 0; + int ret = 0; + + reason = event_reason_begin("trash:clean"); + + trashes = array_get_modifiable(&tuser->trash_boxes, &count); + for (i = 0; i < count; ) { + /* expunge oldest mails first in all trash boxes with + same priority */ + oldest_idx = count; + oldest = (time_t)-1; + for (j = i; j < count; j++) { + if (trashes[j].priority != trashes[i].priority) + break; + + ret = trash_clean_mailbox_get_next(&trashes[j], + &received); + if (ret < 0) + goto err; + if (ret > 0) { + if (oldest == (time_t)-1 || received < oldest) { + oldest = received; + oldest_idx = j; + } + } + } + + if (oldest_idx < count) { + if (mail_get_physical_size(trashes[oldest_idx].mail, + &size) < 0) { + /* maybe expunged already? */ + trashes[oldest_idx].mail = NULL; + continue; + } + + mail_expunge(trashes[oldest_idx].mail); + expunged_count++; + size_expunged += size; + if (size_expunged >= size_needed && + expunged_count >= count_needed) + break; + trashes[oldest_idx].mail = NULL; + } else { + /* find more mails from next priority's mailbox */ + i = j; + } + } + +err: + for (i = 0; i < count; i++) { + struct trash_mailbox *trash = &trashes[i]; + + if (trash->box == NULL) + continue; + + trash->mail = NULL; + (void)mailbox_search_deinit(&trash->search_ctx); + + if (size_expunged >= size_needed && + expunged_count >= count_needed) { + (void)mailbox_transaction_commit(&trash->trans); + (void)mailbox_sync(trash->box, 0); + } else { + /* couldn't get enough space, don't expunge anything */ + mailbox_transaction_rollback(&trash->trans); + } + + mailbox_free(&trash->box); + } + event_reason_end(&reason); + + if (size_expunged < size_needed) { + e_debug(ctx->quota->user->event, + "trash plugin: Failed to remove enough messages " + "(needed %"PRIu64" bytes, expunged only %"PRIu64" bytes)", + size_needed, size_expunged); + return 0; + } + if (expunged_count < count_needed) { + e_debug(ctx->quota->user->event, + "trash plugin: Failed to remove enough messages " + "(needed %u messages, expunged only %u messages)", + count_needed, expunged_count); + return 0; + } + + if (ctx->bytes_over > 0) { + /* user is over quota. drop the over-bytes first. */ + i_assert(ctx->bytes_over <= size_expunged); + size_expunged -= ctx->bytes_over; + ctx->bytes_over = 0; + } + if (ctx->count_over > 0) { + /* user is over quota. drop the over-count first. */ + i_assert(ctx->count_over <= expunged_count); + expunged_count -= ctx->count_over; + ctx->count_over = 0; + } + + if (ctx->bytes_ceil > ((uint64_t)-1 - size_expunged)) { + ctx->bytes_ceil = (uint64_t)-1; + } else { + ctx->bytes_ceil += size_expunged; + } + if (ctx->count_ceil < ((uint64_t)-1 - expunged_count)) { + ctx->count_ceil = (uint64_t)-1; + } else { + ctx->count_ceil += expunged_count; + } + return 1; +} + +static enum quota_alloc_result +trash_quota_test_alloc(struct quota_transaction_context *ctx, + uoff_t size, const char **error_r) +{ + int i; + uint64_t size_needed = 0; + unsigned int count_needed = 0; + + for (i = 0; ; i++) { + enum quota_alloc_result ret; + ret = trash_next_quota_test_alloc(ctx, size, error_r); + if (ret != QUOTA_ALLOC_RESULT_OVER_QUOTA) { + if (ret == QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT && + ctx->quota->user->mail_debug) + i_debug("trash plugin: Mail is larger than " + "quota, won't even try to handle"); + return ret; + } + + if (i == MAX_RETRY_COUNT) { + /* trash_try_clean_mails() should have returned 0 if + it couldn't get enough space, but allow retrying + it a couple of times if there was some extra space + that was needed.. */ + break; + } + + if (ctx->bytes_ceil != (uint64_t)-1 && + ctx->bytes_ceil < size + ctx->bytes_over) + size_needed = size + ctx->bytes_over - ctx->bytes_ceil; + if (ctx->count_ceil != (uint64_t)-1 && + ctx->count_ceil < 1 + ctx->count_over) + count_needed = 1 + ctx->count_over - ctx->count_ceil; + + /* not enough space. try deleting some from mailbox. */ + if (trash_try_clean_mails(ctx, size_needed, count_needed) <= 0) { + *error_r = t_strdup_printf( + "Allocating %"PRIuUOFF_T" bytes would exceed quota", size); + return QUOTA_ALLOC_RESULT_OVER_QUOTA; + } + } + *error_r = t_strdup_printf( + "Allocating %"PRIuUOFF_T" bytes would exceed quota", size); + return QUOTA_ALLOC_RESULT_OVER_QUOTA; +} + +static bool trash_find_storage(struct mail_user *user, + struct trash_mailbox *trash) +{ + struct mail_namespace *ns; + + ns = mail_namespace_find(user->namespaces, trash->name); + if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) + return FALSE; + + trash->ns = ns; + return TRUE; +} + +static int trash_mailbox_priority_cmp(const struct trash_mailbox *t1, + const struct trash_mailbox *t2) +{ + return t1->priority - t2->priority; +} + +static int read_configuration(struct mail_user *user, const char *path) +{ + struct trash_user *tuser = TRASH_USER_CONTEXT_REQUIRE(user); + struct istream *input; + const char *line, *name; + struct trash_mailbox *trash; + int fd, ret = 0; + + fd = open(path, O_RDONLY); + if (fd == -1) { + i_error("trash plugin: open(%s) failed: %m", path); + return -1; + } + + p_array_init(&tuser->trash_boxes, user->pool, INIT_TRASH_MAILBOX_COUNT); + + input = i_stream_create_fd(fd, SIZE_MAX); + i_stream_set_return_partial_line(input, TRUE); + while ((line = i_stream_read_next_line(input)) != NULL) { + /* <priority> <mailbox name> */ + name = strchr(line, ' '); + if (name == NULL || name[1] == '\0' || *line == '#') + continue; + + trash = array_append_space(&tuser->trash_boxes); + trash->name = p_strdup(user->pool, name+1); + if (str_to_int(t_strdup_until(line, name), + &trash->priority) < 0) { + i_error("trash: Invalid priority for mailbox '%s'", + trash->name); + ret = -1; + } + + if (!uni_utf8_str_is_valid(trash->name)) { + i_error("trash: Mailbox name not UTF-8: %s", + trash->name); + ret = -1; + } + if (!trash_find_storage(user, trash)) { + i_error("trash: Namespace not found for mailbox '%s'", + trash->name); + ret = -1; + } + + e_debug(user->event, "trash plugin: Added '%s' with priority %d", + trash->name, trash->priority); + } + i_stream_destroy(&input); + i_close_fd(&fd); + + array_sort(&tuser->trash_boxes, trash_mailbox_priority_cmp); + return ret; +} + +static void +trash_mail_user_created(struct mail_user *user) +{ + struct quota_user *quser = QUOTA_USER_CONTEXT(user); + struct trash_user *tuser; + const char *env; + + env = mail_user_plugin_getenv(user, "trash"); + if (env == NULL) { + e_debug(user->event, "trash: No trash setting - plugin disabled"); + } else if (quser == NULL) { + i_error("trash plugin: quota plugin not initialized"); + } else { + tuser = p_new(user->pool, struct trash_user, 1); + tuser->config_file = env; + MODULE_CONTEXT_SET(user, trash_user_module, tuser); + } +} + +static void +trash_mail_namespaces_created(struct mail_namespace *namespaces) +{ + struct mail_user *user = namespaces->user; + struct trash_user *tuser = TRASH_USER_CONTEXT(user); + struct quota_user *quser = QUOTA_USER_CONTEXT(user); + + if (tuser != NULL && read_configuration(user, tuser->config_file) == 0) { + i_assert(quser != NULL); + trash_next_quota_test_alloc = + quser->quota->set->test_alloc; + quser->quota->set->test_alloc = trash_quota_test_alloc; + } +} + +static struct mail_storage_hooks trash_mail_storage_hooks = { + .mail_user_created = trash_mail_user_created, + .mail_namespaces_created = trash_mail_namespaces_created, +}; + +void trash_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &trash_mail_storage_hooks); +} + +void trash_plugin_deinit(void) +{ + mail_storage_hooks_remove(&trash_mail_storage_hooks); +} + +const char *trash_plugin_dependencies[] = { "quota", NULL }; diff --git a/src/plugins/trash/trash-plugin.h b/src/plugins/trash/trash-plugin.h new file mode 100644 index 0000000..bd6db6b --- /dev/null +++ b/src/plugins/trash/trash-plugin.h @@ -0,0 +1,9 @@ +#ifndef TRASH_PLUGIN_H +#define TRASH_PLUGIN_H + +extern const char *trash_plugin_dependencies[]; + +void trash_plugin_init(struct module *module); +void trash_plugin_deinit(void); + +#endif diff --git a/src/plugins/var-expand-crypt/Makefile.am b/src/plugins/var-expand-crypt/Makefile.am new file mode 100644 index 0000000..6feb1a7 --- /dev/null +++ b/src/plugins/var-expand-crypt/Makefile.am @@ -0,0 +1,39 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dcrypt + +NOPLUGIN_LDFLAGS = +lib20_var_expand_crypt_la_LDFLAGS = -module -avoid-version +lib20_auth_var_expand_crypt_la_LDFLAGS = -module -avoid-version + +auth_moduledir = $(moduledir)/auth + +module_LTLIBRARIES = \ + lib20_var_expand_crypt.la + +auth_module_LTLIBRARIES = \ + lib20_auth_var_expand_crypt.la + +lib20_auth_var_expand_crypt_la_SOURCES = \ + var-expand-crypt-plugin.c + +lib20_var_expand_crypt_la_SOURCES = \ + var-expand-crypt-plugin.c + +test_programs = test-var-expand-crypt + +test_var_expand_crypt_CFLAGS = \ + -DDCRYPT_BUILD_DIR=\"$(top_builddir)/src/lib-dcrypt\" +test_var_expand_crypt_SOURCES = \ + test-var-expand-crypt.c +test_var_expand_crypt_LDADD = \ + ../../lib-dovecot/libdovecot.la \ + lib20_auth_var_expand_crypt.la + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +noinst_PROGRAMS = $(test_programs) diff --git a/src/plugins/var-expand-crypt/Makefile.in b/src/plugins/var-expand-crypt/Makefile.in new file mode 100644 index 0000000..e2b9ba0 --- /dev/null +++ b/src/plugins/var-expand-crypt/Makefile.in @@ -0,0 +1,938 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/plugins/var-expand-crypt +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-var-expand-crypt$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(auth_moduledir)" \ + "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(auth_module_LTLIBRARIES) $(module_LTLIBRARIES) +lib20_auth_var_expand_crypt_la_LIBADD = +am_lib20_auth_var_expand_crypt_la_OBJECTS = \ + var-expand-crypt-plugin.lo +lib20_auth_var_expand_crypt_la_OBJECTS = \ + $(am_lib20_auth_var_expand_crypt_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_auth_var_expand_crypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(lib20_auth_var_expand_crypt_la_LDFLAGS) $(LDFLAGS) -o $@ +lib20_var_expand_crypt_la_LIBADD = +am_lib20_var_expand_crypt_la_OBJECTS = var-expand-crypt-plugin.lo +lib20_var_expand_crypt_la_OBJECTS = \ + $(am_lib20_var_expand_crypt_la_OBJECTS) +lib20_var_expand_crypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_var_expand_crypt_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_var_expand_crypt_OBJECTS = \ + test_var_expand_crypt-test-var-expand-crypt.$(OBJEXT) +test_var_expand_crypt_OBJECTS = $(am_test_var_expand_crypt_OBJECTS) +test_var_expand_crypt_DEPENDENCIES = ../../lib-dovecot/libdovecot.la \ + lib20_auth_var_expand_crypt.la +test_var_expand_crypt_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(test_var_expand_crypt_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = \ + ./$(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po \ + ./$(DEPDIR)/var-expand-crypt-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_auth_var_expand_crypt_la_SOURCES) \ + $(lib20_var_expand_crypt_la_SOURCES) \ + $(test_var_expand_crypt_SOURCES) +DIST_SOURCES = $(lib20_auth_var_expand_crypt_la_SOURCES) \ + $(lib20_var_expand_crypt_la_SOURCES) \ + $(test_var_expand_crypt_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dcrypt + +lib20_var_expand_crypt_la_LDFLAGS = -module -avoid-version +lib20_auth_var_expand_crypt_la_LDFLAGS = -module -avoid-version +auth_moduledir = $(moduledir)/auth +module_LTLIBRARIES = \ + lib20_var_expand_crypt.la + +auth_module_LTLIBRARIES = \ + lib20_auth_var_expand_crypt.la + +lib20_auth_var_expand_crypt_la_SOURCES = \ + var-expand-crypt-plugin.c + +lib20_var_expand_crypt_la_SOURCES = \ + var-expand-crypt-plugin.c + +test_programs = test-var-expand-crypt +test_var_expand_crypt_CFLAGS = \ + -DDCRYPT_BUILD_DIR=\"$(top_builddir)/src/lib-dcrypt\" + +test_var_expand_crypt_SOURCES = \ + test-var-expand-crypt.c + +test_var_expand_crypt_LDADD = \ + ../../lib-dovecot/libdovecot.la \ + lib20_auth_var_expand_crypt.la + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/var-expand-crypt/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/var-expand-crypt/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +install-auth_moduleLTLIBRARIES: $(auth_module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(auth_module_LTLIBRARIES)'; test -n "$(auth_moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(auth_moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(auth_moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(auth_moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(auth_moduledir)"; \ + } + +uninstall-auth_moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(auth_module_LTLIBRARIES)'; test -n "$(auth_moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(auth_moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(auth_moduledir)/$$f"; \ + done + +clean-auth_moduleLTLIBRARIES: + -test -z "$(auth_module_LTLIBRARIES)" || rm -f $(auth_module_LTLIBRARIES) + @list='$(auth_module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_auth_var_expand_crypt.la: $(lib20_auth_var_expand_crypt_la_OBJECTS) $(lib20_auth_var_expand_crypt_la_DEPENDENCIES) $(EXTRA_lib20_auth_var_expand_crypt_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_auth_var_expand_crypt_la_LINK) -rpath $(auth_moduledir) $(lib20_auth_var_expand_crypt_la_OBJECTS) $(lib20_auth_var_expand_crypt_la_LIBADD) $(LIBS) + +lib20_var_expand_crypt.la: $(lib20_var_expand_crypt_la_OBJECTS) $(lib20_var_expand_crypt_la_DEPENDENCIES) $(EXTRA_lib20_var_expand_crypt_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_var_expand_crypt_la_LINK) -rpath $(moduledir) $(lib20_var_expand_crypt_la_OBJECTS) $(lib20_var_expand_crypt_la_LIBADD) $(LIBS) + +test-var-expand-crypt$(EXEEXT): $(test_var_expand_crypt_OBJECTS) $(test_var_expand_crypt_DEPENDENCIES) $(EXTRA_test_var_expand_crypt_DEPENDENCIES) + @rm -f test-var-expand-crypt$(EXEEXT) + $(AM_V_CCLD)$(test_var_expand_crypt_LINK) $(test_var_expand_crypt_OBJECTS) $(test_var_expand_crypt_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/var-expand-crypt-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +test_var_expand_crypt-test-var-expand-crypt.o: test-var-expand-crypt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_var_expand_crypt_CFLAGS) $(CFLAGS) -MT test_var_expand_crypt-test-var-expand-crypt.o -MD -MP -MF $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Tpo -c -o test_var_expand_crypt-test-var-expand-crypt.o `test -f 'test-var-expand-crypt.c' || echo '$(srcdir)/'`test-var-expand-crypt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Tpo $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-var-expand-crypt.c' object='test_var_expand_crypt-test-var-expand-crypt.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_var_expand_crypt_CFLAGS) $(CFLAGS) -c -o test_var_expand_crypt-test-var-expand-crypt.o `test -f 'test-var-expand-crypt.c' || echo '$(srcdir)/'`test-var-expand-crypt.c + +test_var_expand_crypt-test-var-expand-crypt.obj: test-var-expand-crypt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_var_expand_crypt_CFLAGS) $(CFLAGS) -MT test_var_expand_crypt-test-var-expand-crypt.obj -MD -MP -MF $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Tpo -c -o test_var_expand_crypt-test-var-expand-crypt.obj `if test -f 'test-var-expand-crypt.c'; then $(CYGPATH_W) 'test-var-expand-crypt.c'; else $(CYGPATH_W) '$(srcdir)/test-var-expand-crypt.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Tpo $(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-var-expand-crypt.c' object='test_var_expand_crypt-test-var-expand-crypt.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_var_expand_crypt_CFLAGS) $(CFLAGS) -c -o test_var_expand_crypt-test-var-expand-crypt.obj `if test -f 'test-var-expand-crypt.c'; then $(CYGPATH_W) 'test-var-expand-crypt.c'; else $(CYGPATH_W) '$(srcdir)/test-var-expand-crypt.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(auth_moduledir)" "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-auth_moduleLTLIBRARIES clean-generic clean-libtool \ + clean-moduleLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po + -rm -f ./$(DEPDIR)/var-expand-crypt-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-auth_moduleLTLIBRARIES \ + install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/test_var_expand_crypt-test-var-expand-crypt.Po + -rm -f ./$(DEPDIR)/var-expand-crypt-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-auth_moduleLTLIBRARIES \ + uninstall-moduleLTLIBRARIES + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-auth_moduleLTLIBRARIES clean-generic \ + clean-libtool clean-moduleLTLIBRARIES clean-noinstPROGRAMS \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-auth_moduleLTLIBRARIES install-data install-data-am \ + install-dvi install-dvi-am install-exec install-exec-am \ + install-html install-html-am install-info install-info-am \ + install-man install-moduleLTLIBRARIES install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-auth_moduleLTLIBRARIES uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! env $(test_options) $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/var-expand-crypt/test-var-expand-crypt.c b/src/plugins/var-expand-crypt/test-var-expand-crypt.c new file mode 100644 index 0000000..e12a040 --- /dev/null +++ b/src/plugins/var-expand-crypt/test-var-expand-crypt.c @@ -0,0 +1,102 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-common.h" +#include "str.h" +#include "var-expand.h" +#include "randgen.h" +#include "dcrypt.h" + +struct module; + +extern void var_expand_crypt_init(struct module *module); +extern void var_expand_crypt_deinit(void); + +static void test_var_expand_crypt(void) +{ + struct var_expand_table table[] = { + { '\0', "98b3b40a48ca40f998b3b40a48ca40f9", "iv" }, + { '\0', "cc2981c8f38aea59cc2981c8f38aea59", "key" }, + { '\0', "46b58741763fe22598014be26331a082", "encrypted_noiv" }, + { '\0', "98b3b40a48ca40f998b3b40a48ca40f9$46b58741763fe22598014be26331a082$", "encrypted" }, + { '\0', "hello, world", "decrypted" }, + { '\0', NULL, "encrypted2" }, + { '\0', NULL, NULL } + }; + + static struct { + const char *input; + const char *output; + int expect_ret; + } test_cases[] = { + { "%{encrypt;algo=null:decrypted}", "", -1 }, + { "%{encrypt;algo=aes-128-cbc,iv=98b3b40a48ca40f998b3b40a48ca40f9,key=cc2981c8f38aea59cc2981c8f38aea59:decrypted}", "98b3b40a48ca40f998b3b40a48ca40f9$46b58741763fe22598014be26331a082$", 1 }, + { "%{encrypt;noiv=yes,algo=aes-128-cbc,iv=98b3b40a48ca40f998b3b40a48ca40f9,key=cc2981c8f38aea59cc2981c8f38aea59:decrypted}", "46b58741763fe22598014be26331a082", 1 }, + { "%{encrypt;algo=aes-128-cbc,iv=%{iv},key=%{key}:decrypted}", "98b3b40a48ca40f998b3b40a48ca40f9$46b58741763fe22598014be26331a082$", 1 }, + { "%{decrypt;algo=null:encrypted}", "", -1 }, + { "%{decrypt;algo=aes-128-cbc,key=%{key}:encrypted}", "hello, world", 1 }, + { "%{decrypt;algo=aes-128-cbc,iv=%{iv},key=%{key}:encrypted_noiv}", "hello, world", 1 }, + { "%{decrypt;algo=aes-128-cbc,iv=98b3b40a48ca40f998b3b40a48ca40f9,key=cc2981c8f38aea59cc2981c8f38aea59:encrypted_noiv}", "hello, world", 1 }, + }; + + unsigned int i; + + test_begin("var_expand_crypt"); + var_expand_crypt_init(NULL); + + for(i=0; i < N_ELEMENTS(test_cases); i++) T_BEGIN { + const char *error; + string_t *dest = t_str_new(32); + int ret = var_expand(dest, test_cases[i].input, table, &error); + if (ret < 0) { + if (test_cases[i].expect_ret == -1) + i_info("Expected: var_expand(%s): %s", test_cases[i].input, error); + else + i_error("var_expand(%s): %s", test_cases[i].input, error); + } + test_assert_idx(strcmp(str_c(dest), test_cases[i].output)==0, i); + test_assert_idx(ret == test_cases[i].expect_ret, i); + } T_END; + + test_end(); + + test_begin("var_expand_crypt_random"); + + string_t *input = t_str_new(32); + string_t *output = t_str_new(32); + + for(i=0;i<1000;i++) { + const char *error; + str_truncate(input, 0); + str_truncate(output, 0); + + test_assert_idx(var_expand(input, "%{encrypt;algo=aes-128-cbc,key=%{key}:decrypted}", table, &error) == 1, i); + table[5].value = str_c(input); + test_assert_idx(var_expand(output, "%{decrypt;algo=aes-128-cbc,key=%{key}:encrypted2}", table, &error) == 1, i); + test_assert_idx(strcmp(str_c(output), table[4].value)==0, i); + }; + + var_expand_crypt_deinit(); + test_end(); +} + +int main(void) +{ + int ret = 0; + static void (*const test_functions[])(void) = { + test_var_expand_crypt, + NULL + }; + struct dcrypt_settings set = { + .module_dir = DCRYPT_BUILD_DIR"/.libs" + }; + + if (!dcrypt_initialize(NULL, &set, NULL)) + return 0; + + ret = test_run(test_functions); + + dcrypt_deinitialize(); + + return ret; +} diff --git a/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c new file mode 100644 index 0000000..1f6cce7 --- /dev/null +++ b/src/plugins/var-expand-crypt/var-expand-crypt-plugin.c @@ -0,0 +1,335 @@ +/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hex-binary.h" +#include "base64.h" +#include "str.h" +#include "strescape.h" +#include "var-expand.h" +#include "var-expand-private.h" +#include "dcrypt.h" + +#define VAR_EXPAND_CRYPT_DEFAULT_ALGO "AES-256-CBC" + +struct module; + +enum crypt_field_format { + FORMAT_HEX, + FORMAT_BASE64 +}; + +struct var_expand_crypt_context { + struct var_expand_context *ctx; + const char *algo; + string_t *iv; + string_t *enckey; + enum crypt_field_format format; + bool enc_result_only:1; +}; + +static bool var_expand_crypt_initialize(const char **error_r); + +void var_expand_crypt_init(struct module *module); +void var_expand_crypt_deinit(void); +void auth_var_expand_crypt_init(struct module *module); +void auth_var_expand_crypt_deinit(void); + +static int +var_expand_crypt_settings(struct var_expand_crypt_context *ctx, + const char *const *args, const char **error_r) +{ + while(args != NULL && *args != NULL) { + const char *k = t_strcut(*args, '='); + const char *value = strchr(*args, '='); + if (value == NULL) { + args++; + continue; + } else { + value++; + } + + if (strcmp(k, "iv") == 0) { + str_truncate(ctx->iv, 0); + if (var_expand_with_funcs(ctx->iv, value, ctx->ctx->table, + ctx->ctx->func_table, + ctx->ctx->context, error_r) < 0) { + return -1; + } + const char *hexiv = t_strdup(str_c(ctx->iv)); + /* try to decode IV */ + str_truncate(ctx->iv, 0); + hex_to_binary(hexiv, ctx->iv); + } if (strcmp(k, "noiv") == 0) { + ctx->enc_result_only = strcasecmp(value, "yes")==0; + } if (strcmp(k, "algo") == 0) { + ctx->algo = value; + } else if (strcmp(k, "key") == 0) { + str_truncate(ctx->enckey, 0); + if (var_expand_with_funcs(ctx->enckey, value, + ctx->ctx->table, + ctx->ctx->func_table, + ctx->ctx->context, + error_r) < 0) { + return -1; + } + const char *hexkey = t_strdup(str_c(ctx->enckey)); + str_truncate(ctx->enckey, 0); + hex_to_binary(hexkey, ctx->enckey); + } else if (strcmp(k, "format") == 0) { + if (strcmp(value, "hex") == 0) { + ctx->format = FORMAT_HEX; + } else if (strcmp(value, "base64") == 0) { + ctx->format = FORMAT_BASE64; + } else { + *error_r = t_strdup_printf( + "Cannot parse hash arguments:" + "'%s' is not supported format", + value); + return -1; + } + } + args++; + } + + if (ctx->algo == NULL) { + ctx->algo = "AES-256-CBC"; + } + + return 0; +} + +static int +var_expand_crypt(struct dcrypt_context_symmetric *dctx, buffer_t *key, buffer_t *iv, + const buffer_t *input, buffer_t *output, const char **error_r) +{ + /* make sure IV is correct */ + if (iv->used == 0) { + dcrypt_ctx_sym_set_key_iv_random(dctx); + /* acquire IV */ + dcrypt_ctx_sym_get_iv(dctx, iv); + } else if (dcrypt_ctx_sym_get_iv_length(dctx) != iv->used) { + *error_r = t_strdup_printf("crypt: IV length invalid (%zu != %u)", + iv->used, + dcrypt_ctx_sym_get_iv_length(dctx)); + return -1; + } else { + dcrypt_ctx_sym_set_iv(dctx, iv->data, iv->used); + } + + if (dcrypt_ctx_sym_get_key_length(dctx) != key->used) { + *error_r = t_strdup_printf("crypt: Key length invalid (%zu != %u)", + key->used, + dcrypt_ctx_sym_get_key_length(dctx)); + return -1; + } else { + dcrypt_ctx_sym_set_key(dctx, key->data, key->used); + } + + if (!dcrypt_ctx_sym_init(dctx, error_r) || + !dcrypt_ctx_sym_update(dctx, input->data, + input->used, output, error_r) || + !dcrypt_ctx_sym_final(dctx, output, error_r)) + return -1; + return 0; +} + +static int +var_expand_encrypt(struct var_expand_context *_ctx, + const char *key, const char *field, + const char **result_r, const char **error_r) +{ + if (!var_expand_crypt_initialize(error_r)) + return -1; + + const char *p = strchr(key, ';'); + const char *const *args = NULL; + const char *value; + struct var_expand_crypt_context ctx; + string_t *dest; + int ret = 0; + + memset(&ctx, 0, sizeof(ctx)); + ctx.ctx = _ctx; + ctx.format = FORMAT_HEX; + + if (p != NULL) { + args = t_strsplit(p+1, ","); + } + + string_t *field_value = t_str_new(64); + ctx.iv = t_str_new(64); + ctx.enckey = t_str_new(64); + string_t *tmp = t_str_new(128); + + if ((ret = var_expand_long(_ctx, field, strlen(field), + &value, error_r)) < 1) { + return ret; + } + + if (*value == '\0') { + *result_r = value; + return ret; + } + + if (var_expand_crypt_settings(&ctx, args, error_r) < 0) + return -1; + + str_append(field_value, value); + + struct dcrypt_context_symmetric *dctx; + if (!dcrypt_ctx_sym_create(ctx.algo, DCRYPT_MODE_ENCRYPT, &dctx, error_r)) + return -1; + + ret = var_expand_crypt(dctx, ctx.enckey, ctx.iv, field_value, tmp, error_r); + dcrypt_ctx_sym_destroy(&dctx); + + if (ret == 0) { + /* makes compiler happy */ + const char *enciv = ""; + const char *res = ""; + + switch(ctx.format) { + case FORMAT_HEX: + enciv = binary_to_hex(ctx.iv->data, ctx.iv->used); + res = binary_to_hex(tmp->data, tmp->used); + break; + case FORMAT_BASE64: + dest = t_str_new(32); + base64_encode(ctx.iv->data, ctx.iv->used, dest); + enciv = str_c(dest); + dest = t_str_new(32); + base64_encode(tmp->data, tmp->used, dest); + res = str_c(dest); + break; + default: + i_unreached(); + } + if (ctx.enc_result_only) + *result_r = t_strdup(res); + else + *result_r = t_strdup_printf("%s$%s$", enciv, res); + ret = 1; + } + + return ret; +} + +static int +var_expand_decrypt(struct var_expand_context *_ctx, + const char *key, const char *field, + const char **result_r, const char **error_r) +{ + if (!var_expand_crypt_initialize(error_r)) + return -1; + + const char *p = strchr(key, ';'); + const char *const *args = NULL; + const char *value; + struct var_expand_crypt_context ctx; + int ret = 0; + + memset(&ctx, 0, sizeof(ctx)); + ctx.ctx = _ctx; + ctx.format = FORMAT_HEX; + + if (p != NULL) { + args = t_strsplit(p+1, ","); + } + + string_t *field_value = t_str_new(64); + ctx.iv = t_str_new(64); + ctx.enckey = t_str_new(64); + string_t *tmp = t_str_new(128); + + if ((ret = var_expand_long(_ctx, field, strlen(field), + &value, error_r)) < 1) { + return ret; + } + + if (*value == '\0') { + *result_r = value; + return ret; + } + + if (var_expand_crypt_settings(&ctx, args, error_r) < 0) + return -1; + + const char *encdata = value; + const char *enciv = ""; + + /* make sure IV is correct */ + if (ctx.iv->used == 0 && (p = strchr(encdata, '$')) != NULL) { + /* see if IV can be taken from data */ + enciv = t_strcut(encdata, '$'); + encdata = t_strcut(p+1,'$'); + } + + str_truncate(field_value, 0); + + /* try to decode iv and encdata */ + switch(ctx.format) { + case FORMAT_HEX: + if (ctx.iv->used == 0) + hex_to_binary(enciv, ctx.iv); + hex_to_binary(encdata, field_value); + break; + case FORMAT_BASE64: + if (ctx.iv->used == 0) + str_append_str(ctx.iv, t_base64_decode_str(enciv)); + str_append_str(field_value, t_base64_decode_str(encdata)); + break; + } + + if (ctx.iv->used == 0) { + *error_r = t_strdup_printf("decrypt: IV missing"); + return -1; + } + + struct dcrypt_context_symmetric *dctx; + if (!dcrypt_ctx_sym_create(ctx.algo, DCRYPT_MODE_DECRYPT, &dctx, error_r)) + return -1; + ret = var_expand_crypt(dctx, ctx.enckey, ctx.iv, field_value, tmp, error_r); + dcrypt_ctx_sym_destroy(&dctx); + + if (ret == 0) { + *result_r = str_c(tmp); + ret = 1; + } + + return ret; +} + +static const struct var_expand_extension_func_table funcs[] = { + { "encrypt", var_expand_encrypt }, + { "decrypt", var_expand_decrypt }, + { NULL, NULL, } +}; + +static bool var_expand_crypt_initialize(const char **error_r) +{ + return dcrypt_initialize(NULL, NULL, error_r); +} + +void var_expand_crypt_init(struct module *module ATTR_UNUSED) +{ + var_expand_register_func_array(funcs); + /* do not initialize dcrypt here - saves alot of memory + to not load openssl every time. Only load it if + needed */ +} + +void var_expand_crypt_deinit(void) +{ + var_expand_unregister_func_array(funcs); +} + +void auth_var_expand_crypt_init(struct module *module) +{ + var_expand_crypt_init(module); +} + +void auth_var_expand_crypt_deinit(void) +{ + var_expand_crypt_deinit(); +} diff --git a/src/plugins/virtual/Makefile.am b/src/plugins/virtual/Makefile.am new file mode 100644 index 0000000..9ba873d --- /dev/null +++ b/src/plugins/virtual/Makefile.am @@ -0,0 +1,29 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-imap-storage + +NOPLUGIN_LDFLAGS = +lib20_virtual_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_virtual_plugin.la + +lib20_virtual_plugin_la_SOURCES = \ + virtual-config.c \ + virtual-mail.c \ + virtual-plugin.c \ + virtual-search.c \ + virtual-storage.c \ + virtual-save.c \ + virtual-sync.c \ + virtual-transaction.c + +noinst_HEADERS = \ + virtual-plugin.h \ + virtual-storage.h \ + virtual-transaction.h diff --git a/src/plugins/virtual/Makefile.in b/src/plugins/virtual/Makefile.in new file mode 100644 index 0000000..aba6783 --- /dev/null +++ b/src/plugins/virtual/Makefile.in @@ -0,0 +1,859 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/virtual +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_virtual_plugin_la_LIBADD = +am_lib20_virtual_plugin_la_OBJECTS = virtual-config.lo virtual-mail.lo \ + virtual-plugin.lo virtual-search.lo virtual-storage.lo \ + virtual-save.lo virtual-sync.lo virtual-transaction.lo +lib20_virtual_plugin_la_OBJECTS = \ + $(am_lib20_virtual_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_virtual_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_virtual_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/virtual-config.Plo \ + ./$(DEPDIR)/virtual-mail.Plo ./$(DEPDIR)/virtual-plugin.Plo \ + ./$(DEPDIR)/virtual-save.Plo ./$(DEPDIR)/virtual-search.Plo \ + ./$(DEPDIR)/virtual-storage.Plo ./$(DEPDIR)/virtual-sync.Plo \ + ./$(DEPDIR)/virtual-transaction.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_virtual_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_virtual_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-imap-storage + +lib20_virtual_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_virtual_plugin.la + +lib20_virtual_plugin_la_SOURCES = \ + virtual-config.c \ + virtual-mail.c \ + virtual-plugin.c \ + virtual-search.c \ + virtual-storage.c \ + virtual-save.c \ + virtual-sync.c \ + virtual-transaction.c + +noinst_HEADERS = \ + virtual-plugin.h \ + virtual-storage.h \ + virtual-transaction.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/virtual/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/virtual/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_virtual_plugin.la: $(lib20_virtual_plugin_la_OBJECTS) $(lib20_virtual_plugin_la_DEPENDENCIES) $(EXTRA_lib20_virtual_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_virtual_plugin_la_LINK) -rpath $(moduledir) $(lib20_virtual_plugin_la_OBJECTS) $(lib20_virtual_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-mail.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-save.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-search.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-sync.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-transaction.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/virtual-config.Plo + -rm -f ./$(DEPDIR)/virtual-mail.Plo + -rm -f ./$(DEPDIR)/virtual-plugin.Plo + -rm -f ./$(DEPDIR)/virtual-save.Plo + -rm -f ./$(DEPDIR)/virtual-search.Plo + -rm -f ./$(DEPDIR)/virtual-storage.Plo + -rm -f ./$(DEPDIR)/virtual-sync.Plo + -rm -f ./$(DEPDIR)/virtual-transaction.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/virtual-config.Plo + -rm -f ./$(DEPDIR)/virtual-mail.Plo + -rm -f ./$(DEPDIR)/virtual-plugin.Plo + -rm -f ./$(DEPDIR)/virtual-save.Plo + -rm -f ./$(DEPDIR)/virtual-search.Plo + -rm -f ./$(DEPDIR)/virtual-storage.Plo + -rm -f ./$(DEPDIR)/virtual-sync.Plo + -rm -f ./$(DEPDIR)/virtual-transaction.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/virtual/virtual-config.c b/src/plugins/virtual/virtual-config.c new file mode 100644 index 0000000..05a6305 --- /dev/null +++ b/src/plugins/virtual/virtual-config.c @@ -0,0 +1,567 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "crc32.h" +#include "istream.h" +#include "str.h" +#include "unichar.h" +#include "wildcard-match.h" +#include "imap-parser.h" +#include "imap-match.h" +#include "mail-namespace.h" +#include "mail-search-build.h" +#include "mail-search-parser.h" +#include "mailbox-attribute.h" +#include "mailbox-list-iter.h" +#include "imap-metadata.h" +#include "virtual-storage.h" +#include "virtual-plugin.h" + +#include <unistd.h> +#include <fcntl.h> + +struct virtual_parse_context { + struct virtual_mailbox *mbox; + struct istream *input; + + pool_t pool; + string_t *rule; + unsigned int rule_idx; + + char sep; + bool have_wildcards; + bool have_mailbox_defines; +}; + +static struct mail_search_args * +virtual_search_args_parse(const string_t *rule, const char **error_r) +{ + struct istream *input; + struct imap_parser *imap_parser; + const struct imap_arg *args; + struct mail_search_parser *parser; + struct mail_search_args *sargs; + const char *charset = "UTF-8"; + int ret; + + if (str_len(rule) == 0) { + sargs = mail_search_build_init(); + mail_search_build_add_all(sargs); + return sargs; + } + + input = i_stream_create_from_data(str_data(rule), str_len(rule)); + (void)i_stream_read(input); + + imap_parser = imap_parser_create(input, NULL, SIZE_MAX); + ret = imap_parser_finish_line(imap_parser, 0, 0, &args); + if (ret < 0) { + sargs = NULL; + *error_r = t_strdup(imap_parser_get_error(imap_parser, NULL)); + } else { + parser = mail_search_parser_init_imap(args); + if (mail_search_build(mail_search_register_get_imap(), + parser, &charset, &sargs, error_r) < 0) + sargs = NULL; + mail_search_parser_deinit(&parser); + } + + imap_parser_unref(&imap_parser); + i_stream_destroy(&input); + return sargs; +} + +static int +virtual_config_add_rule(struct virtual_parse_context *ctx, const char **error_r) +{ + struct virtual_backend_box *const *bboxes; + struct mail_search_args *search_args; + unsigned int i, count; + + *error_r = NULL; + + if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) { + i_assert(str_len(ctx->rule) == 0); + return 0; + } + + ctx->mbox->search_args_crc32 = + crc32_str_more(ctx->mbox->search_args_crc32, str_c(ctx->rule)); + search_args = virtual_search_args_parse(ctx->rule, error_r); + str_truncate(ctx->rule, 0); + if (search_args == NULL) { + i_assert(*error_r != NULL); + *error_r = t_strconcat("Previous search rule is invalid: ", + *error_r, NULL); + return -1; + } + + /* update at all the mailboxes that were introduced since the previous + rule. */ + bboxes = array_get(&ctx->mbox->backend_boxes, &count); + i_assert(ctx->rule_idx < count); + for (i = ctx->rule_idx; i < count; i++) { + i_assert(bboxes[i]->search_args == NULL); + mail_search_args_ref(search_args); + bboxes[i]->search_args = search_args; + } + mail_search_args_unref(&search_args); + + ctx->rule_idx = array_count(&ctx->mbox->backend_boxes); + return 0; +} + +static int +virtual_config_parse_line(struct virtual_parse_context *ctx, const char *line, + const char **error_r) +{ + struct mail_user *user = ctx->mbox->storage->storage.user; + struct virtual_backend_box *bbox; + const char *p; + bool no_wildcards = FALSE; + + if (*line == ' ' || *line == '\t') { + /* continues the previous search rule */ + if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) { + *error_r = "Search rule without a mailbox"; + return -1; + } + while (*line == ' ' || *line == '\t') line++; + str_append_c(ctx->rule, ' '); + str_append(ctx->rule, line); + return 0; + } + /* if there is no rule yet, it means we want the previous mailboxes + to use the rule that comes later */ + if (str_len(ctx->rule) > 0) { + if (virtual_config_add_rule(ctx, error_r) < 0) + return -1; + } + if (!uni_utf8_str_is_valid(line)) { + *error_r = t_strdup_printf("Mailbox name not UTF-8: %s", + line); + return -1; + } + + /* new mailbox. the search args are added to it later. */ + bbox = p_new(ctx->pool, struct virtual_backend_box, 1); + bbox->virtual_mbox = ctx->mbox; + if (strcasecmp(line, "INBOX") == 0) + line = "INBOX"; + bbox->name = p_strdup(ctx->pool, line); + switch (bbox->name[0]) { + case '+': + bbox->name++; + bbox->clear_recent = TRUE; + break; + case '-': + bbox->name++; + bbox->negative_match = TRUE; + break; + case '!': + /* save messages here */ + if (ctx->mbox->save_bbox != NULL) { + *error_r = "Multiple save mailboxes defined"; + return -1; + } + bbox->name++; + ctx->mbox->save_bbox = bbox; + no_wildcards = TRUE; + break; + } + if (bbox->name[0] == '/') { + /* [+-!]/metadata entry:value */ + if ((p = strchr(bbox->name, ':')) == NULL) { + *error_r = "':' separator missing between metadata entry name and value"; + return -1; + } + bbox->metadata_entry = p_strdup_until(ctx->pool, bbox->name, p++); + bbox->metadata_value = p; + if (!imap_metadata_verify_entry_name(bbox->metadata_entry, error_r)) + return -1; + no_wildcards = TRUE; + } + + if (!no_wildcards && + (strchr(bbox->name, '*') != NULL || + strchr(bbox->name, '%') != NULL)) { + bbox->glob = imap_match_init(ctx->pool, bbox->name, TRUE, ctx->sep); + ctx->have_wildcards = TRUE; + } + if (bbox->metadata_entry == NULL) { + /* now that the prefix characters have been processed, + find the namespace */ + bbox->ns = strcasecmp(bbox->name, "INBOX") == 0 ? + mail_namespace_find_inbox(user->namespaces) : + mail_namespace_find(user->namespaces, bbox->name); + if (bbox->ns == NULL) { + *error_r = t_strdup_printf("Namespace not found for %s", + bbox->name); + return -1; + } + if (strcmp(bbox->name, ctx->mbox->box.vname) == 0) { + *error_r = "Virtual mailbox can't point to itself"; + return -1; + } + ctx->have_mailbox_defines = TRUE; + } + + array_push_back(&ctx->mbox->backend_boxes, &bbox); + return 0; +} + +static void +virtual_mailbox_get_list_patterns(struct virtual_parse_context *ctx) +{ + struct virtual_mailbox *mbox = ctx->mbox; + ARRAY_TYPE(mailbox_virtual_patterns) *dest; + struct mailbox_virtual_pattern pattern; + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + i_zero(&pattern); + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + p_array_init(&mbox->list_include_patterns, ctx->pool, count); + p_array_init(&mbox->list_exclude_patterns, ctx->pool, count); + for (i = 0; i < count; i++) { + if (bboxes[i]->metadata_entry == NULL) + continue; + pattern.ns = bboxes[i]->ns; + pattern.pattern = bboxes[i]->name; + if (bboxes[i]->negative_match) + dest = &mbox->list_include_patterns; + else { + dest = &mbox->list_exclude_patterns; + pattern.pattern++; + } + array_push_back(dest, &pattern); + } +} + +static void +separate_wildcard_mailboxes(struct virtual_mailbox *mbox, + ARRAY_TYPE(virtual_backend_box) *wildcard_boxes, + ARRAY_TYPE(virtual_backend_box) *neg_boxes, + ARRAY_TYPE(virtual_backend_box) *metadata_boxes) +{ + struct virtual_backend_box *const *bboxes; + ARRAY_TYPE(virtual_backend_box) *dest; + unsigned int i, count; + + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + t_array_init(wildcard_boxes, I_MIN(16, count)); + t_array_init(neg_boxes, 4); + t_array_init(metadata_boxes, 4); + for (i = 0; i < count;) { + if (bboxes[i]->metadata_entry != NULL) + dest = metadata_boxes; + else if (bboxes[i]->negative_match) + dest = neg_boxes; + else if (bboxes[i]->glob != NULL) + dest = wildcard_boxes; + else { + dest = NULL; + i++; + } + + if (dest != NULL) { + array_push_back(dest, &bboxes[i]); + array_delete(&mbox->backend_boxes, i, 1); + bboxes = array_get_modifiable(&mbox->backend_boxes, + &count); + } + } +} + +static void virtual_config_copy_expanded(struct virtual_parse_context *ctx, + struct virtual_backend_box *wbox, + const char *name) +{ + struct virtual_backend_box *bbox; + + bbox = p_new(ctx->pool, struct virtual_backend_box, 1); + *bbox = *wbox; + bbox->name = p_strdup(ctx->pool, name); + bbox->glob = NULL; + bbox->wildcard = TRUE; + mail_search_args_ref(bbox->search_args); + array_push_back(&ctx->mbox->backend_boxes, &bbox); +} + +static bool virtual_ns_match(struct mail_namespace *config_ns, + struct mail_namespace *iter_ns) +{ + /* we match only one namespace for each pattern, except with shared + namespaces match also autocreated children */ + if (config_ns == iter_ns) + return TRUE; + if (config_ns->type == iter_ns->type && + (config_ns->flags & NAMESPACE_FLAG_AUTOCREATED) == 0 && + (iter_ns->flags & NAMESPACE_FLAG_AUTOCREATED) != 0) + return TRUE; + if ((iter_ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 && + (config_ns->flags & NAMESPACE_FLAG_AUTOCREATED) != 0 && + config_ns->prefix_len == 0) { + /* prefix="" namespace was autocreated, so e.g. "*" would match + only that empty namespace. but we want "*" to also match + the inbox=yes namespace, so check it here separately. */ + return TRUE; + } + return FALSE; +} + +static bool virtual_config_match(const struct mailbox_info *info, + ARRAY_TYPE(virtual_backend_box) *boxes_arr, + unsigned int *idx_r) +{ + struct virtual_backend_box *const *boxes; + unsigned int i, count; + + boxes = array_get_modifiable(boxes_arr, &count); + for (i = 0; i < count; i++) { + if (boxes[i]->glob != NULL) { + if (virtual_ns_match(boxes[i]->ns, info->ns) && + imap_match(boxes[i]->glob, + info->vname) == IMAP_MATCH_YES) { + *idx_r = i; + return TRUE; + } + } else { + if (strcmp(boxes[i]->name, info->vname) == 0) { + *idx_r = i; + return TRUE; + } + } + } + return FALSE; +} + +static int virtual_config_box_metadata_match(struct mailbox *box, + struct virtual_backend_box *bbox, + const char **error_r) +{ + struct imap_metadata_transaction *imtrans; + struct mail_attribute_value value; + int ret; + + imtrans = imap_metadata_transaction_begin(box); + ret = imap_metadata_get(imtrans, bbox->metadata_entry, &value); + if (ret < 0) + *error_r = t_strdup(imap_metadata_transaction_get_last_error(imtrans, NULL)); + if (ret > 0) + ret = wildcard_match(value.value, bbox->metadata_value) ? 1 : 0; + if (ret >= 0 && bbox->negative_match) + ret = ret > 0 ? 0 : 1; + (void)imap_metadata_transaction_commit(&imtrans, NULL, NULL); + return ret; +} + +static int +virtual_config_metadata_match(const struct mailbox_info *info, + ARRAY_TYPE(virtual_backend_box) *boxes_arr, + const char **error_r) +{ + struct virtual_backend_box *const *boxes; + struct mailbox *box; + unsigned int i, count; + int ret = 1; + + boxes = array_get_modifiable(boxes_arr, &count); + if (count == 0) + return 1; + + box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY); + for (i = 0; i < count; i++) { + /* break on error or match */ + if ((ret = virtual_config_box_metadata_match(box, boxes[i], error_r)) < 0 || ret > 0) + break; + } + mailbox_free(&box); + return ret; +} + +static int virtual_config_expand_wildcards(struct virtual_parse_context *ctx, + const char **error_r) +{ + const enum mail_namespace_type iter_ns_types = + MAIL_NAMESPACE_TYPE_MASK_ALL; + const enum mailbox_list_iter_flags iter_flags = + MAILBOX_LIST_ITER_RETURN_NO_FLAGS; + struct mail_user *user = ctx->mbox->storage->storage.user; + ARRAY_TYPE(virtual_backend_box) wildcard_boxes, neg_boxes, metadata_boxes; + struct mailbox_list_iterate_context *iter; + struct virtual_backend_box *const *wboxes, *const *boxp; + const char **patterns; + const struct mailbox_info *info; + unsigned int i, j, count; + int ret = 0; + + separate_wildcard_mailboxes(ctx->mbox, &wildcard_boxes, + &neg_boxes, &metadata_boxes); + + /* get patterns we want to list */ + wboxes = array_get_modifiable(&wildcard_boxes, &count); + if (count == 0) { + /* only negative wildcards - doesn't really make sense. + just ignore. */ + return 0; + } + patterns = t_new(const char *, count + 1); + for (i = 0; i < count; i++) + patterns[i] = wboxes[i]->name; + + /* match listed mailboxes to wildcards */ + iter = mailbox_list_iter_init_namespaces(user->namespaces, patterns, + iter_ns_types, iter_flags); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + /* skip non-selectable mailboxes (especially mbox + directories) */ + if ((info->flags & MAILBOX_NOSELECT) != 0) + continue; + if (strcmp(info->vname, ctx->mbox->box.vname) == 0) { + /* don't allow virtual folder to point to itself */ + continue; + } + + if (virtual_config_match(info, &wildcard_boxes, &i) && + !virtual_config_match(info, &neg_boxes, &j) && + virtual_backend_box_lookup_name(ctx->mbox, + info->vname) == NULL) { + ret = virtual_config_metadata_match(info, &metadata_boxes, error_r); + if (ret < 0) + break; + if (ret > 0) { + virtual_config_copy_expanded(ctx, wboxes[i], + info->vname); + } + } + } + for (i = 0; i < count; i++) + mail_search_args_unref(&wboxes[i]->search_args); + array_foreach(&neg_boxes, boxp) + mail_search_args_unref(&(*boxp)->search_args); + array_foreach(&metadata_boxes, boxp) + mail_search_args_unref(&(*boxp)->search_args); + if (mailbox_list_iter_deinit(&iter) < 0) { + *error_r = mailbox_list_get_last_internal_error(user->namespaces->list, NULL); + return -1; + } + return ret < 0 ? -1 : 0; +} + +static void virtual_config_search_args_dup(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box *const *bboxes; + struct mail_search_args *old_args; + unsigned int i, count; + + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + old_args = bboxes[i]->search_args; + bboxes[i]->search_args = mail_search_args_dup(old_args); + mail_search_args_unref(&old_args); + } +} + +int virtual_config_read(struct virtual_mailbox *mbox) +{ + struct mail_storage *storage = mbox->box.storage; + struct virtual_parse_context ctx; + const char *box_path, *path, *line, *error; + unsigned int linenum = 0; + int fd, ret = 0; + + i_array_init(&mbox->backend_boxes, 8); + mbox->search_args_crc32 = (uint32_t)-1; + + box_path = mailbox_get_path(&mbox->box); + path = t_strconcat(box_path, "/"VIRTUAL_CONFIG_FNAME, NULL); + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == EACCES) { + mailbox_set_critical(&mbox->box, "%s", + mail_error_eacces_msg("open", path)); + } else if (errno != ENOENT) { + mailbox_set_critical(&mbox->box, + "open(%s) failed: %m", path); + } else if (errno == ENOENT) { + mail_storage_set_error(storage, MAIL_ERROR_NOTFOUND, + T_MAIL_ERR_MAILBOX_NOT_FOUND(mbox->box.vname)); + } else { + mailbox_set_critical(&mbox->box, + "stat(%s) failed: %m", box_path); + } + return -1; + } + + i_zero(&ctx); + ctx.sep = mail_namespaces_get_root_sep(storage->user->namespaces); + ctx.mbox = mbox; + ctx.pool = mbox->box.pool; + ctx.rule = t_str_new(256); + ctx.input = i_stream_create_fd(fd, SIZE_MAX); + i_stream_set_return_partial_line(ctx.input, TRUE); + while ((line = i_stream_read_next_line(ctx.input)) != NULL) { + linenum++; + if (*line == '#') + continue; + if (*line == '\0') + ret = virtual_config_add_rule(&ctx, &error); + else + ret = virtual_config_parse_line(&ctx, line, &error); + if (ret < 0) { + mailbox_set_critical(&mbox->box, + "%s: Error at line %u: %s", + path, linenum, error); + break; + } + } + if (ret == 0) { + ret = virtual_config_add_rule(&ctx, &error); + if (ret < 0) { + mailbox_set_critical(&mbox->box, + "%s: Error at line %u: %s", + path, linenum, error); + } + } + + virtual_mailbox_get_list_patterns(&ctx); + if (ret == 0 && ctx.have_wildcards) { + struct event_reason *reason = + event_reason_begin("virtual:config_read"); + ret = virtual_config_expand_wildcards(&ctx, &error); + if (ret < 0) + mailbox_set_critical(&mbox->box, "%s: %s", path, error); + event_reason_end(&reason); + } + + if (ret == 0 && !ctx.have_mailbox_defines) { + mailbox_set_critical(&mbox->box, + "%s: No mailboxes defined", path); + ret = -1; + } + if (ret == 0) + virtual_config_search_args_dup(mbox); + i_stream_unref(&ctx.input); + i_close_fd(&fd); + return ret; +} + +void virtual_config_free(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + if (!array_is_created(&mbox->backend_boxes)) { + /* mailbox wasn't opened */ + return; + } + + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->search_args != NULL) + mail_search_args_unref(&bboxes[i]->search_args); + } + array_free(&mbox->backend_boxes); +} diff --git a/src/plugins/virtual/virtual-mail.c b/src/plugins/virtual/virtual-mail.c new file mode 100644 index 0000000..21459ba --- /dev/null +++ b/src/plugins/virtual/virtual-mail.c @@ -0,0 +1,583 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "index-mail.h" +#include "virtual-storage.h" +#include "virtual-transaction.h" + +struct virtual_mail { + struct index_mail imail; + + enum mail_fetch_field wanted_fields; + struct mailbox_header_lookup_ctx *wanted_headers; + + /* temp_wanted_fields for this mail. Used only when mail doesn't have + a backend mail yet. */ + enum mail_fetch_field delayed_temp_fields; + struct mailbox_header_lookup_ctx *delayed_temp_headers; + + /* currently active mail */ + struct mail *cur_backend_mail; + struct virtual_mail_index_record cur_vrec; + + /* all allocated mails */ + ARRAY(struct mail *) backend_mails; + + /* mail is lost if backend_mail doesn't point to correct mail */ + bool cur_lost:1; +}; + +struct mail * +virtual_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)t->box; + struct virtual_mail *vmail; + pool_t mail_pool, data_pool; + + mail_pool = pool_alloconly_create("vmail", 1024); + data_pool = pool_alloconly_create("virtual index_mail", 512); + vmail = p_new(mail_pool, struct virtual_mail, 1); + vmail->wanted_fields = wanted_fields; + vmail->wanted_headers = wanted_headers; + if (vmail->wanted_headers != NULL) + mailbox_header_lookup_ref(vmail->wanted_headers); + /* Do not pass wanted_fields or wanted_headers to index_mail_init. + It will just cause unwanted behaviour, as we only want these + to be passed to backend mails. */ + index_mail_init(&vmail->imail, t, 0, NULL, mail_pool, data_pool); + vmail->imail.mail.v = virtual_mail_vfuncs; + i_array_init(&vmail->backend_mails, array_count(&mbox->backend_boxes)); + return &vmail->imail.mail.mail; +} + +static void virtual_mail_close(struct mail *mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail **mails; + unsigned int i, count; + + if (mail->seq != 0) { + mailbox_header_lookup_unref(&vmail->delayed_temp_headers); + vmail->delayed_temp_fields = 0; + } + + mails = array_get_modifiable(&vmail->backend_mails, &count); + for (i = 0; i < count; i++) { + struct mail_private *p = (struct mail_private *)mails[i]; + + if (vmail->imail.freeing) + mail_free(&mails[i]); + else + p->v.close(mails[i]); + } + if (vmail->imail.freeing) { + array_free(&vmail->backend_mails); + mailbox_header_lookup_unref(&vmail->wanted_headers); + } + index_mail_close(mail); +} + +static struct mail * +backend_mail_find(struct virtual_mail *vmail, struct mailbox *box) +{ + struct mail *const *mails; + unsigned int i, count; + + mails = array_get(&vmail->backend_mails, &count); + for (i = 0; i < count; i++) { + if (mails[i]->box == box) + return mails[i]; + } + return NULL; +} + +static int backend_mail_get(struct virtual_mail *vmail, + struct mail **backend_mail_r) +{ + struct mail *mail = &vmail->imail.mail.mail; + struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box; + struct virtual_backend_box *bbox; + + *backend_mail_r = NULL; + + if (vmail->cur_backend_mail != NULL) { + if (vmail->cur_lost) { + mail_set_expunged(&vmail->imail.mail.mail); + return -1; + } + *backend_mail_r = vmail->cur_backend_mail; + return 0; + } + + bbox = virtual_backend_box_lookup(mbox, vmail->cur_vrec.mailbox_id); + i_assert(bbox != NULL); + + vmail->cur_backend_mail = backend_mail_find(vmail, bbox->box); + if (vmail->cur_backend_mail == NULL) { + if (!bbox->box->opened && + virtual_backend_box_open(mbox, bbox) < 0) { + virtual_box_copy_error(mail->box, bbox->box); + return -1; + } + (void)virtual_mail_set_backend_mail(mail, bbox); + i_assert(vmail->cur_backend_mail != NULL); + } + virtual_backend_box_accessed(mbox, bbox); + vmail->cur_lost = !mail_set_uid(vmail->cur_backend_mail, + vmail->cur_vrec.real_uid); + mail->expunged = vmail->cur_lost || vmail->cur_backend_mail->expunged; + if (vmail->cur_lost) { + mail_set_expunged(&vmail->imail.mail.mail); + return -1; + } + /* headers need to be converted to backend-headers, so go through + the virtual add_temp_wanted_fields() again. */ + mail_add_temp_wanted_fields(mail, vmail->delayed_temp_fields, + vmail->delayed_temp_headers); + *backend_mail_r = vmail->cur_backend_mail; + return 0; +} + +struct mail * +virtual_mail_set_backend_mail(struct mail *mail, + struct virtual_backend_box *bbox) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail_private *backend_pmail; + struct mailbox_transaction_context *backend_trans; + struct mailbox_header_lookup_ctx *backend_headers; + + i_assert(bbox->box->opened); + + backend_trans = virtual_transaction_get(mail->transaction, bbox->box); + + backend_headers = vmail->wanted_headers == NULL ? NULL : + mailbox_header_lookup_init(bbox->box, + vmail->wanted_headers->name); + vmail->cur_backend_mail = + mail_alloc(backend_trans, vmail->wanted_fields, backend_headers); + mailbox_header_lookup_unref(&backend_headers); + + backend_pmail = (struct mail_private *)vmail->cur_backend_mail; + backend_pmail->vmail = mail; + array_push_back(&vmail->backend_mails, &vmail->cur_backend_mail); + return vmail->cur_backend_mail; +} + +void virtual_mail_set_unattached_backend_mail(struct mail *mail, + struct mail *backend_mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail_private *backend_pmail; + + vmail->cur_backend_mail = backend_mail; + + backend_pmail = (struct mail_private *)backend_mail; + backend_pmail->vmail = mail; +} + +static void virtual_mail_set_seq(struct mail *mail, uint32_t seq, bool saving) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box; + const void *data; + + i_assert(!saving); + + mail_index_lookup_ext(mail->transaction->view, seq, + mbox->virtual_ext_id, &data, NULL); + memcpy(&vmail->cur_vrec, data, sizeof(vmail->cur_vrec)); + + index_mail_set_seq(mail, seq, saving); + + vmail->cur_backend_mail = NULL; +} + +static bool virtual_mail_set_uid(struct mail *mail, uint32_t uid) +{ + uint32_t seq; + + if (!mail_index_lookup_seq(mail->transaction->view, uid, &seq)) + return FALSE; + + virtual_mail_set_seq(mail, seq, FALSE); + return TRUE; +} + +static void virtual_mail_set_uid_cache_updates(struct mail *mail, bool set) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + p = (struct mail_private *)backend_mail; + p->v.set_uid_cache_updates(backend_mail, set); +} + +static bool virtual_mail_prefetch(struct mail *mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return TRUE; + p = (struct mail_private *)backend_mail; + return p->v.prefetch(backend_mail); +} + +static int virtual_mail_precache(struct mail *mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + p = (struct mail_private *)backend_mail; + return p->v.precache(backend_mail); +} + +static void +virtual_mail_add_temp_wanted_fields(struct mail *mail, + enum mail_fetch_field fields, + struct mailbox_header_lookup_ctx *headers) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mailbox_header_lookup_ctx *backend_headers, *new_headers; + + if (mail->seq == 0) { + /* No mail set yet. Delay until it is set. */ + vmail->delayed_temp_fields |= fields; + if (vmail->delayed_temp_headers == NULL) + vmail->delayed_temp_headers = headers; + else { + new_headers = mailbox_header_lookup_merge( + vmail->delayed_temp_headers, headers); + mailbox_header_lookup_unref(&vmail->delayed_temp_headers); + vmail->delayed_temp_headers = new_headers; + } + return; + } + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + /* convert header indexes to backend mailbox's header indexes */ + backend_headers = headers == NULL ? NULL : + mailbox_header_lookup_init(backend_mail->box, headers->name); + mail_add_temp_wanted_fields(backend_mail, fields, backend_headers); + mailbox_header_lookup_unref(&backend_headers); +} + +static int +virtual_mail_get_parts(struct mail *mail, struct message_part **parts_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_parts(backend_mail, parts_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + int tz; + + if (timezone_r == NULL) + timezone_r = &tz; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_date(backend_mail, date_r, timezone_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int virtual_mail_get_received_date(struct mail *mail, time_t *date_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_received_date(backend_mail, date_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int virtual_mail_get_save_date(struct mail *mail, time_t *date_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + int ret; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + ret = mail_get_save_date(backend_mail, date_r); + if (ret < 0) + virtual_box_copy_error(mail->box, backend_mail->box); + return ret; +} + +static int virtual_mail_get_virtual_mail_size(struct mail *mail, uoff_t *size_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_virtual_size(backend_mail, size_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int virtual_mail_get_physical_size(struct mail *mail, uoff_t *size_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_physical_size(backend_mail, size_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_first_header(struct mail *mail, const char *field, + bool decode_to_utf8, const char **value_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + int ret; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + p = (struct mail_private *)backend_mail; + ret = p->v.get_first_header(backend_mail, field, + decode_to_utf8, value_r); + if (ret < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return ret; +} + +static int +virtual_mail_get_headers(struct mail *mail, const char *field, + bool decode_to_utf8, const char *const **value_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + p = (struct mail_private *)backend_mail; + if (p->v.get_headers(backend_mail, field, decode_to_utf8, value_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_header_stream(struct mail *mail, + struct mailbox_header_lookup_ctx *headers, + struct istream **stream_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mailbox_header_lookup_ctx *backend_headers; + int ret; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + + backend_headers = mailbox_header_lookup_init(backend_mail->box, + headers->name); + ret = mail_get_header_stream(backend_mail, backend_headers, stream_r); + mailbox_header_lookup_unref(&backend_headers); + if (ret < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_stream(struct mail *mail, bool get_body, + struct message_size *hdr_size, + struct message_size *body_size, + struct istream **stream_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail_private *vp = (struct mail_private *)mail; + struct mail *backend_mail; + const char *reason = t_strdup_printf("virtual mailbox %s: Opened mail UID=%u: %s", + mailbox_get_vname(mail->box), mail->uid, vp->get_stream_reason); + int ret; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + + if (get_body) { + ret = mail_get_stream_because(backend_mail, hdr_size, body_size, + reason, stream_r); + } else { + ret = mail_get_hdr_stream_because(backend_mail, hdr_size, + reason, stream_r); + } + + if (ret < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_binary_stream(struct mail *mail, + const struct message_part *part, + bool include_hdr, uoff_t *size_r, + unsigned int *lines_r, bool *binary_r, + struct istream **stream_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + + struct mail_private *p = (struct mail_private *)backend_mail; + if (p->v.get_binary_stream(backend_mail, part, include_hdr, + size_r, lines_r, binary_r, stream_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_special(struct mail *mail, enum mail_fetch_field field, + const char **value_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_special(backend_mail, field, value_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int virtual_mail_get_backend_mail(struct mail *mail, + struct mail **real_mail_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + + if (mail_get_backend_mail(backend_mail, real_mail_r) < 0) + return -1; + return 0; +} + +static void virtual_mail_update_pop3_uidl(struct mail *mail, const char *uidl) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + mail_update_pop3_uidl(backend_mail, uidl); +} + +static void virtual_mail_expunge(struct mail *mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + mail_expunge(backend_mail); +} + +static void +virtual_mail_set_cache_corrupted(struct mail *mail, + enum mail_fetch_field field, + const char *reason) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + mail_set_cache_corrupted(backend_mail, field, reason); +} + +struct mail_vfuncs virtual_mail_vfuncs = { + virtual_mail_close, + index_mail_free, + virtual_mail_set_seq, + virtual_mail_set_uid, + virtual_mail_set_uid_cache_updates, + virtual_mail_prefetch, + virtual_mail_precache, + virtual_mail_add_temp_wanted_fields, + + index_mail_get_flags, + index_mail_get_keywords, + index_mail_get_keyword_indexes, + index_mail_get_modseq, + index_mail_get_pvt_modseq, + virtual_mail_get_parts, + virtual_mail_get_date, + virtual_mail_get_received_date, + virtual_mail_get_save_date, + virtual_mail_get_virtual_mail_size, + virtual_mail_get_physical_size, + virtual_mail_get_first_header, + virtual_mail_get_headers, + virtual_mail_get_header_stream, + virtual_mail_get_stream, + virtual_mail_get_binary_stream, + virtual_mail_get_special, + virtual_mail_get_backend_mail, + index_mail_update_flags, + index_mail_update_keywords, + index_mail_update_modseq, + index_mail_update_pvt_modseq, + virtual_mail_update_pop3_uidl, + virtual_mail_expunge, + virtual_mail_set_cache_corrupted, + NULL, +}; diff --git a/src/plugins/virtual/virtual-plugin.c b/src/plugins/virtual/virtual-plugin.c new file mode 100644 index 0000000..43db7cb --- /dev/null +++ b/src/plugins/virtual/virtual-plugin.c @@ -0,0 +1,25 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-namespace.h" +#include "virtual-storage.h" +#include "virtual-plugin.h" + +const char *virtual_plugin_version = DOVECOT_ABI_VERSION; + +static struct mail_storage_hooks acl_mail_storage_hooks = { + .mailbox_allocated = virtual_backend_mailbox_allocated, + .mailbox_opened = virtual_backend_mailbox_opened +}; + +void virtual_plugin_init(struct module *module ATTR_UNUSED) +{ + mail_storage_class_register(&virtual_storage); + mail_storage_hooks_add(module, &acl_mail_storage_hooks); +} + +void virtual_plugin_deinit(void) +{ + mail_storage_class_unregister(&virtual_storage); + mail_storage_hooks_remove(&acl_mail_storage_hooks); +} diff --git a/src/plugins/virtual/virtual-plugin.h b/src/plugins/virtual/virtual-plugin.h new file mode 100644 index 0000000..39f04bb --- /dev/null +++ b/src/plugins/virtual/virtual-plugin.h @@ -0,0 +1,7 @@ +#ifndef VIRTUAL_PLUGIN_H +#define VIRTUAL_PLUGIN_H + +void virtual_plugin_init(struct module *module); +void virtual_plugin_deinit(void); + +#endif diff --git a/src/plugins/virtual/virtual-save.c b/src/plugins/virtual/virtual-save.c new file mode 100644 index 0000000..9499cd3 --- /dev/null +++ b/src/plugins/virtual/virtual-save.c @@ -0,0 +1,153 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "virtual-transaction.h" +#include "virtual-storage.h" + +struct virtual_save_context { + struct mail_save_context ctx; + struct mail_save_context *backend_save_ctx; + struct mailbox *backend_box; + char *open_errstr; + enum mail_error open_error; +}; + +struct mail_save_context * +virtual_save_alloc(struct mailbox_transaction_context *_t) +{ + struct virtual_transaction_context *t = + (struct virtual_transaction_context *)_t; + struct virtual_mailbox *mbox = (struct virtual_mailbox *)_t->box; + struct mailbox_transaction_context *backend_trans; + struct virtual_save_context *ctx; + const char *errstr; + + if (_t->save_ctx == NULL) { + ctx = i_new(struct virtual_save_context, 1); + ctx->ctx.transaction = &t->t; + _t->save_ctx = &ctx->ctx; + } else { + ctx = (struct virtual_save_context *)_t->save_ctx; + } + + if (mbox->save_bbox != NULL) { + i_assert(ctx->backend_save_ctx == NULL); + i_assert(ctx->open_errstr == NULL); + + if (!mbox->save_bbox->box->opened && + virtual_backend_box_open(mbox, mbox->save_bbox) < 0) { + errstr = mailbox_get_last_error(mbox->save_bbox->box, + &ctx->open_error); + ctx->open_errstr = i_strdup(errstr); + } else { + backend_trans = + virtual_transaction_get(_t, mbox->save_bbox->box); + ctx->backend_save_ctx = mailbox_save_alloc(backend_trans); + } + virtual_backend_box_accessed(mbox, mbox->save_bbox); + } + return _t->save_ctx; +} + +static struct mail_keywords * +virtual_copy_keywords(struct mailbox *src_box, + const struct mail_keywords *src_keywords, + struct mailbox *dest_box) +{ + struct mailbox_status status; + ARRAY_TYPE(keywords) kw_strings; + const char *kw; + unsigned int i; + + if (src_keywords == NULL || src_keywords->count == 0) + return NULL; + + t_array_init(&kw_strings, src_keywords->count + 1); + mailbox_get_open_status(src_box, STATUS_KEYWORDS, &status); + + for (i = 0; i < src_keywords->count; i++) { + kw = array_idx_elem(status.keywords, src_keywords->idx[i]); + array_push_back(&kw_strings, &kw); + } + array_append_zero(&kw_strings); + return mailbox_keywords_create_valid(dest_box, + array_front(&kw_strings)); +} + +int virtual_save_begin(struct mail_save_context *_ctx, struct istream *input) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + struct mail_save_data *mdata = &_ctx->data; + struct mail_keywords *keywords; + + if (ctx->backend_save_ctx == NULL) { + if (ctx->open_errstr != NULL) { + /* mailbox_open() failed */ + mail_storage_set_error(_ctx->transaction->box->storage, + ctx->open_error, ctx->open_errstr); + } else { + mail_storage_set_error(_ctx->transaction->box->storage, + MAIL_ERROR_NOTPOSSIBLE, + "Can't save messages to this virtual mailbox"); + } + return -1; + } + + ctx->backend_box = ctx->backend_save_ctx->transaction->box; + keywords = virtual_copy_keywords(_ctx->transaction->box, mdata->keywords, + ctx->backend_box); + mailbox_save_set_flags(ctx->backend_save_ctx, + mdata->flags | mdata->pvt_flags, + keywords); + if (keywords != NULL) + mail_index_keywords_unref(&keywords); + + mailbox_save_set_received_date(ctx->backend_save_ctx, + mdata->received_date, + mdata->received_tz_offset); + mailbox_save_set_from_envelope(ctx->backend_save_ctx, + mdata->from_envelope); + mailbox_save_set_guid(ctx->backend_save_ctx, mdata->guid); + mailbox_save_set_min_modseq(ctx->backend_save_ctx, mdata->min_modseq); + + virtual_mail_set_unattached_backend_mail(_ctx->dest_mail, + ctx->backend_save_ctx->dest_mail); + return mailbox_save_begin(&ctx->backend_save_ctx, input); +} + +int virtual_save_continue(struct mail_save_context *_ctx) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + + return mailbox_save_continue(ctx->backend_save_ctx); +} + +int virtual_save_finish(struct mail_save_context *_ctx) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + int ret; + ret = mailbox_save_finish(&ctx->backend_save_ctx); + index_save_context_free(_ctx); + + return ret; +} + +void virtual_save_cancel(struct mail_save_context *_ctx) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + + if (ctx->backend_save_ctx != NULL) + mailbox_save_cancel(&ctx->backend_save_ctx); + i_free_and_null(ctx->open_errstr); + _ctx->unfinished = FALSE; +} + +void virtual_save_free(struct mail_save_context *_ctx) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + + virtual_save_cancel(_ctx); + mailbox_save_context_deinit(_ctx); + i_free(ctx); +} diff --git a/src/plugins/virtual/virtual-search.c b/src/plugins/virtual/virtual-search.c new file mode 100644 index 0000000..ee04a74 --- /dev/null +++ b/src/plugins/virtual/virtual-search.c @@ -0,0 +1,206 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "mail-search.h" +#include "index-search-private.h" +#include "virtual-storage.h" + + +enum virtual_search_state { + VIRTUAL_SEARCH_STATE_BUILD, + VIRTUAL_SEARCH_STATE_RETURN, + VIRTUAL_SEARCH_STATE_SORT, + VIRTUAL_SEARCH_STATE_SORT_DONE +}; + +struct virtual_search_record { + uint32_t mailbox_id; + uint32_t real_uid; + uint32_t virtual_seq; +}; + +struct virtual_search_context { + union mail_search_module_context module_ctx; + + ARRAY_TYPE(seq_range) result; + struct seq_range_iter result_iter; + ARRAY(struct virtual_search_record) records; + + enum virtual_search_state search_state; + unsigned int next_result_n; + unsigned int next_record_idx; +}; + +static int virtual_search_record_cmp(const struct virtual_search_record *r1, + const struct virtual_search_record *r2) +{ + if (r1->mailbox_id < r2->mailbox_id) + return -1; + if (r1->mailbox_id > r2->mailbox_id) + return 1; + + if (r1->real_uid < r2->real_uid) + return -1; + if (r1->real_uid > r2->real_uid) + return 1; + return 0; +} + +static int mail_search_get_result(struct mail_search_context *ctx) +{ + const struct mail_search_arg *arg; + int ret = 1; + + for (arg = ctx->args->args; arg != NULL; arg = arg->next) { + if (arg->result < 0) + return -1; + if (arg->result == 0) + ret = 0; + } + return ret; +} + +static void virtual_search_get_records(struct mail_search_context *ctx, + struct virtual_search_context *vctx) +{ + struct virtual_mailbox *mbox = + (struct virtual_mailbox *)ctx->transaction->box; + const struct virtual_mail_index_record *vrec; + struct virtual_search_record srec; + const void *data; + int result; + + i_zero(&srec); + while (index_storage_search_next_update_seq(ctx)) { + result = mail_search_get_result(ctx); + i_assert(result != 0); + if (result > 0) { + /* full match, no need to check this any further */ + seq_range_array_add(&vctx->result, ctx->seq); + } else { + /* possible match, save and check later */ + mail_index_lookup_ext(mbox->box.view, ctx->seq, + mbox->virtual_ext_id, + &data, NULL); + vrec = data; + + srec.mailbox_id = vrec->mailbox_id; + srec.real_uid = vrec->real_uid; + srec.virtual_seq = ctx->seq; + array_push_back(&vctx->records, &srec); + } + mail_search_args_reset(ctx->args->args, FALSE); + } + array_sort(&vctx->records, virtual_search_record_cmp); + + ctx->progress_max = array_count(&vctx->records); +} + +struct mail_search_context * +virtual_search_init(struct mailbox_transaction_context *t, + struct mail_search_args *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct mail_search_context *ctx; + struct virtual_search_context *vctx; + + ctx = index_storage_search_init(t, args, sort_program, + wanted_fields, wanted_headers); + + vctx = i_new(struct virtual_search_context, 1); + vctx->search_state = VIRTUAL_SEARCH_STATE_BUILD; + i_array_init(&vctx->result, 64); + i_array_init(&vctx->records, 64); + MODULE_CONTEXT_SET(ctx, virtual_storage_module, vctx); + + virtual_search_get_records(ctx, vctx); + seq_range_array_iter_init(&vctx->result_iter, &vctx->result); + return ctx; +} + +int virtual_search_deinit(struct mail_search_context *ctx) +{ + struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx); + + array_free(&vctx->result); + array_free(&vctx->records); + i_free(vctx); + return index_storage_search_deinit(ctx); +} + +bool virtual_search_next_nonblock(struct mail_search_context *ctx, + struct mail **mail_r, bool *tryagain_r) +{ + struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx); + struct index_search_context *ictx = (struct index_search_context *)ctx; + uint32_t seq; + + switch (vctx->search_state) { + case VIRTUAL_SEARCH_STATE_BUILD: + if (ctx->sort_program == NULL) + vctx->search_state = VIRTUAL_SEARCH_STATE_SORT; + else + vctx->search_state = VIRTUAL_SEARCH_STATE_RETURN; + return virtual_search_next_nonblock(ctx, mail_r, tryagain_r); + case VIRTUAL_SEARCH_STATE_RETURN: + return index_storage_search_next_nonblock(ctx, mail_r, tryagain_r); + case VIRTUAL_SEARCH_STATE_SORT: + /* the messages won't be returned sorted, so we'll have to + do it ourself */ + while (index_storage_search_next_nonblock(ctx, mail_r, tryagain_r)) + seq_range_array_add(&vctx->result, (*mail_r)->seq); + if (*tryagain_r) + return FALSE; + + vctx->next_result_n = 0; + vctx->search_state = VIRTUAL_SEARCH_STATE_SORT_DONE; + /* fall through */ + case VIRTUAL_SEARCH_STATE_SORT_DONE: + *tryagain_r = FALSE; + if (!seq_range_array_iter_nth(&vctx->result_iter, + vctx->next_result_n, &seq)) + return FALSE; + vctx->next_result_n++; + *mail_r = index_search_get_mail(ictx); + i_assert(*mail_r != NULL); + mail_set_seq(*mail_r, seq); + return TRUE; + } + i_unreached(); +} + +static void search_args_set_full_match(struct mail_search_arg *args) +{ + for (; args != NULL; args = args->next) + args->result = 1; +} + +bool virtual_search_next_update_seq(struct mail_search_context *ctx) +{ + struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx); + const struct virtual_search_record *recs; + unsigned int count; + + recs = array_get(&vctx->records, &count); + if (vctx->next_record_idx < count) { + /* go through potential results first */ + ctx->seq = recs[vctx->next_record_idx++].virtual_seq - 1; + if (!index_storage_search_next_update_seq(ctx)) + i_unreached(); + ctx->progress_cur = vctx->next_record_idx; + return TRUE; + } + + if (ctx->sort_program != NULL && + seq_range_array_iter_nth(&vctx->result_iter, + vctx->next_result_n, &ctx->seq)) { + /* this is known to match fully */ + search_args_set_full_match(ctx->args->args); + vctx->next_result_n++; + return TRUE; + } + return FALSE; +} diff --git a/src/plugins/virtual/virtual-storage.c b/src/plugins/virtual/virtual-storage.c new file mode 100644 index 0000000..f88a279 --- /dev/null +++ b/src/plugins/virtual/virtual-storage.c @@ -0,0 +1,950 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "str.h" +#include "llist.h" +#include "mkdir-parents.h" +#include "unlink-directory.h" +#include "index-mail.h" +#include "mail-copy.h" +#include "mail-search.h" +#include "mailbox-list-private.h" +#include "virtual-plugin.h" +#include "virtual-transaction.h" +#include "virtual-storage.h" +#include "mailbox-list-notify.h" + +#include <stdio.h> +#include <unistd.h> +#include <dirent.h> +#include <sys/stat.h> + +#define VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES 64 + +#define VIRTUAL_BACKEND_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, virtual_backend_storage_module) + +struct virtual_backend_mailbox { + union mailbox_module_context module_ctx; +}; + +extern struct mail_storage virtual_storage; +extern struct mailbox virtual_mailbox; +extern struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs; + +struct virtual_storage_module virtual_storage_module = + MODULE_CONTEXT_INIT(&mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(virtual_backend_storage_module, + &mail_storage_module_register); + +static bool ns_is_visible(struct mail_namespace *ns) +{ + return (ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0 || + (ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0 || + (ns->flags & NAMESPACE_FLAG_HIDDEN) == 0; +} + +static const char *get_user_visible_mailbox_name(struct mailbox *box) +{ + if (ns_is_visible(box->list->ns)) + return box->vname; + else { + return t_strdup_printf("<hidden>%c%s", + mail_namespace_get_sep(box->list->ns), + box->vname); + } +} + +void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src) +{ + const char *name, *str; + enum mail_error error; + + name = get_user_visible_mailbox_name(src); + str = mailbox_get_last_error(src, &error); + + str = t_strdup_printf("%s (for backend mailbox %s)", str, name); + mail_storage_set_error(dest->storage, error, str); +} + +static struct mail_storage *virtual_storage_alloc(void) +{ + struct virtual_storage *storage; + pool_t pool; + + pool = pool_alloconly_create("virtual storage", 1024); + storage = p_new(pool, struct virtual_storage, 1); + storage->storage = virtual_storage; + storage->storage.pool = pool; + p_array_init(&storage->open_stack, pool, 8); + return &storage->storage; +} + +static int +virtual_storage_create(struct mail_storage *_storage, + struct mail_namespace *ns ATTR_UNUSED, + const char **error_r) +{ + struct virtual_storage *storage = (struct virtual_storage *)_storage; + const char *value; + + value = mail_user_plugin_getenv(_storage->user, "virtual_max_open_mailboxes"); + if (value == NULL) + storage->max_open_mailboxes = VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES; + else if (str_to_uint(value, &storage->max_open_mailboxes) < 0) { + *error_r = "Invalid virtual_max_open_mailboxes setting"; + return -1; + } + return 0; +} + +static void +virtual_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED, + struct mailbox_list_settings *set) +{ + if (set->layout == NULL) + set->layout = MAILBOX_LIST_NAME_FS; + if (set->subscription_fname == NULL) + set->subscription_fname = VIRTUAL_SUBSCRIPTION_FILE_NAME; +} + +struct virtual_backend_box * +virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (strcmp(bboxes[i]->name, name) == 0) + return bboxes[i]; + } + return NULL; +} + +struct virtual_backend_box * +virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + if (mailbox_id == 0) + return NULL; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->mailbox_id == mailbox_id) + return bboxes[i]; + } + return NULL; +} + +static bool virtual_mailbox_is_in_open_stack(struct virtual_storage *storage, + const char *name) +{ + const char *const *names; + unsigned int i, count; + + names = array_get(&storage->open_stack, &count); + for (i = 0; i < count; i++) { + if (strcmp(names[i], name) == 0) + return TRUE; + } + return FALSE; +} + +static int virtual_backend_box_open_failed(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox) +{ + enum mail_error error; + const char *str; + + str = t_strdup_printf( + "Virtual mailbox open failed because of mailbox %s: %s", + get_user_visible_mailbox_name(bbox->box), + mailbox_get_last_error(bbox->box, &error)); + mail_storage_set_error(mbox->box.storage, error, str); + mailbox_free(&bbox->box); + + if (error == MAIL_ERROR_PERM && bbox->wildcard) { + /* this mailbox wasn't explicitly specified. just skip it. */ + return 0; + } + return -1; +} + +static int virtual_backend_box_alloc(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox, + enum mailbox_flags flags) +{ + struct mail_user *user = mbox->storage->storage.user; + struct mail_namespace *ns; + const char *mailbox; + enum mailbox_existence existence; + + i_assert(bbox->box == NULL); + + if (!bbox->clear_recent) + flags &= ENUM_NEGATE(MAILBOX_FLAG_DROP_RECENT); + + mailbox = bbox->name; + ns = mail_namespace_find(user->namespaces, mailbox); + bbox->box = mailbox_alloc(ns->list, mailbox, flags); + MODULE_CONTEXT_SET(bbox->box, virtual_storage_module, bbox); + + if (bbox == mbox->save_bbox) { + /* Assume that the save_bbox exists, whether or not it truly + does. This at least gives a better error message than crash + later on. */ + existence = MAILBOX_EXISTENCE_SELECT; + } else { + if (mailbox_exists(bbox->box, TRUE, &existence) < 0) + return virtual_backend_box_open_failed(mbox, bbox); + } + if (existence != MAILBOX_EXISTENCE_SELECT) { + /* ignore this. it could be intentional. */ + e_debug(mbox->box.event, + "Skipping non-existing mailbox %s", + bbox->box->vname); + mailbox_free(&bbox->box); + return 0; + } + + i_array_init(&bbox->uids, 64); + i_array_init(&bbox->sync_pending_removes, 64); + /* we use modseqs for being able to check quickly if backend mailboxes + have changed. make sure the backend has them enabled. */ + (void)mailbox_enable(bbox->box, MAILBOX_FEATURE_CONDSTORE); + return 1; +} + +static int virtual_mailboxes_open(struct virtual_mailbox *mbox, + enum mailbox_flags flags) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + int ret; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; ) { + ret = virtual_backend_box_alloc(mbox, bboxes[i], flags); + if (ret <= 0) { + if (ret < 0) + break; + array_delete(&mbox->backend_boxes, i, 1); + bboxes = array_get(&mbox->backend_boxes, &count); + } else { + i++; + } + } + if (i == count) + return 0; + else { + /* failed */ + for (; i > 0; i--) { + mailbox_free(&bboxes[i-1]->box); + array_free(&bboxes[i-1]->uids); + } + return -1; + } +} + +static struct mailbox * +virtual_mailbox_alloc(struct mail_storage *_storage, struct mailbox_list *list, + const char *vname, enum mailbox_flags flags) +{ + struct virtual_storage *storage = (struct virtual_storage *)_storage; + struct virtual_mailbox *mbox; + pool_t pool; + + pool = pool_alloconly_create("virtual mailbox", 2048); + mbox = p_new(pool, struct virtual_mailbox, 1); + mbox->box = virtual_mailbox; + mbox->box.pool = pool; + mbox->box.storage = _storage; + mbox->box.list = list; + mbox->box.mail_vfuncs = &virtual_mail_vfuncs; + mbox->box.virtual_vfuncs = &virtual_mailbox_vfuncs; + + index_storage_mailbox_alloc(&mbox->box, vname, flags, MAIL_INDEX_PREFIX); + + mbox->storage = storage; + mbox->virtual_ext_id = (uint32_t)-1; + mbox->virtual_ext2_id = (uint32_t)-1; + mbox->virtual_guid_ext_id = (uint32_t)-1; + return &mbox->box; +} + +void virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox) +{ + struct mailbox_transaction_context *trans; + + if (bbox->sync_mail != NULL) { + trans = bbox->sync_mail->transaction; + mail_free(&bbox->sync_mail); + (void)mailbox_transaction_commit(&trans); + } +} + +static bool virtual_backend_box_can_close(struct virtual_backend_box *bbox) +{ + if (bbox->box->notify_callback != NULL) { + /* we can close it if notify is set + because we have no need to keep it open + for tracking changes */ + return bbox->notify != NULL; + } + if (array_count(&bbox->sync_pending_removes) > 0) { + /* FIXME: we could probably close this by making + syncing support it? */ + return FALSE; + } + return TRUE; +} + +static bool +virtual_backend_box_close_any_except(struct virtual_mailbox *mbox, + struct virtual_backend_box *except_bbox) +{ + struct virtual_backend_box *bbox; + + /* first try to close a mailbox without any transactions. + we'll also skip any mailbox that has notifications enabled (ideally + these would be handled by mailbox list index) */ + for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) { + i_assert(bbox->box->opened); + + if (bbox != except_bbox && + bbox->box->transaction_count == 0 && + virtual_backend_box_can_close(bbox)) { + i_assert(bbox->sync_mail == NULL); + virtual_backend_box_close(mbox, bbox); + return TRUE; + } + } + + /* next try to close a mailbox that has sync_mail, but no + other transactions */ + for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) { + if (bbox != except_bbox && + bbox->sync_mail != NULL && + bbox->box->transaction_count == 1 && + virtual_backend_box_can_close(bbox)) { + virtual_backend_box_sync_mail_unset(bbox); + i_assert(bbox->box->transaction_count == 0); + virtual_backend_box_close(mbox, bbox); + return TRUE; + } + } + return FALSE; +} + +static void virtual_backend_mailbox_close(struct mailbox *box) +{ + struct virtual_backend_box *bbox = VIRTUAL_CONTEXT(box); + struct virtual_backend_mailbox *vbox = VIRTUAL_BACKEND_CONTEXT(box); + + if (bbox != NULL && bbox->open_tracked) { + /* we could have gotten here from e.g. mailbox_autocreate() + without going through virtual_mailbox_close() */ + virtual_backend_box_close(bbox->virtual_mbox, bbox); + } + vbox->module_ctx.super.close(box); +} + +void virtual_backend_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct virtual_backend_mailbox *vbox; + + vbox = p_new(box->pool, struct virtual_backend_mailbox, 1); + vbox->module_ctx.super = *v; + box->vlast = &vbox->module_ctx.super; + v->close = virtual_backend_mailbox_close; + MODULE_CONTEXT_SET(box, virtual_backend_storage_module, vbox); +} + +void virtual_backend_mailbox_opened(struct mailbox *box) +{ + struct virtual_backend_box *bbox = VIRTUAL_CONTEXT(box); + struct virtual_mailbox *mbox; + + if (bbox == NULL) { + /* not a backend for a virtual mailbox */ + return; + } + i_assert(!bbox->open_tracked); + mbox = bbox->virtual_mbox; + + /* the backend mailbox was already opened. if we didn't get here + from virtual_backend_box_open() we may need to close a mailbox */ + while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes && + virtual_backend_box_close_any_except(mbox, bbox)) + ; + + bbox->open_tracked = TRUE; + mbox->backends_open_count++; + DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head, + &mbox->open_backend_boxes_tail, bbox, + prev_open, next_open); +} + +int virtual_backend_box_open(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox) +{ + i_assert(!bbox->box->opened); + + /* try to keep the number of open mailboxes below the threshold + before opening the mailbox */ + while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes && + virtual_backend_box_close_any_except(mbox, bbox)) + ; + + return mailbox_open(bbox->box); +} + +void virtual_backend_box_close(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox) +{ + i_assert(bbox->box->opened); + i_assert(bbox->open_tracked); + + if (bbox->search_result != NULL) + mailbox_search_result_free(&bbox->search_result); + + if (bbox->search_args != NULL && + bbox->search_args_initialized) { + mail_search_args_deinit(bbox->search_args); + bbox->search_args_initialized = FALSE; + } + i_assert(mbox->backends_open_count > 0); + mbox->backends_open_count--; + bbox->open_tracked = FALSE; + + DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head, + &mbox->open_backend_boxes_tail, bbox, + prev_open, next_open); + + /* stop receiving notifications */ + if (bbox->notify_changes_started) + mailbox_notify_changes_stop(bbox->box); + bbox->notify_changes_started = FALSE; + + mailbox_close(bbox->box); +} + +void virtual_backend_box_accessed(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox) +{ + DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head, + &mbox->open_backend_boxes_tail, bbox, + prev_open, next_open); + DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head, + &mbox->open_backend_boxes_tail, bbox, + prev_open, next_open); +} + +static void virtual_mailbox_close_internal(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box **bboxes; + unsigned int i, count; + + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->box == NULL) + continue; + if (bboxes[i]->notify != NULL) + mailbox_list_notify_deinit(&bboxes[i]->notify); + if (bboxes[i]->box->opened) + virtual_backend_box_close(mbox, bboxes[i]); + mailbox_free(&bboxes[i]->box); + if (array_is_created(&bboxes[i]->sync_outside_expunges)) + array_free(&bboxes[i]->sync_outside_expunges); + array_free(&bboxes[i]->sync_pending_removes); + array_free(&bboxes[i]->uids); + } + i_assert(mbox->backends_open_count == 0); +} + +static int +virtual_mailbox_exists(struct mailbox *box, bool auto_boxes ATTR_UNUSED, + enum mailbox_existence *existence_r) +{ + return index_storage_mailbox_exists_full(box, VIRTUAL_CONFIG_FNAME, + existence_r); +} + +static int virtual_mailbox_open(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + bool broken; + int ret = 0; + + if (virtual_mailbox_is_in_open_stack(mbox->storage, box->name)) { + mailbox_set_critical(box, + "Virtual mailbox loops: %s", box->name); + return -1; + } + + if (!array_is_created(&mbox->backend_boxes)) + ret = virtual_config_read(mbox); + if (ret == 0) { + array_push_back(&mbox->storage->open_stack, &box->name); + ret = virtual_mailboxes_open(mbox, box->flags); + array_pop_back(&mbox->storage->open_stack); + } + if (ret == 0) + ret = index_storage_mailbox_open(box, FALSE); + if (ret < 0) { + virtual_mailbox_close_internal(mbox); + return -1; + } + + mbox->virtual_ext_id = + mail_index_ext_register(mbox->box.index, "virtual", 0, + sizeof(struct virtual_mail_index_record), + sizeof(uint32_t)); + mbox->virtual_ext2_id = + mail_index_ext_register(mbox->box.index, "virtual2", 0, 0, 0); + + mbox->virtual_guid_ext_id = + mail_index_ext_register(mbox->box.index, "virtual-guid", GUID_128_SIZE, + 0, 0); + + if (virtual_mailbox_ext_header_read(mbox, box->view, &broken) < 0) { + virtual_mailbox_close_internal(mbox); + index_storage_mailbox_close(box); + return -1; + } + + /* if GUID is missing write it here */ + if (guid_128_is_empty(mbox->guid)) { + guid_128_generate(mbox->guid); + struct mail_index_transaction *t = + mail_index_transaction_begin(box->view, 0); + mail_index_update_header_ext(t, mbox->virtual_guid_ext_id, + 0, mbox->guid, GUID_128_SIZE); + if (mail_index_transaction_commit(&t) < 0) { + mailbox_set_critical(box, + "Cannot write GUID for virtual mailbox to index"); + virtual_mailbox_close_internal(mbox); + index_storage_mailbox_close(box); + return -1; + } + } + + return 0; +} + +static void virtual_mailbox_close(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + + virtual_mailbox_close_internal(mbox); + index_storage_mailbox_close(box); +} + +static void virtual_mailbox_free(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + + virtual_config_free(mbox); + index_storage_mailbox_free(box); +} + +static int +virtual_mailbox_create(struct mailbox *box, + const struct mailbox_update *update ATTR_UNUSED, + bool directory ATTR_UNUSED) +{ + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't create virtual mailboxes"); + return -1; +} + +static int +virtual_mailbox_update(struct mailbox *box, + const struct mailbox_update *update ATTR_UNUSED) +{ + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't update virtual mailboxes"); + return -1; +} + +static int virtual_storage_set_have_guid_flags(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + struct mailbox_status status; + + if (!mbox->box.opened) { + if (mailbox_open(&mbox->box) < 0) + return -1; + } + + mbox->have_guids = TRUE; + mbox->have_save_guids = TRUE; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (mailbox_get_status(bboxes[i]->box, 0, &status) < 0) { + const char *errstr; + enum mail_error error; + + errstr = mailbox_get_last_error(bboxes[i]->box, &error); + if (error == MAIL_ERROR_NOTFOUND) { + /* backend mailbox was just lost - skip it */ + continue; + } + /* Not expected to happen, but we can't return failure + since this could be called from + mailbox_get_open_status() and it would panic. + So just log the error and skip the mailbox. */ + mailbox_set_critical(&mbox->box, + "Virtual mailbox: Failed to get have_guid existence for backend mailbox %s: %s", + mailbox_get_vname(bboxes[i]->box), errstr); + continue; + } + if (!status.have_guids) + mbox->have_guids = FALSE; + if (!status.have_save_guids) + mbox->have_save_guids = FALSE; + } + return 0; +} + +static int +virtual_storage_get_status(struct mailbox *box, + enum mailbox_status_items items, + struct mailbox_status *status_r) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + + if ((items & STATUS_LAST_CACHED_SEQ) != 0) + items |= STATUS_MESSAGES; + + if (index_storage_get_status(box, items, status_r) < 0) + return -1; + + if ((items & STATUS_LAST_CACHED_SEQ) != 0) { + /* Virtual mailboxes have no cached data of their own, so the + current value is always 0. The most important use for this + functionality is for "doveadm index" to do FTS indexing and + it doesn't really matter there if we set this value + correctly or not. So for now just assume that everything is + indexed. */ + status_r->last_cached_seq = status_r->messages; + } + if (!mbox->have_guid_flags_set) { + if (virtual_storage_set_have_guid_flags(mbox) < 0) + return -1; + mbox->have_guid_flags_set = TRUE; + } + + if (mbox->have_guids) + status_r->have_guids = TRUE; + if (mbox->have_save_guids) + status_r->have_save_guids = TRUE; + return 0; +} + +static int +virtual_mailbox_get_metadata(struct mailbox *box, + enum mailbox_metadata_items items, + struct mailbox_metadata *metadata_r) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + if (index_mailbox_get_metadata(box, items, metadata_r) < 0) + return -1; + i_assert(box->opened); + if ((items & MAILBOX_METADATA_GUID) != 0) { + if (guid_128_is_empty(mbox->guid)) { + mailbox_set_critical(box, "GUID missing for virtual folder"); + return -1; + } + guid_128_copy(metadata_r->guid, mbox->guid); + } + return 0; +} + +static void +virtual_notify_callback(struct mailbox *bbox ATTR_UNUSED, struct mailbox *box) +{ + box->notify_callback(box, box->notify_context); +} + +static void virtual_backend_box_changed(struct virtual_backend_box *bbox) +{ + virtual_notify_callback(bbox->box, &bbox->virtual_mbox->box); +} + +static int virtual_notify_start(struct virtual_backend_box *bbox) +{ + i_assert(bbox->notify == NULL); + if (mailbox_list_notify_init(bbox->box->list, MAILBOX_LIST_NOTIFY_STATUS, + &bbox->notify) < 0) + /* did not support notifications */ + return -1; + mailbox_list_notify_wait(bbox->notify, virtual_backend_box_changed, bbox); + return 0; +} + +static void virtual_notify_changes(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_backend_box **bboxp; + + if (box->notify_callback == NULL) { + array_foreach_modifiable(&mbox->backend_boxes, bboxp) { + if ((*bboxp)->notify_changes_started) { + mailbox_notify_changes_stop((*bboxp)->box); + (*bboxp)->notify_changes_started = FALSE; + } + if ((*bboxp)->notify != NULL) + mailbox_list_notify_deinit(&(*bboxp)->notify); + } + return; + } + + array_foreach_modifiable(&mbox->backend_boxes, bboxp) { + if (array_count(&mbox->backend_boxes) == 1 && + (*bboxp)->box->opened) { + /* There's only a single backend mailbox and its + indexes are already opened. Might as well use the + backend directly for notifications. */ + } else { + /* we are already waiting for notifications */ + if ((*bboxp)->notify != NULL) + continue; + /* wait for notifications */ + if (virtual_notify_start(*bboxp) == 0) + continue; + /* it did not work, so open the mailbox and use + alternative method */ + } + + if (!(*bboxp)->box->opened && + virtual_backend_box_open(mbox, *bboxp) < 0) { + /* we can't report error in here, so do it later */ + (*bboxp)->open_failed = TRUE; + continue; + } + mailbox_notify_changes((*bboxp)->box, + virtual_notify_callback, box); + (*bboxp)->notify_changes_started = TRUE; + } +} + +static void +virtual_uidmap_to_uid_array(struct virtual_backend_box *bbox, + ARRAY_TYPE(seq_range) *uids_r) +{ + const struct virtual_backend_uidmap *uid; + array_foreach(&bbox->uids, uid) { + seq_range_array_add(uids_r, uid->real_uid); + } +} + +static void +virtual_get_virtual_uids(struct mailbox *box, + struct mailbox *backend_mailbox, + const ARRAY_TYPE(seq_range) *backend_uids, + ARRAY_TYPE(seq_range) *virtual_uids_r) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_backend_box *bbox; + const struct virtual_backend_uidmap *uids; + ARRAY_TYPE(seq_range) uid_range; + struct seq_range_iter iter; + unsigned int n, i, count; + uint32_t uid; + + if (mbox->lookup_prev_bbox != NULL && + strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0) + bbox = mbox->lookup_prev_bbox; + else { + bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname); + mbox->lookup_prev_bbox = bbox; + } + if (bbox == NULL) + return; + + uids = array_get(&bbox->uids, &count); i = 0; + + t_array_init(&uid_range, 8); + virtual_uidmap_to_uid_array(bbox, &uid_range); + seq_range_array_intersect(&uid_range, backend_uids); + + seq_range_array_iter_init(&iter, &uid_range); n = 0; + while (seq_range_array_iter_nth(&iter, n++, &uid)) { + while (i < count && uids[i].real_uid < uid) i++; + if (i < count && uids[i].real_uid == uid) { + i_assert(uids[i].virtual_uid > 0); + seq_range_array_add(virtual_uids_r, + uids[i].virtual_uid); + i++; + } + } +} + +static void +virtual_get_virtual_uid_map(struct mailbox *box, + struct mailbox *backend_mailbox, + const ARRAY_TYPE(seq_range) *backend_uids, + ARRAY_TYPE(uint32_t) *virtual_uids_r) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_backend_box *bbox; + const struct virtual_backend_uidmap *uids; + struct seq_range_iter iter; + unsigned int n, i, count; + uint32_t uid; + + if (mbox->lookup_prev_bbox != NULL && + strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0) + bbox = mbox->lookup_prev_bbox; + else { + bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname); + mbox->lookup_prev_bbox = bbox; + } + if (bbox == NULL) + return; + + uids = array_get(&bbox->uids, &count); i = 0; + seq_range_array_iter_init(&iter, backend_uids); n = 0; + while (seq_range_array_iter_nth(&iter, n++, &uid)) { + while (i < count && uids[i].real_uid < uid) i++; + if (i == count || uids[i].real_uid > uid) { + uint32_t zero = 0; + + array_push_back(virtual_uids_r, &zero); + } else { + i_assert(uids[i].virtual_uid > 0); + array_push_back(virtual_uids_r, &uids[i].virtual_uid); + i++; + } + } +} + +static void +virtual_get_virtual_backend_boxes(struct mailbox *box, + ARRAY_TYPE(mailboxes) *mailboxes, + bool only_with_msgs) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (!only_with_msgs || array_count(&bboxes[i]->uids) > 0) + array_push_back(mailboxes, &bboxes[i]->box); + } +} + +static bool virtual_is_inconsistent(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + + if (mbox->inconsistent) + return TRUE; + + return index_storage_is_inconsistent(box); +} + +static int +virtual_list_index_has_changed(struct mailbox *box ATTR_UNUSED, + struct mail_index_view *list_view ATTR_UNUSED, + uint32_t seq ATTR_UNUSED, bool quick ATTR_UNUSED, + const char **reason_r) +{ + /* we don't have any quick and easy optimizations for tracking + virtual folders. ideally we'd completely disable mailbox list + indexes for them, but this is the easiest way to do it for now. */ + *reason_r = "Virtual indexes always change"; + return 1; +} + +static void +virtual_list_index_update_sync(struct mailbox *box ATTR_UNUSED, + struct mail_index_transaction *trans ATTR_UNUSED, + uint32_t seq ATTR_UNUSED) +{ +} + +struct mail_storage virtual_storage = { + .name = VIRTUAL_STORAGE_NAME, + .class_flags = MAIL_STORAGE_CLASS_FLAG_NOQUOTA | + MAIL_STORAGE_CLASS_FLAG_SECONDARY_INDEX, + + .v = { + NULL, + virtual_storage_alloc, + virtual_storage_create, + index_storage_destroy, + NULL, + virtual_storage_get_list_settings, + NULL, + virtual_mailbox_alloc, + NULL, + NULL, + } +}; + +struct mailbox virtual_mailbox = { + .v = { + index_storage_is_readonly, + index_storage_mailbox_enable, + virtual_mailbox_exists, + virtual_mailbox_open, + virtual_mailbox_close, + virtual_mailbox_free, + virtual_mailbox_create, + virtual_mailbox_update, + index_storage_mailbox_delete, + index_storage_mailbox_rename, + virtual_storage_get_status, + virtual_mailbox_get_metadata, + index_storage_set_subscribed, + index_storage_attribute_set, + index_storage_attribute_get, + index_storage_attribute_iter_init, + index_storage_attribute_iter_next, + index_storage_attribute_iter_deinit, + virtual_list_index_has_changed, + virtual_list_index_update_sync, + virtual_storage_sync_init, + index_mailbox_sync_next, + index_mailbox_sync_deinit, + NULL, + virtual_notify_changes, + virtual_transaction_begin, + virtual_transaction_commit, + virtual_transaction_rollback, + NULL, + virtual_mail_alloc, + virtual_search_init, + virtual_search_deinit, + virtual_search_next_nonblock, + virtual_search_next_update_seq, + index_storage_search_next_match_mail, + virtual_save_alloc, + virtual_save_begin, + virtual_save_continue, + virtual_save_finish, + virtual_save_cancel, + mail_storage_copy, + NULL, + NULL, + NULL, + virtual_is_inconsistent + } +}; + +struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs = { + virtual_get_virtual_uids, + virtual_get_virtual_uid_map, + virtual_get_virtual_backend_boxes +}; diff --git a/src/plugins/virtual/virtual-storage.h b/src/plugins/virtual/virtual-storage.h new file mode 100644 index 0000000..1f34e39 --- /dev/null +++ b/src/plugins/virtual/virtual-storage.h @@ -0,0 +1,251 @@ +#ifndef VIRTUAL_STORAGE_H +#define VIRTUAL_STORAGE_H + +#include "seq-range-array.h" +#include "index-storage.h" + +#define VIRTUAL_STORAGE_NAME "virtual" +#define VIRTUAL_SUBSCRIPTION_FILE_NAME ".virtual-subscriptions" +#define VIRTUAL_CONFIG_FNAME "dovecot-virtual" + +#define VIRTUAL_CONTEXT(obj) \ + MODULE_CONTEXT(obj, virtual_storage_module) +#define VIRTUAL_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, virtual_storage_module) + +#define VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION 1 + +struct virtual_save_context; + +struct virtual_mail_index_header { + /* Increased by one each time the header is modified */ + uint32_t change_counter; + /* Number of mailbox records following this header. Mailbox names + follow the mailbox records - they have neither NUL terminator nor + padding. */ + uint32_t mailbox_count; + /* Highest used mailbox ID. IDs are never reused. */ + uint32_t highest_mailbox_id; + /* CRC32 of all the search parameters. If it changes, the mailbox is + rebuilt. */ + uint32_t search_args_crc32; +}; + +struct virtual_mail_index_mailbox_record { + /* Unique mailbox ID used as mailbox_id in records. */ + uint32_t id; + /* Length of this mailbox's name. */ + uint32_t name_len; + /* Synced UID validity value */ + uint32_t uid_validity; + /* Next unseen UID */ + uint32_t next_uid; + /* Synced highest modseq value */ + uint64_t highest_modseq; +}; + +struct virtual_mail_index_ext2_header { + /* Version compatibility number: + VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION */ + uint8_t version; + /* Set to sizeof(struct virtual_mail_index_mailbox_ext2_record) when + writing. */ + uint8_t ext_record_size; + /* Set to sizeof(struct virtual_mail_index_ext2_header) when writing. */ + uint16_t hdr_size; + /* Must be the same as virtual_mail_index_header.change_counter. + If not, it means the header was modified by an older Dovecot version + and this extension header should be ignored/rewritten. */ + uint32_t change_counter; +}; + +struct virtual_mail_index_mailbox_ext2_record { + /* Synced GUID value */ + uint8_t guid[16]; +}; + +struct virtual_mail_index_record { + uint32_t mailbox_id; + uint32_t real_uid; +}; + +struct virtual_storage { + struct mail_storage storage; + + /* List of mailboxes while a virtual mailbox is being opened. + Used to track loops. */ + ARRAY_TYPE(const_string) open_stack; + + unsigned int max_open_mailboxes; +}; + +struct virtual_backend_uidmap { + uint32_t real_uid; + /* can be 0 temporarily while syncing before the UID is assigned */ + uint32_t virtual_uid; +}; + +struct virtual_backend_box { + union mailbox_module_context module_ctx; + struct virtual_mailbox *virtual_mbox; + + /* linked list for virtual_mailbox->open_backend_boxes_{head,tail} */ + struct virtual_backend_box *prev_open, *next_open; + + /* Initially zero, updated by syncing */ + uint32_t mailbox_id; + const char *name; + + unsigned int sync_mailbox_idx1; + uint32_t sync_uid_validity; + uint32_t sync_next_uid; + uint64_t sync_highest_modseq; + guid_128_t sync_guid; + /* this value is either 0 or same as sync_highest_modseq. it's kept 0 + when there are pending removes that have yet to be expunged */ + uint64_t ondisk_highest_modseq; + + struct mail_search_args *search_args; + struct mail_search_result *search_result; + + struct mailbox *box; + /* Messages currently included in the virtual mailbox, + sorted by real_uid */ + ARRAY(struct virtual_backend_uidmap) uids; + + /* temporary mail used while syncing */ + struct mail *sync_mail; + /* pending removed UIDs */ + ARRAY_TYPE(seq_range) sync_pending_removes; + /* another process expunged these UIDs. they need to be removed on + next sync. */ + ARRAY_TYPE(seq_range) sync_outside_expunges; + + /* name contains a wildcard, this is a glob for it */ + struct imap_match_glob *glob; + struct mail_namespace *ns; + /* mailbox metadata matching */ + const char *metadata_entry, *metadata_value; + + /* notify context */ + struct mailbox_list_notify *notify; + + bool open_tracked:1; + bool open_failed:1; + bool sync_seen:1; + bool wildcard:1; + bool clear_recent:1; + bool negative_match:1; + bool uids_nonsorted:1; + bool search_args_initialized:1; + bool deleted:1; + bool notify_changes_started:1; /* if the box was opened for notify_changes */ + bool first_sync:1; /* if this is the first sync after bbox was (re-)created */ +}; +ARRAY_DEFINE_TYPE(virtual_backend_box, struct virtual_backend_box *); + +struct virtual_mailbox { + struct mailbox box; + struct virtual_storage *storage; + + uint32_t virtual_ext_id; + uint32_t virtual_ext2_id; + uint32_t virtual_guid_ext_id; + + uint32_t prev_uid_validity; + uint32_t prev_change_counter; + uint32_t highest_mailbox_id; + uint32_t search_args_crc32; + guid_128_t guid; + + struct virtual_backend_box *lookup_prev_bbox; + uint32_t sync_virtual_next_uid; + + /* Mailboxes this virtual mailbox consists of, sorted by mailbox_id */ + ARRAY_TYPE(virtual_backend_box) backend_boxes; + /* backend mailbox where to save messages when saving to this mailbox */ + struct virtual_backend_box *save_bbox; + + /* linked list of open backend mailboxes. head will contain the oldest + accessed mailbox, tail will contain the newest. */ + struct virtual_backend_box *open_backend_boxes_head; + struct virtual_backend_box *open_backend_boxes_tail; + /* number of backend mailboxes that are open currently. */ + unsigned int backends_open_count; + + ARRAY_TYPE(mailbox_virtual_patterns) list_include_patterns; + ARRAY_TYPE(mailbox_virtual_patterns) list_exclude_patterns; + + bool uids_mapped:1; + bool sync_initialized:1; + bool inconsistent:1; + bool have_guid_flags_set:1; + bool have_guids:1; + bool have_save_guids:1; + bool ext_header_rewrite:1; +}; + +extern MODULE_CONTEXT_DEFINE(virtual_storage_module, + &mail_storage_module_register); + +extern struct mail_storage virtual_storage; +extern struct mail_vfuncs virtual_mail_vfuncs; + +int virtual_config_read(struct virtual_mailbox *mbox); +void virtual_config_free(struct virtual_mailbox *mbox); + +int virtual_mailbox_ext_header_read(struct virtual_mailbox *mbox, + struct mail_index_view *view, + bool *broken_r); + +struct virtual_backend_box * +virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name); +struct virtual_backend_box * +virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id); + +int virtual_backend_box_open(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox); +void virtual_backend_box_close(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox); +void virtual_backend_box_accessed(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox); +void virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox); + +struct mail_search_context * +virtual_search_init(struct mailbox_transaction_context *t, + struct mail_search_args *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers); +int virtual_search_deinit(struct mail_search_context *ctx); +bool virtual_search_next_nonblock(struct mail_search_context *ctx, + struct mail **mail_r, bool *tryagain_r); +bool virtual_search_next_update_seq(struct mail_search_context *ctx); + +struct mail * +virtual_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers); +struct mail * +virtual_mail_set_backend_mail(struct mail *mail, + struct virtual_backend_box *bbox); +void virtual_mail_set_unattached_backend_mail(struct mail *mail, + struct mail *backend_mail); + +struct mailbox_sync_context * +virtual_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags); + +struct mail_save_context * +virtual_save_alloc(struct mailbox_transaction_context *t); +int virtual_save_begin(struct mail_save_context *ctx, struct istream *input); +int virtual_save_continue(struct mail_save_context *ctx); +int virtual_save_finish(struct mail_save_context *ctx); +void virtual_save_cancel(struct mail_save_context *ctx); +void virtual_save_free(struct mail_save_context *ctx); + +void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src); + +void virtual_backend_mailbox_allocated(struct mailbox *box); +void virtual_backend_mailbox_opened(struct mailbox *box); + +#endif diff --git a/src/plugins/virtual/virtual-sync.c b/src/plugins/virtual/virtual-sync.c new file mode 100644 index 0000000..f8c6989 --- /dev/null +++ b/src/plugins/virtual/virtual-sync.c @@ -0,0 +1,1956 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "bsearch-insert-pos.h" +#include "ioloop.h" +#include "str.h" +#include "mail-index-modseq.h" +#include "mail-search-build.h" +#include "mailbox-search-result-private.h" +#include "mailbox-recent-flags.h" +#include "index-sync-private.h" +#include "index-search-result.h" +#include "virtual-storage.h" + + +struct virtual_add_record { + struct virtual_mail_index_record rec; + time_t received_date; +}; + +struct virtual_sync_mail { + uint32_t vseq; + struct virtual_mail_index_record vrec; +}; + +struct virtual_sync_context { + struct virtual_mailbox *mbox; + struct mail_index_sync_ctx *index_sync_ctx; + struct mail_index *index; + struct mail_index_view *sync_view; + struct mail_index_transaction *trans; + const char *const *kw_all; + + /* messages expunged within this sync */ + ARRAY_TYPE(seq_range) sync_expunges; + + ARRAY(struct virtual_add_record) all_adds; + + /* all messages in this sync, sorted by mailbox_id + (but unsorted inside it for now, since it doesn't matter) */ + ARRAY(struct virtual_sync_mail) all_mails; + uint32_t all_mails_idx, all_mails_prev_mailbox_id; + + enum mailbox_sync_flags flags; + uint32_t uid_validity; + + bool ext_header_changed:1; + bool expunge_removed:1; + bool index_broken:1; +}; + +static void virtual_sync_backend_box_deleted(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox); + +static void virtual_sync_set_uidvalidity(struct virtual_sync_context *ctx) +{ + uint32_t uid_validity = ioloop_time; + + mail_index_update_header(ctx->trans, + offsetof(struct mail_index_header, uid_validity), + &uid_validity, sizeof(uid_validity), TRUE); + ctx->uid_validity = uid_validity; +} + +static void virtual_sync_external_flags(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + uint32_t vseq, uint32_t real_uid) +{ + enum mail_flags flags; + const char *const *kw_names; + struct mail_keywords *keywords; + + if (!mail_set_uid(bbox->sync_mail, real_uid)) { + /* we may have reopened the mailbox, which could have + caused the mail to be expunged already. */ + return; + } + + /* copy flags */ + flags = mail_get_flags(bbox->sync_mail); + + /* we don't need to keep recent flags here */ + mail_index_update_flags(ctx->trans, vseq, MODIFY_REPLACE, + flags & ENUM_NEGATE(MAIL_RECENT)); + + /* copy keywords */ + kw_names = mail_get_keywords(bbox->sync_mail); + keywords = mail_index_keywords_create(ctx->index, kw_names); + mail_index_update_keywords(ctx->trans, vseq, MODIFY_REPLACE, keywords); + mail_index_keywords_unref(&keywords); +} + +static int virtual_sync_mail_uid_cmp(const void *p1, const void *p2) +{ + const struct virtual_sync_mail *m1 = p1, *m2 = p2; + + if (m1->vrec.mailbox_id < m2->vrec.mailbox_id) + return -1; + if (m1->vrec.mailbox_id > m2->vrec.mailbox_id) + return 1; + + if (m1->vrec.real_uid < m2->vrec.real_uid) + return -1; + if (m1->vrec.real_uid > m2->vrec.real_uid) + return 1; + /* broken */ + return 0; +} + +static void +virtual_backend_box_sync_mail_set(struct virtual_backend_box *bbox) +{ + struct mailbox_transaction_context *trans; + + if (bbox->sync_mail == NULL) { + trans = mailbox_transaction_begin(bbox->box, 0, __func__); + bbox->sync_mail = mail_alloc(trans, 0, NULL); + } +} + +static int bbox_mailbox_id_cmp(struct virtual_backend_box *const *b1, + struct virtual_backend_box *const *b2) +{ + if ((*b1)->mailbox_id < (*b2)->mailbox_id) + return -1; + if ((*b1)->mailbox_id > (*b2)->mailbox_id) + return 1; + return 0; +} + +static int +virtual_sync_get_backend_box(struct virtual_mailbox *mbox, const char *name, + struct virtual_backend_box **bbox_r) +{ + *bbox_r = virtual_backend_box_lookup_name(mbox, name); + if (*bbox_r != NULL || !mbox->sync_initialized) + return 0; + + /* another process just added a new mailbox. + we can't handle this currently. */ + mbox->inconsistent = TRUE; + mail_storage_set_error(mbox->box.storage, MAIL_ERROR_TEMP, t_strdup_printf( + "Backend mailbox '%s' added by another session. " + "Reopen the virtual mailbox.", name)); + return -1; +} + +static bool +virtual_mailbox_ext2_header_read(struct virtual_mailbox *mbox, + struct mail_index_view *view, + const struct virtual_mail_index_header *ext_hdr) +{ + const char *box_path = mailbox_get_path(&mbox->box); + const struct virtual_mail_index_ext2_header *ext2_hdr; + const struct virtual_mail_index_mailbox_ext2_record *ext2_rec; + const void *ext2_data; + size_t ext2_size; + struct virtual_backend_box *bbox; + + mail_index_get_header_ext(view, mbox->virtual_ext2_id, + &ext2_data, &ext2_size); + ext2_hdr = ext2_data; + if (ext2_size == 0) { + /* ext2 is missing - silently add it */ + return FALSE; + } + if (ext2_size < sizeof(*ext2_hdr)) { + i_error("virtual index %s: Invalid ext2 header size: %zu", + box_path, ext2_size); + return FALSE; + } + if (ext2_hdr->hdr_size > ext2_size) { + i_error("virtual index %s: ext2 header size too large: %u > %zu", + box_path, ext2_hdr->hdr_size, ext2_size); + return FALSE; + } + if (ext2_hdr->ext_record_size < sizeof(*ext2_rec)) { + i_error("virtual index %s: Invalid ext2 record size: %u", + box_path, ext2_hdr->ext_record_size); + return FALSE; + } + + if (ext_hdr->change_counter != ext2_hdr->change_counter) { + i_warning("virtual index %s: " + "Extension header change_counter mismatch (%u != %u) - " + "Index was modified by an older version?", + box_path, ext_hdr->change_counter, + ext2_hdr->change_counter); + return FALSE; + } + size_t mailboxes_size = ext2_size - ext2_hdr->hdr_size; + if (mailboxes_size % ext2_hdr->ext_record_size != 0 || + mailboxes_size / ext2_hdr->ext_record_size != ext_hdr->mailbox_count) { + i_error("virtual index %s: Invalid ext2 size: " + "hdr_size=%u record_size=%u total_size=%zu mailbox_count=%u", + box_path, ext2_hdr->hdr_size, ext2_hdr->ext_record_size, + ext2_size, ext_hdr->mailbox_count); + return FALSE; + } + + ext2_rec = CONST_PTR_OFFSET(ext2_data, ext2_hdr->hdr_size); + array_foreach_elem(&mbox->backend_boxes, bbox) { + if (bbox->sync_mailbox_idx1 == 0) + continue; + + guid_128_copy(bbox->sync_guid, + ext2_rec[bbox->sync_mailbox_idx1-1].guid); + } + return TRUE; +} + +int virtual_mailbox_ext_header_read(struct virtual_mailbox *mbox, + struct mail_index_view *view, + bool *broken_r) +{ + const char *box_path = mailbox_get_path(&mbox->box); + const struct virtual_mail_index_header *ext_hdr; + const struct mail_index_header *hdr; + const struct virtual_mail_index_mailbox_record *mailboxes; + struct virtual_backend_box *bbox, **bboxes; + const void *ext_data; + size_t ext_size; + unsigned int i, count, ext_name_offset, ext_mailbox_count; + uint32_t prev_mailbox_id; + int ret = 1; + + *broken_r = FALSE; + + hdr = mail_index_get_header(view); + mail_index_get_header_ext(view, mbox->virtual_ext_id, + &ext_data, &ext_size); + ext_hdr = ext_data; + if (mbox->sync_initialized && + mbox->prev_uid_validity == hdr->uid_validity && + ext_size >= sizeof(*ext_hdr) && + mbox->prev_change_counter == ext_hdr->change_counter) { + /* fully refreshed */ + return 1; + } + + mbox->prev_uid_validity = hdr->uid_validity; + if (ext_hdr == NULL || + mbox->search_args_crc32 != ext_hdr->search_args_crc32) { + mailboxes = NULL; + ext_name_offset = 0; + ext_mailbox_count = 0; + ret = 0; + } else { + const void *guid_data; + size_t guid_size; + mail_index_get_header_ext(view, mbox->virtual_guid_ext_id, + &guid_data, &guid_size); + if (guid_size >= GUID_128_SIZE) + guid_128_copy(mbox->guid, guid_data); + + mbox->prev_change_counter = ext_hdr->change_counter; + mailboxes = (const void *)(ext_hdr + 1); + ext_name_offset = sizeof(*ext_hdr) + + ext_hdr->mailbox_count * sizeof(*mailboxes); + if (ext_name_offset >= ext_size || + ext_hdr->mailbox_count > INT_MAX/sizeof(*mailboxes)) { + i_error("virtual index %s: Broken mailbox_count header", + box_path); + *broken_r = TRUE; + ext_mailbox_count = 0; + ret = 0; + } else { + ext_mailbox_count = ext_hdr->mailbox_count; + } + } + + /* update mailbox backends */ + prev_mailbox_id = 0; + for (i = 0; i < ext_mailbox_count; i++) { + if (mailboxes[i].id > ext_hdr->highest_mailbox_id || + mailboxes[i].id <= prev_mailbox_id) { + i_error("virtual index %s: Broken mailbox id", + box_path); + break; + } + if (mailboxes[i].name_len == 0 || + mailboxes[i].name_len > ext_size) { + i_error("virtual index %s: Broken mailbox name_len", + box_path); + break; + } + if (ext_name_offset + mailboxes[i].name_len > ext_size) { + i_error("virtual index %s: Broken mailbox list", + box_path); + break; + } + T_BEGIN { + const unsigned char *nameptr; + const char *name; + + nameptr = CONST_PTR_OFFSET(ext_data, ext_name_offset); + name = t_strndup(nameptr, mailboxes[i].name_len); + if (virtual_sync_get_backend_box(mbox, name, &bbox) < 0) + ret = -1; + } T_END; + + if (bbox == NULL) { + if (ret < 0) + return -1; + /* mailbox no longer exists. */ + ret = 0; + } else { + bbox->mailbox_id = mailboxes[i].id; + bbox->sync_uid_validity = mailboxes[i].uid_validity; + bbox->ondisk_highest_modseq = + bbox->sync_highest_modseq = + mailboxes[i].highest_modseq; + bbox->sync_next_uid = mailboxes[i].next_uid; + bbox->sync_mailbox_idx1 = i+1; + } + ext_name_offset += mailboxes[i].name_len; + prev_mailbox_id = mailboxes[i].id; + } + if (i < ext_mailbox_count) { + *broken_r = TRUE; + mbox->ext_header_rewrite = TRUE; + ret = 0; + } + if (!*broken_r && ext_mailbox_count > 0) { + if (!virtual_mailbox_ext2_header_read(mbox, view, ext_hdr)) + mbox->ext_header_rewrite = TRUE; + } + + mbox->highest_mailbox_id = ext_hdr == NULL ? 0 : + ext_hdr->highest_mailbox_id; + /* do not mark it initialized if it's broken */ + mbox->sync_initialized = !*broken_r; + + /* assign new mailbox IDs if any are missing */ + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->mailbox_id == 0) { + bboxes[i]->mailbox_id = ++mbox->highest_mailbox_id; + ret = 0; + } + } + /* sort the backend mailboxes by mailbox_id. */ + array_sort(&mbox->backend_boxes, bbox_mailbox_id_cmp); + if (ret == 0) + mbox->ext_header_rewrite = TRUE; + return ret; +} + +static void virtual_sync_ext_header_rewrite(struct virtual_sync_context *ctx) +{ + struct virtual_mail_index_header ext_hdr; + struct virtual_mail_index_mailbox_record mailbox; + struct virtual_mail_index_mailbox_ext2_record ext2_rec; + struct virtual_backend_box **bboxes; + buffer_t *buf, *buf2; + const void *ext_data; + size_t ext_size; + unsigned int i, mailbox_pos, name_pos, count; + + bboxes = array_get_modifiable(&ctx->mbox->backend_boxes, &count); + mailbox_pos = sizeof(ext_hdr); + name_pos = mailbox_pos + sizeof(mailbox) * count; + + i_zero(&ext_hdr); + i_zero(&mailbox); + i_zero(&ext2_rec); + + ext_hdr.change_counter = ++ctx->mbox->prev_change_counter; + ext_hdr.mailbox_count = count; + ext_hdr.highest_mailbox_id = ctx->mbox->highest_mailbox_id; + ext_hdr.search_args_crc32 = ctx->mbox->search_args_crc32; + + buf = buffer_create_dynamic(default_pool, name_pos + 256); + buffer_append(buf, &ext_hdr, sizeof(ext_hdr)); + + struct virtual_mail_index_ext2_header ext2_hdr = { + .version = VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION, + .ext_record_size = sizeof(struct virtual_mail_index_mailbox_ext2_record), + .hdr_size = sizeof(struct virtual_mail_index_ext2_header), + .change_counter = ext_hdr.change_counter, + }; + buf2 = buffer_create_dynamic(default_pool, sizeof(ext2_hdr) + + sizeof(ext2_rec) * count); + buffer_append(buf2, &ext2_hdr, sizeof(ext2_hdr)); + + for (i = 0; i < count; i++) { + i_assert(i == 0 || + bboxes[i]->mailbox_id > bboxes[i-1]->mailbox_id); + + bboxes[i]->sync_mailbox_idx1 = i+1; + mailbox.id = bboxes[i]->mailbox_id; + mailbox.name_len = strlen(bboxes[i]->name); + mailbox.uid_validity = bboxes[i]->sync_uid_validity; + mailbox.highest_modseq = bboxes[i]->ondisk_highest_modseq; + mailbox.next_uid = bboxes[i]->sync_next_uid; + buffer_write(buf, mailbox_pos, &mailbox, sizeof(mailbox)); + buffer_write(buf, name_pos, bboxes[i]->name, mailbox.name_len); + + guid_128_copy(ext2_rec.guid, bboxes[i]->sync_guid); + buffer_append(buf2, &ext2_rec, sizeof(ext2_rec)); + + mailbox_pos += sizeof(mailbox); + name_pos += mailbox.name_len; + + /* repair the value */ + if (ctx->mbox->highest_mailbox_id < mailbox.id) + ctx->mbox->highest_mailbox_id = mailbox.id; + } + if (ctx->mbox->highest_mailbox_id != ext_hdr.highest_mailbox_id) { + ext_hdr.highest_mailbox_id = ctx->mbox->highest_mailbox_id; + buffer_write(buf, 0, &ext_hdr, sizeof(ext_hdr)); + } + i_assert(buf->used == name_pos); + + /* update base extension */ + mail_index_get_header_ext(ctx->sync_view, ctx->mbox->virtual_ext_id, + &ext_data, &ext_size); + if (ext_size < name_pos) { + mail_index_ext_resize(ctx->trans, ctx->mbox->virtual_ext_id, + name_pos, + sizeof(struct virtual_mail_index_record), + sizeof(uint32_t)); + } + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id, + 0, buf->data, name_pos); + + /* update ext2 */ + mail_index_get_header_ext(ctx->sync_view, ctx->mbox->virtual_ext2_id, + &ext_data, &ext_size); + if (ext_size != buf2->used) { + mail_index_ext_resize(ctx->trans, ctx->mbox->virtual_ext2_id, + buf2->used, 0, 0); + } + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id, + 0, buf2->data, buf2->used); + buffer_free(&buf); + buffer_free(&buf2); +} + +static void virtual_sync_ext_header_update(struct virtual_sync_context *ctx) +{ + struct virtual_mail_index_header ext_hdr; + + if (!ctx->ext_header_changed) + return; + + /* we changed something - update the change counter in header */ + ext_hdr.change_counter = ++ctx->mbox->prev_change_counter; + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id, + offsetof(struct virtual_mail_index_header, change_counter), + &ext_hdr.change_counter, sizeof(ext_hdr.change_counter)); + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id, + offsetof(struct virtual_mail_index_ext2_header, change_counter), + &ext_hdr.change_counter, sizeof(ext_hdr.change_counter)); +} + +static int virtual_sync_index_rec(struct virtual_sync_context *ctx, + const struct mail_index_sync_rec *sync_rec) +{ + uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id; + struct virtual_backend_box *bbox; + const struct virtual_mail_index_record *vrec; + const void *data; + enum mail_flags flags; + struct mail_keywords *keywords; + enum modify_type modify_type; + const char *kw_names[2]; + uint32_t vseq, seq1, seq2; + + switch (sync_rec->type) { + case MAIL_INDEX_SYNC_TYPE_EXPUNGE: + case MAIL_INDEX_SYNC_TYPE_FLAGS: + case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD: + case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE: + break; + } + if (!mail_index_lookup_seq_range(ctx->sync_view, + sync_rec->uid1, sync_rec->uid2, + &seq1, &seq2)) { + /* already expunged, nothing to do. */ + return 0; + } + + for (vseq = seq1; vseq <= seq2; vseq++) { + mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id, + &data, NULL); + vrec = data; + + bbox = virtual_backend_box_lookup(ctx->mbox, vrec->mailbox_id); + if (bbox == NULL) + continue; + if (!bbox->box->opened) { + if (virtual_backend_box_open(ctx->mbox, bbox) < 0) { + virtual_box_copy_error(&ctx->mbox->box, + bbox->box); + return -1; + } + } else { + virtual_backend_box_accessed(ctx->mbox, bbox); + } + + virtual_backend_box_sync_mail_set(bbox); + if (!mail_set_uid(bbox->sync_mail, vrec->real_uid)) { + /* message is already expunged from backend mailbox. */ + continue; + } + + switch (sync_rec->type) { + case MAIL_INDEX_SYNC_TYPE_EXPUNGE: + mail_expunge(bbox->sync_mail); + break; + case MAIL_INDEX_SYNC_TYPE_FLAGS: + flags = sync_rec->add_flags & MAIL_FLAGS_NONRECENT; + if (flags != 0) { + mail_update_flags(bbox->sync_mail, + MODIFY_ADD, flags); + } + flags = sync_rec->remove_flags & MAIL_FLAGS_NONRECENT; + if (flags != 0) { + mail_update_flags(bbox->sync_mail, + MODIFY_REMOVE, flags); + } + break; + case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD: + case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE: + kw_names[0] = ctx->kw_all[sync_rec->keyword_idx]; + kw_names[1] = NULL; + keywords = mailbox_keywords_create_valid(bbox->box, + kw_names); + + modify_type = sync_rec->type == + MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD ? + MODIFY_ADD : MODIFY_REMOVE; + mail_update_keywords(bbox->sync_mail, + modify_type, keywords); + mailbox_keywords_unref(&keywords); + break; + } + } + return 0; +} + +static int virtual_sync_index_changes(struct virtual_sync_context *ctx) +{ + const ARRAY_TYPE(keywords) *keywords; + struct mail_index_sync_rec sync_rec; + + keywords = mail_index_get_keywords(ctx->index); + ctx->kw_all = array_count(keywords) == 0 ? NULL : + array_front(keywords); + while (mail_index_sync_next(ctx->index_sync_ctx, &sync_rec)) { + if (virtual_sync_index_rec(ctx, &sync_rec) < 0) + return -1; + } + return 0; +} + +static void virtual_sync_index_finish(struct virtual_sync_context *ctx) +{ + struct mailbox *box = &ctx->mbox->box; + const struct mail_index_header *hdr; + struct mail_index_view *view; + uint32_t seq1, seq2; + + view = mail_index_transaction_open_updated_view(ctx->trans); + + hdr = mail_index_get_header(ctx->sync_view); + if (hdr->uid_validity != 0) + ctx->uid_validity = hdr->uid_validity; + else + virtual_sync_set_uidvalidity(ctx); + + /* mark the newly seen messages as recent */ + if (mail_index_lookup_seq_range(view, hdr->first_recent_uid, + (uint32_t)-1, &seq1, &seq2)) { + mailbox_recent_flags_set_seqs(&ctx->mbox->box, view, + seq1, seq2); + } + + mail_index_view_close(&view); + + if (ctx->mbox->ext_header_rewrite) { + /* entire mailbox list needs to be rewritten */ + virtual_sync_ext_header_rewrite(ctx); + } else { + /* update only changed parts in the header */ + virtual_sync_ext_header_update(ctx); + } + + mailbox_sync_notify(box, 0, 0); +} + +static int virtual_sync_backend_box_init(struct virtual_backend_box *bbox) +{ + struct mailbox_transaction_context *trans; + struct mail_search_context *search_ctx; + struct mail *mail; + struct virtual_backend_uidmap uidmap; + enum mailbox_search_result_flags result_flags; + int ret; + + trans = mailbox_transaction_begin(bbox->box, 0, __func__); + + if (!bbox->search_args_initialized) { + mail_search_args_init(bbox->search_args, bbox->box, FALSE, NULL); + bbox->search_args_initialized = TRUE; + } + search_ctx = mailbox_search_init(trans, bbox->search_args, NULL, + 0, NULL); + + /* save the result and keep it updated */ + result_flags = MAILBOX_SEARCH_RESULT_FLAG_UPDATE | + MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC; + bbox->search_result = + mailbox_search_result_save(search_ctx, result_flags); + + /* add the found UIDs to uidmap. virtual_uid gets assigned later. */ + i_zero(&uidmap); + array_clear(&bbox->uids); + while (mailbox_search_next(search_ctx, &mail)) { + uidmap.real_uid = mail->uid; + array_push_back(&bbox->uids, &uidmap); + } + ret = mailbox_search_deinit(&search_ctx); + + (void)mailbox_transaction_commit(&trans); + return ret; +} + +static int +virtual_backend_uidmap_bsearch_cmp(const uint32_t *uidp, + const struct virtual_backend_uidmap *uidmap) +{ + return *uidp < uidmap->real_uid ? -1 : + (*uidp > uidmap->real_uid ? 1 : 0); +} + +static void +virtual_sync_mailbox_box_remove(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + const ARRAY_TYPE(seq_range) *removed_uids) +{ + const struct seq_range *uids; + struct virtual_backend_uidmap *uidmap; + unsigned int i, src, dest, uid_count, rec_count; + uint32_t uid, vseq; + + uids = array_get(removed_uids, &uid_count); + if (uid_count == 0) + return; + + /* everything in removed_uids should exist in bbox->uids */ + uidmap = array_get_modifiable(&bbox->uids, &rec_count); + i_assert(rec_count >= uid_count); + + /* find the first uidmap record to be removed */ + if (!array_bsearch_insert_pos(&bbox->uids, &uids[0].seq1, + virtual_backend_uidmap_bsearch_cmp, &src)) + i_unreached(); + + /* remove the unwanted messages */ + dest = src; + for (i = 0; i < uid_count; i++) { + uid = uids[i].seq1; + while (uidmap[src].real_uid != uid) { + uidmap[dest++] = uidmap[src++]; + i_assert(src < rec_count); + } + + for (; uid <= uids[i].seq2; uid++, src++) { + i_assert(src < rec_count); + i_assert(uidmap[src].real_uid == uid); + if (uidmap[src].virtual_uid == 0) { + /* has not been assigned yet */ + continue; + } + if (mail_index_lookup_seq(ctx->sync_view, + uidmap[src].virtual_uid, + &vseq)) + mail_index_expunge(ctx->trans, vseq); + } + } + array_delete(&bbox->uids, dest, src - dest); +} + +static void +virtual_sync_mailbox_box_add(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + const ARRAY_TYPE(seq_range) *added_uids_arr) +{ + const struct seq_range *added_uids; + struct virtual_backend_uidmap *uidmap; + struct virtual_add_record rec; + unsigned int i, src, dest, uid_count, add_count, rec_count; + uint32_t add_uid; + + added_uids = array_get(added_uids_arr, &uid_count); + if (uid_count == 0) + return; + add_count = seq_range_count(added_uids_arr); + + /* none of added_uids should exist in bbox->uids. find the position + of the first inserted index. */ + uidmap = array_get_modifiable(&bbox->uids, &rec_count); + if (rec_count == 0 || + added_uids[0].seq1 > uidmap[rec_count-1].real_uid) { + /* fast path: usually messages are appended */ + dest = rec_count; + } else if (array_bsearch_insert_pos(&bbox->uids, &added_uids[0].seq1, + virtual_backend_uidmap_bsearch_cmp, + &dest)) + i_unreached(); + + /* make space for all added UIDs. */ + if (rec_count == dest) + array_idx_clear(&bbox->uids, dest + add_count-1); + else { + array_copy(&bbox->uids.arr, dest + add_count, + &bbox->uids.arr, dest, rec_count - dest); + } + uidmap = array_get_modifiable(&bbox->uids, &rec_count); + src = dest + add_count; + + /* add/move the UIDs to their correct positions */ + i_zero(&rec); + rec.rec.mailbox_id = bbox->mailbox_id; + for (i = 0; i < uid_count; i++) { + add_uid = added_uids[i].seq1; + while (src < rec_count && uidmap[src].real_uid < add_uid) + uidmap[dest++] = uidmap[src++]; + + for (; add_uid <= added_uids[i].seq2; add_uid++, dest++) { + i_assert(dest < rec_count); + + uidmap[dest].real_uid = add_uid; + uidmap[dest].virtual_uid = 0; + + if (ctx->mbox->uids_mapped) { + rec.rec.real_uid = add_uid; + array_push_back(&ctx->all_adds, &rec); + } + } + } +} + +static void +virtual_sync_mailbox_box_update_flags(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + const ARRAY_TYPE(seq_range) *uids_arr) +{ + unsigned int i, uid, vseq; + struct virtual_backend_uidmap *vuid; + struct seq_range_iter iter; + + i = 0; + seq_range_array_iter_init(&iter, uids_arr); + while(seq_range_array_iter_nth(&iter, i++, &uid)) { + vuid = array_bsearch(&bbox->uids, &uid, + virtual_backend_uidmap_bsearch_cmp); + if (vuid == NULL || + vuid->virtual_uid == 0 || + !mail_index_lookup_seq(ctx->sync_view, + vuid->virtual_uid, &vseq)) { + /* the entry has been already removed either by + us or some other session. doesn't matter, + we don't need to update the flags. + + it might also have not yet been assigned a uid + so we don't want to update the flags then either. + */ + continue; + } + virtual_sync_external_flags(ctx, bbox, vseq, + vuid->real_uid); + } +} + +static int virtual_backend_uidmap_cmp(const struct virtual_backend_uidmap *u1, + const struct virtual_backend_uidmap *u2) +{ + if (u1->real_uid < u2->real_uid) + return -1; + if (u1->real_uid > u2->real_uid) + return 1; + return 0; +} + +static void virtual_sync_bbox_uids_sort(struct virtual_backend_box *bbox) +{ + /* the uidmap must be sorted by real_uids */ + array_sort(&bbox->uids, virtual_backend_uidmap_cmp); + bbox->uids_nonsorted = FALSE; +} + +static void virtual_sync_backend_boxes_sort_uids(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->uids_nonsorted) + virtual_sync_bbox_uids_sort(bboxes[i]); + } +} + +static void +virtual_sync_backend_add_vmsgs_results(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + uint32_t real_uid, + struct mail_search_result *result, + const uint32_t vseq) +{ + struct virtual_backend_uidmap uidmap; + uint32_t vuid, seq; + + mail_index_lookup_uid(ctx->sync_view, vseq, &vuid); + + i_zero(&uidmap); + uidmap.real_uid = real_uid; + uidmap.virtual_uid = vuid; + array_push_back(&bbox->uids, &uidmap); + + if (result == NULL) + ; + else if (mail_index_lookup_seq(bbox->box->view, real_uid, &seq)) + seq_range_array_add(&result->uids, real_uid); + else + seq_range_array_add(&result->removed_uids, real_uid); +} + +static void +virtual_sync_backend_handle_old_vmsgs(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + struct mail_search_result *result) +{ + const struct virtual_mail_index_record *vrec; + const struct virtual_sync_mail *sync_mail, *sync_mails; + const void *data; + uint32_t i, vseq, messages; + + /* find the messages that currently exist in virtual index and add them + to the backend mailbox's list of uids. */ + array_clear(&bbox->uids); + + if (array_is_created(&ctx->all_mails)) { + i_assert(ctx->all_mails_prev_mailbox_id < bbox->mailbox_id); + sync_mails = array_get(&ctx->all_mails, &messages); + for (i = ctx->all_mails_idx; i < messages; i++) { + sync_mail = &sync_mails[i]; + if (sync_mail->vrec.mailbox_id != bbox->mailbox_id) { + if (sync_mail->vrec.mailbox_id < bbox->mailbox_id) { + /* stale mailbox_id, ignore */ + continue; + } + /* Should be in mailbox_id order, + so skip to next box */ + break; + } + + virtual_sync_backend_add_vmsgs_results(ctx, bbox, + sync_mail->vrec.real_uid, result, sync_mail->vseq); + } + ctx->all_mails_idx = i; + ctx->all_mails_prev_mailbox_id = bbox->mailbox_id; + } else { + /* there should be only a single backend mailbox, but in the + existing index there may be stale mailbox_ids that we'll + just skip over. */ + messages = mail_index_view_get_messages_count(ctx->sync_view); + for (vseq = 1; vseq <= messages; vseq++) { + mail_index_lookup_ext(ctx->sync_view, vseq, + ctx->mbox->virtual_ext_id, &data, NULL); + vrec = data; + if (vrec->mailbox_id == bbox->mailbox_id) { + virtual_sync_backend_add_vmsgs_results(ctx, + bbox, vrec->real_uid, result, vseq); + } + } + } + virtual_sync_bbox_uids_sort(bbox); +} + +static int virtual_sync_backend_box_continue(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + const enum mailbox_search_result_flags result_flags = + MAILBOX_SEARCH_RESULT_FLAG_UPDATE | + MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC; + struct mail_index_view *view = bbox->box->view; + struct mail_search_result *result; + ARRAY_TYPE(seq_range) expunged_uids = ARRAY_INIT, removed_uids; + ARRAY_TYPE(seq_range) added_uids, flag_update_uids; + uint64_t modseq, old_highest_modseq; + uint32_t seq, uid, old_msg_count; + + /* initialize the search result from all the existing messages in + virtual index. */ + if (!bbox->search_args_initialized) { + mail_search_args_init(bbox->search_args, bbox->box, FALSE, NULL); + bbox->search_args_initialized = TRUE; + } + result = mailbox_search_result_alloc(bbox->box, bbox->search_args, + result_flags); + mailbox_search_result_initial_done(result); + i_assert(array_count(&result->removed_uids) == 0); + virtual_sync_backend_handle_old_vmsgs(ctx, bbox, result); + if (array_count(&result->removed_uids) > 0) { + /* these are all expunged messages. treat them separately from + "no longer matching messages" (=removed_uids) */ + t_array_init(&expunged_uids, array_count(&result->removed_uids)); + array_append_array(&expunged_uids, &result->removed_uids); + array_clear(&result->removed_uids); + } + + /* get list of changed old messages (messages already once seen by + virtual index), based on modseq changes. (we'll assume all modseq + changes are due to flag changes, which may not be true in future) */ + if (bbox->sync_next_uid <= 1 || + !mail_index_lookup_seq_range(view, 1, bbox->sync_next_uid-1, + &seq, &old_msg_count)) + old_msg_count = 0; + old_highest_modseq = mail_index_modseq_get_highest(view); + + t_array_init(&flag_update_uids, I_MIN(128, old_msg_count)); + if (bbox->sync_highest_modseq < old_highest_modseq) { + for (seq = 1; seq <= old_msg_count; seq++) { + modseq = mail_index_modseq_lookup(view, seq); + if (modseq > bbox->sync_highest_modseq) { + mail_index_lookup_uid(view, seq, &uid); + seq_range_array_add(&flag_update_uids, uid); + } + } + } + + /* update the search result based on the flag changes and + new messages */ + if (index_search_result_update_flags(result, &flag_update_uids) < 0 || + index_search_result_update_appends(result, old_msg_count) < 0) { + mailbox_search_result_free(&result); + return -1; + } + + t_array_init(&removed_uids, 128); + t_array_init(&added_uids, 128); + mailbox_search_result_sync(result, &removed_uids, &added_uids); + if (array_is_created(&expunged_uids)) { + seq_range_array_remove_seq_range(&removed_uids, &expunged_uids); + virtual_sync_mailbox_box_remove(ctx, bbox, &expunged_uids); + } + if (ctx->expunge_removed) + virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids); + else { + /* delayed remove */ + seq_range_array_merge(&bbox->sync_pending_removes, + &removed_uids); + } + virtual_sync_mailbox_box_add(ctx, bbox, &added_uids); + virtual_sync_mailbox_box_update_flags(ctx, bbox, &flag_update_uids); + + bbox->search_result = result; + return 0; +} + +static void virtual_sync_drop_existing(struct virtual_backend_box *bbox, + ARRAY_TYPE(seq_range) *added_uids) +{ + ARRAY_TYPE(seq_range) drop_uids; + const struct virtual_backend_uidmap *uidmap; + struct seq_range_iter iter; + unsigned int i, n = 0, count; + uint32_t add_uid; + + seq_range_array_iter_init(&iter, added_uids); + if (!seq_range_array_iter_nth(&iter, n++, &add_uid)) + return; + + (void)array_bsearch_insert_pos(&bbox->uids, &add_uid, + virtual_backend_uidmap_bsearch_cmp, &i); + + uidmap = array_get_modifiable(&bbox->uids, &count); + if (i == count) + return; + + t_array_init(&drop_uids, array_count(added_uids)); + for (; i < count; ) { + if (uidmap[i].real_uid < add_uid) { + i++; + continue; + } + if (uidmap[i].real_uid == add_uid) { + seq_range_array_add(&drop_uids, add_uid); + i++; + } + if (!seq_range_array_iter_nth(&iter, n++, &add_uid)) + break; + } + seq_range_array_remove_seq_range(added_uids, &drop_uids); +} + +static void virtual_sync_drop_nonexistent(struct virtual_backend_box *bbox, + ARRAY_TYPE(seq_range) *removed_uids) +{ + ARRAY_TYPE(seq_range) drop_uids; + const struct virtual_backend_uidmap *uidmap; + struct seq_range_iter iter; + unsigned int i, n = 0, count; + uint32_t remove_uid; + bool iter_done = FALSE; + + seq_range_array_iter_init(&iter, removed_uids); + if (!seq_range_array_iter_nth(&iter, n++, &remove_uid)) + return; + + (void)array_bsearch_insert_pos(&bbox->uids, &remove_uid, + virtual_backend_uidmap_bsearch_cmp, &i); + + t_array_init(&drop_uids, array_count(removed_uids)); iter_done = FALSE; + uidmap = array_get_modifiable(&bbox->uids, &count); + for (; i < count; ) { + if (uidmap[i].real_uid < remove_uid) { + i++; + continue; + } + if (uidmap[i].real_uid != remove_uid) + seq_range_array_add(&drop_uids, remove_uid); + else + i++; + if (!seq_range_array_iter_nth(&iter, n++, &remove_uid)) { + iter_done = TRUE; + break; + } + } + if (!iter_done) { + do { + seq_range_array_add(&drop_uids, remove_uid); + } while (seq_range_array_iter_nth(&iter, n++, &remove_uid)); + } + seq_range_array_remove_seq_range(removed_uids, &drop_uids); +} + +static void virtual_sync_mailbox_box_update(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + ARRAY_TYPE(seq_range) removed_uids, added_uids, temp_uids; + unsigned int count1, count2; + + t_array_init(&removed_uids, 128); + t_array_init(&added_uids, 128); + + mailbox_search_result_sync(bbox->search_result, + &removed_uids, &added_uids); + if (array_is_created(&bbox->sync_outside_expunges)) { + seq_range_array_remove_seq_range(&bbox->sync_outside_expunges, + &added_uids); + seq_range_array_merge(&removed_uids, + &bbox->sync_outside_expunges); + array_clear(&bbox->sync_outside_expunges); + } + + virtual_sync_drop_existing(bbox, &added_uids); + virtual_sync_drop_nonexistent(bbox, &removed_uids); + + /* if any of the pending removes came back, we don't want to expunge + them anymore. also since they already exist, remove them from + added_uids. */ + count1 = array_count(&bbox->sync_pending_removes); + count2 = array_count(&added_uids); + if (count1 > 0 && count2 > 0) { + t_array_init(&temp_uids, count1); + array_append_array(&temp_uids, &bbox->sync_pending_removes); + if (seq_range_array_remove_seq_range( + &bbox->sync_pending_removes, &added_uids) > 0) { + seq_range_array_remove_seq_range(&added_uids, + &temp_uids); + } + } + + if (!ctx->expunge_removed) { + /* delay removing messages that don't match the search + criteria, but don't delay removing expunged messages */ + if (array_count(&ctx->sync_expunges) > 0) { + seq_range_array_remove_seq_range(&bbox->sync_pending_removes, + &ctx->sync_expunges); + seq_range_array_remove_seq_range(&removed_uids, + &ctx->sync_expunges); + virtual_sync_mailbox_box_remove(ctx, bbox, + &ctx->sync_expunges); + } + seq_range_array_merge(&bbox->sync_pending_removes, + &removed_uids); + } else if (array_count(&bbox->sync_pending_removes) > 0) { + /* remove all current and old */ + seq_range_array_merge(&bbox->sync_pending_removes, + &removed_uids); + virtual_sync_mailbox_box_remove(ctx, bbox, + &bbox->sync_pending_removes); + array_clear(&bbox->sync_pending_removes); + } else { + virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids); + } + virtual_sync_mailbox_box_add(ctx, bbox, &added_uids); +} + +static bool virtual_sync_find_seqs(struct virtual_backend_box *bbox, + const struct mailbox_sync_rec *sync_rec, + unsigned int *idx1_r, + unsigned int *idx2_r) +{ + const struct virtual_backend_uidmap *uidmap; + unsigned int idx, count; + uint32_t uid1, uid2; + + mail_index_lookup_uid(bbox->box->view, sync_rec->seq1, &uid1); + mail_index_lookup_uid(bbox->box->view, sync_rec->seq2, &uid2); + (void)array_bsearch_insert_pos(&bbox->uids, &uid1, + virtual_backend_uidmap_bsearch_cmp, + &idx); + + uidmap = array_get_modifiable(&bbox->uids, &count); + if (idx == count || uidmap[idx].real_uid > uid2) + return FALSE; + + *idx1_r = idx; + while (idx < count && uidmap[idx].real_uid <= uid2) idx++; + *idx2_r = idx - 1; + return TRUE; +} + +static void virtual_sync_expunge_add(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + const struct mailbox_sync_rec *sync_rec) +{ + struct virtual_backend_uidmap *uidmap; + uint32_t uid1, uid2; + unsigned int i, idx1, count; + + mail_index_lookup_uid(bbox->box->view, sync_rec->seq1, &uid1); + mail_index_lookup_uid(bbox->box->view, sync_rec->seq2, &uid2); + + /* remember only the expunges for messages that + already exist for this mailbox */ + (void)array_bsearch_insert_pos(&bbox->uids, &uid1, + virtual_backend_uidmap_bsearch_cmp, + &idx1); + uidmap = array_get_modifiable(&bbox->uids, &count); + for (i = idx1; i < count; i++) { + if (uidmap[i].real_uid > uid2) + break; + seq_range_array_add(&ctx->sync_expunges, uidmap[i].real_uid); + } +} + +static int virtual_sync_backend_box_sync(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + enum mailbox_sync_flags sync_flags) +{ + struct mailbox_sync_context *sync_ctx; + const struct virtual_backend_uidmap *uidmap; + struct mailbox_sync_rec sync_rec; + struct mailbox_sync_status sync_status; + unsigned int idx1, idx2; + uint32_t vseq, vuid; + + sync_ctx = mailbox_sync_init(bbox->box, sync_flags); + virtual_backend_box_sync_mail_set(bbox); + while (mailbox_sync_next(sync_ctx, &sync_rec)) { + switch (sync_rec.type) { + case MAILBOX_SYNC_TYPE_EXPUNGE: + if (ctx->expunge_removed) { + /* no need to keep track of expunges */ + break; + } + virtual_sync_expunge_add(ctx, bbox, &sync_rec); + break; + case MAILBOX_SYNC_TYPE_FLAGS: + if (!virtual_sync_find_seqs(bbox, &sync_rec, + &idx1, &idx2)) + break; + uidmap = array_front(&bbox->uids); + for (; idx1 <= idx2; idx1++) { + vuid = uidmap[idx1].virtual_uid; + if (vuid == 0) { + /* has not been even assigned yet */ + continue; + } + if (!mail_index_lookup_seq(ctx->sync_view, + vuid, &vseq)) { + /* expunged by another session, + but we haven't yet updated + bbox->uids. */ + continue; + } + virtual_sync_external_flags(ctx, bbox, vseq, + uidmap[idx1].real_uid); + } + break; + case MAILBOX_SYNC_TYPE_MODSEQ: + break; + } + } + if (mailbox_sync_deinit(&sync_ctx, &sync_status) < 0) { + if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND) + return -1; + /* mailbox was deleted */ + virtual_sync_backend_box_deleted(ctx, bbox); + return 0; + } + return 0; +} + +static bool +virtual_bbox_mailbox_equals(struct virtual_backend_box *bbox, + const struct mailbox_status *status, + struct mailbox_metadata *metadata, + const char **reason_r) +{ + if (!guid_128_equals(bbox->sync_guid, metadata->guid)) { + *reason_r = t_strdup_printf("GUID changed: %s -> %s", + guid_128_to_string(bbox->sync_guid), + guid_128_to_string(metadata->guid)); + return FALSE; + } + if (bbox->sync_uid_validity != status->uidvalidity) { + *reason_r = t_strdup_printf("UIDVALIDITY changed: %u -> %u", + bbox->sync_uid_validity, status->uidvalidity); + return FALSE; + } + if (bbox->sync_next_uid != status->uidnext) { + *reason_r = t_strdup_printf("UIDNEXT changed: %u -> %u", + bbox->sync_next_uid, status->uidnext); + return FALSE; + } + if (bbox->sync_highest_modseq != status->highest_modseq) { + *reason_r = t_strdup_printf("HIGHESTMODSEQ changed: " + "%"PRIu64" -> %"PRIu64, + bbox->sync_highest_modseq, status->highest_modseq); + return FALSE; + } + return TRUE; +} + +static void virtual_sync_backend_ext_header(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + const unsigned int uidval_pos = + offsetof(struct virtual_mail_index_mailbox_record, + uid_validity); + struct mailbox_status status; + struct virtual_mail_index_mailbox_record mailbox; + struct virtual_mail_index_mailbox_ext2_record ext2; + unsigned int mailbox_offset, ext2_offset; + uint64_t wanted_ondisk_highest_modseq; + struct mailbox_metadata metadata; + const char *reason; + + mailbox_get_open_status(bbox->box, STATUS_UIDVALIDITY | + STATUS_HIGHESTMODSEQ, &status); + wanted_ondisk_highest_modseq = + array_count(&bbox->sync_pending_removes) > 0 ? 0 : + status.highest_modseq; + + if (mailbox_get_metadata(bbox->box, MAILBOX_METADATA_GUID, + &metadata) < 0) { + /* Either a temporary failure or the mailbox was already + deleted. Either way, it doesn't really matter at this point. + We'll just leave the error handling until the next sync. */ + return; + } + + if (virtual_bbox_mailbox_equals(bbox, &status, &metadata, &reason) && + bbox->ondisk_highest_modseq == wanted_ondisk_highest_modseq) + return; + + /* mailbox changed - update extension header */ + bbox->sync_uid_validity = status.uidvalidity; + bbox->sync_highest_modseq = status.highest_modseq; + bbox->ondisk_highest_modseq = wanted_ondisk_highest_modseq; + bbox->sync_next_uid = status.uidnext; + guid_128_copy(bbox->sync_guid, metadata.guid); + + if (ctx->mbox->ext_header_rewrite) { + /* we'll rewrite the entire header later */ + return; + } + + i_zero(&mailbox); + mailbox.uid_validity = bbox->sync_uid_validity; + mailbox.highest_modseq = bbox->ondisk_highest_modseq; + mailbox.next_uid = bbox->sync_next_uid; + + i_zero(&ext2); + guid_128_copy(ext2.guid, bbox->sync_guid); + + i_assert(bbox->sync_mailbox_idx1 > 0); + mailbox_offset = sizeof(struct virtual_mail_index_header) + + (bbox->sync_mailbox_idx1-1) * sizeof(mailbox); + ext2_offset = sizeof(struct virtual_mail_index_ext2_header) + + (bbox->sync_mailbox_idx1-1) * sizeof(ext2); + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id, + mailbox_offset + uidval_pos, + CONST_PTR_OFFSET(&mailbox, uidval_pos), + sizeof(mailbox) - uidval_pos); + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id, + ext2_offset, &ext2, sizeof(ext2)); + ctx->ext_header_changed = TRUE; +} + +static void virtual_sync_backend_box_deleted(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + ARRAY_TYPE(seq_range) removed_uids; + const struct virtual_backend_uidmap *uidmap; + + /* delay its full removal until the next time we open the virtual + mailbox. for now just treat it as if it was empty. */ + + t_array_init(&removed_uids, 128); + array_foreach(&bbox->uids, uidmap) + seq_range_array_add(&removed_uids, uidmap->real_uid); + virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids); + + bbox->deleted = TRUE; +} + +static int +virtual_try_open_and_sync_backend_box(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + enum mailbox_sync_flags sync_flags) +{ + int ret = 0; + + if (!bbox->box->opened) + ret = virtual_backend_box_open(ctx->mbox, bbox); + if (ret == 0) + ret = mailbox_sync(bbox->box, sync_flags); + if (ret < 0) { + if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND) + return -1; + /* mailbox was deleted */ + virtual_sync_backend_box_deleted(ctx, bbox); + return 0; + } + return 1; +} + +static int virtual_sync_backend_box(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + enum mailbox_sync_flags sync_flags; + struct mailbox_status status; + const char *reason; + int ret; + + if (bbox->deleted) + return 0; + + /* if we already did some changes to index, commit them before + syncing starts. */ + virtual_backend_box_sync_mail_unset(bbox); + + sync_flags = ctx->flags & (MAILBOX_SYNC_FLAG_FULL_READ | + MAILBOX_SYNC_FLAG_FULL_WRITE | + MAILBOX_SYNC_FLAG_FAST); + + if (bbox->search_result == NULL) { + struct mailbox_metadata metadata; + + /* a) first sync in this process. + b) we had auto-closed this backend mailbox. + + first try to quickly check if the mailbox has changed. + if we can do that check from mailbox list index, we don't + even need to open the mailbox. */ + i_assert(array_count(&bbox->sync_pending_removes) == 0); + if (bbox->box->opened || bbox->open_failed) { + /* a) index already opened, refresh it + b) delayed error handling for mailbox_open() + that failed in virtual_notify_changes() */ + if ((ret = virtual_try_open_and_sync_backend_box(ctx, bbox, sync_flags)) <= 0) + return ret; + bbox->open_failed = FALSE; + } + + if ((mailbox_get_status(bbox->box, STATUS_UIDVALIDITY | + STATUS_UIDNEXT | STATUS_HIGHESTMODSEQ, + &status) < 0) || + (mailbox_get_metadata(bbox->box, MAILBOX_METADATA_GUID, + &metadata) < 0)) { + if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND) + return -1; + /* mailbox was deleted */ + virtual_sync_backend_box_deleted(ctx, bbox); + return 0; + } + if (guid_128_is_empty(bbox->sync_guid)) { + /* upgrading from old virtual index */ + guid_128_copy(bbox->sync_guid, metadata.guid); + ctx->mbox->ext_header_rewrite = TRUE; + } + if (virtual_bbox_mailbox_equals(bbox, &status, &metadata, &reason)) { + /* mailbox hasn't changed since we last opened it, + skip it for now. + + we'll still need to create the bbox->uids mapping + using the current index. */ + if (array_count(&bbox->uids) == 0) + virtual_sync_backend_handle_old_vmsgs(ctx, bbox, NULL); + return 0; + } + e_debug(ctx->mbox->box.event, "Backend mailbox %s changed: %s", + bbox->box->vname, reason); + if (!bbox->box->opened) { + /* first time we're opening the index */ + if ((ret = virtual_try_open_and_sync_backend_box(ctx, bbox, sync_flags)) <= 0) + return ret; + } + + virtual_backend_box_sync_mail_set(bbox); + if ((status.uidvalidity != bbox->sync_uid_validity) || + !guid_128_equals(metadata.guid, bbox->sync_guid)) { + /* UID validity or GUID changed since last sync (or + this is the first sync), do a full search */ + bbox->first_sync = TRUE; + ret = virtual_sync_backend_box_init(bbox); + } else { + /* build the initial search using the saved modseq. */ + ret = virtual_sync_backend_box_continue(ctx, bbox); + } + i_assert(bbox->search_result != NULL || ret < 0); + } else { + /* sync using the existing search result */ + i_assert(bbox->box->opened); + i_array_init(&ctx->sync_expunges, 32); + ret = virtual_sync_backend_box_sync(ctx, bbox, sync_flags); + if (ret == 0) T_BEGIN { + virtual_sync_mailbox_box_update(ctx, bbox); + } T_END; + array_free(&ctx->sync_expunges); + } + + virtual_sync_backend_ext_header(ctx, bbox); + return ret; +} + +static void virtual_sync_backend_map_uids(struct virtual_sync_context *ctx) +{ + uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id; + struct virtual_sync_mail *vmails; + struct virtual_backend_box *bbox; + struct virtual_backend_uidmap *uidmap = NULL; + struct virtual_add_record add_rec; + const struct virtual_mail_index_record *vrec; + const void *data; + uint32_t i, vseq, vuid, messages; + unsigned int j = 0, uidmap_count = 0; + + messages = mail_index_view_get_messages_count(ctx->sync_view); + if (messages == 0) + return; + + /* sort the messages in current view by their backend mailbox and + real UID */ + vmails = i_new(struct virtual_sync_mail, messages); + for (vseq = 1; vseq <= messages; vseq++) { + mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id, + &data, NULL); + vrec = data; + vmails[vseq-1].vseq = vseq; + vmails[vseq-1].vrec = *vrec; + } + qsort(vmails, messages, sizeof(*vmails), virtual_sync_mail_uid_cmp); + + /* create real mailbox uid -> virtual uid mapping and expunge + messages no longer matching the search rule */ + i_zero(&add_rec); + bbox = NULL; + for (i = 0; i < messages; i++) { + vseq = vmails[i].vseq; + vrec = &vmails[i].vrec; + + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + /* add the rest of the newly seen messages */ + for (; j < uidmap_count; j++) { + add_rec.rec.real_uid = uidmap[j].real_uid; + array_push_back(&ctx->all_adds, &add_rec); + } + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + if (bbox == NULL || bbox->first_sync) { + /* the entire mailbox is lost */ + mail_index_expunge(ctx->trans, vseq); + continue; + } + uidmap = array_get_modifiable(&bbox->uids, + &uidmap_count); + j = 0; + add_rec.rec.mailbox_id = bbox->mailbox_id; + bbox->sync_seen = TRUE; + } + mail_index_lookup_uid(ctx->sync_view, vseq, &vuid); + + /* if virtual record doesn't exist in uidmap, it's expunged */ + for (; j < uidmap_count; j++) { + if (uidmap[j].real_uid >= vrec->real_uid) + break; + + /* newly seen message */ + add_rec.rec.real_uid = uidmap[j].real_uid; + array_push_back(&ctx->all_adds, &add_rec); + } + if (j == uidmap_count || uidmap[j].real_uid != vrec->real_uid) + mail_index_expunge(ctx->trans, vseq); + else { + /* exists - update uidmap and flags */ + uidmap[j++].virtual_uid = vuid; + if (bbox->search_result == NULL) { + /* mailbox is completely unchanged since last + sync - no need to sync flags */ + } else { + virtual_sync_external_flags(ctx, bbox, vseq, + vrec->real_uid); + } + } + } + i_free(vmails); + + /* finish adding messages to the last mailbox */ + for (; j < uidmap_count; j++) { + add_rec.rec.real_uid = uidmap[j].real_uid; + array_push_back(&ctx->all_adds, &add_rec); + } +} + +static void virtual_sync_new_backend_boxes(struct virtual_sync_context *ctx) +{ + struct virtual_backend_box *const *bboxes; + struct virtual_add_record add_rec; + struct virtual_backend_uidmap *uidmap; + unsigned int i, j, count, uidmap_count; + + /* if there are any mailboxes we didn't yet sync, add new messages in + them */ + i_zero(&add_rec); + bboxes = array_get(&ctx->mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + bboxes[i]->first_sync = FALSE; /* this is the end of the sync */ + + if (bboxes[i]->sync_seen) + continue; + + add_rec.rec.mailbox_id = bboxes[i]->mailbox_id; + uidmap = array_get_modifiable(&bboxes[i]->uids, &uidmap_count); + for (j = 0; j < uidmap_count; j++) { + add_rec.rec.real_uid = uidmap[j].real_uid; + array_push_back(&ctx->all_adds, &add_rec); + } + } +} + +static int virtual_add_record_cmp(const struct virtual_add_record *add1, + const struct virtual_add_record *add2) +{ + if (add1->received_date < add2->received_date) + return -1; + if (add1->received_date > add2->received_date) + return 1; + + /* if they're in same mailbox, we can order them correctly by the UID. + if they're in different mailboxes, ordering by UID doesn't really + help but it doesn't really harm either. */ + if (add1->rec.real_uid < add2->rec.real_uid) + return -1; + if (add1->rec.real_uid > add2->rec.real_uid) + return 1; + + /* two messages in different mailboxes have the same received date + and UID. */ + return 0; +} + +static int virtual_sync_backend_sort_new(struct virtual_sync_context *ctx) +{ + struct virtual_backend_box *bbox; + struct virtual_add_record *adds; + const struct virtual_mail_index_record *vrec; + unsigned int i, count; + + /* get all messages' received dates */ + adds = array_get_modifiable(&ctx->all_adds, &count); + for (bbox = NULL, i = 0; i < count; i++) { + vrec = &adds[i].rec; + + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + if (!bbox->box->opened && + virtual_backend_box_open(ctx->mbox, bbox) < 0) + return -1; + virtual_backend_box_sync_mail_set(bbox); + } + if (!mail_set_uid(bbox->sync_mail, vrec->real_uid)) { + /* we may have reopened the mailbox, which could have + caused the mail to be expunged already. */ + adds[i].received_date = 0; + } else if (mail_get_received_date(bbox->sync_mail, + &adds[i].received_date) < 0) { + if (!bbox->sync_mail->expunged) + return -1; + /* expunged already, just add it somewhere */ + adds[i].received_date = 0; + } + } + + array_sort(&ctx->all_adds, virtual_add_record_cmp); + return 0; +} + +static int virtual_sync_backend_add_new(struct virtual_sync_context *ctx) +{ + uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id; + struct virtual_add_record *adds; + struct virtual_backend_box *bbox; + struct virtual_backend_uidmap *uidmap; + const struct mail_index_header *hdr; + const struct virtual_mail_index_record *vrec; + unsigned int i, count, idx; + ARRAY_TYPE(seq_range) saved_uids; + uint32_t vseq, first_uid; + + hdr = mail_index_get_header(ctx->sync_view); + adds = array_get_modifiable(&ctx->all_adds, &count); + if (count == 0) { + ctx->mbox->sync_virtual_next_uid = hdr->next_uid; + return 0; + } + + if (adds[0].rec.mailbox_id == adds[count-1].rec.mailbox_id) { + /* all messages are from a single mailbox. add them in + the same order. */ + } else { + /* sort new messages by received date to get the add order */ + if (virtual_sync_backend_sort_new(ctx) < 0) + return -1; + } + + for (bbox = NULL, i = 0; i < count; i++) { + vrec = &adds[i].rec; + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + if (!bbox->box->opened && + virtual_backend_box_open(ctx->mbox, bbox) < 0) + return -1; + virtual_backend_box_sync_mail_set(bbox); + } + + mail_index_append(ctx->trans, 0, &vseq); + mail_index_update_ext(ctx->trans, vseq, virtual_ext_id, + vrec, NULL); + virtual_sync_external_flags(ctx, bbox, vseq, vrec->real_uid); + } + + /* assign UIDs to new messages */ + first_uid = hdr->next_uid; + t_array_init(&saved_uids, 1); + mail_index_append_finish_uids(ctx->trans, first_uid, &saved_uids); + i_assert(seq_range_count(&saved_uids) == count); + + /* update virtual UIDs in uidmap */ + for (bbox = NULL, i = 0; i < count; i++) { + vrec = &adds[i].rec; + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + } + + if (!array_bsearch_insert_pos(&bbox->uids, &vrec->real_uid, + virtual_backend_uidmap_bsearch_cmp, + &idx)) + i_unreached(); + uidmap = array_idx_modifiable(&bbox->uids, idx); + i_assert(uidmap->virtual_uid == 0); + uidmap->virtual_uid = first_uid + i; + } + ctx->mbox->sync_virtual_next_uid = first_uid + i; + return 0; +} + +static int +virtual_sync_apply_existing_appends(struct virtual_sync_context *ctx) +{ + uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id; + struct virtual_backend_box *bbox = NULL; + const struct mail_index_header *hdr; + const struct virtual_mail_index_record *vrec; + struct virtual_backend_uidmap uidmap; + const void *data; + uint32_t seq, seq2; + + if (!ctx->mbox->uids_mapped) + return 0; + + hdr = mail_index_get_header(ctx->sync_view); + if (ctx->mbox->sync_virtual_next_uid >= hdr->next_uid) + return 0; + + /* another process added messages to virtual index. get backend boxes' + uid lists up-to-date by adding the new messages there. */ + if (!mail_index_lookup_seq_range(ctx->sync_view, + ctx->mbox->sync_virtual_next_uid, + (uint32_t)-1, &seq, &seq2)) + return 0; + + i_zero(&uidmap); + for (; seq <= seq2; seq++) { + mail_index_lookup_ext(ctx->sync_view, seq, virtual_ext_id, + &data, NULL); + vrec = data; + uidmap.real_uid = vrec->real_uid; + mail_index_lookup_uid(ctx->sync_view, seq, &uidmap.virtual_uid); + + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + if (bbox == NULL) { + mail_index_expunge(ctx->trans, seq); + continue; + } + } + array_push_back(&bbox->uids, &uidmap); + bbox->uids_nonsorted = TRUE; + } + + virtual_sync_backend_boxes_sort_uids(ctx->mbox); + return 0; +} + +static void +virtual_sync_apply_existing_expunges(struct virtual_mailbox *mbox, + struct mailbox_sync_context *sync_ctx) +{ + struct index_mailbox_sync_context *isync_ctx = + (struct index_mailbox_sync_context *)sync_ctx; + struct virtual_backend_box *bbox = NULL; + struct seq_range_iter iter; + const struct virtual_mail_index_record *vrec; + const void *data; + unsigned int n = 0; + uint32_t seq; + + if (isync_ctx->expunges == NULL) + return; + + seq_range_array_iter_init(&iter, isync_ctx->expunges); + while (seq_range_array_iter_nth(&iter, n++, &seq)) { + mail_index_lookup_ext(mbox->box.view, seq, + mbox->virtual_ext_id, &data, NULL); + vrec = data; + + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(mbox, + vrec->mailbox_id); + if (!array_is_created(&bbox->sync_outside_expunges)) + i_array_init(&bbox->sync_outside_expunges, 32); + } + seq_range_array_add(&bbox->sync_outside_expunges, + vrec->real_uid); + } +} + +static int virtual_sync_mail_mailbox_cmp(const struct virtual_sync_mail *m1, + const struct virtual_sync_mail *m2) +{ + if (m1->vrec.mailbox_id < m2->vrec.mailbox_id) + return -1; + if (m1->vrec.mailbox_id > m2->vrec.mailbox_id) + return 1; + return 0; +} + +static void virtual_sync_bboxes_get_mails(struct virtual_sync_context *ctx) +{ + uint32_t messages, vseq; + const void *mail_data; + const struct virtual_mail_index_record *vrec; + struct virtual_sync_mail *sync_mail; + + messages = mail_index_view_get_messages_count(ctx->sync_view); + i_array_init(&ctx->all_mails, messages); + for (vseq = 1; vseq <= messages; vseq++) { + mail_index_lookup_ext(ctx->sync_view, vseq, + ctx->mbox->virtual_ext_id, &mail_data, NULL); + vrec = mail_data; + sync_mail = array_append_space(&ctx->all_mails); + sync_mail->vseq = vseq; + sync_mail->vrec = *vrec; + } + array_sort(&ctx->all_mails, virtual_sync_mail_mailbox_cmp); +} + +static int virtual_sync_backend_boxes(struct virtual_sync_context *ctx) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + int ret; + + if (virtual_sync_apply_existing_appends(ctx) < 0) + return -1; + + i_array_init(&ctx->all_adds, 128); + bboxes = array_get(&ctx->mbox->backend_boxes, &count); + + /* we have different optimizations depending on whether the virtual + mailbox consists of multiple backend boxes or just one */ + if (count > 1) + virtual_sync_bboxes_get_mails(ctx); + + for (i = 0; i < count; i++) { + if (virtual_sync_backend_box(ctx, bboxes[i]) < 0) { + /* backend failed, copy the error */ + virtual_box_copy_error(&ctx->mbox->box, + bboxes[i]->box); + return -1; + } + } + + if (!ctx->mbox->uids_mapped) { + /* initial sync: assign virtual UIDs to existing messages and + sync all flags */ + ctx->mbox->uids_mapped = TRUE; + virtual_sync_backend_map_uids(ctx); + virtual_sync_new_backend_boxes(ctx); + } + ret = virtual_sync_backend_add_new(ctx); +#ifdef DEBUG + for (i = 0; i < count; i++) { + const struct virtual_backend_uidmap *uidmap; + + array_foreach(&bboxes[i]->uids, uidmap) + i_assert(uidmap->virtual_uid > 0); + } +#endif + array_free(&ctx->all_adds); + if (array_is_created(&ctx->all_mails)) + array_free(&ctx->all_mails); + return ret; +} + +static void virtual_sync_backend_boxes_finish(struct virtual_sync_context *ctx) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + bboxes = array_get(&ctx->mbox->backend_boxes, &count); + for (i = 0; i < count; i++) + virtual_backend_box_sync_mail_unset(bboxes[i]); +} + +static int virtual_sync_finish(struct virtual_sync_context *ctx, bool success) +{ + int ret = success ? 0 : -1; + + virtual_sync_backend_boxes_finish(ctx); + if (success) { + if (mail_index_sync_commit(&ctx->index_sync_ctx) < 0) { + mailbox_set_index_error(&ctx->mbox->box); + ret = -1; + } + ctx->mbox->ext_header_rewrite = FALSE; + } else { + if (ctx->index_broken) { + /* make sure we don't complain about the same errors + over and over again. */ + if (mail_index_unlink(ctx->index) < 0) { + i_error("virtual index %s: Failed to unlink() " + "broken indexes: %m", + mailbox_get_path(&ctx->mbox->box)); + } + } + mail_index_sync_rollback(&ctx->index_sync_ctx); + } + i_free(ctx); + return ret; +} + +static int virtual_sync(struct virtual_mailbox *mbox, + enum mailbox_sync_flags flags) +{ + struct virtual_sync_context *ctx; + enum mail_index_sync_flags index_sync_flags; + bool broken; + int ret; + + ctx = i_new(struct virtual_sync_context, 1); + ctx->mbox = mbox; + ctx->flags = flags; + ctx->index = mbox->box.index; + /* Removed messages are expunged when + a) EXPUNGE is used + b) Mailbox is being opened (FIX_INCONSISTENT is set) */ + ctx->expunge_removed = + (ctx->flags & (MAILBOX_SYNC_FLAG_EXPUNGE | + MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) != 0; + + index_sync_flags = MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY | + MAIL_INDEX_SYNC_FLAG_AVOID_FLAG_UPDATES; + if ((mbox->box.flags & MAILBOX_FLAG_DROP_RECENT) != 0) + index_sync_flags |= MAIL_INDEX_SYNC_FLAG_DROP_RECENT; + + ret = mail_index_sync_begin(ctx->index, &ctx->index_sync_ctx, + &ctx->sync_view, &ctx->trans, + index_sync_flags); + if (ret <= 0) { + if (ret < 0) + mailbox_set_index_error(&mbox->box); + i_free(ctx); + return ret; + } + + ret = virtual_mailbox_ext_header_read(mbox, ctx->sync_view, &broken); + if (ret < 0) + return virtual_sync_finish(ctx, FALSE); + if (broken) + ctx->index_broken = TRUE; + /* apply changes from virtual index to backend mailboxes */ + if (virtual_sync_index_changes(ctx) < 0) + return virtual_sync_finish(ctx, FALSE); + /* update list of UIDs in backend mailboxes */ + if (virtual_sync_backend_boxes(ctx) < 0) + return virtual_sync_finish(ctx, FALSE); + + virtual_sync_index_finish(ctx); + return virtual_sync_finish(ctx, TRUE); +} + +struct mailbox_sync_context * +virtual_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct mailbox_sync_context *sync_ctx; + int ret = 0; + + if (!box->opened) { + if (mailbox_open(box) < 0) + ret = -1; + } + + if (index_mailbox_want_full_sync(&mbox->box, flags) && ret == 0) + ret = virtual_sync(mbox, flags); + + sync_ctx = index_mailbox_sync_init(box, flags, ret < 0); + virtual_sync_apply_existing_expunges(mbox, sync_ctx); + return sync_ctx; +} diff --git a/src/plugins/virtual/virtual-transaction.c b/src/plugins/virtual/virtual-transaction.c new file mode 100644 index 0000000..53ac0a8 --- /dev/null +++ b/src/plugins/virtual/virtual-transaction.c @@ -0,0 +1,87 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "virtual-storage.h" +#include "virtual-transaction.h" + +struct mailbox_transaction_context * +virtual_transaction_get(struct mailbox_transaction_context *trans, + struct mailbox *backend_box) +{ + struct virtual_transaction_context *vt = + (struct virtual_transaction_context *)trans; + struct mailbox_transaction_context *const *bt, *new_bt; + unsigned int i, count; + + bt = array_get(&vt->backend_transactions, &count); + for (i = 0; i < count; i++) { + if (bt[i]->box == backend_box) + return bt[i]; + } + + new_bt = mailbox_transaction_begin(backend_box, trans->flags, __func__); + array_push_back(&vt->backend_transactions, &new_bt); + return new_bt; +} + +struct mailbox_transaction_context * +virtual_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_transaction_context *vt; + + vt = i_new(struct virtual_transaction_context, 1); + i_array_init(&vt->backend_transactions, + array_count(&mbox->backend_boxes)); + index_transaction_init(&vt->t, box, flags, reason); + return &vt->t; +} + +int virtual_transaction_commit(struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes_r) +{ + struct virtual_transaction_context *vt = + (struct virtual_transaction_context *)t; + struct mailbox_transaction_context **bt; + unsigned int i, count; + int ret = 0; + + if (t->save_ctx != NULL) { + virtual_save_free(t->save_ctx); + t->save_ctx = NULL; + } + + bt = array_get_modifiable(&vt->backend_transactions, &count); + for (i = 0; i < count; i++) { + if (mailbox_transaction_commit(&bt[i]) < 0) + ret = -1; + } + array_free(&vt->backend_transactions); + + if (index_transaction_commit(t, changes_r) < 0) + ret = -1; + return ret; +} + +void virtual_transaction_rollback(struct mailbox_transaction_context *t) +{ + struct virtual_transaction_context *vt = + (struct virtual_transaction_context *)t; + struct mailbox_transaction_context **bt; + unsigned int i, count; + + if (t->save_ctx != NULL) { + virtual_save_free(t->save_ctx); + t->save_ctx = NULL; + } + + bt = array_get_modifiable(&vt->backend_transactions, &count); + for (i = 0; i < count; i++) + mailbox_transaction_rollback(&bt[i]); + array_free(&vt->backend_transactions); + + index_transaction_rollback(t); +} diff --git a/src/plugins/virtual/virtual-transaction.h b/src/plugins/virtual/virtual-transaction.h new file mode 100644 index 0000000..a950fcb --- /dev/null +++ b/src/plugins/virtual/virtual-transaction.h @@ -0,0 +1,24 @@ +#ifndef VIRTUAL_TRANSACTION_H +#define VIRTUAL_TRANSACTION_H + +#include "index-storage.h" + +struct virtual_transaction_context { + struct mailbox_transaction_context t; + + ARRAY(struct mailbox_transaction_context *) backend_transactions; +}; + +struct mailbox_transaction_context * +virtual_transaction_get(struct mailbox_transaction_context *trans, + struct mailbox *backend_box); + +struct mailbox_transaction_context * +virtual_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason); +int virtual_transaction_commit(struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes_r); +void virtual_transaction_rollback(struct mailbox_transaction_context *t); + +#endif diff --git a/src/plugins/welcome/Makefile.am b/src/plugins/welcome/Makefile.am new file mode 100644 index 0000000..98968f5 --- /dev/null +++ b/src/plugins/welcome/Makefile.am @@ -0,0 +1,14 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib99_welcome_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib99_welcome_plugin.la + +lib99_welcome_plugin_la_SOURCES = \ + welcome-plugin.c diff --git a/src/plugins/welcome/Makefile.in b/src/plugins/welcome/Makefile.in new file mode 100644 index 0000000..5e55c1c --- /dev/null +++ b/src/plugins/welcome/Makefile.in @@ -0,0 +1,814 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/welcome +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib99_welcome_plugin_la_LIBADD = +am_lib99_welcome_plugin_la_OBJECTS = welcome-plugin.lo +lib99_welcome_plugin_la_OBJECTS = \ + $(am_lib99_welcome_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib99_welcome_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib99_welcome_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/welcome-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib99_welcome_plugin_la_SOURCES) +DIST_SOURCES = $(lib99_welcome_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +lib99_welcome_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib99_welcome_plugin.la + +lib99_welcome_plugin_la_SOURCES = \ + welcome-plugin.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/welcome/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/welcome/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib99_welcome_plugin.la: $(lib99_welcome_plugin_la_OBJECTS) $(lib99_welcome_plugin_la_DEPENDENCIES) $(EXTRA_lib99_welcome_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib99_welcome_plugin_la_LINK) -rpath $(moduledir) $(lib99_welcome_plugin_la_OBJECTS) $(lib99_welcome_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/welcome-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/welcome-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/welcome-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/welcome/welcome-plugin.c b/src/plugins/welcome/welcome-plugin.c new file mode 100644 index 0000000..e98d126 --- /dev/null +++ b/src/plugins/welcome/welcome-plugin.c @@ -0,0 +1,146 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "str.h" +#include "strescape.h" +#include "eacces-error.h" +#include "write-full.h" +#include "module-context.h" +#include "mail-storage-private.h" + +#define WELCOME_SOCKET_TIMEOUT_SECS 30 + +#define WELCOME_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, welcome_storage_module) + +struct welcome_mailbox { + union mailbox_module_context module_ctx; + bool created; +}; + +static MODULE_CONTEXT_DEFINE_INIT(welcome_storage_module, + &mail_storage_module_register); + +static void script_execute(struct mail_user *user, const char *cmd, bool wait) +{ + const char *socket_path, *const *args; + string_t *str; + char buf[1024]; + int fd, ret; + + e_debug(user->event, "welcome: Executing %s (wait=%d)", cmd, wait ? 1 : 0); + + args = t_strsplit_spaces(cmd, " "); + socket_path = args[0]; + args++; + + if (*socket_path != '/') { + socket_path = t_strconcat(user->set->base_dir, "/", + socket_path, NULL); + } + if ((fd = net_connect_unix_with_retries(socket_path, 1000)) < 0) { + if (errno == EACCES) { + i_error("welcome: %s", + eacces_error_get("net_connect_unix", + socket_path)); + } else { + i_error("welcome: net_connect_unix(%s) failed: %m", + socket_path); + } + return; + } + + str = t_str_new(1024); + str_append(str, "VERSION\tscript\t4\t0\n"); + if (!wait) + str_append(str, "noreply\n"); + else + str_append(str, "-\n"); + for (; *args != NULL; args++) { + str_append_tabescaped(str, *args); + str_append_c(str, '\n'); + } + str_append_c(str, '\n'); + + alarm(WELCOME_SOCKET_TIMEOUT_SECS); + net_set_nonblock(fd, FALSE); + if (write_full(fd, str_data(str), str_len(str)) < 0) + i_error("write(%s) failed: %m", socket_path); + else if (wait) { + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) + i_error("welcome: read(%s) failed: %m", socket_path); + else if (ret < 2) + i_error("welcome: %s failed: Only %d bytes read", socket_path, ret); + else if (buf[0] != '+') + i_error("welcome: %s failed: Script returned error", socket_path); + } + if (close(fd) < 0) + i_error("close(%s) failed: %m", socket_path); +} + +static int +welcome_create_box(struct mailbox *box, + const struct mailbox_update *update, bool directory) +{ + struct welcome_mailbox *wbox = WELCOME_CONTEXT(box); + + if (wbox->module_ctx.super.create_box(box, update, directory) < 0) + return -1; + /* the mailbox isn't fully created here yet, so just mark it as created + and wait until open() time to actually run it */ + wbox->created = TRUE; + return 0; +} + +static int welcome_open_box(struct mailbox *box) +{ + struct welcome_mailbox *wbox = WELCOME_CONTEXT(box); + const char *cmd; + + cmd = !wbox->created ? NULL : + mail_user_plugin_getenv(box->storage->user, "welcome_script"); + if (cmd != NULL) { + bool wait = mail_user_plugin_getenv_bool(box->storage->user, + "welcome_wait"); + script_execute(box->storage->user, cmd, wait); + } + return wbox->module_ctx.super.open(box); +} + +static void welcome_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct welcome_mailbox *wbox; + + if (!box->inbox_user) + return; + + wbox = p_new(box->pool, struct welcome_mailbox, 1); + wbox->module_ctx.super = *v; + box->vlast = &wbox->module_ctx.super; + + v->create_box = welcome_create_box; + v->open = welcome_open_box; + MODULE_CONTEXT_SET(box, welcome_storage_module, wbox); +} + +static struct mail_storage_hooks welcome_mail_storage_hooks = { + .mailbox_allocated = welcome_mailbox_allocated +}; + +void welcome_plugin_init(struct module *module); +void welcome_plugin_deinit(void); + +void welcome_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &welcome_mail_storage_hooks); +} + +void welcome_plugin_deinit(void) +{ + mail_storage_hooks_remove(&welcome_mail_storage_hooks); +} + +const char *welcome_plugin_version = DOVECOT_ABI_VERSION; diff --git a/src/plugins/zlib/Makefile.am b/src/plugins/zlib/Makefile.am new file mode 100644 index 0000000..45c3d76 --- /dev/null +++ b/src/plugins/zlib/Makefile.am @@ -0,0 +1,24 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-compression \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/dbox-common + +NOPLUGIN_LDFLAGS = +lib20_zlib_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_zlib_plugin.la + +lib20_zlib_plugin_la_LIBADD = \ + ../../lib-compression/libcompression.la + +lib20_zlib_plugin_la_SOURCES = \ + zlib-plugin.c + +noinst_HEADERS = \ + zlib-plugin.h diff --git a/src/plugins/zlib/Makefile.in b/src/plugins/zlib/Makefile.in new file mode 100644 index 0000000..5b3a3ab --- /dev/null +++ b/src/plugins/zlib/Makefile.in @@ -0,0 +1,827 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/plugins/zlib +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_zlib_plugin_la_DEPENDENCIES = \ + ../../lib-compression/libcompression.la +am_lib20_zlib_plugin_la_OBJECTS = zlib-plugin.lo +lib20_zlib_plugin_la_OBJECTS = $(am_lib20_zlib_plugin_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +lib20_zlib_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_zlib_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/zlib-plugin.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(lib20_zlib_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_zlib_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-master \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-compression \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/dbox-common + +lib20_zlib_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_zlib_plugin.la + +lib20_zlib_plugin_la_LIBADD = \ + ../../lib-compression/libcompression.la + +lib20_zlib_plugin_la_SOURCES = \ + zlib-plugin.c + +noinst_HEADERS = \ + zlib-plugin.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/plugins/zlib/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/zlib/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +lib20_zlib_plugin.la: $(lib20_zlib_plugin_la_OBJECTS) $(lib20_zlib_plugin_la_DEPENDENCIES) $(EXTRA_lib20_zlib_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_zlib_plugin_la_LINK) -rpath $(moduledir) $(lib20_zlib_plugin_la_OBJECTS) $(lib20_zlib_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zlib-plugin.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/zlib-plugin.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-moduleLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/zlib-plugin.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-moduleLTLIBRARIES install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/plugins/zlib/zlib-plugin.c b/src/plugins/zlib/zlib-plugin.c new file mode 100644 index 0000000..f716058 --- /dev/null +++ b/src/plugins/zlib/zlib-plugin.c @@ -0,0 +1,388 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "istream-seekable.h" +#include "ostream.h" +#include "str.h" +#include "mail-user.h" +#include "index-storage.h" +#include "index-mail.h" +#include "compression.h" +#include "zlib-plugin.h" + +#include <fcntl.h> + +#define ZLIB_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, zlib_storage_module) +#define ZLIB_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, zlib_mail_module) +#define ZLIB_USER_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, zlib_user_module) + +#define MAX_INBUF_SIZE (1024*1024) +#define ZLIB_MAIL_CACHE_EXPIRE_MSECS (60*1000) + +struct zlib_mail { + union mail_module_context module_ctx; + bool verifying_save; +}; + +struct zlib_mail_cache { + struct timeout *to; + struct mailbox *box; + uint32_t uid; + + struct istream *input; +}; + +struct zlib_user { + union mail_user_module_context module_ctx; + + struct zlib_mail_cache cache; + + const struct compression_handler *save_handler; + int save_level; +}; + +const char *zlib_plugin_version = DOVECOT_ABI_VERSION; + +static MODULE_CONTEXT_DEFINE_INIT(zlib_user_module, + &mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(zlib_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(zlib_mail_module, &mail_module_register); + +static bool zlib_mailbox_is_permail(struct mailbox *box) +{ + enum mail_storage_class_flags class_flags = box->storage->class_flags; + + return (class_flags & MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) == 0 && + (class_flags & MAIL_STORAGE_CLASS_FLAG_BINARY_DATA) != 0; +} + +static void zlib_mail_cache_close(struct zlib_user *zuser) +{ + struct zlib_mail_cache *cache = &zuser->cache; + + timeout_remove(&cache->to); + i_stream_unref(&cache->input); + i_zero(cache); +} + +static struct istream * +zlib_mail_cache_open(struct zlib_user *zuser, struct mail *mail, + struct istream *input, bool do_cache) +{ + struct zlib_mail_cache *cache = &zuser->cache; + struct istream *inputs[2]; + string_t *temp_prefix = t_str_new(128); + + if (do_cache) + zlib_mail_cache_close(zuser); + + /* zlib istream is seekable, but very slow. create a seekable istream + which we can use to quickly seek around in the stream that's been + read so far. usually the partial IMAP FETCHes continue from where + the previous left off, so this isn't strictly necessary, but with + the way lib-imap-storage's CRLF-cache works it has to seek backwards + somewhat, which causes a zlib stream reset. And the CRLF-cache isn't + easy to fix.. */ + input->seekable = FALSE; + inputs[0] = input; + inputs[1] = NULL; + mail_user_set_get_temp_prefix(temp_prefix, mail->box->storage->user->set); + input = i_stream_create_seekable_path(inputs, + i_stream_get_max_buffer_size(inputs[0]), + str_c(temp_prefix)); + i_stream_set_name(input, t_strdup_printf("compress(%s)", + i_stream_get_name(inputs[0]))); + i_stream_unref(&inputs[0]); + + if (do_cache) { + cache->to = timeout_add(ZLIB_MAIL_CACHE_EXPIRE_MSECS, + zlib_mail_cache_close, zuser); + cache->box = mail->box; + cache->uid = mail->uid; + cache->input = input; + /* index-mail wants the stream to be destroyed at close, so create + a new stream instead of just increasing reference. */ + return i_stream_create_limit(cache->input, UOFF_T_MAX); + } else { + return input; + } +} + +static int zlib_istream_opened(struct mail *_mail, struct istream **stream) +{ + struct zlib_user *zuser = ZLIB_USER_CONTEXT(_mail->box->storage->user); + struct zlib_mail_cache *cache = &zuser->cache; + struct mail_private *mail = (struct mail_private *)_mail; + struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail); + struct istream *input; + const struct compression_handler *handler; + + if (zmail->verifying_save) { + /* zlib_mail_save_finish() is verifying that the user-given + input doesn't look compressed. */ + return zmail->module_ctx.super.istream_opened(_mail, stream); + } + + if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) { + /* use the cached stream. when doing partial reads it should + already be seeked into the wanted offset. */ + i_stream_unref(stream); + i_stream_seek(cache->input, 0); + *stream = i_stream_create_limit(cache->input, UOFF_T_MAX); + return zmail->module_ctx.super.istream_opened(_mail, stream); + } + + handler = compression_detect_handler(*stream); + if (handler != NULL) { + if (handler->create_istream == NULL) { + mail_set_critical(_mail, + "zlib plugin: Detected %s compression " + "but support not compiled in", handler->ext); + return -1; + } + + input = *stream; + *stream = handler->create_istream(input); + i_stream_unref(&input); + /* dont cache the stream if _mail->uid is 0 */ + *stream = zlib_mail_cache_open(zuser, _mail, *stream, (_mail->uid > 0)); + } + return zmail->module_ctx.super.istream_opened(_mail, stream); +} + +static void zlib_mail_close(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail); + struct zlib_user *zuser = ZLIB_USER_CONTEXT(_mail->box->storage->user); + struct zlib_mail_cache *cache = &zuser->cache; + uoff_t size; + + if (_mail->uid > 0 && cache->uid == _mail->uid && cache->box == _mail->box) { + /* make sure we have read the entire email into the seekable + stream (which causes the original input stream to be + unrefed). we can't safely keep the original input stream + open after the mail is closed. */ + if (i_stream_get_size(cache->input, TRUE, &size) < 0) + zlib_mail_cache_close(zuser); + } + zmail->module_ctx.super.close(_mail); +} + +static void zlib_mail_allocated(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct mail_vfuncs *v = mail->vlast; + struct zlib_mail *zmail; + + if (!zlib_mailbox_is_permail(_mail->box)) + return; + + zmail = p_new(mail->pool, struct zlib_mail, 1); + zmail->module_ctx.super = *v; + mail->vlast = &zmail->module_ctx.super; + + v->istream_opened = zlib_istream_opened; + v->close = zlib_mail_close; + MODULE_CONTEXT_SET(mail, zlib_mail_module, zmail); +} + +static int zlib_mail_save_finish(struct mail_save_context *ctx) +{ + struct mailbox *box = ctx->transaction->box; + union mailbox_module_context *zbox = ZLIB_CONTEXT(box); + struct mail_private *mail = (struct mail_private *)ctx->dest_mail; + struct zlib_mail *zmail = ZLIB_MAIL_CONTEXT(mail); + struct istream *input; + int ret; + + if (zbox->super.save_finish(ctx) < 0) + return -1; + + zmail->verifying_save = TRUE; + ret = mail_get_stream(ctx->dest_mail, NULL, NULL, &input); + zmail->verifying_save = FALSE; + if (ret < 0) + return -1; + + if (compression_detect_handler(input) != NULL) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Saving mails compressed by client isn't supported"); + return -1; + } + return 0; +} + +static int +zlib_mail_save_compress_begin(struct mail_save_context *ctx, + struct istream *input) +{ + struct mailbox *box = ctx->transaction->box; + struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user); + union mailbox_module_context *zbox = ZLIB_CONTEXT(box); + struct ostream *output; + + if (zbox->super.save_begin(ctx, input) < 0) + return -1; + + output = zuser->save_handler->create_ostream(ctx->data.output, + zuser->save_level); + o_stream_unref(&ctx->data.output); + ctx->data.output = output; + o_stream_cork(ctx->data.output); + return 0; +} + +static void +zlib_permail_alloc_init(struct mailbox *box, struct mailbox_vfuncs *v) +{ + struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user); + + if (zuser->save_handler == NULL) { + v->save_finish = zlib_mail_save_finish; + } else { + v->save_begin = zlib_mail_save_compress_begin; + } +} + +static void zlib_mailbox_open_input(struct mailbox *box) +{ + const struct compression_handler *handler; + struct istream *input; + struct stat st; + int fd; + + if (compression_lookup_handler_from_ext(box->name, &handler) <= 0) + return; + + if (mail_storage_is_mailbox_file(box->storage)) { + /* looks like a compressed single file mailbox. we should be + able to handle this. */ + const char *box_path = mailbox_get_path(box); + + fd = open(box_path, O_RDONLY); + if (fd == -1) { + /* let the standard handler figure out what to do + with the failure */ + return; + } + if (fstat(fd, &st) == 0 && S_ISDIR(st.st_mode)) { + i_close_fd(&fd); + return; + } + input = i_stream_create_fd_autoclose(&fd, MAX_INBUF_SIZE); + i_stream_set_name(input, box_path); + box->input = handler->create_istream(input); + i_stream_unref(&input); + box->flags |= MAILBOX_FLAG_READONLY; + } +} + +static int zlib_mailbox_open(struct mailbox *box) +{ + union mailbox_module_context *zbox = ZLIB_CONTEXT(box); + + if (box->input == NULL && + (box->storage->class_flags & + MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS) != 0) + zlib_mailbox_open_input(box); + + return zbox->super.open(box); +} + +static void zlib_mailbox_close(struct mailbox *box) +{ + union mailbox_module_context *zbox = ZLIB_CONTEXT(box); + struct zlib_user *zuser = ZLIB_USER_CONTEXT(box->storage->user); + + if (zuser->cache.box == box) + zlib_mail_cache_close(zuser); + zbox->super.close(box); +} + +static void zlib_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + union mailbox_module_context *zbox; + + zbox = p_new(box->pool, union mailbox_module_context, 1); + zbox->super = *v; + box->vlast = &zbox->super; + v->open = zlib_mailbox_open; + v->close = zlib_mailbox_close; + + MODULE_CONTEXT_SET_SELF(box, zlib_storage_module, zbox); + + if (zlib_mailbox_is_permail(box)) + zlib_permail_alloc_init(box, v); +} + +static void zlib_mail_user_deinit(struct mail_user *user) +{ + struct zlib_user *zuser = ZLIB_USER_CONTEXT(user); + + zlib_mail_cache_close(zuser); + zuser->module_ctx.super.deinit(user); +} + +static void zlib_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct zlib_user *zuser; + const char *name; + int ret; + + zuser = p_new(user->pool, struct zlib_user, 1); + zuser->module_ctx.super = *v; + user->vlast = &zuser->module_ctx.super; + v->deinit = zlib_mail_user_deinit; + + name = mail_user_plugin_getenv(user, "zlib_save"); + if (name != NULL && *name != '\0') { + ret = compression_lookup_handler(name, &zuser->save_handler); + if (ret <= 0) { + i_error("zlib_save: %s: %s", ret == 0 ? + "Support not compiled in for handler" : + "Unknown handler", name); + zuser->save_handler = NULL; + } + } + name = zuser->save_handler == NULL ? NULL : + mail_user_plugin_getenv(user, "zlib_save_level"); + if (name != NULL && name[0] != '\0') { + if (str_to_int(name, &zuser->save_level) < 0 || + zuser->save_level < zuser->save_handler->get_min_level() || + zuser->save_level > zuser->save_handler->get_max_level()) { + i_error("zlib_save_level: Level must be between %d..%d", + zuser->save_handler->get_min_level(), + zuser->save_handler->get_max_level()); + zuser->save_level = + zuser->save_handler->get_default_level(); + } + } else if (zuser->save_handler != NULL) { + zuser->save_level = zuser->save_handler->get_default_level(); + } + MODULE_CONTEXT_SET(user, zlib_user_module, zuser); +} + +static struct mail_storage_hooks zlib_mail_storage_hooks = { + .mail_user_created = zlib_mail_user_created, + .mailbox_allocated = zlib_mailbox_allocated, + .mail_allocated = zlib_mail_allocated +}; + +void zlib_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &zlib_mail_storage_hooks); +} + +void zlib_plugin_deinit(void) +{ + mail_storage_hooks_remove(&zlib_mail_storage_hooks); +} diff --git a/src/plugins/zlib/zlib-plugin.h b/src/plugins/zlib/zlib-plugin.h new file mode 100644 index 0000000..98f520d --- /dev/null +++ b/src/plugins/zlib/zlib-plugin.h @@ -0,0 +1,7 @@ +#ifndef ZLIB_PLUGIN_H +#define ZLIB_PLUGIN_H + +void zlib_plugin_init(struct module *module); +void zlib_plugin_deinit(void); + +#endif |