summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/imapc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/imapc')
-rw-r--r--src/lib-storage/index/imapc/Makefile.am36
-rw-r--r--src/lib-storage/index/imapc/Makefile.in861
-rw-r--r--src/lib-storage/index/imapc/imapc-list.c1013
-rw-r--r--src/lib-storage/index/imapc/imapc-list.h41
-rw-r--r--src/lib-storage/index/imapc/imapc-mail-fetch.c911
-rw-r--r--src/lib-storage/index/imapc/imapc-mail.c675
-rw-r--r--src/lib-storage/index/imapc/imapc-mail.h51
-rw-r--r--src/lib-storage/index/imapc/imapc-mailbox.c1015
-rw-r--r--src/lib-storage/index/imapc/imapc-save.c829
-rw-r--r--src/lib-storage/index/imapc/imapc-search.c332
-rw-r--r--src/lib-storage/index/imapc/imapc-search.h18
-rw-r--r--src/lib-storage/index/imapc/imapc-settings.c173
-rw-r--r--src/lib-storage/index/imapc/imapc-settings.h63
-rw-r--r--src/lib-storage/index/imapc/imapc-storage.c1353
-rw-r--r--src/lib-storage/index/imapc/imapc-storage.h274
-rw-r--r--src/lib-storage/index/imapc/imapc-sync.c702
-rw-r--r--src/lib-storage/index/imapc/imapc-sync.h39
17 files changed, 8386 insertions, 0 deletions
diff --git a/src/lib-storage/index/imapc/Makefile.am b/src/lib-storage/index/imapc/Makefile.am
new file mode 100644
index 0000000..72ee102
--- /dev/null
+++ b/src/lib-storage/index/imapc/Makefile.am
@@ -0,0 +1,36 @@
+noinst_LTLIBRARIES = libstorage_imapc.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-client \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/list \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-ssl-iostream
+
+libstorage_imapc_la_SOURCES = \
+ imapc-list.c \
+ imapc-mail.c \
+ imapc-mail-fetch.c \
+ imapc-mailbox.c \
+ imapc-save.c \
+ imapc-search.c \
+ imapc-settings.c \
+ imapc-sync.c \
+ imapc-storage.c
+
+headers = \
+ imapc-list.h \
+ imapc-mail.h \
+ imapc-search.h \
+ imapc-settings.h \
+ imapc-storage.h \
+ imapc-sync.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
diff --git a/src/lib-storage/index/imapc/Makefile.in b/src/lib-storage/index/imapc/Makefile.in
new file mode 100644
index 0000000..4d73707
--- /dev/null
+++ b/src/lib-storage/index/imapc/Makefile.in
@@ -0,0 +1,861 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-storage/index/imapc
+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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libstorage_imapc_la_LIBADD =
+am_libstorage_imapc_la_OBJECTS = imapc-list.lo imapc-mail.lo \
+ imapc-mail-fetch.lo imapc-mailbox.lo imapc-save.lo \
+ imapc-search.lo imapc-settings.lo imapc-sync.lo \
+ imapc-storage.lo
+libstorage_imapc_la_OBJECTS = $(am_libstorage_imapc_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 =
+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)/imapc-list.Plo \
+ ./$(DEPDIR)/imapc-mail-fetch.Plo ./$(DEPDIR)/imapc-mail.Plo \
+ ./$(DEPDIR)/imapc-mailbox.Plo ./$(DEPDIR)/imapc-save.Plo \
+ ./$(DEPDIR)/imapc-search.Plo ./$(DEPDIR)/imapc-settings.Plo \
+ ./$(DEPDIR)/imapc-storage.Plo ./$(DEPDIR)/imapc-sync.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 = $(libstorage_imapc_la_SOURCES)
+DIST_SOURCES = $(libstorage_imapc_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__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)$(pkginc_libdir)"
+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 = @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@
+noinst_LTLIBRARIES = libstorage_imapc.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-client \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/list \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-ssl-iostream
+
+libstorage_imapc_la_SOURCES = \
+ imapc-list.c \
+ imapc-mail.c \
+ imapc-mail-fetch.c \
+ imapc-mailbox.c \
+ imapc-save.c \
+ imapc-search.c \
+ imapc-settings.c \
+ imapc-sync.c \
+ imapc-storage.c
+
+headers = \
+ imapc-list.h \
+ imapc-mail.h \
+ imapc-search.h \
+ imapc-settings.h \
+ imapc-storage.h \
+ imapc-sync.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/lib-storage/index/imapc/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-storage/index/imapc/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libstorage_imapc.la: $(libstorage_imapc_la_OBJECTS) $(libstorage_imapc_la_DEPENDENCIES) $(EXTRA_libstorage_imapc_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libstorage_imapc_la_OBJECTS) $(libstorage_imapc_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-list.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-mail-fetch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-mail.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-mailbox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-save.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-search.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-sync.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)$(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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imapc-list.Plo
+ -rm -f ./$(DEPDIR)/imapc-mail-fetch.Plo
+ -rm -f ./$(DEPDIR)/imapc-mail.Plo
+ -rm -f ./$(DEPDIR)/imapc-mailbox.Plo
+ -rm -f ./$(DEPDIR)/imapc-save.Plo
+ -rm -f ./$(DEPDIR)/imapc-search.Plo
+ -rm -f ./$(DEPDIR)/imapc-settings.Plo
+ -rm -f ./$(DEPDIR)/imapc-storage.Plo
+ -rm -f ./$(DEPDIR)/imapc-sync.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-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)/imapc-list.Plo
+ -rm -f ./$(DEPDIR)/imapc-mail-fetch.Plo
+ -rm -f ./$(DEPDIR)/imapc-mail.Plo
+ -rm -f ./$(DEPDIR)/imapc-mailbox.Plo
+ -rm -f ./$(DEPDIR)/imapc-save.Plo
+ -rm -f ./$(DEPDIR)/imapc-search.Plo
+ -rm -f ./$(DEPDIR)/imapc-settings.Plo
+ -rm -f ./$(DEPDIR)/imapc-storage.Plo
+ -rm -f ./$(DEPDIR)/imapc-sync.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-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-noinstLTLIBRARIES \
+ 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-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-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/lib-storage/index/imapc/imapc-list.c b/src/lib-storage/index/imapc/imapc-list.c
new file mode 100644
index 0000000..d987538
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-list.c
@@ -0,0 +1,1013 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+/*
+ There are various different mailbox names here. Here's an example assuming
+ - imapc_list_prefix = "prefix"
+ - remote imapc server separator = '/'
+ - mailbox_list separator = '^' (actually this is currently always the same
+ as remote separator, but this clarifies the example)
+ - namespace separator = ':'
+ - fs_list separator = '.'
+ - mailbox_list storage_name_escape_char = '+'
+ - mailbox_list vname_escape_char = '~'
+ - fs_list storage_name_escape_char = '%'
+
+ remote_name = "prefix/~foo/bar^baz+_%_."
+ storage_name = "prefix^~foo^bar+5ebaz+2b_%_."
+ - separator is changed from / to ^
+ - conflicting ^ separator in remote_name is escaped as +5e
+ - storage_name_escape character + is escaped as +2b
+ vname = "~7efoo:bar.baz+_%_."
+ - imapc_list_prefix is dropped
+ - vname_escape_character ~ is escaped into ~7e
+ - separator is changed from ^ to :
+ - storage_name_escape_characters are unescaped
+ fs_name = "prefix.~foo.bar^baz+_%25_%2e"
+ - this is generated from remote_name
+ - separator is changed from / to .
+ - storage_name_escape_character=% and fs_list separator . are escaped
+*/
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "imap-arg.h"
+#include "imap-match.h"
+#include "imap-utf7.h"
+#include "mailbox-tree.h"
+#include "mailbox-list-subscriptions.h"
+#include "imapc-storage.h"
+#include "imapc-list.h"
+
+struct imapc_mailbox_list_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+ struct mailbox_tree_context *tree;
+ struct mailbox_node *ns_root;
+
+ struct mailbox_tree_iterate_context *iter;
+ struct mailbox_info info;
+ string_t *special_use;
+};
+
+static struct {
+ const char *str;
+ enum mailbox_info_flags flag;
+} imap_list_flags[] = {
+ { "\\NoSelect", MAILBOX_NOSELECT },
+ { "\\NonExistent", MAILBOX_NONEXISTENT },
+ { "\\NoInferiors", MAILBOX_NOINFERIORS },
+ { "\\Subscribed", MAILBOX_SUBSCRIBED },
+ { "\\All", MAILBOX_SPECIALUSE_ALL },
+ { "\\Archive", MAILBOX_SPECIALUSE_ARCHIVE },
+ { "\\Drafts", MAILBOX_SPECIALUSE_DRAFTS },
+ { "\\Flagged", MAILBOX_SPECIALUSE_FLAGGED },
+ { "\\Junk", MAILBOX_SPECIALUSE_JUNK },
+ { "\\Sent", MAILBOX_SPECIALUSE_SENT },
+ { "\\Trash", MAILBOX_SPECIALUSE_TRASH },
+ { "\\Important", MAILBOX_SPECIALUSE_IMPORTANT }
+};
+
+extern struct mailbox_list imapc_mailbox_list;
+
+static void imapc_list_send_hierarchy_sep_lookup(struct imapc_mailbox_list *list);
+static void imapc_untagged_list(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client);
+static void imapc_untagged_lsub(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client);
+
+static struct mailbox_list *imapc_list_alloc(void)
+{
+ struct imapc_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imapc mailbox list", 1024);
+ list = p_new(pool, struct imapc_mailbox_list, 1);
+ list->list = imapc_mailbox_list;
+ list->list.pool = pool;
+ /* separator is set lazily */
+ list->mailboxes = mailbox_tree_init('\0');
+ mailbox_tree_set_parents_nonexistent(list->mailboxes);
+ return &list->list;
+}
+
+static int imapc_list_init(struct mailbox_list *_list, const char **error_r)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+
+ list->set = mail_user_set_get_driver_settings(_list->ns->user->set_info,
+ _list->ns->user_set,
+ IMAPC_STORAGE_NAME);
+ if (imapc_storage_client_create(_list->ns, list->set, _list->mail_set,
+ &list->client, error_r) < 0)
+ return -1;
+ list->client->_list = list;
+
+ imapc_storage_client_register_untagged(list->client, "LIST",
+ imapc_untagged_list);
+ imapc_storage_client_register_untagged(list->client, "LSUB",
+ imapc_untagged_lsub);
+ imapc_list_send_hierarchy_sep_lookup(list);
+ return 0;
+}
+
+static void imapc_list_deinit(struct mailbox_list *_list)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+
+ /* make sure all pending commands are aborted before anything is
+ deinitialized */
+ if (list->client != NULL) {
+ list->client->destroying = TRUE;
+ imapc_client_logout(list->client->client);
+ imapc_storage_client_unref(&list->client);
+ }
+ if (list->index_list != NULL)
+ mailbox_list_destroy(&list->index_list);
+ mailbox_tree_deinit(&list->mailboxes);
+ if (list->tmp_subscriptions != NULL)
+ mailbox_tree_deinit(&list->tmp_subscriptions);
+ pool_unref(&list->list.pool);
+}
+
+static void
+imapc_list_copy_error_from_reply(struct imapc_mailbox_list *list,
+ enum mail_error default_error,
+ const struct imapc_command_reply *reply)
+{
+ enum mail_error error;
+
+ if (imapc_resp_text_code_parse(reply->resp_text_key, &error)) {
+ mailbox_list_set_error(&list->list, error,
+ reply->text_without_resp);
+ } else {
+ mailbox_list_set_error(&list->list, default_error,
+ reply->text_without_resp);
+ }
+}
+
+static void imapc_list_simple_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_simple_context *ctx = context;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ ctx->ret = 0;
+ else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_list_copy_error_from_reply(ctx->client->_list,
+ MAIL_ERROR_PARAMS, reply);
+ ctx->ret = -1;
+ } else if (imapc_storage_client_handle_auth_failure(ctx->client)) {
+ ctx->ret = -1;
+ } else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED) {
+ mailbox_list_set_internal_error(&ctx->client->_list->list);
+ ctx->ret = -1;
+ } else {
+ mailbox_list_set_critical(&ctx->client->_list->list,
+ "imapc: Command failed: %s", reply->text_full);
+ ctx->ret = -1;
+ }
+ imapc_client_stop(ctx->client->client);
+}
+
+static bool
+imap_list_flag_parse(const char *str, enum mailbox_info_flags *flag_r)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(imap_list_flags); i++) {
+ if (strcasecmp(str, imap_list_flags[i].str) == 0) {
+ *flag_r = imap_list_flags[i].flag;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static const char *
+imapc_list_remote_to_storage_name(struct imapc_mailbox_list *list,
+ const char *remote_name)
+{
+ /* typically mailbox_list_escape_name() is used to escape vname into
+ a list name. but we want to convert remote IMAP name to a list name,
+ so we need to use the remote IMAP separator. */
+ return mailbox_list_escape_name_params(remote_name, "",
+ list->root_sep,
+ mailbox_list_get_hierarchy_sep(&list->list),
+ list->list.set.storage_name_escape_char, "");
+}
+
+static const char *
+imapc_list_remote_to_vname(struct imapc_mailbox_list *list,
+ const char *remote_name)
+{
+ return mailbox_list_get_vname(&list->list,
+ imapc_list_remote_to_storage_name(list, remote_name));
+}
+
+const char *
+imapc_list_storage_to_remote_name(struct imapc_mailbox_list *list,
+ const char *storage_name)
+{
+ return mailbox_list_unescape_name_params(storage_name, "", list->root_sep,
+ mailbox_list_get_hierarchy_sep(&list->list),
+ list->list.set.storage_name_escape_char);
+}
+
+static struct mailbox_node *
+imapc_list_update_tree(struct imapc_mailbox_list *list,
+ struct mailbox_tree_context *tree,
+ const struct imap_arg *args)
+{
+ struct mailbox_node *node;
+ const struct imap_arg *flags;
+ const char *remote_name, *flag;
+ enum mailbox_info_flags info_flag, info_flags = 0;
+ bool created;
+
+ if (!imap_arg_get_list(&args[0], &flags) ||
+ args[1].type == IMAP_ARG_EOL ||
+ !imap_arg_get_astring(&args[2], &remote_name))
+ return NULL;
+
+ while (imap_arg_get_atom(flags, &flag)) {
+ if (imap_list_flag_parse(flag, &info_flag))
+ info_flags |= info_flag;
+ flags++;
+ }
+
+ T_BEGIN {
+ const char *vname =
+ imapc_list_remote_to_vname(list, remote_name);
+
+ if ((info_flags & MAILBOX_NONEXISTENT) != 0)
+ node = mailbox_tree_lookup(tree, vname);
+ else
+ node = mailbox_tree_get(tree, vname, &created);
+ } T_END;
+ if (node != NULL)
+ node->flags = info_flags;
+ return node;
+}
+
+static void imapc_untagged_list(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ struct imapc_mailbox_list *list = client->_list;
+ const struct imap_arg *args = reply->args;
+ const char *sep, *remote_name;
+
+ if (list->root_sep == '\0') {
+ /* we haven't asked for the separator yet.
+ lets see if this is the reply for its request. */
+ if (args[0].type == IMAP_ARG_EOL ||
+ !imap_arg_get_nstring(&args[1], &sep) ||
+ !imap_arg_get_astring(&args[2], &remote_name))
+ return;
+
+ /* we can't handle NIL separator yet */
+ list->root_sep = sep == NULL ? '/' : sep[0];
+ mailbox_tree_set_separator(list->mailboxes, list->root_sep);
+ } else {
+ (void)imapc_list_update_tree(list, list->mailboxes, args);
+ }
+}
+
+static void imapc_untagged_lsub(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ struct imapc_mailbox_list *list = client->_list;
+ const struct imap_arg *args = reply->args;
+ struct mailbox_node *node;
+
+ if (list->root_sep == '\0') {
+ /* we haven't asked for the separator yet */
+ return;
+ }
+ node = imapc_list_update_tree(list, list->tmp_subscriptions != NULL ?
+ list->tmp_subscriptions :
+ list->list.subscriptions, args);
+ if (node != NULL) {
+ if ((node->flags & MAILBOX_NOSELECT) == 0)
+ node->flags |= MAILBOX_SUBSCRIBED;
+ else {
+ /* LSUB \Noselect means that the mailbox isn't
+ subscribed, but it has children that are */
+ node->flags &= ENUM_NEGATE(MAILBOX_NOSELECT);
+ }
+ }
+}
+
+static void imapc_list_sep_verify(struct imapc_mailbox_list *list)
+{
+ const char *imapc_list_prefix = list->set->imapc_list_prefix;
+
+ if (list->root_sep == '\0') {
+ mailbox_list_set_critical(&list->list,
+ "imapc: LIST didn't return hierarchy separator");
+ } else if (imapc_list_prefix[0] != '\0' &&
+ imapc_list_prefix[strlen(imapc_list_prefix)-1] == list->root_sep) {
+ mailbox_list_set_critical(&list->list,
+ "imapc_list_prefix must not end with hierarchy separator");
+ }
+}
+
+static void imapc_storage_sep_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_mailbox_list *list = context;
+
+ list->root_sep_pending = FALSE;
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ imapc_list_sep_verify(list);
+ else if (reply->state == IMAPC_COMMAND_STATE_NO)
+ imapc_list_copy_error_from_reply(list, MAIL_ERROR_PARAMS, reply);
+ else if (imapc_storage_client_handle_auth_failure(list->client))
+ ;
+ else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED)
+ mailbox_list_set_internal_error(&list->list);
+ else if (!list->list.ns->user->deinitializing) {
+ mailbox_list_set_critical(&list->list,
+ "imapc: Command failed: %s", reply->text_full);
+ }
+ imapc_client_stop(list->client->client);
+}
+
+static void imapc_list_send_hierarchy_sep_lookup(struct imapc_mailbox_list *list)
+{
+ struct imapc_command *cmd;
+
+ if (list->root_sep_pending)
+ return;
+ list->root_sep_pending = TRUE;
+
+ cmd = imapc_client_cmd(list->client->client,
+ imapc_storage_sep_callback, list);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, "LIST \"\" \"\"");
+}
+
+int imapc_list_try_get_root_sep(struct imapc_mailbox_list *list, char *sep_r)
+{
+ if (list->root_sep == '\0') {
+ if (imapc_storage_client_handle_auth_failure(list->client))
+ return -1;
+ imapc_list_send_hierarchy_sep_lookup(list);
+ while (list->root_sep_pending)
+ imapc_client_run(list->client->client);
+ if (list->root_sep == '\0')
+ return -1;
+ }
+ *sep_r = list->root_sep;
+ return 0;
+}
+
+static char imapc_list_get_hierarchy_sep(struct mailbox_list *_list)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ char sep;
+
+ if (imapc_list_try_get_root_sep(list, &sep) < 0) {
+ /* we can't really return a failure here. just return a common
+ separator and fail all the future list operations. */
+ return '/';
+ }
+ return sep;
+}
+
+static const char *
+imapc_list_get_storage_name(struct mailbox_list *_list, const char *vname)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ const char *prefix = list->set->imapc_list_prefix;
+ const char *storage_name;
+
+ storage_name = mailbox_list_default_get_storage_name(_list, vname);
+ if (*prefix != '\0' && strcasecmp(storage_name, "INBOX") != 0) {
+ storage_name = storage_name[0] == '\0' ? prefix :
+ t_strdup_printf("%s%c%s", prefix,
+ mailbox_list_get_hierarchy_sep(_list),
+ storage_name);
+ }
+ return storage_name;
+}
+
+static const char *
+imapc_list_get_vname(struct mailbox_list *_list, const char *storage_name)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ const char *prefix = list->set->imapc_list_prefix;
+ size_t prefix_len;
+
+ if (*storage_name == '\0') {
+ /* ACL plugin does these lookups */
+ } else if (*prefix != '\0' && strcasecmp(storage_name, "INBOX") != 0) {
+ prefix_len = strlen(prefix);
+ i_assert(str_begins(storage_name, prefix));
+ storage_name += prefix_len;
+ if (storage_name[0] == '\0') {
+ /* we're looking up the prefix itself */
+ } else {
+ i_assert(storage_name[0] ==
+ mailbox_list_get_hierarchy_sep(_list));
+ storage_name++;
+ }
+ }
+ return mailbox_list_default_get_vname(_list, storage_name);
+}
+
+static struct mailbox_list *imapc_list_get_fs(struct imapc_mailbox_list *list)
+{
+ struct mailbox_list_settings list_set;
+ const char *error, *dir;
+
+ dir = list->list.set.index_dir;
+ if (dir == NULL)
+ dir = list->list.set.root_dir;
+
+ if (dir == NULL || dir[0] == '\0') {
+ /* indexes disabled */
+ } else if (list->index_list == NULL && !list->index_list_failed) {
+ mailbox_list_settings_init_defaults(&list_set);
+ list_set.layout = MAILBOX_LIST_NAME_MAILDIRPLUSPLUS;
+ list_set.root_dir = dir;
+ list_set.index_pvt_dir = p_strdup_empty(list->list.pool, list->list.set.index_pvt_dir);
+ /* Filesystem needs to be able to store any kind of a mailbox
+ name. */
+ list_set.storage_name_escape_char =
+ IMAPC_LIST_FS_NAME_ESCAPE_CHAR;
+
+ if (mailbox_list_create(list_set.layout, list->list.ns,
+ &list_set, MAILBOX_LIST_FLAG_SECONDARY,
+ &list->index_list, &error) < 0) {
+ i_error("imapc: Couldn't create %s mailbox list: %s",
+ list_set.layout, error);
+ list->index_list_failed = TRUE;
+ }
+ }
+ return list->index_list;
+}
+
+static const char *
+imapc_list_storage_to_fs_name(struct imapc_mailbox_list *list,
+ const char *storage_name)
+{
+ struct mailbox_list *fs_list = imapc_list_get_fs(list);
+ const char *remote_name;
+
+ if (storage_name == NULL)
+ return NULL;
+
+ remote_name = imapc_list_storage_to_remote_name(list, storage_name);
+ return mailbox_list_escape_name_params(remote_name, "",
+ list->root_sep,
+ mailbox_list_get_hierarchy_sep(fs_list),
+ fs_list->set.storage_name_escape_char, "");
+}
+
+static const char *
+imapc_list_fs_to_storage_name(struct imapc_mailbox_list *list,
+ const char *fs_name)
+{
+ struct mailbox_list *fs_list = imapc_list_get_fs(list);
+ const char *remote_name;
+
+ if (fs_name == NULL)
+ return NULL;
+
+ remote_name = mailbox_list_unescape_name_params(fs_name, "",
+ list->root_sep,
+ mailbox_list_get_hierarchy_sep(fs_list),
+ fs_list->set.storage_name_escape_char);
+ return imapc_list_remote_to_storage_name(list, remote_name);
+}
+
+static int
+imapc_list_get_path(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type, const char **path_r)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ struct mailbox_list *fs_list = imapc_list_get_fs(list);
+ const char *fs_name;
+
+ if (fs_list != NULL) {
+ fs_name = imapc_list_storage_to_fs_name(list, name);
+ return mailbox_list_get_path(fs_list, fs_name, type, path_r);
+ } else {
+ *path_r = NULL;
+ return 0;
+ }
+}
+
+static const char *
+imapc_list_get_temp_prefix(struct mailbox_list *_list, bool global)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ struct mailbox_list *fs_list = imapc_list_get_fs(list);
+
+ if (fs_list != NULL) {
+ return global ?
+ mailbox_list_get_global_temp_prefix(fs_list) :
+ mailbox_list_get_temp_prefix(fs_list);
+ } else {
+ i_panic("imapc: Can't return a temp prefix for '%s'",
+ _list->ns->prefix);
+ }
+}
+
+static const char *
+imapc_list_join_refpattern(struct mailbox_list *list ATTR_UNUSED,
+ const char *ref, const char *pattern)
+{
+ return t_strconcat(ref, pattern, NULL);
+}
+
+static struct imapc_command *
+imapc_list_simple_context_init(struct imapc_simple_context *ctx,
+ struct imapc_mailbox_list *list)
+{
+ imapc_simple_context_init(ctx, list->client);
+ return imapc_client_cmd(list->client->client,
+ imapc_list_simple_callback, ctx);
+}
+
+static void imapc_list_delete_unused_indexes(struct imapc_mailbox_list *list)
+{
+ struct mailbox_list *fs_list = imapc_list_get_fs(list);
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ const char *fs_name, *storage_name, *vname;
+
+ if (fs_list == NULL)
+ return;
+
+ iter = mailbox_list_iter_init(fs_list, "*",
+ MAILBOX_LIST_ITER_RAW_LIST |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
+ fs_name = mailbox_list_get_storage_name(fs_list, info->vname);
+ storage_name = imapc_list_fs_to_storage_name(list, fs_name);
+ vname = mailbox_list_get_vname(&list->list, storage_name);
+
+ /* list->mailboxes contains proper vnames. fs_vname */
+ if (mailbox_tree_lookup(list->mailboxes, vname) == NULL)
+ (void)fs_list->v.delete_mailbox(fs_list, fs_name);
+ } T_END;
+ (void)mailbox_list_iter_deinit(&iter);
+}
+
+static int imapc_list_refresh(struct imapc_mailbox_list *list)
+{
+ struct imapc_command *cmd;
+ struct imapc_simple_context ctx;
+ struct mailbox_node *node;
+ const char *pattern;
+ char sep;
+
+ if (imapc_list_try_get_root_sep(list, &sep) < 0)
+ return -1;
+ if (list->refreshed_mailboxes)
+ return 0;
+
+ if (*list->set->imapc_list_prefix == '\0')
+ pattern = "*";
+ else {
+ /* list "prefix*" instead of "prefix.*". this may return a bit
+ more than we want, but we're also interested in the flags
+ of the prefix itself. */
+ pattern = t_strdup_printf("%s*", list->set->imapc_list_prefix);
+ }
+
+ cmd = imapc_list_simple_context_init(&ctx, list);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_sendf(cmd, "LIST \"\" %s", pattern);
+ mailbox_tree_deinit(&list->mailboxes);
+ list->mailboxes = mailbox_tree_init(mail_namespace_get_sep(list->list.ns));
+ mailbox_tree_set_parents_nonexistent(list->mailboxes);
+ imapc_simple_run(&ctx, &cmd);
+
+ if ((list->list.ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* INBOX always exists in IMAP server. since this namespace is
+ marked with inbox=yes, show the INBOX even if
+ imapc_list_prefix doesn't match it */
+ bool created;
+ node = mailbox_tree_get(list->mailboxes, "INBOX", &created);
+ if (*list->set->imapc_list_prefix != '\0') {
+ /* this listing didn't include the INBOX itself, but
+ might have included its children. make sure there
+ aren't any extra flags in it (especially
+ \NonExistent) */
+ node->flags &= MAILBOX_CHILDREN;
+ }
+ }
+
+ if (ctx.ret == 0) {
+ list->refreshed_mailboxes = TRUE;
+ list->refreshed_mailboxes_recently = TRUE;
+ list->last_refreshed_mailboxes = ioloop_time;
+ imapc_list_delete_unused_indexes(list);
+ }
+ return ctx.ret;
+}
+
+static void
+imapc_list_build_match_tree(struct imapc_mailbox_list_iterate_context *ctx)
+{
+ struct imapc_mailbox_list *list =
+ (struct imapc_mailbox_list *)ctx->ctx.list;
+ struct mailbox_list_iter_update_context update_ctx;
+ struct mailbox_tree_iterate_context *iter;
+ struct mailbox_node *node;
+ const char *vname;
+
+ i_zero(&update_ctx);
+ update_ctx.iter_ctx = &ctx->ctx;
+ update_ctx.tree_ctx = ctx->tree;
+ update_ctx.glob = ctx->ctx.glob;
+ update_ctx.match_parents = TRUE;
+
+ iter = mailbox_tree_iterate_init(list->mailboxes, NULL, 0);
+ while ((node = mailbox_tree_iterate_next(iter, &vname)) != NULL) {
+ update_ctx.leaf_flags = node->flags;
+ mailbox_list_iter_update(&update_ctx, vname);
+ }
+ mailbox_tree_iterate_deinit(&iter);
+}
+
+static struct mailbox_list_iterate_context *
+imapc_list_iter_init(struct mailbox_list *_list, const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ struct mailbox_list_iterate_context *_ctx;
+ struct imapc_mailbox_list_iterate_context *ctx;
+ pool_t pool;
+ const char *ns_root_name;
+ char ns_sep;
+ int ret = 0;
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 ||
+ (flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0)
+ ret = imapc_list_refresh(list);
+
+ list->iter_count++;
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ /* we're listing only subscriptions. just use the cached
+ subscriptions list. */
+ _ctx = mailbox_list_subscriptions_iter_init(_list, patterns,
+ flags);
+ if (ret < 0)
+ _ctx->failed = TRUE;
+ return _ctx;
+ }
+
+ /* if we've already failed, make sure we don't call
+ mailbox_list_get_hierarchy_sep(), since it clears the error */
+ ns_sep = ret < 0 ? '/' : mail_namespace_get_sep(_list->ns);
+
+ pool = pool_alloconly_create("mailbox list imapc iter", 1024);
+ ctx = p_new(pool, struct imapc_mailbox_list_iterate_context, 1);
+ ctx->ctx.pool = pool;
+ ctx->ctx.list = _list;
+ ctx->ctx.flags = flags;
+ ctx->ctx.glob = imap_match_init_multiple(pool, patterns, FALSE, ns_sep);
+ array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
+
+ ctx->info.ns = _list->ns;
+
+ ctx->tree = mailbox_tree_init(ns_sep);
+ mailbox_tree_set_parents_nonexistent(ctx->tree);
+ if (ret == 0)
+ imapc_list_build_match_tree(ctx);
+
+ if (list->list.ns->prefix_len > 0) {
+ ns_root_name = t_strndup(_list->ns->prefix,
+ _list->ns->prefix_len - 1);
+ ctx->ns_root = mailbox_tree_lookup(ctx->tree, ns_root_name);
+ }
+
+ ctx->iter = mailbox_tree_iterate_init(ctx->tree, NULL, 0);
+ if (ret < 0)
+ ctx->ctx.failed = TRUE;
+ return &ctx->ctx;
+}
+
+static void
+imapc_list_write_special_use(struct imapc_mailbox_list_iterate_context *ctx,
+ struct mailbox_node *node)
+{
+ unsigned int i;
+
+ if (ctx->special_use == NULL)
+ ctx->special_use = str_new(ctx->ctx.pool, 64);
+ str_truncate(ctx->special_use, 0);
+
+ for (i = 0; i < N_ELEMENTS(imap_list_flags); i++) {
+ if ((node->flags & imap_list_flags[i].flag) != 0 &&
+ (node->flags & MAILBOX_SPECIALUSE_MASK) != 0) {
+ str_append(ctx->special_use, imap_list_flags[i].str);
+ str_append_c(ctx->special_use, ' ');
+ }
+ }
+
+ if (str_len(ctx->special_use) > 0) {
+ str_truncate(ctx->special_use, str_len(ctx->special_use) - 1);
+ ctx->info.special_use = str_c(ctx->special_use);
+ } else {
+ ctx->info.special_use = NULL;
+ }
+}
+
+static bool
+imapc_list_is_ns_root(struct imapc_mailbox_list_iterate_context *ctx,
+ struct mailbox_node *node)
+{
+ struct mailbox_node *root_node = ctx->ns_root;
+
+ while (root_node != NULL) {
+ if (node == root_node)
+ return TRUE;
+ root_node = root_node->parent;
+ }
+ return FALSE;
+}
+
+static const struct mailbox_info *
+imapc_list_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct imapc_mailbox_list_iterate_context *ctx =
+ (struct imapc_mailbox_list_iterate_context *)_ctx;
+ struct imapc_mailbox_list *list =
+ (struct imapc_mailbox_list *)_ctx->list;
+ struct mailbox_node *node;
+ const char *vname;
+
+ if (_ctx->failed)
+ return NULL;
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_next(_ctx);
+
+ do {
+ node = mailbox_tree_iterate_next(ctx->iter, &vname);
+ if (node == NULL)
+ return mailbox_list_iter_default_next(_ctx);
+ } while ((node->flags & MAILBOX_MATCHED) == 0 ||
+ (imapc_list_is_ns_root(ctx, node) &&
+ (strcasecmp(vname, "INBOX") != 0 ||
+ (ctx->info.ns->flags & NAMESPACE_FLAG_INBOX_ANY) == 0)));
+
+ if (ctx->info.ns->prefix_len > 0 &&
+ strcasecmp(vname, "INBOX") != 0 &&
+ strncmp(vname, ctx->info.ns->prefix, ctx->info.ns->prefix_len-1) == 0 &&
+ vname[ctx->info.ns->prefix_len] == '\0' &&
+ list->set->imapc_list_prefix[0] == '\0') {
+ /* don't return "" name */
+ return imapc_list_iter_next(_ctx);
+ }
+
+ ctx->info.vname = vname;
+ ctx->info.flags = node->flags;
+ if ((_ctx->list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* we're iterating the INBOX namespace. pass through the
+ SPECIAL-USE flags if they exist. */
+ imapc_list_write_special_use(ctx, node);
+ } else {
+ ctx->info.special_use = NULL;
+ }
+ return &ctx->info;
+}
+
+static int imapc_list_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct imapc_mailbox_list_iterate_context *ctx =
+ (struct imapc_mailbox_list_iterate_context *)_ctx;
+ struct imapc_mailbox_list *list =
+ (struct imapc_mailbox_list *)_ctx->list;
+ int ret = _ctx->failed ? -1 : 0;
+
+ i_assert(list->iter_count > 0);
+
+ if (--list->iter_count == 0) {
+ list->refreshed_mailboxes = FALSE;
+ list->refreshed_subscriptions = FALSE;
+ }
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_deinit(_ctx);
+
+ mailbox_tree_iterate_deinit(&ctx->iter);
+ mailbox_tree_deinit(&ctx->tree);
+ pool_unref(&_ctx->pool);
+ return ret;
+}
+
+static int
+imapc_list_subscriptions_refresh(struct mailbox_list *_src_list,
+ struct mailbox_list *dest_list)
+{
+ struct imapc_mailbox_list *src_list =
+ (struct imapc_mailbox_list *)_src_list;
+ struct imapc_simple_context ctx;
+ struct imapc_command *cmd;
+ const char *pattern;
+ char list_sep, dest_sep = mail_namespace_get_sep(dest_list->ns);
+
+ i_assert(src_list->tmp_subscriptions == NULL);
+
+ if (imapc_list_try_get_root_sep(src_list, &list_sep) < 0)
+ return -1;
+
+ if (src_list->refreshed_subscriptions) {
+ if (dest_list->subscriptions == NULL)
+ dest_list->subscriptions = mailbox_tree_init(dest_sep);
+ return 0;
+ }
+
+ src_list->tmp_subscriptions =
+ mailbox_tree_init(mail_namespace_get_sep(_src_list->ns));
+
+ cmd = imapc_list_simple_context_init(&ctx, src_list);
+ if (*src_list->set->imapc_list_prefix == '\0')
+ pattern = "*";
+ else
+ pattern = t_strdup_printf("%s*", src_list->set->imapc_list_prefix);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_sendf(cmd, "LSUB \"\" %s", pattern);
+ imapc_simple_run(&ctx, &cmd);
+
+ if (ctx.ret < 0)
+ return -1;
+
+ /* replace subscriptions tree in destination */
+ if (dest_list->subscriptions != NULL)
+ mailbox_tree_deinit(&dest_list->subscriptions);
+ dest_list->subscriptions = src_list->tmp_subscriptions;
+ src_list->tmp_subscriptions = NULL;
+ mailbox_tree_set_separator(dest_list->subscriptions, dest_sep);
+
+ src_list->refreshed_subscriptions = TRUE;
+ return 0;
+}
+
+static int imapc_list_set_subscribed(struct mailbox_list *_list,
+ const char *name, bool set)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ struct imapc_command *cmd;
+ struct imapc_simple_context ctx;
+
+ cmd = imapc_list_simple_context_init(&ctx, list);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_sendf(cmd, set ? "SUBSCRIBE %s" : "UNSUBSCRIBE %s",
+ imapc_list_storage_to_remote_name(list, name));
+ imapc_simple_run(&ctx, &cmd);
+ return ctx.ret;
+}
+
+static int
+imapc_list_delete_mailbox(struct mailbox_list *_list, const char *name)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ struct mailbox_list *fs_list = imapc_list_get_fs(list);
+ enum imapc_capability capa;
+ struct imapc_command *cmd;
+ struct imapc_simple_context ctx;
+
+ if (imapc_storage_client_handle_auth_failure(list->client))
+ return -1;
+ if (imapc_client_get_capabilities(list->client->client, &capa) < 0)
+ return -1;
+
+ cmd = imapc_list_simple_context_init(&ctx, list);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ if (!imapc_command_connection_is_selected(cmd))
+ imapc_command_abort(&cmd);
+ else {
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT);
+ if ((capa & IMAPC_CAPABILITY_UNSELECT) != 0)
+ imapc_command_sendf(cmd, "UNSELECT");
+ else
+ imapc_command_sendf(cmd, "SELECT \"~~~\"");
+ imapc_simple_run(&ctx, &cmd);
+ }
+
+ cmd = imapc_list_simple_context_init(&ctx, list);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_sendf(cmd, "DELETE %s", imapc_list_storage_to_remote_name(list, name));
+ imapc_simple_run(&ctx, &cmd);
+
+ if (fs_list != NULL && ctx.ret == 0) {
+ const char *fs_name = imapc_list_storage_to_fs_name(list, name);
+ (void)fs_list->v.delete_mailbox(fs_list, fs_name);
+ }
+ return ctx.ret;
+}
+
+static int
+imapc_list_delete_dir(struct mailbox_list *_list, const char *name)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ struct mailbox_list *fs_list = imapc_list_get_fs(list);
+
+ if (fs_list != NULL) {
+ const char *fs_name = imapc_list_storage_to_fs_name(list, name);
+ (void)mailbox_list_delete_dir(fs_list, fs_name);
+ }
+ return 0;
+}
+
+static int
+imapc_list_delete_symlink(struct mailbox_list *list,
+ const char *name ATTR_UNUSED)
+{
+ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Not supported");
+ return -1;
+}
+
+static int
+imapc_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)oldlist;
+ struct mailbox_list *fs_list = imapc_list_get_fs(list);
+ struct imapc_command *cmd;
+ struct imapc_simple_context ctx;
+
+ if (oldlist != newlist) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename mailboxes across storages.");
+ return -1;
+ }
+
+ cmd = imapc_list_simple_context_init(&ctx, list);
+ imapc_command_sendf(cmd, "RENAME %s %s",
+ imapc_list_storage_to_remote_name(list, oldname),
+ imapc_list_storage_to_remote_name(list, newname));
+ imapc_simple_run(&ctx, &cmd);
+ if (ctx.ret == 0 && fs_list != NULL && oldlist == newlist) {
+ const char *old_fs_name =
+ imapc_list_storage_to_fs_name(list, oldname);
+ const char *new_fs_name =
+ imapc_list_storage_to_fs_name(list, newname);
+ (void)fs_list->v.rename_mailbox(fs_list, old_fs_name,
+ fs_list, new_fs_name);
+ }
+ return ctx.ret;
+}
+
+int imapc_list_get_mailbox_flags(struct mailbox_list *_list, const char *name,
+ enum mailbox_info_flags *flags_r)
+{
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
+ struct mailbox_node *node;
+ const char *vname;
+
+ vname = mailbox_list_get_vname(_list, name);
+ if (!list->refreshed_mailboxes_recently) {
+ if (imapc_list_refresh(list) < 0)
+ return -1;
+ i_assert(list->refreshed_mailboxes_recently);
+ }
+
+ if (list->mailboxes == NULL) {
+ /* imapc list isn't used, but e.g. LAYOUT=none */
+ *flags_r = 0;
+ return 0;
+ }
+ node = mailbox_tree_lookup(list->mailboxes, vname);
+ if (node == NULL)
+ *flags_r = MAILBOX_NONEXISTENT;
+ else
+ *flags_r = node->flags;
+ return 0;
+}
+
+struct mailbox_list imapc_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_IMAPC,
+ .props = MAILBOX_LIST_PROP_NO_ROOT | MAILBOX_LIST_PROP_AUTOCREATE_DIRS |
+ MAILBOX_LIST_PROP_NO_LIST_INDEX,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = imapc_list_alloc,
+ .init = imapc_list_init,
+ .deinit = imapc_list_deinit,
+ .get_hierarchy_sep = imapc_list_get_hierarchy_sep,
+ .get_vname = imapc_list_get_vname,
+ .get_storage_name = imapc_list_get_storage_name,
+ .get_path = imapc_list_get_path,
+ .get_temp_prefix = imapc_list_get_temp_prefix,
+ .join_refpattern = imapc_list_join_refpattern,
+ .iter_init = imapc_list_iter_init,
+ .iter_next = imapc_list_iter_next,
+ .iter_deinit = imapc_list_iter_deinit,
+ .subscriptions_refresh = imapc_list_subscriptions_refresh,
+ .set_subscribed = imapc_list_set_subscribed,
+ .delete_mailbox = imapc_list_delete_mailbox,
+ .delete_dir = imapc_list_delete_dir,
+ .delete_symlink = imapc_list_delete_symlink,
+ .rename_mailbox = imapc_list_rename_mailbox,
+ }
+};
diff --git a/src/lib-storage/index/imapc/imapc-list.h b/src/lib-storage/index/imapc/imapc-list.h
new file mode 100644
index 0000000..11df6b0
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-list.h
@@ -0,0 +1,41 @@
+#ifndef IMAPC_LIST_H
+#define IMAPC_LIST_H
+
+struct imap_arg;
+
+#include "mailbox-list-private.h"
+
+#define MAILBOX_LIST_NAME_IMAPC "imapc"
+
+struct imapc_mailbox_list {
+ struct mailbox_list list;
+ const struct imapc_settings *set;
+ struct imapc_storage_client *client;
+ struct mailbox_list *index_list;
+
+ /* mailboxes are stored as vnames */
+ struct mailbox_tree_context *mailboxes, *tmp_subscriptions;
+ char root_sep;
+ time_t last_refreshed_mailboxes;
+
+ unsigned int iter_count;
+
+ /* mailboxes/subscriptions are fully refreshed only during
+ mailbox list iteration. */
+ bool refreshed_subscriptions:1;
+ bool refreshed_mailboxes:1;
+ /* mailbox list's "recently refreshed" state is reset by syncing a
+ mailbox. mainly we use this to cache mailboxes' existence to avoid
+ issuing a LIST command every time. */
+ bool refreshed_mailboxes_recently:1;
+ bool index_list_failed:1;
+ bool root_sep_pending:1;
+};
+
+int imapc_list_get_mailbox_flags(struct mailbox_list *list, const char *name,
+ enum mailbox_info_flags *flags_r);
+int imapc_list_try_get_root_sep(struct imapc_mailbox_list *list, char *sep_r);
+const char *imapc_list_storage_to_remote_name(struct imapc_mailbox_list *list,
+ const char *storage_name);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-mail-fetch.c b/src/lib-storage/index/imapc/imapc-mail-fetch.c
new file mode 100644
index 0000000..1b0eee7
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-mail-fetch.c
@@ -0,0 +1,911 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "istream-concat.h"
+#include "istream-header-filter.h"
+#include "message-header-parser.h"
+#include "imap-arg.h"
+#include "imap-util.h"
+#include "imap-date.h"
+#include "imap-quote.h"
+#include "imap-bodystructure.h"
+#include "imap-resp-code.h"
+#include "imapc-mail.h"
+#include "imapc-storage.h"
+
+static void imapc_mail_set_failure(struct imapc_mail *mail,
+ const struct imapc_command_reply *reply)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+
+ mail->last_fetch_reply = p_strdup(mail->imail.mail.pool, reply->text_full);
+
+ switch (reply->state) {
+ case IMAPC_COMMAND_STATE_OK:
+ break;
+ case IMAPC_COMMAND_STATE_NO:
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_FIX_BROKEN_MAILS)) {
+ /* fetch-fix-broken-mails feature disabled -
+ fail any mails with missing replies */
+ break;
+ }
+ if (reply->resp_text_key != NULL &&
+ (strcasecmp(reply->resp_text_key, IMAP_RESP_CODE_SERVERBUG) == 0 ||
+ strcasecmp(reply->resp_text_key, IMAP_RESP_CODE_LIMIT) == 0)) {
+ /* this is a temporary error, retrying should work.
+ Yahoo sends * BYE +
+ NO [LIMIT] UID FETCH Rate limit hit. */
+ } else {
+ /* hopefully this is a permanent failure */
+ mail->fetch_ignore_if_missing = TRUE;
+ }
+ break;
+ case IMAPC_COMMAND_STATE_BAD:
+ case IMAPC_COMMAND_STATE_DISCONNECTED:
+ case IMAPC_COMMAND_STATE_AUTH_FAILED:
+ mail->fetch_failed = TRUE;
+ break;
+ }
+}
+
+static void
+imapc_mail_fetch_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_fetch_request *request = context;
+ struct imapc_fetch_request *const *requests;
+ struct imapc_mail *mail;
+ struct imapc_mailbox *mbox = NULL;
+ unsigned int i, count;
+
+ array_foreach_elem(&request->mails, mail) {
+ i_assert(mail->fetch_count > 0);
+ imapc_mail_set_failure(mail, reply);
+ if (--mail->fetch_count == 0)
+ mail->fetching_fields = 0;
+ mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+ }
+ i_assert(mbox != NULL);
+
+ requests = array_get(&mbox->fetch_requests, &count);
+ for (i = 0; i < count; i++) {
+ if (requests[i] == request) {
+ array_delete(&mbox->fetch_requests, i, 1);
+ break;
+ }
+ }
+ i_assert(i < count);
+
+ array_free(&request->mails);
+ i_free(request);
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ ;
+ else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_copy_error_from_reply(mbox->storage, MAIL_ERROR_PARAMS,
+ reply);
+ } else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED) {
+ /* The disconnection message was already logged */
+ mail_storage_set_internal_error(&mbox->storage->storage);
+ } else {
+ mailbox_set_critical(&mbox->box,
+ "imapc: Mail FETCH failed: %s", reply->text_full);
+ }
+ imapc_client_stop(mbox->storage->client->client);
+}
+
+static bool
+headers_have_subset(const char *const *superset, const char *const *subset)
+{
+ unsigned int i;
+
+ if (superset == NULL)
+ return FALSE;
+ if (subset != NULL) {
+ for (i = 0; subset[i] != NULL; i++) {
+ if (!str_array_icase_find(superset, subset[i]))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static const char *const *
+headers_merge(pool_t pool, const char *const *h1, const char *const *h2)
+{
+ ARRAY_TYPE(const_string) headers;
+ const char *value;
+ unsigned int i;
+
+ p_array_init(&headers, pool, 16);
+ if (h1 != NULL) {
+ for (i = 0; h1[i] != NULL; i++) {
+ value = p_strdup(pool, h1[i]);
+ array_push_back(&headers, &value);
+ }
+ }
+ if (h2 != NULL) {
+ for (i = 0; h2[i] != NULL; i++) {
+ if (h1 == NULL || !str_array_icase_find(h1, h2[i])) {
+ value = p_strdup(pool, h2[i]);
+ array_push_back(&headers, &value);
+ }
+ }
+ }
+ array_append_zero(&headers);
+ return array_front(&headers);
+}
+
+static bool
+imapc_mail_try_merge_fetch(struct imapc_mailbox *mbox, string_t *str)
+{
+ const char *s1 = str_c(str);
+ const char *s2 = str_c(mbox->pending_fetch_cmd);
+ const char *p1, *p2;
+
+ i_assert(str_begins(s1, "UID FETCH "));
+ i_assert(str_begins(s2, "UID FETCH "));
+
+ /* skip over UID range */
+ p1 = strchr(s1+10, ' ');
+ p2 = strchr(s2+10, ' ');
+
+ if (null_strcmp(p1, p2) != 0)
+ return FALSE;
+ /* append the new UID to the pending FETCH UID range */
+ str_truncate(str, p1-s1);
+ str_insert(mbox->pending_fetch_cmd, p2-s2, ",");
+ str_insert(mbox->pending_fetch_cmd, p2-s2+1, str_c(str) + 10);
+ return TRUE;
+}
+
+static void
+imapc_mail_delayed_send_or_merge(struct imapc_mail *mail, string_t *str)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+
+ if (mbox->pending_fetch_request != NULL &&
+ !imapc_mail_try_merge_fetch(mbox, str)) {
+ /* send the previous FETCH and create a new one */
+ imapc_mail_fetch_flush(mbox);
+ }
+ if (mbox->pending_fetch_request == NULL) {
+ mbox->pending_fetch_request =
+ i_new(struct imapc_fetch_request, 1);
+ i_array_init(&mbox->pending_fetch_request->mails, 4);
+ i_assert(mbox->pending_fetch_cmd->used == 0);
+ str_append_str(mbox->pending_fetch_cmd, str);
+ }
+ array_push_back(&mbox->pending_fetch_request->mails, &mail);
+
+ if (mbox->to_pending_fetch_send == NULL &&
+ array_count(&mbox->pending_fetch_request->mails) >
+ mbox->box.storage->set->mail_prefetch_count) {
+ /* we're now prefetching the maximum number of mails. this
+ most likely means that we need to flush out the command now
+ before sending anything else. delay it a little bit though
+ in case the sending code doesn't actually use
+ mail_prefetch_count and wants to fetch more.
+
+ note that we don't want to add this timeout too early,
+ because we want to optimize the maximum number of messages
+ placed into a single FETCH. even without timeout the command
+ gets flushed by imapc_mail_fetch() call. */
+ mbox->to_pending_fetch_send =
+ timeout_add_short(0, imapc_mail_fetch_flush, mbox);
+ }
+}
+
+static int
+imapc_mail_send_fetch(struct mail *_mail, enum mail_fetch_field fields,
+ const char *const *headers)
+{
+ struct imapc_mail *mail = IMAPC_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct mail_index_view *view;
+ string_t *str;
+ uint32_t seq;
+ unsigned int i;
+
+ i_assert(headers == NULL ||
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_HEADERS));
+
+ if (!mbox->selected) {
+ mail_storage_set_error(_mail->box->storage,
+ MAIL_ERROR_NOTPOSSIBLE, "Can't fetch mails before selecting mailbox");
+ return -1;
+ }
+
+ if (!mail_stream_access_start(_mail))
+ return -1;
+
+ /* drop any fields that we may already be fetching currently */
+ fields &= ENUM_NEGATE(mail->fetching_fields);
+ if (headers_have_subset(mail->fetching_headers, headers))
+ headers = NULL;
+ if (fields == 0 && headers == NULL)
+ return mail->fetch_sent ? 0 : 1;
+
+ if (!_mail->saving) {
+ /* if we already know that the mail is expunged,
+ don't try to FETCH it */
+ view = mbox->delayed_sync_view != NULL ?
+ mbox->delayed_sync_view : mbox->box.view;
+ if (!mail_index_lookup_seq(view, _mail->uid, &seq) ||
+ mail_index_is_expunged(view, seq)) {
+ mail_set_expunged(_mail);
+ return -1;
+ }
+ } else if (mbox->client_box == NULL) {
+ /* opened as save-only. we'll need to fetch the mail,
+ so actually SELECT/EXAMINE the mailbox */
+ i_assert(mbox->box.opened);
+
+ if (imapc_mailbox_select(mbox) < 0)
+ return -1;
+ }
+
+ if ((fields & MAIL_FETCH_STREAM_BODY) != 0)
+ fields |= MAIL_FETCH_STREAM_HEADER;
+
+ str = t_str_new(64);
+ str_printfa(str, "UID FETCH %u (", _mail->uid);
+ if ((fields & MAIL_FETCH_RECEIVED_DATE) != 0)
+ str_append(str, "INTERNALDATE ");
+ if ((fields & MAIL_FETCH_SAVE_DATE) != 0) {
+ i_assert(HAS_ALL_BITS(mbox->capabilities,
+ IMAPC_CAPABILITY_SAVEDATE));
+ str_append(str, "SAVEDATE ");
+ }
+ if ((fields & (MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE)) != 0)
+ str_append(str, "RFC822.SIZE ");
+ if ((fields & MAIL_FETCH_GUID) != 0) {
+ str_append(str, mbox->guid_fetch_field_name);
+ str_append_c(str, ' ');
+ }
+ if ((fields & MAIL_FETCH_IMAP_BODY) != 0)
+ str_append(str, "BODY ");
+ if ((fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0)
+ str_append(str, "BODYSTRUCTURE ");
+
+ if ((fields & MAIL_FETCH_STREAM_BODY) != 0) {
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_ZIMBRA_WORKAROUNDS))
+ str_append(str, "BODY.PEEK[] ");
+ else {
+ /* BODY.PEEK[] can return different headers than
+ BODY.PEEK[HEADER] (e.g. invalid 8bit chars replaced
+ with '?' in HEADER) - this violates IMAP protocol
+ and messes up dsync since it sometimes fetches the
+ full body and sometimes only the headers. */
+ str_append(str, "BODY.PEEK[HEADER] BODY.PEEK[TEXT] ");
+ }
+ fields |= MAIL_FETCH_STREAM_HEADER;
+ } else if ((fields & MAIL_FETCH_STREAM_HEADER) != 0)
+ str_append(str, "BODY.PEEK[HEADER] ");
+ else if (headers != NULL) {
+ mail->fetching_headers =
+ headers_merge(mail->imail.mail.data_pool, headers,
+ mail->fetching_headers);
+ str_append(str, "BODY.PEEK[HEADER.FIELDS (");
+ for (i = 0; mail->fetching_headers[i] != NULL; i++) {
+ if (i > 0)
+ str_append_c(str, ' ');
+ imap_append_astring(str, mail->fetching_headers[i]);
+ }
+ str_append(str, ")] ");
+ mail->header_list_fetched = FALSE;
+ }
+ str_truncate(str, str_len(str)-1);
+ str_append_c(str, ')');
+
+ mail->fetching_fields |= fields;
+ mail->fetch_count++;
+ mail->fetch_sent = FALSE;
+ mail->fetch_failed = FALSE;
+
+ imapc_mail_delayed_send_or_merge(mail, str);
+ return 1;
+}
+
+static void imapc_mail_cache_get(struct imapc_mail *mail,
+ struct imapc_mail_cache *cache)
+{
+ if (mail->body_fetched)
+ return;
+
+ if (cache->fd != -1) {
+ mail->fd = cache->fd;
+ mail->imail.data.stream = i_stream_create_fd(mail->fd, 0);
+ cache->fd = -1;
+ } else if (cache->buf != NULL) {
+ mail->body = cache->buf;
+ mail->imail.data.stream =
+ i_stream_create_from_data(mail->body->data,
+ mail->body->used);
+ cache->buf = NULL;
+ } else {
+ return;
+ }
+ mail->header_fetched = TRUE;
+ mail->body_fetched = TRUE;
+ /* The stream was already accessed and now it's cached.
+ It still needs to be set accessed to avoid assert-crash. */
+ mail->imail.mail.mail.mail_stream_accessed = TRUE;
+ imapc_mail_init_stream(mail);
+}
+
+static enum mail_fetch_field
+imapc_mail_get_wanted_fetch_fields(struct imapc_mail *mail)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+ struct index_mail_data *data = &mail->imail.data;
+ enum mail_fetch_field fields = 0;
+
+ if ((data->wanted_fields & MAIL_FETCH_RECEIVED_DATE) != 0 &&
+ data->received_date == (time_t)-1)
+ fields |= MAIL_FETCH_RECEIVED_DATE;
+ if ((data->wanted_fields & MAIL_FETCH_SAVE_DATE) != 0 &&
+ data->save_date == (time_t)-1) {
+ if (HAS_ALL_BITS(mbox->capabilities, IMAPC_CAPABILITY_SAVEDATE))
+ fields |= MAIL_FETCH_SAVE_DATE;
+ else
+ fields |= MAIL_FETCH_RECEIVED_DATE;
+ }
+ if ((data->wanted_fields & (MAIL_FETCH_PHYSICAL_SIZE |
+ MAIL_FETCH_VIRTUAL_SIZE)) != 0 &&
+ data->physical_size == UOFF_T_MAX &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE))
+ fields |= MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE;
+ if ((data->wanted_fields & MAIL_FETCH_IMAP_BODY) != 0 &&
+ data->body == NULL &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE))
+ fields |= MAIL_FETCH_IMAP_BODY;
+ if ((data->wanted_fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0 &&
+ data->bodystructure == NULL &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE))
+ fields |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
+ if ((data->wanted_fields & MAIL_FETCH_GUID) != 0 &&
+ data->guid == NULL && mbox->guid_fetch_field_name != NULL)
+ fields |= MAIL_FETCH_GUID;
+
+ if (data->stream == NULL && data->access_part != 0) {
+ if ((data->access_part & (READ_BODY | PARSE_BODY)) != 0)
+ fields |= MAIL_FETCH_STREAM_BODY;
+ fields |= MAIL_FETCH_STREAM_HEADER;
+ }
+ return fields;
+}
+
+void imapc_mail_try_init_stream_from_cache(struct imapc_mail *mail)
+{
+ struct mail *_mail = &mail->imail.mail.mail;
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+
+ if (mbox->prev_mail_cache.uid == _mail->uid)
+ imapc_mail_cache_get(mail, &mbox->prev_mail_cache);
+}
+
+bool imapc_mail_prefetch(struct mail *_mail)
+{
+ struct imapc_mail *mail = IMAPC_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct index_mail_data *data = &mail->imail.data;
+ enum mail_fetch_field fields;
+ const char *const *headers = NULL;
+
+ /* try to get as much from cache as possible */
+ imapc_mail_update_access_parts(&mail->imail);
+ /* If mail is already cached we can avoid re-FETCHing the mail.
+ However, don't initialize the stream if we don't actually want to
+ access the mail. */
+ if (mail->imail.data.access_part != 0)
+ imapc_mail_try_init_stream_from_cache(mail);
+
+ fields = imapc_mail_get_wanted_fetch_fields(mail);
+ if (data->wanted_headers != NULL && data->stream == NULL &&
+ (fields & MAIL_FETCH_STREAM_HEADER) == 0 &&
+ !imapc_mail_has_headers_in_cache(&mail->imail, data->wanted_headers)) {
+ /* fetch specific headers */
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_HEADERS))
+ headers = data->wanted_headers->name;
+ else
+ fields |= MAIL_FETCH_STREAM_HEADER;
+ }
+ if (fields != 0 || headers != NULL) T_BEGIN {
+ if (imapc_mail_send_fetch(_mail, fields, headers) > 0)
+ mail->imail.data.prefetch_sent = TRUE;
+ } T_END;
+ return !mail->imail.data.prefetch_sent;
+}
+
+static bool
+imapc_mail_have_fields(struct imapc_mail *imail, enum mail_fetch_field fields)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(imail->imail.mail.mail.box);
+
+ if ((fields & MAIL_FETCH_RECEIVED_DATE) != 0) {
+ if (imail->imail.data.received_date == (time_t)-1)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_RECEIVED_DATE);
+ }
+ if ((fields & MAIL_FETCH_SAVE_DATE) != 0) {
+ i_assert(HAS_ALL_BITS(mbox->capabilities,
+ IMAPC_CAPABILITY_SAVEDATE));
+ if (imail->imail.data.save_date == (time_t)-1)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_SAVE_DATE);
+ }
+ if ((fields & (MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE)) != 0) {
+ if (imail->imail.data.physical_size == UOFF_T_MAX)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE);
+ }
+ if ((fields & MAIL_FETCH_GUID) != 0) {
+ if (imail->imail.data.guid == NULL)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_GUID);
+ }
+ if ((fields & MAIL_FETCH_IMAP_BODY) != 0) {
+ if (imail->imail.data.body == NULL)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_IMAP_BODY);
+ }
+ if ((fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0) {
+ if (imail->imail.data.bodystructure == NULL)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_IMAP_BODYSTRUCTURE);
+ }
+ if ((fields & (MAIL_FETCH_STREAM_HEADER |
+ MAIL_FETCH_STREAM_BODY)) != 0) {
+ if (imail->imail.data.stream == NULL)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY);
+ }
+ i_assert(fields == 0);
+ return TRUE;
+}
+
+int imapc_mail_fetch(struct mail *_mail, enum mail_fetch_field fields,
+ const char *const *headers)
+{
+ struct imapc_mail *imail = IMAPC_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ int ret;
+
+ if ((fields & MAIL_FETCH_GUID) != 0 &&
+ mbox->guid_fetch_field_name == NULL) {
+ mail_storage_set_error(_mail->box->storage,
+ MAIL_ERROR_NOTPOSSIBLE,
+ "Message GUID not available in this server");
+ return -1;
+ }
+ if (_mail->saving) {
+ mail_storage_set_error(_mail->box->storage,
+ MAIL_ERROR_NOTPOSSIBLE,
+ "Attempting to issue FETCH for a mail not yet committed");
+ return -1;
+ }
+
+ fields |= imapc_mail_get_wanted_fetch_fields(imail);
+ T_BEGIN {
+ ret = imapc_mail_send_fetch(_mail, fields, headers);
+ } T_END;
+ if (ret < 0)
+ return -1;
+
+ /* we'll continue waiting until we've got all the fields we wanted,
+ or until all FETCH replies have been received (i.e. some FETCHes
+ failed) */
+ if (ret > 0)
+ imapc_mail_fetch_flush(mbox);
+ while (imail->fetch_count > 0 &&
+ (!imapc_mail_have_fields(imail, fields) ||
+ !imail->header_list_fetched)) {
+ imapc_mailbox_run_nofetch(mbox);
+ }
+ if (imail->fetch_failed) {
+ mail_storage_set_internal_error(&mbox->storage->storage);
+ return -1;
+ }
+ return 0;
+}
+
+void imapc_mail_fetch_flush(struct imapc_mailbox *mbox)
+{
+ struct imapc_command *cmd;
+ struct imapc_mail *mail;
+
+ if (mbox->pending_fetch_request == NULL) {
+ i_assert(mbox->to_pending_fetch_send == NULL);
+ return;
+ }
+
+ array_foreach_elem(&mbox->pending_fetch_request->mails, mail)
+ mail->fetch_sent = TRUE;
+
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_mail_fetch_callback,
+ mbox->pending_fetch_request);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ array_push_back(&mbox->fetch_requests, &mbox->pending_fetch_request);
+
+ imapc_command_send(cmd, str_c(mbox->pending_fetch_cmd));
+
+ mbox->pending_fetch_request = NULL;
+ timeout_remove(&mbox->to_pending_fetch_send);
+ str_truncate(mbox->pending_fetch_cmd, 0);
+}
+
+static bool imapc_find_lfile_arg(const struct imapc_untagged_reply *reply,
+ const struct imap_arg *arg, int *fd_r)
+{
+ const struct imap_arg *list;
+ unsigned int i, count;
+
+ for (i = 0; i < reply->file_args_count; i++) {
+ const struct imapc_arg_file *farg = &reply->file_args[i];
+
+ if (farg->parent_arg == arg->parent &&
+ imap_arg_get_list_full(arg->parent, &list, &count) &&
+ farg->list_idx < count && &list[farg->list_idx] == arg) {
+ *fd_r = farg->fd;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void imapc_stream_filter(struct istream **input)
+{
+ static const char *imapc_hide_headers[] = {
+ /* Added by MS Exchange 2010 when \Flagged flag is set.
+ This violates IMAP guarantee of messages being immutable. */
+ "X-Message-Flag"
+ };
+ struct istream *filter_input;
+
+ filter_input = i_stream_create_header_filter(*input,
+ HEADER_FILTER_EXCLUDE,
+ imapc_hide_headers, N_ELEMENTS(imapc_hide_headers),
+ *null_header_filter_callback, NULL);
+ i_stream_unref(input);
+ *input = filter_input;
+}
+
+void imapc_mail_init_stream(struct imapc_mail *mail)
+{
+ struct index_mail *imail = &mail->imail;
+ struct mail *_mail = &imail->mail.mail;
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct istream *input;
+ uoff_t size;
+ int ret;
+
+ i_stream_set_name(imail->data.stream,
+ t_strdup_printf("imapc mail uid=%u", _mail->uid));
+ index_mail_set_read_buffer_size(_mail, imail->data.stream);
+
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE)) {
+ /* enable filtering only when we're not passing through
+ RFC822.SIZE. otherwise we'll get size mismatches. */
+ imapc_stream_filter(&imail->data.stream);
+ }
+ if (imail->mail.v.istream_opened != NULL) {
+ if (imail->mail.v.istream_opened(_mail,
+ &imail->data.stream) < 0) {
+ index_mail_close_streams(imail);
+ return;
+ }
+ }
+ ret = i_stream_get_size(imail->data.stream, TRUE, &size);
+ if (ret < 0) {
+ index_mail_close_streams(imail);
+ return;
+ }
+ i_assert(ret != 0);
+ /* Once message body is fetched, we can be sure of what its size is.
+ If we had already received RFC822.SIZE, overwrite it here in case
+ it's wrong. Also in more special cases the RFC822.SIZE may be
+ smaller than the fetched message header. In this case change the
+ size as well, otherwise reading via istream-mail will fail. */
+ if (mail->body_fetched || imail->data.physical_size < size) {
+ if (mail->body_fetched) {
+ imail->data.inexact_total_sizes = FALSE;
+ /* Don't trust any existing virtual_size. Also don't
+ set it to size, because there's no guarantees about
+ the content having proper CRLF newlines, especially
+ not if istream_opened() has changed the stream. */
+ imail->data.virtual_size = UOFF_T_MAX;
+ }
+ imail->data.physical_size = size;
+ }
+
+ imail->data.stream_has_only_header = !mail->body_fetched;
+ if (index_mail_init_stream(imail, NULL, NULL, &input) < 0)
+ index_mail_close_streams(imail);
+}
+
+static void
+imapc_fetch_stream(struct imapc_mail *mail,
+ const struct imapc_untagged_reply *reply,
+ const struct imap_arg *arg,
+ bool have_header, bool have_body)
+{
+ struct index_mail *imail = &mail->imail;
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(imail->mail.mail.box);
+ struct istream *hdr_stream = NULL;
+ const char *value;
+ int fd;
+
+ if (imail->data.stream != NULL) {
+ i_assert(mail->header_fetched);
+ if (mail->body_fetched || !have_body)
+ return;
+ if (have_header) {
+ /* replace the existing stream */
+ } else if (mail->fd == -1) {
+ /* append this body stream to the existing
+ header stream */
+ hdr_stream = imail->data.stream;
+ i_stream_ref(hdr_stream);
+ } else {
+ /* append this body stream to the existing
+ header stream. we'll need to recreate the stream
+ with autoclosed fd. */
+ if (lseek(mail->fd, 0, SEEK_SET) < 0)
+ i_error("lseek(imapc) failed: %m");
+ hdr_stream = i_stream_create_fd_autoclose(&mail->fd, 0);
+ }
+ index_mail_close_streams(imail);
+ i_close_fd(&mail->fd);
+ } else {
+ if (!have_header) {
+ /* BODY.PEEK[TEXT] received - we can't currently handle
+ this before receiving BODY.PEEK[HEADER] reply */
+ return;
+ }
+ }
+
+ if (arg->type == IMAP_ARG_LITERAL_SIZE) {
+ if (!imapc_find_lfile_arg(reply, arg, &fd)) {
+ i_stream_unref(&hdr_stream);
+ return;
+ }
+ if ((fd = dup(fd)) == -1) {
+ i_error("dup() failed: %m");
+ i_stream_unref(&hdr_stream);
+ return;
+ }
+ mail->fd = fd;
+ imail->data.stream = i_stream_create_fd(fd, 0);
+ } else {
+ if (!imap_arg_get_nstring(arg, &value))
+ value = NULL;
+ if (value == NULL ||
+ (value[0] == '\0' &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_EMPTY_IS_EXPUNGED))) {
+ mail_set_expunged(&imail->mail.mail);
+ i_stream_unref(&hdr_stream);
+ return;
+ }
+ if (mail->body == NULL) {
+ mail->body = buffer_create_dynamic(default_pool,
+ arg->str_len + 1);
+ } else if (!have_header && hdr_stream != NULL) {
+ /* header is already in the buffer - add body now
+ without destroying the existing header data */
+ i_stream_unref(&hdr_stream);
+ } else {
+ buffer_set_used_size(mail->body, 0);
+ }
+ buffer_append(mail->body, value, arg->str_len);
+ imail->data.stream = i_stream_create_from_data(mail->body->data,
+ mail->body->used);
+ }
+ if (have_header)
+ mail->header_fetched = TRUE;
+ mail->body_fetched = have_body;
+
+ if (hdr_stream != NULL) {
+ struct istream *inputs[3];
+
+ inputs[0] = hdr_stream;
+ inputs[1] = imail->data.stream;
+ inputs[2] = NULL;
+ imail->data.stream = i_stream_create_concat(inputs);
+ i_stream_unref(&inputs[0]);
+ i_stream_unref(&inputs[1]);
+ }
+
+ imapc_mail_init_stream(mail);
+}
+
+static void
+imapc_fetch_header_stream(struct imapc_mail *mail,
+ const struct imapc_untagged_reply *reply,
+ const struct imap_arg *args)
+{
+ const enum message_header_parser_flags hdr_parser_flags =
+ MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+ MESSAGE_HEADER_PARSER_FLAG_DROP_CR;
+ const struct imap_arg *hdr_list;
+ struct mailbox_header_lookup_ctx *headers_ctx;
+ struct message_header_parser_ctx *parser;
+ struct message_header_line *hdr;
+ struct istream *input;
+ ARRAY_TYPE(const_string) hdr_arr;
+ const char *value;
+ int ret, fd;
+
+ if (!imap_arg_get_list(args, &hdr_list))
+ return;
+ if (!imap_arg_atom_equals(args+1, "]"))
+ return;
+ args += 2;
+
+ /* see if this is reply to the latest headers list request
+ (parse it even if it's not) */
+ t_array_init(&hdr_arr, 16);
+ while (imap_arg_get_astring(hdr_list, &value)) {
+ array_push_back(&hdr_arr, &value);
+ hdr_list++;
+ }
+ if (hdr_list->type != IMAP_ARG_EOL)
+ return;
+ array_append_zero(&hdr_arr);
+
+ if (headers_have_subset(array_front(&hdr_arr), mail->fetching_headers))
+ mail->header_list_fetched = TRUE;
+
+ if (args->type == IMAP_ARG_LITERAL_SIZE) {
+ if (!imapc_find_lfile_arg(reply, args, &fd))
+ return;
+ input = i_stream_create_fd(fd, 0);
+ } else {
+ if (!imap_arg_get_nstring(args, &value))
+ return;
+ if (value == NULL) {
+ mail_set_expunged(&mail->imail.mail.mail);
+ return;
+ }
+ input = i_stream_create_from_data(value, args->str_len);
+ }
+
+ headers_ctx = mailbox_header_lookup_init(mail->imail.mail.mail.box,
+ array_front(&hdr_arr));
+ index_mail_parse_header_init(&mail->imail, headers_ctx);
+
+ parser = message_parse_header_init(input, NULL, hdr_parser_flags);
+ while ((ret = message_parse_header_next(parser, &hdr)) > 0)
+ index_mail_parse_header(NULL, hdr, &mail->imail);
+ i_assert(ret != 0);
+ index_mail_parse_header(NULL, NULL, &mail->imail);
+ message_parse_header_deinit(&parser);
+
+ mailbox_header_lookup_unref(&headers_ctx);
+ i_stream_destroy(&input);
+}
+
+static const char *
+imapc_args_to_bodystructure(struct imapc_mail *mail,
+ const struct imap_arg *list_arg, bool extended)
+{
+ const struct imap_arg *args;
+ struct message_part *parts = NULL;
+ const char *ret, *error;
+ pool_t pool;
+
+ if (!imap_arg_get_list(list_arg, &args)) {
+ mail_set_critical(&mail->imail.mail.mail,
+ "imapc: Server sent invalid BODYSTRUCTURE parameters");
+ return NULL;
+ }
+
+ pool = pool_alloconly_create("imap bodystructure", 1024);
+ if (imap_bodystructure_parse_args(args, pool, &parts, &error) < 0) {
+ mail_set_critical(&mail->imail.mail.mail,
+ "imapc: Server sent invalid BODYSTRUCTURE: %s", error);
+ ret = NULL;
+ } else {
+ string_t *str = t_str_new(128);
+ if (imap_bodystructure_write(parts, str, extended, &error) < 0) {
+ /* All the input to imap_bodystructure_write() came
+ from imap_bodystructure_parse_args(). We should never
+ get here. Instead, if something is wrong the
+ parsing should have returned an error already. */
+ str_truncate(str, 0);
+ imap_write_args(str, args);
+ i_panic("Failed to write parsed BODYSTRUCTURE: %s "
+ "(original string: '%s')", error, str_c(str));
+ }
+ ret = p_strdup(mail->imail.mail.data_pool, str_c(str));
+ }
+ pool_unref(&pool);
+ return ret;
+}
+
+void imapc_mail_fetch_update(struct imapc_mail *mail,
+ const struct imapc_untagged_reply *reply,
+ const struct imap_arg *args)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+ const char *key, *value;
+ unsigned int i;
+ uoff_t size;
+ time_t t;
+ int tz;
+ bool match = FALSE;
+
+ for (i = 0; args[i].type != IMAP_ARG_EOL; i += 2) {
+ if (!imap_arg_get_atom(&args[i], &key) ||
+ args[i+1].type == IMAP_ARG_EOL)
+ break;
+
+ if (strcasecmp(key, "BODY[]") == 0) {
+ imapc_fetch_stream(mail, reply, &args[i+1], TRUE, TRUE);
+ match = TRUE;
+ } else if (strcasecmp(key, "BODY[HEADER]") == 0) {
+ imapc_fetch_stream(mail, reply, &args[i+1], TRUE, FALSE);
+ match = TRUE;
+ } else if (strcasecmp(key, "BODY[TEXT]") == 0) {
+ imapc_fetch_stream(mail, reply, &args[i+1], FALSE, TRUE);
+ match = TRUE;
+ } else if (strcasecmp(key, "BODY[HEADER.FIELDS") == 0) {
+ imapc_fetch_header_stream(mail, reply, &args[i+1]);
+ match = TRUE;
+ } else if (strcasecmp(key, "INTERNALDATE") == 0) {
+ if (imap_arg_get_astring(&args[i+1], &value) &&
+ imap_parse_datetime(value, &t, &tz)) {
+ mail->imail.data.received_date = t;
+ if (HAS_NO_BITS(mbox->capabilities,
+ IMAPC_CAPABILITY_SAVEDATE))
+ mail->imail.data.save_date = t;
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "SAVEDATE") == 0) {
+ if (imap_arg_get_astring(&args[i+1], &value)) {
+ if (strcasecmp(value, "NIL") == 0)
+ mail->imail.data.save_date = 0;
+ else if (imap_parse_datetime(value, &t, &tz))
+ mail->imail.data.save_date = t;
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "BODY") == 0) {
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE)) {
+ mail->imail.data.body =
+ imapc_args_to_bodystructure(mail, &args[i+1], FALSE);
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "BODYSTRUCTURE") == 0) {
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE)) {
+ mail->imail.data.bodystructure =
+ imapc_args_to_bodystructure(mail, &args[i+1], TRUE);
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "RFC822.SIZE") == 0) {
+ if (imap_arg_get_atom(&args[i+1], &value) &&
+ str_to_uoff(value, &size) == 0 &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE)) {
+ mail->imail.data.physical_size = size;
+ mail->imail.data.virtual_size = size;
+ mail->imail.data.inexact_total_sizes = TRUE;
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "X-GM-MSGID") == 0 ||
+ strcasecmp(key, "X-GUID") == 0) {
+ if (imap_arg_get_astring(&args[i+1], &value)) {
+ mail->imail.data.guid =
+ p_strdup(mail->imail.mail.pool, value);
+ }
+ match = TRUE;
+ }
+ }
+ if (!match) {
+ /* this is only a FETCH FLAGS update for the wanted mail */
+ } else {
+ imapc_client_stop(mbox->storage->client->client);
+ }
+}
diff --git a/src/lib-storage/index/imapc/imapc-mail.c b/src/lib-storage/index/imapc/imapc-mail.c
new file mode 100644
index 0000000..1ecf03e
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-mail.c
@@ -0,0 +1,675 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "sha1.h"
+#include "istream.h"
+#include "message-part-data.h"
+#include "imap-envelope.h"
+#include "imapc-msgmap.h"
+#include "imapc-mail.h"
+#include "imapc-storage.h"
+
+static bool imapc_mail_get_cached_guid(struct mail *_mail);
+
+struct mail *
+imapc_mail_alloc(struct mailbox_transaction_context *t,
+ enum mail_fetch_field wanted_fields,
+ struct mailbox_header_lookup_ctx *wanted_headers)
+{
+ struct imapc_mail *mail;
+ pool_t pool;
+
+ pool = pool_alloconly_create("mail", 2048);
+ mail = p_new(pool, struct imapc_mail, 1);
+ mail->fd = -1;
+
+ index_mail_init(&mail->imail, t, wanted_fields, wanted_headers, pool, NULL);
+ return &mail->imail.mail.mail;
+}
+
+static bool imapc_mail_is_expunged(struct mail *_mail)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct imapc_msgmap *msgmap;
+ uint32_t lseq, rseq;
+
+ if (!mbox->initial_sync_done) {
+ /* unknown at this point */
+ return FALSE;
+ }
+
+ if (mbox->sync_view != NULL) {
+ /* check if another session has already expunged it */
+ if (!mail_index_lookup_seq(mbox->sync_view, _mail->uid, &lseq))
+ return TRUE;
+ }
+
+ /* check if we've received EXPUNGE for it */
+ msgmap = imapc_client_mailbox_get_msgmap(mbox->client_box);
+ if (!imapc_msgmap_uid_to_rseq(msgmap, _mail->uid, &rseq))
+ return TRUE;
+
+ /* we may be running against a server that hasn't bothered sending
+ us an EXPUNGE. see if NOOP sends it. */
+ imapc_mailbox_noop(mbox);
+ if (!mbox->initial_sync_done) {
+ /* NOOP caused a reconnection and desync */
+ return FALSE;
+ }
+
+ return !imapc_msgmap_uid_to_rseq(msgmap, _mail->uid, &rseq);
+}
+
+static int imapc_mail_failed(struct mail *mail, const char *field)
+{
+ struct imapc_mail *imail = IMAPC_MAIL(mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->box);
+ bool fix_broken_mail = FALSE;
+
+ if (mail->expunged || imapc_mail_is_expunged(mail)) {
+ mail_set_expunged(mail);
+ } else if (!imapc_client_mailbox_is_opened(mbox->client_box)) {
+ /* we've already logged a disconnection error */
+ mail_storage_set_internal_error(mail->box->storage);
+ } else {
+ /* By default we'll assume that this is a critical failure,
+ because we don't want to lose any data. We can be here
+ either because it's a temporary failure on the server or
+ it's a permanent failure. Unfortunately we can't know
+ which case it is, so permanent failures need to be worked
+ around by setting imapc_features=fetch-fix-broken-mails.
+
+ One reason for permanent failures was that earlier Exchange
+ versions failed to return any data for messages in Calendars
+ mailbox. This seems to be fixed in newer versions.
+ */
+ fix_broken_mail = imail->fetch_ignore_if_missing;
+ mail_set_critical(mail,
+ "imapc: Remote server didn't send %s%s (FETCH replied: %s)",
+ field, fix_broken_mail ? " - treating it as empty" : "",
+ imail->last_fetch_reply);
+ }
+ return fix_broken_mail ? 0 : -1;
+}
+
+static uint64_t imapc_mail_get_modseq(struct mail *_mail)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct imapc_msgmap *msgmap;
+ const uint64_t *modseqs;
+ unsigned int count;
+ uint32_t rseq;
+
+ if (!imapc_mailbox_has_modseqs(mbox))
+ return index_mail_get_modseq(_mail);
+
+ msgmap = imapc_client_mailbox_get_msgmap(mbox->client_box);
+ if (imapc_msgmap_uid_to_rseq(msgmap, _mail->uid, &rseq)) {
+ modseqs = array_get(&mbox->rseq_modseqs, &count);
+ if (rseq <= count)
+ return modseqs[rseq-1];
+ }
+ return 1; /* unknown modseq */
+}
+
+static int imapc_mail_get_received_date(struct mail *_mail, time_t *date_r)
+{
+ struct index_mail *mail = INDEX_MAIL(_mail);
+ struct index_mail_data *data = &mail->data;
+
+ if (index_mail_get_received_date(_mail, date_r) == 0)
+ return 0;
+
+ if (data->received_date == (time_t)-1) {
+ if (imapc_mail_fetch(_mail, MAIL_FETCH_RECEIVED_DATE, NULL) < 0)
+ return -1;
+ if (data->received_date == (time_t)-1) {
+ if (imapc_mail_failed(_mail, "INTERNALDATE") < 0)
+ return -1;
+ /* assume that the server never returns INTERNALDATE
+ for this mail (see BODY[] failure handling) */
+ data->received_date = 0;
+ }
+ }
+ *date_r = data->received_date;
+ return 0;
+}
+
+static int imapc_mail_get_save_date(struct mail *_mail, time_t *date_r)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct index_mail *mail = INDEX_MAIL(_mail);
+ struct index_mail_data *data = &mail->data;
+
+ if (data->save_date != 0 && index_mail_get_save_date(_mail, date_r) > 0)
+ return 1;
+
+ if (HAS_NO_BITS(mbox->capabilities, IMAPC_CAPABILITY_SAVEDATE)) {
+ data->save_date = 0;
+ } else if (data->save_date == (time_t)-1) {
+ if (imapc_mail_fetch(_mail, MAIL_FETCH_SAVE_DATE, NULL) < 0)
+ return -1;
+ if (data->save_date == (time_t)-1 &&
+ imapc_mail_failed(_mail, "SAVEDATE") < 0)
+ return -1;
+ }
+ if (data->save_date == (time_t)-1 || data->save_date == 0) {
+ if (imapc_mail_get_received_date(_mail, date_r) < 0)
+ return -1;
+ return 0;
+ }
+ *date_r = data->save_date;
+ return 1;
+}
+
+static int imapc_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct index_mail *mail = INDEX_MAIL(_mail);
+ struct index_mail_data *data = &mail->data;
+ struct istream *input;
+ uoff_t old_offset;
+ int ret;
+
+ if (data->physical_size == UOFF_T_MAX)
+ (void)index_mail_get_physical_size(_mail, size_r);
+ if (data->physical_size != UOFF_T_MAX) {
+ *size_r = data->physical_size;
+ return 0;
+ }
+
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE) &&
+ data->stream == NULL) {
+ /* Trust RFC822.SIZE to be correct enough to present to the
+ IMAP client. However, it can be wrong in some implementation
+ so try not to trust it too much. */
+ if (imapc_mail_fetch(_mail, MAIL_FETCH_PHYSICAL_SIZE, NULL) < 0)
+ return -1;
+ if (data->physical_size == UOFF_T_MAX) {
+ if (imapc_mail_failed(_mail, "RFC822.SIZE") < 0)
+ return -1;
+ /* assume that the server never returns RFC822.SIZE
+ for this mail (see BODY[] failure handling) */
+ data->physical_size = 0;
+ }
+ *size_r = data->physical_size;
+ return 0;
+ }
+
+ old_offset = data->stream == NULL ? 0 : data->stream->v_offset;
+ if (mail_get_stream(_mail, NULL, NULL, &input) < 0)
+ return -1;
+ i_assert(data->stream != NULL);
+ i_stream_seek(data->stream, old_offset);
+
+ ret = i_stream_get_size(data->stream, TRUE,
+ &data->physical_size);
+ if (ret <= 0) {
+ i_assert(ret != 0);
+ mail_set_critical(_mail, "imapc: stat(%s) failed: %m",
+ i_stream_get_name(data->stream));
+ return -1;
+ }
+ *size_r = data->physical_size;
+ return 0;
+}
+
+static int imapc_mail_get_virtual_size(struct mail *_mail, uoff_t *size_r)
+{
+ struct index_mail *mail = INDEX_MAIL(_mail);
+ struct index_mail_data *data = &mail->data;
+
+ if (imapc_mail_get_physical_size(_mail, size_r) < 0)
+ return -1;
+ data->virtual_size = data->physical_size;
+ return 0;
+}
+
+static int
+imapc_mail_get_header_stream(struct mail *_mail,
+ struct mailbox_header_lookup_ctx *headers,
+ struct istream **stream_r)
+{
+ struct imapc_mail *mail = IMAPC_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ enum mail_lookup_abort old_abort = _mail->lookup_abort;
+ int ret;
+
+ if (mail->imail.data.access_part != 0 ||
+ !IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_HEADERS)) {
+ /* we're going to be reading the header/body anyway */
+ return index_mail_get_header_stream(_mail, headers, stream_r);
+ }
+
+ /* see if the wanted headers are already in cache */
+ _mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL;
+ ret = index_mail_get_header_stream(_mail, headers, stream_r);
+ _mail->lookup_abort = old_abort;
+ if (ret == 0)
+ return 0;
+
+ /* fetch only the wanted headers */
+ if (imapc_mail_fetch(_mail, 0, headers->name) < 0)
+ return -1;
+ /* the headers should cached now. */
+ return index_mail_get_header_stream(_mail, headers, stream_r);
+}
+
+static int
+imapc_mail_get_headers(struct mail *_mail, const char *field,
+ bool decode_to_utf8, const char *const **value_r)
+{
+ struct mailbox_header_lookup_ctx *headers;
+ const char *header_names[2];
+ const unsigned char *data;
+ size_t size;
+ struct istream *input;
+ int ret;
+
+ header_names[0] = field;
+ header_names[1] = NULL;
+ headers = mailbox_header_lookup_init(_mail->box, header_names);
+ ret = mail_get_header_stream(_mail, headers, &input);
+ mailbox_header_lookup_unref(&headers);
+ if (ret < 0)
+ return -1;
+
+ while (i_stream_read_more(input, &data, &size) > 0)
+ i_stream_skip(input, size);
+ /* the header should cached now. */
+ return index_mail_get_headers(_mail, field, decode_to_utf8, value_r);
+}
+
+static int
+imapc_mail_get_first_header(struct mail *_mail, const char *field,
+ bool decode_to_utf8, const char **value_r)
+{
+ const char *const *values;
+ int ret;
+
+ ret = imapc_mail_get_headers(_mail, field, decode_to_utf8, &values);
+ if (ret <= 0)
+ return ret;
+ *value_r = values[0];
+ return 1;
+}
+
+static int
+imapc_mail_get_stream(struct mail *_mail, bool get_body,
+ struct message_size *hdr_size,
+ struct message_size *body_size, struct istream **stream_r)
+{
+ struct imapc_mail *mail = IMAPC_MAIL(_mail);
+ struct index_mail_data *data = &mail->imail.data;
+ enum mail_fetch_field fetch_field;
+
+ if (get_body && !mail->body_fetched &&
+ mail->imail.data.stream != NULL) {
+ /* we've fetched the header, but we need the body now too */
+ index_mail_close_streams(&mail->imail);
+ /* don't re-use any cached header sizes. we may be
+ intentionally downloading the full body because the header
+ wasn't returned correctly (e.g. pop3-migration does this) */
+ data->hdr_size_set = FALSE;
+ }
+
+ /* See if we can get it from cache. If the wanted_fields/headers are
+ set properly, this is usually already done by prefetching. */
+ imapc_mail_try_init_stream_from_cache(mail);
+
+ if (data->stream == NULL) {
+ if (!data->initialized) {
+ /* coming here from mail_set_seq() */
+ mail_set_aborted(_mail);
+ return -1;
+ }
+ if (_mail->expunged) {
+ /* We already detected that the mail is expunged.
+ Don't spend time trying to FETCH it again. */
+ mail_set_expunged(_mail);
+ return -1;
+ }
+ fetch_field = get_body ||
+ (data->access_part & READ_BODY) != 0 ?
+ MAIL_FETCH_STREAM_BODY : MAIL_FETCH_STREAM_HEADER;
+ if (imapc_mail_fetch(_mail, fetch_field, NULL) < 0)
+ return -1;
+
+ if (data->stream == NULL) {
+ if (imapc_mail_failed(_mail, "BODY[]") < 0)
+ return -1;
+ i_assert(data->stream == NULL);
+
+ /* return the broken email as empty */
+ mail->body_fetched = TRUE;
+ data->stream = i_stream_create_from_data(NULL, 0);
+ imapc_mail_init_stream(mail);
+ }
+ }
+
+ return index_mail_init_stream(&mail->imail, hdr_size, body_size,
+ stream_r);
+}
+
+bool imapc_mail_has_headers_in_cache(struct index_mail *mail,
+ struct mailbox_header_lookup_ctx *headers)
+{
+ struct mail *_mail = &mail->mail.mail;
+ unsigned int i;
+
+ for (i = 0; i < headers->count; i++) {
+ if (mail_cache_field_exists(_mail->transaction->cache_view,
+ _mail->seq, headers->idx[i]) <= 0)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void imapc_mail_update_access_parts(struct index_mail *mail)
+{
+ struct mail *_mail = &mail->mail.mail;
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct index_mail_data *data = &mail->data;
+ struct mailbox_header_lookup_ctx *header_ctx;
+ const char *str;
+ time_t date;
+ uoff_t size;
+
+ if ((data->wanted_fields & MAIL_FETCH_RECEIVED_DATE) != 0)
+ (void)index_mail_get_received_date(_mail, &date);
+ if ((data->wanted_fields & MAIL_FETCH_SAVE_DATE) != 0) {
+ if (index_mail_get_save_date(_mail, &date) < 0 &&
+ HAS_NO_BITS(mbox->capabilities,
+ IMAPC_CAPABILITY_SAVEDATE)) {
+ (void)index_mail_get_received_date(_mail, &date);
+ data->save_date = data->received_date;
+ }
+ }
+ if ((data->wanted_fields & (MAIL_FETCH_PHYSICAL_SIZE |
+ MAIL_FETCH_VIRTUAL_SIZE)) != 0) {
+ if (index_mail_get_physical_size(_mail, &size) < 0 &&
+ !IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE))
+ data->access_part |= READ_HDR | READ_BODY;
+ }
+ if ((data->wanted_fields & MAIL_FETCH_GUID) != 0)
+ (void)imapc_mail_get_cached_guid(_mail);
+ if ((data->wanted_fields & MAIL_FETCH_IMAP_BODY) != 0)
+ (void)index_mail_get_cached_body(mail, &str);
+ if ((data->wanted_fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0)
+ (void)index_mail_get_cached_bodystructure(mail, &str);
+
+ if (data->access_part == 0 && data->wanted_headers != NULL &&
+ !IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_HEADERS)) {
+ /* see if all wanted headers exist in cache */
+ if (!imapc_mail_has_headers_in_cache(mail, data->wanted_headers))
+ data->access_part |= PARSE_HDR;
+ }
+ if (data->access_part == 0 &&
+ (data->wanted_fields & MAIL_FETCH_IMAP_ENVELOPE) != 0 &&
+ !IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_HEADERS)) {
+ /* the common code already checked this partially,
+ but we need a guaranteed correct answer */
+ header_ctx = mailbox_header_lookup_init(_mail->box,
+ message_part_envelope_headers);
+ if (!imapc_mail_has_headers_in_cache(mail, header_ctx))
+ data->access_part |= PARSE_HDR;
+ mailbox_header_lookup_unref(&header_ctx);
+ }
+}
+
+static void imapc_mail_set_seq(struct mail *_mail, uint32_t seq, bool saving)
+{
+ struct imapc_mail *imail = IMAPC_MAIL(_mail);
+ struct index_mail *mail = &imail->imail;
+ struct imapc_mailbox *mbox = (struct imapc_mailbox *)_mail->box;
+
+ index_mail_set_seq(_mail, seq, saving);
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE)) {
+ /* RFC822.SIZE may be read from vsize record or cache. It may
+ not be exactly correct. */
+ mail->data.inexact_total_sizes = TRUE;
+ }
+
+ /* searching code handles prefetching internally,
+ elsewhere we want to do it immediately */
+ if (!mail->mail.search_mail && !_mail->saving)
+ (void)imapc_mail_prefetch(_mail);
+}
+
+static void
+imapc_mail_add_temp_wanted_fields(struct mail *_mail,
+ enum mail_fetch_field fields,
+ struct mailbox_header_lookup_ctx *headers)
+{
+ struct index_mail *mail = INDEX_MAIL(_mail);
+
+ index_mail_add_temp_wanted_fields(_mail, fields, headers);
+ if (_mail->seq != 0)
+ imapc_mail_update_access_parts(mail);
+}
+
+static void imapc_mail_close(struct mail *_mail)
+{
+ struct imapc_mail *mail = IMAPC_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct imapc_mail_cache *cache = &mbox->prev_mail_cache;
+
+ if (mail->fetch_count > 0) {
+ imapc_mail_fetch_flush(mbox);
+ while (mail->fetch_count > 0)
+ imapc_mailbox_run_nofetch(mbox);
+ }
+
+ index_mail_close(_mail);
+
+ mail->fetching_headers = NULL;
+ if (mail->body_fetched) {
+ imapc_mail_cache_free(cache);
+ cache->uid = _mail->uid;
+ if (mail->fd != -1) {
+ cache->fd = mail->fd;
+ mail->fd = -1;
+ } else {
+ cache->buf = mail->body;
+ mail->body = NULL;
+ }
+ }
+ i_close_fd(&mail->fd);
+ buffer_free(&mail->body);
+ mail->header_fetched = FALSE;
+ mail->body_fetched = FALSE;
+
+ i_assert(mail->fetch_count == 0);
+}
+
+static int imapc_mail_get_hdr_hash(struct index_mail *imail)
+{
+ struct istream *input;
+ const unsigned char *data;
+ size_t size;
+ uoff_t old_offset;
+ struct sha1_ctxt sha1_ctx;
+ unsigned char sha1_output[SHA1_RESULTLEN];
+ const char *sha1_str;
+
+ sha1_init(&sha1_ctx);
+ old_offset = imail->data.stream == NULL ? 0 :
+ imail->data.stream->v_offset;
+ if (mail_get_hdr_stream(&imail->mail.mail, NULL, &input) < 0)
+ return -1;
+ i_assert(imail->data.stream != NULL);
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ sha1_loop(&sha1_ctx, data, size);
+ i_stream_skip(input, size);
+ }
+ i_stream_seek(imail->data.stream, old_offset);
+ sha1_result(&sha1_ctx, sha1_output);
+
+ sha1_str = binary_to_hex(sha1_output, sizeof(sha1_output));
+ imail->data.guid = p_strdup(imail->mail.data_pool, sha1_str);
+ return 0;
+}
+
+static bool imapc_mail_get_cached_guid(struct mail *_mail)
+{
+ struct index_mail *imail = INDEX_MAIL(_mail);
+ const enum index_cache_field cache_idx =
+ imail->ibox->cache_fields[MAIL_CACHE_GUID].idx;
+ string_t *str;
+
+ if (imail->data.guid != NULL) {
+ if (mail_cache_field_can_add(_mail->transaction->cache_trans,
+ _mail->seq, cache_idx)) {
+ /* GUID was prefetched - add to cache */
+ index_mail_cache_add_idx(imail, cache_idx,
+ imail->data.guid, strlen(imail->data.guid));
+ }
+ return TRUE;
+ }
+
+ str = str_new(imail->mail.data_pool, 64);
+ if (mail_cache_lookup_field(_mail->transaction->cache_view,
+ str, imail->mail.mail.seq, cache_idx) > 0) {
+ imail->data.guid = str_c(str);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int imapc_mail_get_guid(struct mail *_mail, const char **value_r)
+{
+ struct index_mail *imail = INDEX_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ const enum index_cache_field cache_idx =
+ imail->ibox->cache_fields[MAIL_CACHE_GUID].idx;
+
+ if (imapc_mail_get_cached_guid(_mail)) {
+ *value_r = imail->data.guid;
+ return 0;
+ }
+
+ /* GUID not in cache, fetch it */
+ if (mbox->guid_fetch_field_name != NULL) {
+ if (imapc_mail_fetch(_mail, MAIL_FETCH_GUID, NULL) < 0)
+ return -1;
+ if (imail->data.guid == NULL) {
+ (void)imapc_mail_failed(_mail, mbox->guid_fetch_field_name);
+ return -1;
+ }
+ } else {
+ /* use hash of message headers as the GUID */
+ if (imapc_mail_get_hdr_hash(imail) < 0)
+ return -1;
+ }
+
+ index_mail_cache_add_idx(imail, cache_idx,
+ imail->data.guid, strlen(imail->data.guid));
+ *value_r = imail->data.guid;
+ return 0;
+}
+
+static int
+imapc_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
+ const char **value_r)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct index_mail *imail = INDEX_MAIL(_mail);
+ uint64_t num;
+
+ switch (field) {
+ case MAIL_FETCH_GUID:
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_GUID_FORCED) &&
+ mbox->guid_fetch_field_name == NULL) {
+ /* GUIDs not supported by server */
+ break;
+ }
+ *value_r = "";
+ return imapc_mail_get_guid(_mail, value_r);
+ case MAIL_FETCH_UIDL_BACKEND:
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_GMAIL_MIGRATION))
+ break;
+ if (imapc_mail_get_guid(_mail, value_r) < 0)
+ return -1;
+ if (str_to_uint64(*value_r, &num) < 0) {
+ mail_set_critical(_mail,
+ "X-GM-MSGID not 64bit integer as expected for POP3 UIDL generation: %s", *value_r);
+ return -1;
+ }
+
+ *value_r = p_strdup_printf(imail->mail.data_pool,
+ "GmailId%"PRIx64, num);
+ return 0;
+ case MAIL_FETCH_IMAP_BODY:
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE))
+ break;
+
+ if (index_mail_get_cached_body(imail, value_r))
+ return 0;
+ if (imapc_mail_fetch(_mail, field, NULL) < 0)
+ return -1;
+ if (imail->data.body == NULL) {
+ (void)imapc_mail_failed(_mail, "BODY");
+ return -1;
+ }
+ *value_r = imail->data.body;
+ return 0;
+ case MAIL_FETCH_IMAP_BODYSTRUCTURE:
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE))
+ break;
+
+ if (index_mail_get_cached_bodystructure(imail, value_r))
+ return 0;
+ if (imapc_mail_fetch(_mail, field, NULL) < 0)
+ return -1;
+ if (imail->data.bodystructure == NULL) {
+ (void)imapc_mail_failed(_mail, "BODYSTRUCTURE");
+ return -1;
+ }
+ *value_r = imail->data.bodystructure;
+ return 0;
+ default:
+ break;
+ }
+
+ return index_mail_get_special(_mail, field, value_r);
+}
+
+struct mail_vfuncs imapc_mail_vfuncs = {
+ imapc_mail_close,
+ index_mail_free,
+ imapc_mail_set_seq,
+ index_mail_set_uid,
+ index_mail_set_uid_cache_updates,
+ imapc_mail_prefetch,
+ index_mail_precache,
+ imapc_mail_add_temp_wanted_fields,
+
+ index_mail_get_flags,
+ index_mail_get_keywords,
+ index_mail_get_keyword_indexes,
+ imapc_mail_get_modseq,
+ index_mail_get_pvt_modseq,
+ index_mail_get_parts,
+ index_mail_get_date,
+ imapc_mail_get_received_date,
+ imapc_mail_get_save_date,
+ imapc_mail_get_virtual_size,
+ imapc_mail_get_physical_size,
+ imapc_mail_get_first_header,
+ imapc_mail_get_headers,
+ imapc_mail_get_header_stream,
+ imapc_mail_get_stream,
+ index_mail_get_binary_stream,
+ imapc_mail_get_special,
+ index_mail_get_backend_mail,
+ index_mail_update_flags,
+ index_mail_update_keywords,
+ index_mail_update_modseq,
+ index_mail_update_pvt_modseq,
+ NULL,
+ index_mail_expunge,
+ index_mail_set_cache_corrupted,
+ index_mail_opened,
+};
diff --git a/src/lib-storage/index/imapc/imapc-mail.h b/src/lib-storage/index/imapc/imapc-mail.h
new file mode 100644
index 0000000..52dfe5e
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-mail.h
@@ -0,0 +1,51 @@
+#ifndef IMAPC_MAIL_H
+#define IMAPC_MAIL_H
+
+#include "index-mail.h"
+
+struct imap_arg;
+struct imapc_untagged_reply;
+struct imapc_mailbox;
+
+struct imapc_mail {
+ struct index_mail imail;
+
+ enum mail_fetch_field fetching_fields;
+ const char *const *fetching_headers;
+ unsigned int fetch_count;
+ bool fetch_sent;
+ const char *last_fetch_reply;
+
+ int fd;
+ buffer_t *body;
+ bool header_fetched;
+ bool body_fetched;
+ bool header_list_fetched;
+ bool fetch_ignore_if_missing;
+ bool fetch_failed;
+};
+
+#define IMAPC_MAIL(s) container_of(s, struct imapc_mail, imail.mail.mail)
+
+extern struct mail_vfuncs imapc_mail_vfuncs;
+
+struct mail *
+imapc_mail_alloc(struct mailbox_transaction_context *t,
+ enum mail_fetch_field wanted_fields,
+ struct mailbox_header_lookup_ctx *wanted_headers);
+int imapc_mail_fetch(struct mail *mail, enum mail_fetch_field fields,
+ const char *const *headers);
+void imapc_mail_try_init_stream_from_cache(struct imapc_mail *mail);
+bool imapc_mail_prefetch(struct mail *mail);
+void imapc_mail_fetch_flush(struct imapc_mailbox *mbox);
+void imapc_mail_init_stream(struct imapc_mail *mail);
+bool imapc_mail_has_headers_in_cache(struct index_mail *mail,
+ struct mailbox_header_lookup_ctx *headers);
+
+void imapc_mail_fetch_update(struct imapc_mail *mail,
+ const struct imapc_untagged_reply *reply,
+ const struct imap_arg *args);
+void imapc_mail_update_access_parts(struct index_mail *mail);
+void imapc_mail_command_flush(struct imapc_mailbox *mbox);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-mailbox.c b/src/lib-storage/index/imapc/imapc-mailbox.c
new file mode 100644
index 0000000..73a38e2
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-mailbox.c
@@ -0,0 +1,1015 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "mail-index-modseq.h"
+#include "imap-arg.h"
+#include "imap-seqset.h"
+#include "imap-util.h"
+#include "imapc-mail.h"
+#include "imapc-msgmap.h"
+#include "imapc-list.h"
+#include "imapc-search.h"
+#include "imapc-sync.h"
+#include "imapc-storage.h"
+
+#define NOTIFY_DELAY_MSECS 500
+
+void imapc_mailbox_set_corrupted(struct imapc_mailbox *mbox,
+ const char *reason, ...)
+{
+ const char *errmsg;
+ va_list va;
+
+ va_start(va, reason);
+ errmsg = t_strdup_printf("Mailbox '%s' state corrupted: %s",
+ mbox->box.name, t_strdup_vprintf(reason, va));
+ va_end(va);
+
+ mail_storage_set_internal_error(&mbox->storage->storage);
+
+ if (!mbox->initial_sync_done) {
+ /* we failed during initial sync. need to rebuild indexes if
+ we want to get this fixed */
+ mail_index_mark_corrupted(mbox->box.index);
+ } else {
+ /* maybe the remote server is buggy and has become confused.
+ try reconnecting. */
+ }
+ imapc_client_mailbox_reconnect(mbox->client_box, errmsg);
+}
+
+struct mail_index_view *
+imapc_mailbox_get_sync_view(struct imapc_mailbox *mbox)
+{
+ if (mbox->sync_view == NULL)
+ mbox->sync_view = mail_index_view_open(mbox->box.index);
+ return mbox->sync_view;
+}
+
+static void imapc_mailbox_init_delayed_trans(struct imapc_mailbox *mbox)
+{
+ if (mbox->delayed_sync_trans != NULL)
+ return;
+
+ i_assert(mbox->delayed_sync_cache_view == NULL);
+ i_assert(mbox->delayed_sync_cache_trans == NULL);
+
+ mbox->delayed_sync_trans =
+ mail_index_transaction_begin(imapc_mailbox_get_sync_view(mbox),
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ mbox->delayed_sync_view =
+ mail_index_transaction_open_updated_view(mbox->delayed_sync_trans);
+
+ mbox->delayed_sync_cache_view =
+ mail_cache_view_open(mbox->box.cache, mbox->delayed_sync_view);
+ mbox->delayed_sync_cache_trans =
+ mail_cache_get_transaction(mbox->delayed_sync_cache_view,
+ mbox->delayed_sync_trans);
+}
+
+static int imapc_mailbox_commit_delayed_expunges(struct imapc_mailbox *mbox)
+{
+ struct mail_index_view *view = imapc_mailbox_get_sync_view(mbox);
+ struct mail_index_transaction *trans;
+ struct seq_range_iter iter;
+ unsigned int n;
+ uint32_t lseq, uid;
+ int ret;
+
+ trans = mail_index_transaction_begin(view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+
+ seq_range_array_iter_init(&iter, &mbox->delayed_expunged_uids); n = 0;
+ while (seq_range_array_iter_nth(&iter, n++, &uid)) {
+ if (mail_index_lookup_seq(view, uid, &lseq))
+ mail_index_expunge(trans, lseq);
+ }
+ array_clear(&mbox->delayed_expunged_uids);
+ ret = mail_index_transaction_commit(&trans);
+ if (ret < 0)
+ mailbox_set_index_error(&mbox->box);
+ return ret;
+}
+
+int imapc_mailbox_commit_delayed_trans(struct imapc_mailbox *mbox,
+ bool force, bool *changes_r)
+{
+ int ret = 0;
+
+ *changes_r = FALSE;
+
+ if (mbox->delayed_sync_view != NULL)
+ mail_index_view_close(&mbox->delayed_sync_view);
+ if (mbox->delayed_sync_trans == NULL)
+ ;
+ else if (!mbox->selected && !force) {
+ /* ignore any changes done during SELECT */
+ mail_index_transaction_rollback(&mbox->delayed_sync_trans);
+ } else {
+ if (mail_index_transaction_commit(&mbox->delayed_sync_trans) < 0) {
+ mailbox_set_index_error(&mbox->box);
+ ret = -1;
+ }
+ *changes_r = TRUE;
+ }
+ mbox->delayed_sync_cache_trans = NULL;
+ if (mbox->delayed_sync_cache_view != NULL)
+ mail_cache_view_close(&mbox->delayed_sync_cache_view);
+
+ if (array_count(&mbox->delayed_expunged_uids) > 0) {
+ /* delayed expunges - commit them now in a separate
+ transaction. Reopen mbox->sync_view to see changes
+ committed in delayed_sync_trans. */
+ if (mbox->sync_view != NULL)
+ mail_index_view_close(&mbox->sync_view);
+ if (imapc_mailbox_commit_delayed_expunges(mbox) < 0)
+ ret = -1;
+ }
+
+ if (mbox->sync_view != NULL)
+ mail_index_view_close(&mbox->sync_view);
+ i_assert(mbox->delayed_sync_trans == NULL);
+ i_assert(mbox->delayed_sync_view == NULL);
+ i_assert(mbox->delayed_sync_cache_trans == NULL);
+ return ret;
+}
+
+static void imapc_mailbox_idle_timeout(struct imapc_mailbox *mbox)
+{
+ timeout_remove(&mbox->to_idle_delay);
+ if (mbox->box.notify_callback != NULL)
+ mbox->box.notify_callback(&mbox->box, mbox->box.notify_context);
+}
+
+static void imapc_mailbox_idle_notify(struct imapc_mailbox *mbox)
+{
+ struct ioloop *old_ioloop = current_ioloop;
+
+ if (mbox->box.notify_callback != NULL &&
+ mbox->to_idle_delay == NULL) {
+ io_loop_set_current(mbox->storage->root_ioloop);
+ mbox->to_idle_delay =
+ timeout_add_short(NOTIFY_DELAY_MSECS,
+ imapc_mailbox_idle_timeout, mbox);
+ io_loop_set_current(old_ioloop);
+ }
+}
+
+static void
+imapc_mailbox_index_expunge(struct imapc_mailbox *mbox, uint32_t uid)
+{
+ uint32_t lseq;
+
+ if (mail_index_lookup_seq(mbox->sync_view, uid, &lseq))
+ mail_index_expunge(mbox->delayed_sync_trans, lseq);
+ else if (mail_index_lookup_seq(mbox->delayed_sync_view, uid, &lseq)) {
+ /* this message exists only in this transaction. lib-index
+ can't currently handle expunging anything except the last
+ appended message in a transaction, and fixing it would be
+ quite a lot of trouble. so instead we'll just delay doing
+ this expunge until after the current transaction has been
+ committed. */
+ seq_range_array_add(&mbox->delayed_expunged_uids, uid);
+ } else {
+ /* already expunged by another session */
+ }
+}
+
+static void
+imapc_mailbox_fetch_state_finish(struct imapc_mailbox *mbox)
+{
+ uint32_t lseq, uid, msg_count;
+
+ if (mbox->sync_next_lseq == 0) {
+ /* FETCH n:*, not 1:* */
+ i_assert(mbox->state_fetched_success ||
+ (mbox->box.flags & MAILBOX_FLAG_SAVEONLY) != 0);
+ return;
+ }
+
+ /* if we haven't seen FETCH reply for some messages at the end of
+ mailbox they've been externally expunged. */
+ msg_count = mail_index_view_get_messages_count(mbox->delayed_sync_view);
+ for (lseq = mbox->sync_next_lseq; lseq <= msg_count; lseq++) {
+ mail_index_lookup_uid(mbox->delayed_sync_view, lseq, &uid);
+ if (uid >= mbox->sync_uid_next) {
+ /* another process already added new messages to index
+ that our IMAP connection hasn't seen yet */
+ break;
+ }
+ imapc_mailbox_index_expunge(mbox, uid);
+ }
+
+ mbox->sync_next_lseq = 0;
+ mbox->sync_next_rseq = 0;
+ mbox->state_fetched_success = TRUE;
+}
+
+static void
+imapc_mailbox_fetch_state_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_mailbox *mbox = context;
+
+ mbox->state_fetching_uid1 = FALSE;
+ mbox->delayed_untagged_exists = FALSE;
+ imapc_client_stop(mbox->storage->client->client);
+
+ switch (reply->state) {
+ case IMAPC_COMMAND_STATE_OK:
+ imapc_mailbox_fetch_state_finish(mbox);
+ break;
+ case IMAPC_COMMAND_STATE_NO:
+ imapc_copy_error_from_reply(mbox->storage, MAIL_ERROR_PARAMS, reply);
+ break;
+ case IMAPC_COMMAND_STATE_DISCONNECTED:
+ mail_storage_set_internal_error(mbox->box.storage);
+
+ break;
+ default:
+ mail_storage_set_critical(mbox->box.storage,
+ "imapc: state FETCH failed: %s", reply->text_full);
+ break;
+ }
+}
+
+void imap_mailbox_select_finish(struct imapc_mailbox *mbox)
+{
+ if (mbox->exists_count == 0) {
+ /* no mails. expunge everything. */
+ mbox->sync_next_lseq = 1;
+ imapc_mailbox_init_delayed_trans(mbox);
+ imapc_mailbox_fetch_state_finish(mbox);
+ } else {
+ /* We don't know the latest flags, refresh them. */
+ (void)imapc_mailbox_fetch_state(mbox, 1);
+ }
+ mbox->selected = TRUE;
+}
+
+bool
+imapc_mailbox_fetch_state(struct imapc_mailbox *mbox, uint32_t first_uid)
+{
+ struct imapc_command *cmd;
+
+ if (mbox->exists_count == 0) {
+ /* empty mailbox - no point in fetching anything.
+ just make sure everything is expunged in local index.
+ Delay calling imapc_mailbox_fetch_state_finish() until
+ SELECT finishes, so we see the updated UIDNEXT. */
+ return FALSE;
+ }
+ if (mbox->state_fetching_uid1) {
+ /* retrying after reconnection - don't send duplicate */
+ return FALSE;
+ }
+
+ string_t *str = t_str_new(64);
+ str_printfa(str, "UID FETCH %u:* (FLAGS", first_uid);
+ if (imapc_mailbox_has_modseqs(mbox)) {
+ str_append(str, " MODSEQ");
+ mail_index_modseq_enable(mbox->box.index);
+ }
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_GMAIL_MIGRATION)) {
+ enum mailbox_info_flags flags;
+
+ if (!mail_index_is_in_memory(mbox->box.index)) {
+ /* these can be efficiently fetched among flags and
+ stored into cache */
+ str_append(str, " X-GM-MSGID");
+ }
+ /* do this only for the \All mailbox */
+ if (imapc_list_get_mailbox_flags(mbox->box.list,
+ mbox->box.name, &flags) == 0 &&
+ (flags & MAILBOX_SPECIALUSE_ALL) != 0)
+ str_append(str, " X-GM-LABELS");
+
+ }
+ str_append_c(str, ')');
+
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_mailbox_fetch_state_callback, mbox);
+ if (first_uid == 1) {
+ mbox->sync_next_lseq = 1;
+ mbox->sync_next_rseq = 1;
+ mbox->state_fetched_success = FALSE;
+ /* only the FETCH 1:* is retriable - others will be retried
+ by the 1:* after the reconnection */
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ }
+ mbox->state_fetching_uid1 = first_uid == 1;
+ imapc_command_send(cmd, str_c(str));
+ return TRUE;
+}
+
+static void
+imapc_untagged_exists(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ struct mail_index_view *view;
+ uint32_t exists_count = reply->num;
+
+ if (mbox == NULL)
+ return;
+ if (IMAPC_MAILBOX_IS_FULLY_SELECTED(mbox) &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_MSN_UPDATES)) {
+ /* ignore all except the first EXISTS reply (returned by
+ SELECT) */
+ return;
+ }
+
+ mbox->exists_count = exists_count;
+ mbox->exists_received = TRUE;
+
+ view = mbox->delayed_sync_view;
+ if (view == NULL)
+ view = imapc_mailbox_get_sync_view(mbox);
+
+ if (!mbox->selecting && mbox->sync_fetch_first_uid != 1) {
+ const struct mail_index_header *hdr;
+ hdr = mail_index_get_header(view);
+ mbox->sync_fetch_first_uid = hdr->next_uid;
+ mbox->delayed_untagged_exists = TRUE;
+ }
+ imapc_mailbox_idle_notify(mbox);
+}
+
+static bool keywords_are_equal(struct mail_keywords *kw,
+ const ARRAY_TYPE(keyword_indexes) *kw_arr)
+{
+ const unsigned int *kw_idx;
+ unsigned int i, j, count;
+
+ kw_idx = array_get(kw_arr, &count);
+ if (count != kw->count)
+ return FALSE;
+
+ /* there are normally only a few keywords, so O(n^2) is fine */
+ for (i = 0; i < count; i++) {
+ for (j = 0; j < count; j++) {
+ if (kw->idx[i] == kw_idx[j])
+ break;
+ }
+ if (j == count)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+imapc_mailbox_msgmap_update(struct imapc_mailbox *mbox,
+ uint32_t rseq, uint32_t fetch_uid,
+ uint32_t *lseq_r, uint32_t *uid_r,
+ bool *new_message_r)
+{
+ struct imapc_msgmap *msgmap;
+ uint32_t uid, msg_count, rseq2;
+
+ *lseq_r = 0;
+ *uid_r = uid = fetch_uid;
+ *new_message_r = FALSE;
+
+ if (rseq > mbox->exists_count) {
+ /* Receiving a FETCH for a message that EXISTS hasn't
+ announced yet. MS Exchange has a bug where our UID FETCH
+ request sometimes sends replies where sequences are above
+ EXISTS value, but their UIDs are for existing messages.
+ We'll just ignore these replies. */
+ return 0;
+ }
+ if (rseq < mbox->prev_skipped_rseq &&
+ fetch_uid > mbox->prev_skipped_uid) {
+ /* This was the initial attempt at catching the above
+ MS Exchange bug, but the above one appears to catch all
+ these cases. But keep it here just in case. */
+ imapc_mailbox_set_corrupted(mbox,
+ "FETCH sequence/UID order is mixed "
+ "(seq=%u,%u vs uid=%u,%u)",
+ mbox->prev_skipped_rseq, rseq,
+ mbox->prev_skipped_uid, fetch_uid);
+ return -1;
+ }
+
+ msgmap = imapc_client_mailbox_get_msgmap(mbox->client_box);
+ msg_count = imapc_msgmap_count(msgmap);
+ if (fetch_uid != 0 && mbox->state_fetched_success &&
+ (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_MSN_WORKAROUNDS) ||
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_MSN_UPDATES))) {
+ /* if we know the UID, use own own generated rseq instead of
+ the potentially broken rseq that the server sent.
+ Skip this during the initial FETCH 1:* (UID ..) handling,
+ or we can't detect duplicate UIDs and will instead
+ assert-crash later on. */
+ uint32_t fixed_rseq;
+
+ if (imapc_msgmap_uid_to_rseq(msgmap, fetch_uid, &fixed_rseq))
+ rseq = fixed_rseq;
+ else if (fetch_uid >= imapc_msgmap_uidnext(msgmap) &&
+ rseq <= msg_count) {
+ /* The current rseq is wrong. Lets hope that the
+ correct rseq is the next new one. This happens
+ especially with no-msn-updates when mails have been
+ expunged and new mails arrive in the same session. */
+ rseq = msg_count+1;
+ }
+ }
+
+ if (rseq <= msg_count) {
+ uid = imapc_msgmap_rseq_to_uid(msgmap, rseq);
+ if (uid != fetch_uid && fetch_uid != 0) {
+ imapc_mailbox_set_corrupted(mbox,
+ "FETCH UID mismatch (%u != %u)",
+ fetch_uid, uid);
+ return -1;
+ }
+ *uid_r = uid;
+ } else if (fetch_uid == 0 || rseq != msg_count+1) {
+ /* probably a flag update for a message we haven't yet
+ received our initial UID FETCH for. we should get
+ another one. */
+ if (fetch_uid == 0)
+ return 0;
+
+ if (imapc_msgmap_uid_to_rseq(msgmap, fetch_uid, &rseq2)) {
+ imapc_mailbox_set_corrupted(mbox,
+ "FETCH returned wrong sequence for UID %u "
+ "(%u != %u)", fetch_uid, rseq, rseq2);
+ return -1;
+ }
+ mbox->prev_skipped_rseq = rseq;
+ mbox->prev_skipped_uid = fetch_uid;
+ /* Check if this uid must be added later when syncing. */
+ *new_message_r = TRUE;
+ } else if (fetch_uid < imapc_msgmap_uidnext(msgmap)) {
+ imapc_mailbox_set_corrupted(mbox,
+ "Expunged message reappeared in session "
+ "(uid=%u < next_uid=%u)",
+ fetch_uid, imapc_msgmap_uidnext(msgmap));
+ return -1;
+ } else {
+ /* newly seen message */
+ imapc_msgmap_append(msgmap, rseq, uid);
+ if (uid < mbox->min_append_uid ||
+ uid < mail_index_get_header(mbox->delayed_sync_view)->next_uid) {
+ /* message is already added to index */
+ } else if (mbox->state_fetching_uid1) {
+ /* Initial fetching, allow messages to be appened to
+ index directly */
+ mail_index_append(mbox->delayed_sync_trans,
+ uid, lseq_r);
+ mbox->min_append_uid = uid + 1;
+ } else {
+ /* message is not yet added to index, in order to
+ prevent log synchronization errors add this
+ message later, when the mailbox is synced. */
+ *new_message_r = TRUE;
+ }
+ }
+ return 0;
+}
+
+bool imapc_mailbox_name_equals(struct imapc_mailbox *mbox,
+ const char *remote_name)
+{
+ const char *imapc_remote_name =
+ imapc_mailbox_get_remote_name(mbox);
+
+ if (strcmp(imapc_remote_name, remote_name) == 0) {
+ /* match */
+ return TRUE;
+ } else if (strcasecmp(mbox->box.name, "INBOX") == 0 &&
+ strcasecmp(remote_name, "INBOX") == 0) {
+ /* case-insensitive INBOX */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static struct imapc_untagged_fetch_ctx *
+imapc_untagged_fetch_ctx_create(void)
+{
+ pool_t pool = pool_alloconly_create("imapc untagged fetch ctx", 128);
+ struct imapc_untagged_fetch_ctx *ctx =
+ p_new(pool, struct imapc_untagged_fetch_ctx, 1);
+ ctx->pool = pool;
+ return ctx;
+}
+
+void imapc_untagged_fetch_ctx_free(struct imapc_untagged_fetch_ctx **_ctx)
+{
+ struct imapc_untagged_fetch_ctx *ctx = *_ctx;
+
+ *_ctx = NULL;
+ i_assert(ctx != NULL);
+
+ pool_unref(&ctx->pool);
+}
+
+void imapc_untagged_fetch_update_flags(struct imapc_mailbox *mbox,
+ struct imapc_untagged_fetch_ctx *ctx,
+ struct mail_index_view *view,
+ uint32_t lseq)
+{
+ ARRAY_TYPE(keyword_indexes) old_kws;
+ struct mail_keywords *kw;
+ const struct mail_index_record *rec = NULL;
+ const char *atom;
+
+ if (!ctx->have_flags)
+ return;
+
+ rec = mail_index_lookup(view, lseq);
+ if (rec->flags != ctx->flags) {
+ mail_index_update_flags(mbox->delayed_sync_trans, lseq,
+ MODIFY_REPLACE, ctx->flags);
+ }
+
+ t_array_init(&old_kws, 8);
+ mail_index_lookup_keywords(view, lseq, &old_kws);
+
+ if (ctx->have_gmail_labels) {
+ /* add keyword for mails that have GMail labels.
+ this can be used for "All Mail" mailbox migrations
+ with dsync */
+ atom = "$GMailHaveLabels";
+ array_push_back(&ctx->keywords, &atom);
+ }
+
+ array_append_zero(&ctx->keywords);
+ kw = mail_index_keywords_create(mbox->box.index,
+ array_front(&ctx->keywords));
+ if (!keywords_are_equal(kw, &old_kws)) {
+ mail_index_update_keywords(mbox->delayed_sync_trans,
+ lseq, MODIFY_REPLACE, kw);
+ }
+ mail_index_keywords_unref(&kw);
+}
+
+static bool imapc_untagged_fetch_handle(struct imapc_mailbox *mbox,
+ struct imapc_untagged_fetch_ctx *ctx,
+ uint32_t rseq)
+{
+ uint32_t lseq;
+ bool new_message;
+
+ imapc_mailbox_init_delayed_trans(mbox);
+ if (imapc_mailbox_msgmap_update(mbox, rseq, ctx->fetch_uid,
+ &lseq, &ctx->uid,
+ &new_message) < 0 || ctx->uid == 0)
+ return FALSE;
+
+ if ((ctx->flags & MAIL_RECENT) == 0 && mbox->highest_nonrecent_uid < ctx->uid) {
+ /* remember for STATUS_FIRST_RECENT_UID */
+ mbox->highest_nonrecent_uid = ctx->uid;
+ }
+ /* FIXME: we should ideally also pass these through so they show up
+ to clients. */
+ ctx->flags &= ENUM_NEGATE(MAIL_RECENT);
+
+ if (lseq == 0) {
+ if (!mail_index_lookup_seq(mbox->delayed_sync_view,
+ ctx->uid, &lseq)) {
+ /* already expunged by another session */
+ if (rseq == mbox->sync_next_rseq)
+ mbox->sync_next_rseq++;
+ return new_message;
+ }
+ }
+
+ if (rseq == mbox->sync_next_rseq) {
+ /* we're doing the initial full sync of mails. expunge any
+ mails that no longer exist. */
+ while (mbox->sync_next_lseq < lseq) {
+ mail_index_lookup_uid(mbox->delayed_sync_view,
+ mbox->sync_next_lseq, &ctx->uid);
+ imapc_mailbox_index_expunge(mbox, ctx->uid);
+ mbox->sync_next_lseq++;
+ }
+ i_assert(lseq == mbox->sync_next_lseq);
+ mbox->sync_next_rseq++;
+ mbox->sync_next_lseq++;
+ }
+
+ if (!new_message) {
+ /* Only update flags immediately for existing messages */
+ imapc_untagged_fetch_update_flags(mbox, ctx,
+ mbox->delayed_sync_view, lseq);
+ }
+
+ if (ctx->modseq != 0) {
+ if (mail_index_modseq_lookup(mbox->delayed_sync_view, lseq) < ctx->modseq)
+ mail_index_update_modseq(mbox->delayed_sync_trans, lseq, ctx->modseq);
+ array_idx_set(&mbox->rseq_modseqs, rseq-1, &ctx->modseq);
+ }
+ if (ctx->guid != NULL) {
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(&mbox->box);
+ const enum index_cache_field guid_cache_idx =
+ ibox->cache_fields[MAIL_CACHE_GUID].idx;
+
+ if (mail_cache_field_can_add(mbox->delayed_sync_cache_trans,
+ lseq, guid_cache_idx)) {
+ mail_cache_add(mbox->delayed_sync_cache_trans, lseq,
+ guid_cache_idx, ctx->guid, strlen(ctx->guid));
+ }
+ }
+ return new_message;
+}
+
+static bool imapc_untagged_fetch_parse(struct imapc_mailbox *mbox,
+ struct imapc_untagged_fetch_ctx *ctx,
+ const struct imap_arg *list)
+{
+ const struct imap_arg *flags_list, *modseq_list;
+ const char *atom, *patom;
+ unsigned int i, j;
+
+ ctx->fetch_uid = 0; ctx->flags = 0;
+ for (i = 0; list[i].type != IMAP_ARG_EOL; i += 2) {
+ if (!imap_arg_get_atom(&list[i], &atom) ||
+ list[i+1].type == IMAP_ARG_EOL)
+ return FALSE;
+
+ if (strcasecmp(atom, "UID") == 0) {
+ if (!imap_arg_get_atom(&list[i+1], &atom) ||
+ str_to_uint32(atom, &ctx->fetch_uid) < 0)
+ return FALSE;
+ } else if (strcasecmp(atom, "FLAGS") == 0) {
+ if (!imap_arg_get_list(&list[i+1], &flags_list))
+ return FALSE;
+
+ p_array_init(&ctx->keywords, ctx->pool, 8);
+ ctx->have_flags = TRUE;
+ for (j = 0; flags_list[j].type != IMAP_ARG_EOL; j++) {
+ if (!imap_arg_get_atom(&flags_list[j], &atom))
+ return FALSE;
+ if (atom[0] == '\\')
+ ctx->flags |= imap_parse_system_flag(atom);
+ else {
+ patom = p_strdup(ctx->pool, atom);
+ /* keyword */
+ array_push_back(&ctx->keywords, &patom);
+ }
+ }
+ } else if (strcasecmp(atom, "MODSEQ") == 0 &&
+ imapc_mailbox_has_modseqs(mbox)) {
+ /* (modseq-number) */
+ if (!imap_arg_get_list(&list[i+1], &modseq_list))
+ return FALSE;
+ if (!imap_arg_get_atom(&modseq_list[0], &atom) ||
+ str_to_uint64(atom, &ctx->modseq) < 0 ||
+ modseq_list[1].type != IMAP_ARG_EOL)
+ return FALSE;
+ } else if (strcasecmp(atom, "X-GM-MSGID") == 0 &&
+ !mbox->initial_sync_done) {
+ if (imap_arg_get_atom(&list[i+1], &atom))
+ ctx->guid = atom;
+ } else if (strcasecmp(atom, "X-GM-LABELS") == 0 &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_GMAIL_MIGRATION)) {
+ if (!imap_arg_get_list(&list[i+1], &flags_list))
+ return FALSE;
+ for (j = 0; flags_list[j].type != IMAP_ARG_EOL; j++) {
+ if (!imap_arg_get_astring(&flags_list[j], &atom))
+ return FALSE;
+ if (strcasecmp(atom, "\\Muted") != 0)
+ ctx->have_gmail_labels = TRUE;
+ }
+ }
+ }
+ if (ctx->fetch_uid == 0 &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_MSN_UPDATES)) {
+ /* UID missing and we're not tracking MSNs */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void imapc_untagged_fetch(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ const struct imap_arg *list;
+ struct imapc_fetch_request *fetch_request;
+ struct imapc_mail *mail;
+ bool new_message = FALSE;
+
+ if (mbox == NULL || reply->num == 0 || !imap_arg_get_list(reply->args, &list))
+ return;
+ if (!IMAPC_MAILBOX_IS_FULLY_SELECTED(mbox)) {
+ /* SELECTing another mailbox - this FETCH is still for the
+ previous selected mailbox. */
+ return;
+ }
+
+ struct imapc_untagged_fetch_ctx *ctx =
+ imapc_untagged_fetch_ctx_create();
+ if (!imapc_untagged_fetch_parse(mbox, ctx, list)) {
+ imapc_untagged_fetch_ctx_free(&ctx);
+ return;
+ }
+
+ new_message = imapc_untagged_fetch_handle(mbox, ctx, reply->num);
+
+ /* if this is a reply to some FETCH request, update the mail's fields */
+ array_foreach_elem(&mbox->fetch_requests, fetch_request) {
+ array_foreach_elem(&fetch_request->mails, mail) {
+ if (mail->imail.mail.mail.uid == ctx->uid)
+ imapc_mail_fetch_update(mail, reply, list);
+ }
+ }
+
+ if (!new_message) {
+ /* Handling this context is finished if the mail was not new
+ to the local index. It has not been added to
+ mbox->untagged_fetch_contexts so no need to delete it from
+ the array. The context itself can be freed here. */
+ imapc_untagged_fetch_ctx_free(&ctx);
+ } else {
+ /* If this is a new message store this context to be handled
+ when syncing */
+ array_push_back(&mbox->untagged_fetch_contexts, &ctx);
+ }
+ imapc_mailbox_idle_notify(mbox);
+}
+
+static void imapc_untagged_expunge(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ struct imapc_msgmap *msgmap;
+ uint32_t uid, rseq = reply->num;
+
+ if (mbox == NULL || rseq == 0 ||
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_MSN_UPDATES))
+ return;
+ if (!IMAPC_MAILBOX_IS_FULLY_SELECTED(mbox)) {
+ /* SELECTing another mailbox - this EXPUNGE is still for the
+ previous selected mailbox. */
+ return;
+ }
+
+ mbox->prev_skipped_rseq = 0;
+ mbox->prev_skipped_uid = 0;
+
+ if (mbox->exists_count == 0) {
+ imapc_mailbox_set_corrupted(mbox,
+ "EXPUNGE received for empty mailbox");
+ return;
+ }
+ mbox->exists_count--;
+
+ msgmap = imapc_client_mailbox_get_msgmap(mbox->client_box);
+ if (rseq > imapc_msgmap_count(msgmap)) {
+ /* we haven't even seen this message yet */
+ return;
+ }
+ uid = imapc_msgmap_rseq_to_uid(msgmap, rseq);
+ imapc_msgmap_expunge(msgmap, rseq);
+ if (array_is_created(&mbox->rseq_modseqs))
+ array_delete(&mbox->rseq_modseqs, rseq-1, 1);
+
+ imapc_mailbox_init_delayed_trans(mbox);
+ imapc_mailbox_index_expunge(mbox, uid);
+ imapc_mailbox_idle_notify(mbox);
+}
+
+static void
+imapc_untagged_esearch_gmail_pop3(const struct imap_arg *args,
+ struct imapc_mailbox *mbox)
+{
+ struct imapc_msgmap *msgmap;
+ const char *atom;
+ struct seq_range_iter iter;
+ ARRAY_TYPE(seq_range) rseqs;
+ unsigned int n;
+ uint32_t rseq, lseq, uid;
+ ARRAY_TYPE(keyword_indexes) keywords;
+ struct mail_keywords *kw;
+ unsigned int pop3_deleted_kw_idx;
+
+ i_free_and_null(mbox->sync_gmail_pop3_search_tag);
+
+ /* It should contain ALL <seqset> or nonexistent if nothing matched */
+ if (args[0].type == IMAP_ARG_EOL)
+ return;
+ t_array_init(&rseqs, 64);
+ if (!imap_arg_atom_equals(&args[0], "ALL") ||
+ !imap_arg_get_atom(&args[1], &atom) ||
+ imap_seq_set_nostar_parse(atom, &rseqs) < 0) {
+ i_error("Invalid gmail-pop3 ESEARCH reply");
+ return;
+ }
+
+ mail_index_keyword_lookup_or_create(mbox->box.index,
+ mbox->storage->set->pop3_deleted_flag, &pop3_deleted_kw_idx);
+
+ t_array_init(&keywords, 1);
+ array_push_back(&keywords, &pop3_deleted_kw_idx);
+ kw = mail_index_keywords_create_from_indexes(mbox->box.index, &keywords);
+
+ msgmap = imapc_client_mailbox_get_msgmap(mbox->client_box);
+ seq_range_array_iter_init(&iter, &rseqs); n = 0;
+ while (seq_range_array_iter_nth(&iter, n++, &rseq)) {
+ if (rseq > imapc_msgmap_count(msgmap)) {
+ /* we haven't even seen this message yet */
+ break;
+ }
+ uid = imapc_msgmap_rseq_to_uid(msgmap, rseq);
+ if (!mail_index_lookup_seq(mbox->delayed_sync_view,
+ uid, &lseq))
+ continue;
+
+ /* add the pop3_deleted_flag */
+ mail_index_update_keywords(mbox->delayed_sync_trans,
+ lseq, MODIFY_ADD, kw);
+ }
+ mail_index_keywords_unref(&kw);
+}
+
+static void imapc_untagged_search(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ if (mbox == NULL)
+ return;
+ if (!IMAPC_MAILBOX_IS_FULLY_SELECTED(mbox)) {
+ /* SELECTing another mailbox - this SEARCH is still for the
+ previous selected mailbox. */
+ return;
+ }
+ imapc_search_reply_search(reply->args, mbox);
+}
+
+static void imapc_untagged_esearch(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ const struct imap_arg *tag_list;
+ const char *str;
+
+ if (mbox == NULL || !imap_arg_get_list(reply->args, &tag_list))
+ return;
+ if (!IMAPC_MAILBOX_IS_FULLY_SELECTED(mbox)) {
+ /* SELECTing another mailbox - this ESEARCH is still for the
+ previous selected mailbox. */
+ return;
+ }
+
+ /* ESEARCH begins with (TAG <tag>) */
+ if (!imap_arg_atom_equals(&tag_list[0], "TAG") ||
+ !imap_arg_get_string(&tag_list[1], &str) ||
+ tag_list[2].type != IMAP_ARG_EOL)
+ return;
+
+ /* for now the only ESEARCH reply that we have is for getting GMail's
+ list of hidden POP3 messages. */
+ if (mbox->sync_gmail_pop3_search_tag != NULL &&
+ strcmp(mbox->sync_gmail_pop3_search_tag, str) == 0)
+ imapc_untagged_esearch_gmail_pop3(reply->args+1, mbox);
+ else
+ imapc_search_reply_esearch(reply->args+1, mbox);
+}
+
+static void imapc_sync_uid_validity(struct imapc_mailbox *mbox)
+{
+ const struct mail_index_header *hdr;
+
+ imapc_mailbox_init_delayed_trans(mbox);
+ hdr = mail_index_get_header(mbox->delayed_sync_view);
+ if (hdr->uid_validity != mbox->sync_uid_validity &&
+ mbox->sync_uid_validity != 0) {
+ if (hdr->uid_validity != 0) {
+ /* uidvalidity changed, reset the entire mailbox */
+ mail_index_reset(mbox->delayed_sync_trans);
+ mbox->sync_fetch_first_uid = 1;
+ /* The reset needs to be committed before FETCH 1:*
+ results are received. */
+ bool changes;
+ if (imapc_mailbox_commit_delayed_trans(mbox, TRUE, &changes) < 0)
+ mail_index_mark_corrupted(mbox->box.index);
+ imapc_mailbox_init_delayed_trans(mbox);
+ }
+ mail_index_update_header(mbox->delayed_sync_trans,
+ offsetof(struct mail_index_header, uid_validity),
+ &mbox->sync_uid_validity,
+ sizeof(mbox->sync_uid_validity), TRUE);
+ }
+}
+
+static void
+imapc_resp_text_uidvalidity(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ uint32_t uid_validity;
+
+ if (mbox == NULL ||
+ str_to_uint32(reply->resp_text_value, &uid_validity) < 0 ||
+ uid_validity == 0)
+ return;
+
+ if (mbox->sync_uid_validity != uid_validity) {
+ mbox->sync_uid_validity = uid_validity;
+ imapc_mail_cache_free(&mbox->prev_mail_cache);
+ imapc_sync_uid_validity(mbox);
+ }
+}
+
+static void
+imapc_resp_text_uidnext(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ uint32_t uid_next;
+
+ if (mbox == NULL ||
+ str_to_uint32(reply->resp_text_value, &uid_next) < 0)
+ return;
+
+ mbox->sync_uid_next = uid_next;
+}
+
+static void
+imapc_resp_text_highestmodseq(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ uint64_t highestmodseq;
+
+ if (mbox == NULL ||
+ str_to_uint64(reply->resp_text_value, &highestmodseq) < 0)
+ return;
+
+ mbox->sync_highestmodseq = highestmodseq;
+}
+
+static void
+imapc_resp_text_permanentflags(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox)
+{
+ const struct imap_arg *flags_args, *arg;
+ const char *flag;
+ unsigned int idx;
+
+ i_assert(reply->args[0].type == IMAP_ARG_ATOM);
+
+ if (mbox == NULL || !imap_arg_get_list(&reply->args[1], &flags_args))
+ return;
+
+ mbox->permanent_flags = 0;
+ mbox->box.disallow_new_keywords = TRUE;
+
+ for (arg = flags_args; arg->type != IMAP_ARG_EOL; arg++) {
+ if (!imap_arg_get_atom(arg, &flag))
+ continue;
+
+ if (strcmp(flag, "\\*") == 0)
+ mbox->box.disallow_new_keywords = FALSE;
+ else if (*flag == '\\')
+ mbox->permanent_flags |= imap_parse_system_flag(flag);
+ else {
+ /* we'll simply make sure that it exists in the index */
+ mail_index_keyword_lookup_or_create(mbox->box.index,
+ flag, &idx);
+ }
+ }
+}
+
+void imapc_mailbox_register_untagged(struct imapc_mailbox *mbox,
+ const char *key,
+ imapc_mailbox_callback_t *callback)
+{
+ struct imapc_mailbox_event_callback *cb;
+
+ cb = array_append_space(&mbox->untagged_callbacks);
+ cb->name = p_strdup(mbox->box.pool, key);
+ cb->callback = callback;
+}
+
+void imapc_mailbox_register_resp_text(struct imapc_mailbox *mbox,
+ const char *key,
+ imapc_mailbox_callback_t *callback)
+{
+ struct imapc_mailbox_event_callback *cb;
+
+ cb = array_append_space(&mbox->resp_text_callbacks);
+ cb->name = p_strdup(mbox->box.pool, key);
+ cb->callback = callback;
+}
+
+void imapc_mailbox_register_callbacks(struct imapc_mailbox *mbox)
+{
+ imapc_mailbox_register_untagged(mbox, "EXISTS",
+ imapc_untagged_exists);
+ imapc_mailbox_register_untagged(mbox, "FETCH",
+ imapc_untagged_fetch);
+ imapc_mailbox_register_untagged(mbox, "EXPUNGE",
+ imapc_untagged_expunge);
+ imapc_mailbox_register_untagged(mbox, "SEARCH",
+ imapc_untagged_search);
+ imapc_mailbox_register_untagged(mbox, "ESEARCH",
+ imapc_untagged_esearch);
+ imapc_mailbox_register_resp_text(mbox, "UIDVALIDITY",
+ imapc_resp_text_uidvalidity);
+ imapc_mailbox_register_resp_text(mbox, "UIDNEXT",
+ imapc_resp_text_uidnext);
+ imapc_mailbox_register_resp_text(mbox, "HIGHESTMODSEQ",
+ imapc_resp_text_highestmodseq);
+ imapc_mailbox_register_resp_text(mbox, "PERMANENTFLAGS",
+ imapc_resp_text_permanentflags);
+}
diff --git a/src/lib-storage/index/imapc/imapc-save.c b/src/lib-storage/index/imapc/imapc-save.c
new file mode 100644
index 0000000..c50f46b
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-save.c
@@ -0,0 +1,829 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "istream-crlf.h"
+#include "ostream.h"
+#include "imap-date.h"
+#include "imap-util.h"
+#include "imap-seqset.h"
+#include "imap-quote.h"
+#include "index-mail.h"
+#include "mail-copy.h"
+#include "mailbox-list-private.h"
+#include "imapc-msgmap.h"
+#include "imapc-storage.h"
+#include "imapc-sync.h"
+#include "imapc-mail.h"
+#include "seq-set-builder.h"
+
+struct imapc_save_context {
+ struct mail_save_context ctx;
+
+ struct imapc_mailbox *mbox;
+ struct imapc_mailbox *src_mbox;
+ struct mail_index_transaction *trans;
+
+ int fd;
+ char *temp_path;
+ struct istream *input;
+
+ uint32_t dest_uid_validity;
+ ARRAY_TYPE(seq_range) dest_saved_uids;
+ unsigned int save_count;
+
+ bool failed:1;
+ bool finished:1;
+};
+
+struct imapc_save_cmd_context {
+ struct imapc_save_context *ctx;
+ int ret;
+};
+
+#define IMAPC_SAVECTX(s) container_of(s, struct imapc_save_context, ctx)
+#define IMAPC_SERVER_CMDLINE_MAX_LEN 8000
+
+void imapc_transaction_save_rollback(struct mail_save_context *_ctx);
+static void imapc_mail_copy_bulk_flush(struct imapc_mailbox *mbox);
+
+struct mail_save_context *
+imapc_save_alloc(struct mailbox_transaction_context *t)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(t->box);
+ struct imapc_save_context *ctx;
+
+ i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
+
+ if (t->save_ctx == NULL) {
+ ctx = i_new(struct imapc_save_context, 1);
+ ctx->ctx.transaction = t;
+ ctx->mbox = mbox;
+ ctx->src_mbox = NULL;
+ ctx->trans = t->itrans;
+ ctx->fd = -1;
+ t->save_ctx = &ctx->ctx;
+ }
+ return t->save_ctx;
+}
+
+int imapc_save_begin(struct mail_save_context *_ctx, struct istream *input)
+{
+ struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
+ const char *path;
+
+ i_assert(ctx->fd == -1);
+
+ if (imapc_storage_client_handle_auth_failure(ctx->mbox->storage->client))
+ return -1;
+
+ ctx->fd = imapc_client_create_temp_fd(ctx->mbox->storage->client->client,
+ &path);
+ if (ctx->fd == -1) {
+ mail_set_critical(_ctx->dest_mail,
+ "Couldn't create temp file %s", path);
+ ctx->failed = TRUE;
+ return -1;
+ }
+ /* we may not know the size of the input, or be sure that it contains
+ only CRLFs. so we'll always first write the mail to a temp file and
+ upload it from there to remote server. */
+ ctx->finished = FALSE;
+ ctx->temp_path = i_strdup(path);
+ ctx->input = i_stream_create_crlf(input);
+ _ctx->data.output = o_stream_create_fd_file(ctx->fd, 0, FALSE);
+ o_stream_cork(_ctx->data.output);
+ return 0;
+}
+
+int imapc_save_continue(struct mail_save_context *_ctx)
+{
+ struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
+
+ if (ctx->failed)
+ return -1;
+
+ if (index_storage_save_continue(_ctx, ctx->input, NULL) < 0) {
+ ctx->failed = TRUE;
+ return -1;
+ }
+ return 0;
+}
+
+static void imapc_save_appenduid(struct imapc_save_context *ctx,
+ const struct imapc_command_reply *reply,
+ uint32_t *uid_r)
+{
+ const char *const *args;
+ uint32_t uid_validity, dest_uid;
+
+ *uid_r = 0;
+
+ /* <uidvalidity> <dest uid-set> */
+ args = t_strsplit(reply->resp_text_value, " ");
+ if (str_array_length(args) != 2)
+ return;
+
+ if (str_to_uint32(args[0], &uid_validity) < 0)
+ return;
+ if (ctx->dest_uid_validity == 0)
+ ctx->dest_uid_validity = uid_validity;
+ else if (ctx->dest_uid_validity != uid_validity)
+ return;
+
+ if (str_to_uint32(args[1], &dest_uid) == 0) {
+ seq_range_array_add_with_init(&ctx->dest_saved_uids,
+ 32, dest_uid);
+ *uid_r = dest_uid;
+ }
+}
+
+static void
+imapc_save_add_to_index(struct imapc_save_context *ctx, uint32_t uid)
+{
+ struct mail *_mail = ctx->ctx.dest_mail;
+ struct index_mail *imail = INDEX_MAIL(_mail);
+ uint32_t seq;
+
+ /* we'll temporarily append messages and at commit time expunge
+ them all, since we can't guarantee that no one else has saved
+ messages to remote server during our transaction */
+ mail_index_append(ctx->trans, uid, &seq);
+ mail_set_seq_saving(_mail, seq);
+ imail->data.no_caching = TRUE;
+ imail->data.forced_no_caching = TRUE;
+
+ if (ctx->fd != -1) {
+ struct imapc_mail *imapc_mail = IMAPC_MAIL(_mail);
+ imail->data.stream = i_stream_create_fd_autoclose(&ctx->fd, 0);
+ imapc_mail->header_fetched = TRUE;
+ imapc_mail->body_fetched = TRUE;
+ /* The saved stream wasn't actually read, but it needs to be
+ set accessed to avoid assert-crash. */
+ _mail->mail_stream_accessed = TRUE;
+ imapc_mail_init_stream(imapc_mail);
+ }
+
+ ctx->save_count++;
+}
+
+static void imapc_save_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_save_cmd_context *ctx = context;
+ uint32_t uid = 0;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ if (reply->resp_text_key != NULL &&
+ strcasecmp(reply->resp_text_key, "APPENDUID") == 0)
+ imapc_save_appenduid(ctx->ctx, reply, &uid);
+ imapc_save_add_to_index(ctx->ctx, uid);
+ ctx->ret = 0;
+ } else if (imapc_storage_client_handle_auth_failure(ctx->ctx->mbox->storage->client)) {
+ ctx->ret = -1;
+ } else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_copy_error_from_reply(ctx->ctx->mbox->storage,
+ MAIL_ERROR_PARAMS, reply);
+ ctx->ret = -1;
+ } else {
+ mailbox_set_critical(&ctx->ctx->mbox->box,
+ "imapc: APPEND failed: %s", reply->text_full);
+ ctx->ret = -1;
+ }
+ imapc_client_stop(ctx->ctx->mbox->storage->client->client);
+}
+
+static void
+imapc_save_noop_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context)
+{
+ struct imapc_save_cmd_context *ctx = context;
+
+ /* we don't really care about the reply */
+ ctx->ret = 0;
+ imapc_client_stop(ctx->ctx->mbox->storage->client->client);
+}
+
+static void
+imapc_copy_rollback_store_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context)
+{
+ struct imapc_save_context *ctx = context;
+ /* Can't do much about a non successful STORE here */
+ if (reply->state != IMAPC_COMMAND_STATE_OK) {
+ e_error(ctx->src_mbox->box.event,
+ "imapc: Failed to set \\Deleted flag for rolling back "
+ "failed copy: %s", reply->text_full);
+ ctx->src_mbox->rollback_pending = FALSE;
+ ctx->finished = TRUE;
+ ctx->failed = TRUE;
+ } else {
+ i_assert(ctx->src_mbox->rollback_pending);
+ }
+ /* No need stop the imapc client here there is always an additional
+ expunge callback after this. */
+}
+
+static void
+imapc_copy_rollback_expunge_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context)
+{
+ struct imapc_save_context *ctx = context;
+
+ /* Can't do much about a non successful EXPUNGE here */
+ if (reply->state != IMAPC_COMMAND_STATE_OK) {
+ e_error(ctx->src_mbox->box.event,
+ "imapc: Failed to expunge messages for rolling back "
+ "failed copy: %s", reply->text_full);
+ ctx->src_mbox->rollback_pending = FALSE;
+ ctx->finished = TRUE;
+ ctx->failed = TRUE;
+ } else {
+ ctx->finished = TRUE;
+ ctx->src_mbox->rollback_pending = FALSE;
+ }
+ imapc_client_stop(ctx->src_mbox->storage->client->client);
+}
+
+static void
+imapc_append_keywords(string_t *str, struct mail_keywords *kw)
+{
+ const ARRAY_TYPE(keywords) *kw_arr;
+ const char *kw_str;
+ unsigned int i;
+
+ kw_arr = mail_index_get_keywords(kw->index);
+ for (i = 0; i < kw->count; i++) {
+ kw_str = array_idx_elem(kw_arr, kw->idx[i]);
+ if (str_len(str) > 1)
+ str_append_c(str, ' ');
+ str_append(str, kw_str);
+ }
+}
+
+static int imapc_save_append(struct imapc_save_context *ctx)
+{
+ struct mail_save_context *_ctx = &ctx->ctx;
+ struct mail_save_data *mdata = &_ctx->data;
+ struct imapc_command *cmd;
+ struct imapc_save_cmd_context sctx;
+ struct istream *input;
+ const char *flags = "", *internaldate = "";
+
+ if (mdata->flags != 0 || mdata->keywords != NULL) {
+ string_t *str = t_str_new(64);
+
+ str_append(str, " (");
+ imap_write_flags(str, mdata->flags & ENUM_NEGATE(MAIL_RECENT),
+ NULL);
+ if (mdata->keywords != NULL)
+ imapc_append_keywords(str, mdata->keywords);
+ str_append_c(str, ')');
+ flags = str_c(str);
+ }
+ if (mdata->received_date != (time_t)-1) {
+ internaldate = t_strdup_printf(" \"%s\"",
+ imap_to_datetime(mdata->received_date));
+ }
+
+ ctx->mbox->exists_received = FALSE;
+
+ input = i_stream_create_fd(ctx->fd, IO_BLOCK_SIZE);
+ sctx.ctx = ctx;
+ sctx.ret = -2;
+ cmd = imapc_client_cmd(ctx->mbox->storage->client->client,
+ imapc_save_callback, &sctx);
+ imapc_command_sendf(cmd, "APPEND %s%1s%1s %p",
+ imapc_mailbox_get_remote_name(ctx->mbox),
+ flags, internaldate, input);
+ i_stream_unref(&input);
+ while (sctx.ret == -2)
+ imapc_mailbox_run(ctx->mbox);
+
+ if (sctx.ret == 0 && ctx->mbox->selected &&
+ !ctx->mbox->exists_received) {
+ /* e.g. Courier doesn't send EXISTS reply before the tagged
+ APPEND reply. That isn't exactly required by the IMAP RFC,
+ but it makes the behavior better. See if NOOP finds
+ the mail. */
+ sctx.ret = -2;
+ cmd = imapc_client_cmd(ctx->mbox->storage->client->client,
+ imapc_save_noop_callback, &sctx);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, "NOOP");
+ while (sctx.ret == -2)
+ imapc_mailbox_run(ctx->mbox);
+ }
+ return sctx.ret;
+}
+
+int imapc_save_finish(struct mail_save_context *_ctx)
+{
+ struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
+ struct mail_storage *storage = _ctx->transaction->box->storage;
+
+ ctx->finished = TRUE;
+
+ if (!ctx->failed) {
+ if (o_stream_finish(_ctx->data.output) < 0) {
+ if (!mail_storage_set_error_from_errno(storage)) {
+ mail_set_critical(_ctx->dest_mail,
+ "write(%s) failed: %s", ctx->temp_path,
+ o_stream_get_error(_ctx->data.output));
+ }
+ ctx->failed = TRUE;
+ }
+ }
+
+ if (!ctx->failed) {
+ if (imapc_save_append(ctx) < 0)
+ ctx->failed = TRUE;
+ }
+
+ o_stream_unref(&_ctx->data.output);
+ i_stream_unref(&ctx->input);
+ i_close_fd_path(&ctx->fd, ctx->temp_path);
+ i_free(ctx->temp_path);
+ index_save_context_free(_ctx);
+ return ctx->failed ? -1 : 0;
+}
+
+void imapc_save_cancel(struct mail_save_context *_ctx)
+{
+ struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
+
+ ctx->failed = TRUE;
+ (void)imapc_transaction_save_commit_pre(_ctx);
+ (void)imapc_save_finish(_ctx);
+}
+
+static void imapc_copy_bulk_finish(struct imapc_save_context *ctx)
+{
+ while (ctx->src_mbox != NULL && ctx->src_mbox->pending_copy_request != NULL)
+ imapc_mailbox_run_nofetch(ctx->src_mbox);
+}
+
+int imapc_transaction_save_commit_pre(struct mail_save_context *_ctx)
+{
+ struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
+ struct mail_transaction_commit_changes *changes =
+ _ctx->transaction->changes;
+ uint32_t i, last_seq;
+
+ i_assert(ctx->finished || ctx->failed);
+
+ /* expunge all added messages from index before commit */
+ last_seq = mail_index_view_get_messages_count(_ctx->transaction->view);
+ if (last_seq == 0)
+ return -1;
+ for (i = 0; i < ctx->save_count; i++)
+ mail_index_expunge(ctx->trans, last_seq - i);
+
+ if (!ctx->failed && array_is_created(&ctx->dest_saved_uids)) {
+ changes->uid_validity = ctx->dest_uid_validity;
+ array_append_array(&changes->saved_uids, &ctx->dest_saved_uids);
+ }
+ return 0;
+}
+
+int imapc_transaction_save_commit(struct mailbox_transaction_context *t)
+{
+ struct imapc_save_context *ctx = NULL;
+ struct imapc_mailbox *src_mbox = NULL;
+
+ if (t->save_ctx != NULL) {
+ ctx = IMAPC_SAVECTX(t->save_ctx);
+ src_mbox = ctx->src_mbox;
+ }
+
+ if (src_mbox != NULL && src_mbox->pending_copy_request != NULL) {
+ /* If there is still a copy command to send flush it now */
+ imapc_mail_copy_bulk_flush(src_mbox);
+ imapc_copy_bulk_finish(ctx);
+ }
+
+ if (ctx != NULL)
+ return ctx->failed ? -1 : 0;
+ return 0;
+}
+
+void imapc_transaction_save_commit_post(struct mail_save_context *_ctx,
+ struct mail_index_transaction_commit_result *result ATTR_UNUSED)
+{
+ imapc_transaction_save_rollback(_ctx);
+}
+
+static void
+imapc_expunge_construct_cmd_str(string_t *store_cmd,
+ string_t *expunge_cmd,
+ string_t *uids)
+{
+ str_append(store_cmd, "UID STORE ");
+ str_append_str(store_cmd, uids);
+ str_append(store_cmd, " +FLAGS (\\Deleted)");
+ str_append(expunge_cmd, "UID EXPUNGE ");
+ str_append_str(expunge_cmd, uids);
+ /* Clear already appened uids */
+ str_truncate(uids, 0);
+}
+
+static void
+imapc_expunge_send_cmd_str(struct imapc_save_context *ctx,
+ string_t *uids)
+{
+ struct imapc_command *store_cmd, *expunge_cmd;
+
+ string_t *store_cmd_str, *expunge_cmd_str;
+ store_cmd_str = t_str_new(128);
+ expunge_cmd_str = t_str_new(128);
+
+ imapc_expunge_construct_cmd_str(store_cmd_str, expunge_cmd_str, uids);
+ /* Make sure line length is less than 8k */
+ i_assert(str_len(store_cmd_str) < IMAPC_SERVER_CMDLINE_MAX_LEN);
+ i_assert(str_len(expunge_cmd_str) < IMAPC_SERVER_CMDLINE_MAX_LEN);
+
+ store_cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box,
+ imapc_copy_rollback_store_callback,
+ ctx);
+ expunge_cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box,
+ imapc_copy_rollback_expunge_callback,
+ ctx);
+ ctx->src_mbox->rollback_pending = TRUE;
+ imapc_command_send(store_cmd, str_c(store_cmd_str));
+ imapc_command_send(expunge_cmd, str_c(expunge_cmd_str));
+}
+
+static void
+imapc_rollback_send_expunge(struct imapc_save_context *ctx)
+{
+ string_t *uids_str;
+ struct seqset_builder *seqset_builder;
+ struct seq_range_iter iter;
+ unsigned int i = 0;
+ uint32_t uid;
+
+ if (!array_not_empty(&ctx->src_mbox->copy_rollback_expunge_uids))
+ return;
+
+ uids_str = t_str_new(128);
+ seqset_builder = seqset_builder_init(uids_str);
+ seq_range_array_iter_init(&iter, &ctx->src_mbox->copy_rollback_expunge_uids);
+
+ /* Iterate over all uids that must be rolled back */
+ while (seq_range_array_iter_nth(&iter, i++, &uid)) {
+ /* Try to add the to the seqset builder while respecting
+ the maximum length of IMAPC_SERVER_CMDLINE_MAX_LEN. */
+ if (!seqset_builder_try_add(seqset_builder,
+ IMAPC_SERVER_CMDLINE_MAX_LEN -
+ strlen("UID STORE +FLAGS (\\Deleted)"),
+ uid)) {
+ /* Maximum length is reached send the rollback
+ and wait for it to be finished. */
+ imapc_expunge_send_cmd_str(ctx, uids_str);
+ while (ctx->src_mbox->rollback_pending)
+ imapc_mailbox_run_nofetch(ctx->src_mbox);
+
+ /* Truncate the uids_str and create a new
+ seqset_builder for the next command */
+ seqset_builder_deinit(&seqset_builder);
+ str_truncate(uids_str, 0);
+ seqset_builder = seqset_builder_init(uids_str);
+ /* Make sure the current uid which is part of
+ the next uid_str */
+ seqset_builder_add(seqset_builder, uid);
+ }
+ }
+ if (str_len(uids_str) > 0)
+ imapc_expunge_send_cmd_str(ctx, uids_str);
+ while (ctx->src_mbox->rollback_pending)
+ imapc_mailbox_run_nofetch(ctx->src_mbox);
+}
+
+static void imapc_copy_bulk_ctx_deinit(struct imapc_save_context *ctx)
+{
+ /* Clean up the pending copy and the context attached to it */
+ str_truncate(ctx->src_mbox->pending_copy_cmd, 0);
+ i_free(ctx->src_mbox->copy_dest_box);
+}
+
+void imapc_transaction_save_rollback(struct mail_save_context *_ctx)
+{
+ struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
+
+ if ((ctx->src_mbox != NULL && ctx->src_mbox->pending_copy_request != NULL) ||
+ !ctx->finished) {
+ /* There is still a pending copy which should not be send
+ as rollback() is called or the transaction has not yet
+ finished and rollback is called */
+ ctx->failed = TRUE;
+ (void)imapc_transaction_save_commit_pre(_ctx);
+
+ i_assert(ctx->finished || ctx->src_mbox != NULL);
+ /* Clean up the pending copy and the context attached to it */
+ if (ctx->src_mbox != NULL) {
+ if (ctx->src_mbox->pending_copy_request != NULL) {
+ seqset_builder_deinit(&ctx->src_mbox->pending_copy_request->uidset_builder);
+ i_free(ctx->src_mbox->pending_copy_request);
+ }
+ imapc_copy_bulk_ctx_deinit(ctx);
+ imapc_client_stop(ctx->src_mbox->storage->client->client);
+ }
+ }
+
+ /* Expunge all added messages from index */
+ if (ctx->failed && array_is_created(&ctx->dest_saved_uids)) {
+ i_assert(ctx->src_mbox != NULL);
+ seq_range_array_merge(&ctx->src_mbox->copy_rollback_expunge_uids, &ctx->dest_saved_uids);
+ /* Make sure context is not finished already */
+ ctx->finished = FALSE;
+ imapc_rollback_send_expunge(ctx);
+ array_free(&ctx->dest_saved_uids);
+ }
+
+ if (ctx->finished || ctx->failed) {
+ array_free(&ctx->dest_saved_uids);
+ i_free(ctx);
+ }
+}
+
+static bool imapc_save_copyuid(struct imapc_save_context *ctx,
+ const struct imapc_command_reply *reply,
+ uint32_t *uid_r)
+{
+ ARRAY_TYPE(seq_range) dest_uidset, source_uidset;
+ struct seq_range_iter iter;
+ const char *const *args;
+ uint32_t uid_validity;
+
+ *uid_r = 0;
+
+ /* <uidvalidity> <source uid-set> <dest uid-set> */
+ args = t_strsplit(reply->resp_text_value, " ");
+ if (str_array_length(args) != 3)
+ return FALSE;
+
+ if (str_to_uint32(args[0], &uid_validity) < 0)
+ return FALSE;
+ if (ctx->dest_uid_validity == 0)
+ ctx->dest_uid_validity = uid_validity;
+ else if (ctx->dest_uid_validity != uid_validity)
+ return FALSE;
+
+ t_array_init(&source_uidset, 8);
+ t_array_init(&dest_uidset, 8);
+
+ if (imap_seq_set_nostar_parse(args[1], &source_uidset) < 0)
+ return FALSE;
+ if (imap_seq_set_nostar_parse(args[2], &dest_uidset) < 0)
+ return FALSE;
+
+ if (!array_is_created(&ctx->dest_saved_uids))
+ i_array_init(&ctx->dest_saved_uids, 8);
+
+ seq_range_array_merge(&ctx->dest_saved_uids, &dest_uidset);
+
+ seq_range_array_iter_init(&iter, &dest_uidset);
+ (void)seq_range_array_iter_nth(&iter, 0, uid_r);
+ return TRUE;
+}
+
+static void imapc_copy_set_error(struct imapc_save_context *sctx,
+ const struct imapc_command_reply *reply)
+{
+ sctx->failed = TRUE;
+
+ if (reply->state != IMAPC_COMMAND_STATE_BAD)
+ imapc_copy_error_from_reply(sctx->mbox->storage,
+ MAIL_ERROR_PARAMS, reply);
+ else
+ mailbox_set_critical(&sctx->mbox->box,
+ "imapc: COPY failed: %s",
+ reply->text_full);
+}
+
+static void
+imapc_copy_simple_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_save_cmd_context *ctx = context;
+ uint32_t uid = 0;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ if (reply->resp_text_key != NULL &&
+ strcasecmp(reply->resp_text_key, "COPYUID") == 0)
+ imapc_save_copyuid(ctx->ctx, reply, &uid);
+ imapc_save_add_to_index(ctx->ctx, uid);
+ ctx->ret = 0;
+ } else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_copy_error_from_reply(ctx->ctx->mbox->storage,
+ MAIL_ERROR_PARAMS, reply);
+ ctx->ret = -1;
+ } else {
+ mailbox_set_critical(&ctx->ctx->mbox->box,
+ "imapc: COPY failed: %s", reply->text_full);
+ ctx->ret = -1;
+ }
+ imapc_client_stop(ctx->ctx->mbox->storage->client->client);
+}
+
+static int
+imapc_copy_simple(struct mail_save_context *_ctx, struct mail *mail)
+{
+ struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
+ struct mailbox_transaction_context *_t = _ctx->transaction;
+ struct imapc_save_cmd_context sctx;
+ struct imapc_command *cmd;
+
+ sctx.ret = -2;
+ sctx.ctx = ctx;
+ cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box,
+ imapc_copy_simple_callback,
+ &sctx);
+ imapc_command_sendf(cmd, "UID COPY %u %s", mail->uid, _t->box->name);
+ while (sctx.ret == -2)
+ imapc_mailbox_run(ctx->src_mbox);
+ ctx->finished = TRUE;
+ return sctx.ret;
+}
+
+static void imapc_copy_bulk_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_copy_request *request = context;
+ struct imapc_save_context *ctx = request->sctx;
+ struct imapc_mailbox *mbox = ctx->src_mbox;
+ unsigned int uid;
+
+ i_assert(mbox != NULL);
+ i_assert(request == mbox->pending_copy_request);
+
+ /* Check the reply state and add uid's to index and
+ dest_saved_uids. */
+ if (ctx->failed) {
+ /* If the saving already failed try to find UIDs already
+ copied from the reply so that rollback can expunge
+ them */
+ if (null_strcasecmp(reply->resp_text_key, "COPYUID") == 0) {
+ (void)imapc_save_copyuid(ctx, reply, &uid);
+ imapc_transaction_save_rollback(&ctx->ctx);
+ }
+ } else if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ if (reply->resp_text_key != NULL &&
+ strcasecmp(reply->resp_text_key, "COPYUID") == 0 &&
+ imapc_save_copyuid(ctx, reply, &uid)) {
+ ctx->finished = TRUE;
+ }
+ } else {
+ imapc_copy_set_error(ctx, reply);
+ }
+
+ ctx->src_mbox->pending_copy_request = NULL;
+ i_free(request);
+ imapc_client_stop(mbox->storage->client->client);
+}
+
+static void imapc_mail_copy_bulk_flush(struct imapc_mailbox *mbox)
+{
+ struct imapc_command *cmd;
+
+ i_assert(mbox != NULL);
+ i_assert(mbox->pending_copy_request != NULL);
+ i_assert(mbox->client_box != NULL);
+
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_copy_bulk_callback,
+ mbox->pending_copy_request);
+
+ seqset_builder_deinit(&mbox->pending_copy_request->uidset_builder);
+
+ str_append(mbox->pending_copy_cmd, " ");
+ imap_append_astring(mbox->pending_copy_cmd, mbox->copy_dest_box);
+
+ imapc_command_send(cmd, str_c(mbox->pending_copy_cmd));
+
+ imapc_copy_bulk_ctx_deinit(mbox->pending_copy_request->sctx);
+}
+
+static bool
+imapc_mail_copy_bulk_try_merge(struct imapc_mailbox *mbox, uint32_t uid,
+ const char *box)
+{
+ i_assert(str_begins(str_c(mbox->pending_copy_cmd), "UID COPY "));
+
+ if (strcmp(box, mbox->copy_dest_box) != 0) {
+ /* Not the same mailbox merging not possible */
+ return FALSE;
+ }
+ return seqset_builder_try_add(mbox->pending_copy_request->uidset_builder,
+ IMAPC_SERVER_CMDLINE_MAX_LEN, uid);
+}
+
+static void
+imapc_mail_copy_bulk_delayed_send_or_merge(struct imapc_save_context *ctx,
+ uint32_t uid,
+ const char *box)
+{
+ struct imapc_mailbox *mbox = ctx->src_mbox;
+
+ if (mbox->pending_copy_request != NULL &&
+ !imapc_mail_copy_bulk_try_merge(mbox, uid, box)) {
+ /* send the previous COPY and create new one after
+ waiting for this one to be finished. */
+ imapc_mail_copy_bulk_flush(mbox);
+ imapc_copy_bulk_finish(mbox->pending_copy_request->sctx);
+ }
+ if (mbox->pending_copy_request == NULL) {
+ mbox->pending_copy_request =
+ i_new(struct imapc_copy_request, 1);
+ str_printfa(mbox->pending_copy_cmd, "UID COPY ");
+ mbox->pending_copy_request->uidset_builder =
+ seqset_builder_init(mbox->pending_copy_cmd);
+ seqset_builder_add(mbox->pending_copy_request->uidset_builder,
+ uid);
+ mbox->copy_dest_box = i_strdup(box);
+ } else {
+ i_assert(mbox->pending_copy_request->sctx == ctx);
+ }
+ mbox->pending_copy_request->sctx = ctx;
+}
+
+static int
+imapc_copy_bulk(struct imapc_save_context *ctx, struct mail *mail)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(ctx->ctx.transaction->box);
+
+ imapc_mail_copy_bulk_delayed_send_or_merge(ctx, mail->uid,
+ imapc_mailbox_get_remote_name(mbox));
+ imapc_save_add_to_index(ctx, 0);
+
+ return ctx->failed ? -1 : 0;
+}
+
+static bool imapc_is_mail_expunged(struct imapc_mailbox *mbox, uint32_t uid)
+{
+ if (array_is_created(&mbox->delayed_expunged_uids) &&
+ seq_range_exists(&mbox->delayed_expunged_uids, uid))
+ return TRUE;
+ if (mbox->delayed_sync_trans == NULL)
+ return FALSE;
+
+ struct mail_index_view *view =
+ mail_index_transaction_get_view(mbox->delayed_sync_trans);
+ uint32_t seq;
+ return mail_index_lookup_seq(view, uid, &seq) &&
+ mail_index_transaction_is_expunged(mbox->delayed_sync_trans, seq);
+}
+
+int imapc_copy(struct mail_save_context *_ctx, struct mail *mail)
+{
+ struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
+ struct mailbox_transaction_context *_t = _ctx->transaction;
+ struct imapc_msgmap *src_msgmap;
+ uint32_t rseq;
+ int ret;
+
+ i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
+
+ if (_t->box->storage == mail->box->storage) {
+ /* Currently we don't support copying mails from multiple
+ different source mailboxes within the same transaction. */
+ i_assert(ctx->src_mbox == NULL || &ctx->src_mbox->box == mail->box);
+ ctx->src_mbox = IMAPC_MAILBOX(mail->box);
+ if (!mail->expunged && imapc_is_mail_expunged(ctx->mbox, mail->uid))
+ mail_set_expunged(mail);
+ /* same server, we can use COPY for the mail */
+ src_msgmap =
+ imapc_client_mailbox_get_msgmap(ctx->src_mbox->client_box);
+ if (mail->expunged ||
+ !imapc_msgmap_uid_to_rseq(src_msgmap, mail->uid, &rseq)) {
+ mail_storage_set_error(mail->box->storage,
+ MAIL_ERROR_EXPUNGED,
+ "Some of the requested messages no longer exist.");
+ ctx->finished = TRUE;
+ index_save_context_free(_ctx);
+ return -1;
+ }
+ /* Mail has not been expunged and can be copied. */
+ if (ctx->mbox->capabilities == 0) {
+ /* The destination mailbox has not yet been selected
+ so the capabilities are unknown */
+ if (imapc_client_get_capabilities(ctx->mbox->storage->client->client,
+ &ctx->mbox->capabilities) < 0) {
+ mail_storage_set_error(mail->box->storage,
+ MAIL_ERROR_UNAVAILABLE,
+ "Failed to determine capabilities for mailbox.");
+ ctx->finished = TRUE;
+ index_save_context_free(_ctx);
+ return -1;
+ }
+ }
+ if ((ctx->mbox->capabilities & IMAPC_CAPABILITY_UIDPLUS) != 0)
+ ret = imapc_copy_bulk(ctx, mail);
+ else
+ ret = imapc_copy_simple(_ctx, mail);
+ index_save_context_free(_ctx);
+ return ret;
+ }
+ return mail_storage_copy(_ctx, mail);
+}
diff --git a/src/lib-storage/index/imapc/imapc-search.c b/src/lib-storage/index/imapc/imapc-search.c
new file mode 100644
index 0000000..98fa8b5
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-search.c
@@ -0,0 +1,332 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "imap-arg.h"
+#include "imap-seqset.h"
+#include "imap-util.h"
+#include "mail-search.h"
+#include "imapc-msgmap.h"
+#include "imapc-storage.h"
+#include "imapc-search.h"
+
+#define IMAPC_SEARCHCTX(obj) \
+ MODULE_CONTEXT(obj, imapc_storage_module)
+
+struct imapc_search_context {
+ union mail_search_module_context module_ctx;
+
+ ARRAY_TYPE(seq_range) rseqs;
+ struct seq_range_iter iter;
+ unsigned int n;
+ bool finished;
+ bool success;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(imapc_storage_module,
+ &mail_storage_module_register);
+
+static bool
+imapc_build_search_query_args(struct imapc_mailbox *mbox,
+ const struct mail_search_arg *args,
+ bool parent_or, string_t *str);
+
+static bool imapc_search_is_fast_local(const struct mail_search_arg *args)
+{
+ const struct mail_search_arg *arg;
+
+ for (arg = args; arg != NULL; arg = arg->next) {
+ switch (arg->type) {
+ case SEARCH_OR:
+ case SEARCH_SUB:
+ if (!imapc_search_is_fast_local(arg->value.subargs))
+ return FALSE;
+ break;
+ case SEARCH_ALL:
+ case SEARCH_SEQSET:
+ case SEARCH_UIDSET:
+ case SEARCH_FLAGS:
+ case SEARCH_KEYWORDS:
+ case SEARCH_MODSEQ:
+ case SEARCH_MAILBOX:
+ case SEARCH_MAILBOX_GUID:
+ case SEARCH_MAILBOX_GLOB:
+ case SEARCH_REAL_UID:
+ break;
+ default:
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static bool
+imapc_build_search_query_arg(struct imapc_mailbox *mbox,
+ const struct mail_search_arg *arg,
+ string_t *str)
+{
+ struct mail_search_arg arg2 = *arg;
+ const char *error;
+
+ if (arg->match_not)
+ str_append(str, "NOT ");
+ arg2.match_not = FALSE;
+ arg = &arg2;
+
+ switch (arg->type) {
+ case SEARCH_OR:
+ imapc_build_search_query_args(mbox, arg->value.subargs, TRUE, str);
+ return TRUE;
+ case SEARCH_SUB:
+ str_append_c(str, '(');
+ imapc_build_search_query_args(mbox, arg->value.subargs, FALSE, str);
+ str_append_c(str, ')');
+ return TRUE;
+ case SEARCH_SEQSET:
+ /* translate to UIDs */
+ T_BEGIN {
+ ARRAY_TYPE(seq_range) uids;
+
+ t_array_init(&uids, 64);
+ mailbox_get_uid_range(&mbox->box, &arg->value.seqset,
+ &uids);
+ str_append(str, "UID ");
+ imap_write_seq_range(str, &uids);
+ } T_END;
+ return TRUE;
+ case SEARCH_BEFORE:
+ case SEARCH_SINCE:
+ case SEARCH_ON:
+ if (arg->type != SEARCH_ON &&
+ (mbox->capabilities & IMAPC_CAPABILITY_WITHIN) == 0) {
+ /* a bit kludgy way to check this.. */
+ size_t pos = str_len(str);
+ if (!mail_search_arg_to_imap(str, arg, &error))
+ return FALSE;
+ if (strncasecmp(str_c(str) + pos, "OLDER", 5) == 0 ||
+ strncasecmp(str_c(str) + pos, "YOUNGER", 7) == 0)
+ return FALSE;
+ return TRUE;
+ }
+ if (arg->value.date_type == MAIL_SEARCH_DATE_TYPE_SAVED &&
+ (mbox->capabilities & IMAPC_CAPABILITY_SAVEDATE) == 0) {
+ /* Fall back to internal date if save date is not
+ supported. */
+ arg2.value.date_type = MAIL_SEARCH_DATE_TYPE_RECEIVED;
+ }
+ /* fall through */
+ case SEARCH_ALL:
+ case SEARCH_UIDSET:
+ case SEARCH_FLAGS:
+ case SEARCH_KEYWORDS:
+ case SEARCH_SMALLER:
+ case SEARCH_LARGER:
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ case SEARCH_BODY:
+ case SEARCH_TEXT:
+ return mail_search_arg_to_imap(str, arg, &error);
+ /* extensions */
+ case SEARCH_MODSEQ:
+ if ((mbox->capabilities & IMAPC_CAPABILITY_CONDSTORE) == 0)
+ return FALSE;
+ return mail_search_arg_to_imap(str, arg, &error);
+ case SEARCH_SAVEDATESUPPORTED:
+ if ((mbox->capabilities & IMAPC_CAPABILITY_SAVEDATE) == 0)
+ return FALSE;
+ return mail_search_arg_to_imap(str, arg, &error);
+ case SEARCH_INTHREAD:
+ case SEARCH_GUID:
+ case SEARCH_MAILBOX:
+ case SEARCH_MAILBOX_GUID:
+ case SEARCH_MAILBOX_GLOB:
+ case SEARCH_REAL_UID:
+ case SEARCH_MIMEPART:
+ /* not supported for now */
+ break;
+ case SEARCH_NIL:
+ i_unreached();
+ }
+ return FALSE;
+}
+
+static bool
+imapc_build_search_query_args(struct imapc_mailbox *mbox,
+ const struct mail_search_arg *args,
+ bool parent_or, string_t *str)
+{
+ const struct mail_search_arg *arg;
+
+ for (arg = args; arg != NULL; arg = arg->next) {
+ if (parent_or && arg->next != NULL)
+ str_append(str, "OR ");
+ if (!imapc_build_search_query_arg(mbox, arg, str))
+ return FALSE;
+ str_append_c(str, ' ');
+ }
+ str_truncate(str, str_len(str)-1);
+ return TRUE;
+}
+
+static bool imapc_build_search_query(struct imapc_mailbox *mbox,
+ const struct mail_search_args *args,
+ const char **query_r)
+{
+ string_t *str = t_str_new(128);
+
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_SEARCH)) {
+ /* SEARCH command passthrough not enabled */
+ return FALSE;
+ }
+ if (imapc_search_is_fast_local(args->args))
+ return FALSE;
+
+ if ((mbox->capabilities & IMAPC_CAPABILITY_ESEARCH) != 0)
+ str_append(str, "SEARCH RETURN (ALL) ");
+ else
+ str_append(str, "UID SEARCH ");
+ if (!imapc_build_search_query_args(mbox, args->args, FALSE, str))
+ return FALSE;
+ *query_r = str_c(str);
+ return TRUE;
+}
+
+static void imapc_search_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct mail_search_context *ctx = context;
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(ctx->transaction->box);
+ struct imapc_search_context *ictx = IMAPC_SEARCHCTX(ctx);
+ i_assert(ictx != NULL);
+
+ ictx->finished = TRUE;
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ seq_range_array_iter_init(&ictx->iter, &ictx->rseqs);
+ ictx->success = TRUE;
+ } else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED) {
+ mail_storage_set_internal_error(mbox->box.storage);
+ } else {
+ mailbox_set_critical(&mbox->box,
+ "imapc: Command failed: %s", reply->text_full);
+ }
+ imapc_client_stop(mbox->storage->client->client);
+}
+
+struct mail_search_context *
+imapc_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 imapc_mailbox *mbox = IMAPC_MAILBOX(t->box);
+ struct mail_search_context *ctx;
+ struct imapc_search_context *ictx;
+ struct imapc_command *cmd;
+ const char *search_query;
+
+ ctx = index_storage_search_init(t, args, sort_program,
+ wanted_fields, wanted_headers);
+
+ if (!imapc_build_search_query(mbox, args, &search_query)) {
+ /* can't optimize this with SEARCH */
+ return ctx;
+ }
+
+ ictx = i_new(struct imapc_search_context, 1);
+ i_array_init(&ictx->rseqs, 64);
+ MODULE_CONTEXT_SET(ctx, imapc_storage_module, ictx);
+
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_search_callback, ctx);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, search_query);
+
+ i_assert(mbox->search_ctx == NULL);
+ mbox->search_ctx = ictx;
+ while (!ictx->finished)
+ imapc_client_run(mbox->storage->client->client);
+ mbox->search_ctx = NULL;
+ return ctx;
+}
+
+static void imapc_search_set_matches(struct mail_search_arg *args)
+{
+ for (; args != NULL; args = args->next) {
+ if (args->type == SEARCH_OR ||
+ args->type == SEARCH_SUB)
+ imapc_search_set_matches(args->value.subargs);
+ args->match_always = TRUE;
+ args->result = 1;
+ }
+}
+
+bool imapc_search_next_update_seq(struct mail_search_context *ctx)
+{
+ struct imapc_search_context *ictx = IMAPC_SEARCHCTX(ctx);
+
+ if (ictx == NULL || !ictx->success)
+ return index_storage_search_next_update_seq(ctx);
+
+ if (!seq_range_array_iter_nth(&ictx->iter, ictx->n++, &ctx->seq))
+ return FALSE;
+ ctx->progress_cur = ctx->seq;
+
+ imapc_search_set_matches(ctx->args->args);
+ return TRUE;
+}
+
+int imapc_search_deinit(struct mail_search_context *ctx)
+{
+ struct imapc_search_context *ictx = IMAPC_SEARCHCTX(ctx);
+
+ if (ictx != NULL) {
+ array_free(&ictx->rseqs);
+ i_free(ictx);
+ }
+ return index_storage_search_deinit(ctx);
+}
+
+void imapc_search_reply_search(const struct imap_arg *args,
+ struct imapc_mailbox *mbox)
+{
+ struct imapc_msgmap *msgmap =
+ imapc_client_mailbox_get_msgmap(mbox->client_box);
+ const char *atom;
+ uint32_t uid, rseq;
+
+ if (mbox->search_ctx == NULL) {
+ i_error("Unexpected SEARCH reply");
+ return;
+ }
+
+ /* we're doing UID SEARCH, so need to convert UIDs to sequences */
+ for (unsigned int i = 0; args[i].type != IMAP_ARG_EOL; i++) {
+ if (!imap_arg_get_atom(&args[i], &atom) ||
+ str_to_uint32(atom, &uid) < 0 || uid == 0) {
+ i_error("Invalid SEARCH reply");
+ break;
+ }
+ if (imapc_msgmap_uid_to_rseq(msgmap, uid, &rseq))
+ seq_range_array_add(&mbox->search_ctx->rseqs, rseq);
+ }
+}
+
+void imapc_search_reply_esearch(const struct imap_arg *args,
+ struct imapc_mailbox *mbox)
+{
+ const char *atom;
+
+ if (mbox->search_ctx == NULL) {
+ i_error("Unexpected ESEARCH reply");
+ return;
+ }
+
+ /* It should contain ALL <seqset> or nonexistent if nothing matched */
+ if (args[0].type != IMAP_ARG_EOL &&
+ (!imap_arg_atom_equals(&args[0], "ALL") ||
+ !imap_arg_get_atom(&args[1], &atom) ||
+ imap_seq_set_nostar_parse(atom, &mbox->search_ctx->rseqs) < 0))
+ i_error("Invalid ESEARCH reply");
+}
diff --git a/src/lib-storage/index/imapc/imapc-search.h b/src/lib-storage/index/imapc/imapc-search.h
new file mode 100644
index 0000000..0966569
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-search.h
@@ -0,0 +1,18 @@
+#ifndef IMAPC_SEARCH_H
+#define IMAPC_SEARCH_H
+
+struct mail_search_context *
+imapc_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);
+bool imapc_search_next_update_seq(struct mail_search_context *ctx);
+int imapc_search_deinit(struct mail_search_context *ctx);
+
+void imapc_search_reply_search(const struct imap_arg *args,
+ struct imapc_mailbox *mbox);
+void imapc_search_reply_esearch(const struct imap_arg *args,
+ struct imapc_mailbox *mbox);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-settings.c b/src/lib-storage/index/imapc/imapc-settings.c
new file mode 100644
index 0000000..6183f8f
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-settings.c
@@ -0,0 +1,173 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "settings-parser.h"
+#include "mail-storage-settings.h"
+#include "imapc-settings.h"
+
+#include <stddef.h>
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct imapc_settings)
+
+static bool imapc_settings_check(void *_set, pool_t pool, const char **error_r);
+
+static const struct setting_define imapc_setting_defines[] = {
+ DEF(STR, imapc_host),
+ DEF(IN_PORT, imapc_port),
+
+ DEF(STR_VARS, imapc_user),
+ DEF(STR_VARS, imapc_master_user),
+ DEF(STR, imapc_password),
+ DEF(STR, imapc_sasl_mechanisms),
+
+ DEF(ENUM, imapc_ssl),
+ DEF(BOOL, imapc_ssl_verify),
+
+ DEF(STR, imapc_features),
+ DEF(STR, imapc_rawlog_dir),
+ DEF(STR, imapc_list_prefix),
+ DEF(TIME, imapc_cmd_timeout),
+ DEF(TIME, imapc_max_idle_time),
+ DEF(UINT, imapc_connection_retry_count),
+ DEF(TIME_MSECS, imapc_connection_retry_interval),
+ DEF(SIZE, imapc_max_line_length),
+
+ DEF(STR, pop3_deleted_flag),
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct imapc_settings imapc_default_settings = {
+ .imapc_host = "",
+ .imapc_port = 143,
+
+ .imapc_user = "",
+ .imapc_master_user = "",
+ .imapc_password = "",
+ .imapc_sasl_mechanisms = "",
+
+ .imapc_ssl = "no:imaps:starttls",
+ .imapc_ssl_verify = TRUE,
+
+ .imapc_features = "",
+ .imapc_rawlog_dir = "",
+ .imapc_list_prefix = "",
+ .imapc_cmd_timeout = 5*60,
+ .imapc_max_idle_time = 60*29,
+ .imapc_connection_retry_count = 1,
+ .imapc_connection_retry_interval = 1000,
+ .imapc_max_line_length = 0,
+
+ .pop3_deleted_flag = ""
+};
+
+static const struct setting_parser_info imapc_setting_parser_info = {
+ .module_name = "imapc",
+ .defines = imapc_setting_defines,
+ .defaults = &imapc_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct imapc_settings),
+
+ .parent_offset = SIZE_MAX,
+ .parent = &mail_user_setting_parser_info,
+
+ .check_func = imapc_settings_check
+};
+
+const struct setting_parser_info *imapc_get_setting_parser_info(void)
+{
+ return &imapc_setting_parser_info;
+}
+
+/* <settings checks> */
+struct imapc_feature_list {
+ const char *name;
+ enum imapc_features num;
+};
+
+static const struct imapc_feature_list imapc_feature_list[] = {
+ { "rfc822.size", IMAPC_FEATURE_RFC822_SIZE },
+ { "guid-forced", IMAPC_FEATURE_GUID_FORCED },
+ { "fetch-headers", IMAPC_FEATURE_FETCH_HEADERS },
+ { "gmail-migration", IMAPC_FEATURE_GMAIL_MIGRATION },
+ { "search", IMAPC_FEATURE_SEARCH },
+ { "zimbra-workarounds", IMAPC_FEATURE_ZIMBRA_WORKAROUNDS },
+ { "no-examine", IMAPC_FEATURE_NO_EXAMINE },
+ { "proxyauth", IMAPC_FEATURE_PROXYAUTH },
+ { "fetch-msn-workarounds", IMAPC_FEATURE_FETCH_MSN_WORKAROUNDS },
+ { "fetch-fix-broken-mails", IMAPC_FEATURE_FETCH_FIX_BROKEN_MAILS },
+ { "modseq", IMAPC_FEATURE_MODSEQ },
+ { "delay-login", IMAPC_FEATURE_DELAY_LOGIN },
+ { "fetch-bodystructure", IMAPC_FEATURE_FETCH_BODYSTRUCTURE },
+ { "send-id", IMAPC_FEATURE_SEND_ID },
+ { "fetch-empty-is-expunged", IMAPC_FEATURE_FETCH_EMPTY_IS_EXPUNGED },
+ { "no-msn-updates", IMAPC_FEATURE_NO_MSN_UPDATES },
+ { "acl", IMAPC_FEATURE_ACL },
+ { NULL, 0 }
+};
+
+static int
+imapc_settings_parse_throttle(struct imapc_settings *set,
+ const char *throttle_str, const char **error_r)
+{
+ const char *const *tmp;
+
+ tmp = t_strsplit(throttle_str, ":");
+ if (str_array_length(tmp) != 3 ||
+ str_to_uint(tmp[0], &set->throttle_init_msecs) < 0 ||
+ str_to_uint(tmp[1], &set->throttle_max_msecs) < 0 ||
+ str_to_uint(tmp[2], &set->throttle_shrink_min_msecs) < 0) {
+ *error_r = "imapc_features: Invalid throttle settings";
+ return -1;
+ }
+ return 0;
+}
+
+static int
+imapc_settings_parse_features(struct imapc_settings *set,
+ const char **error_r)
+{
+ enum imapc_features features = 0;
+ const struct imapc_feature_list *list;
+ const char *const *str;
+
+ str = t_strsplit_spaces(set->imapc_features, " ,");
+ for (; *str != NULL; str++) {
+ list = imapc_feature_list;
+ for (; list->name != NULL; list++) {
+ if (strcasecmp(*str, list->name) == 0) {
+ features |= list->num;
+ break;
+ }
+ }
+ if (strncasecmp(*str, "throttle:", 9) == 0) {
+ if (imapc_settings_parse_throttle(set, *str + 9, error_r) < 0)
+ return -1;
+ continue;
+ }
+ if (list->name == NULL) {
+ *error_r = t_strdup_printf("imapc_features: "
+ "Unknown feature: %s", *str);
+ return -1;
+ }
+ }
+ set->parsed_features = features;
+ return 0;
+}
+
+static bool imapc_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+ const char **error_r)
+{
+ struct imapc_settings *set = _set;
+
+ if (set->imapc_max_idle_time == 0) {
+ *error_r = "imapc_max_idle_time must not be 0";
+ return FALSE;
+ }
+ if (imapc_settings_parse_features(set, error_r) < 0)
+ return FALSE;
+ return TRUE;
+}
diff --git a/src/lib-storage/index/imapc/imapc-settings.h b/src/lib-storage/index/imapc/imapc-settings.h
new file mode 100644
index 0000000..e4c1da1
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-settings.h
@@ -0,0 +1,63 @@
+#ifndef IMAPC_SETTINGS_H
+#define IMAPC_SETTINGS_H
+
+#include "net.h"
+
+/* <settings checks> */
+enum imapc_features {
+ IMAPC_FEATURE_RFC822_SIZE = 0x01,
+ IMAPC_FEATURE_GUID_FORCED = 0x02,
+ IMAPC_FEATURE_FETCH_HEADERS = 0x04,
+ IMAPC_FEATURE_GMAIL_MIGRATION = 0x08,
+ IMAPC_FEATURE_SEARCH = 0x10,
+ IMAPC_FEATURE_ZIMBRA_WORKAROUNDS = 0x20,
+ IMAPC_FEATURE_NO_EXAMINE = 0x40,
+ IMAPC_FEATURE_PROXYAUTH = 0x80,
+ IMAPC_FEATURE_FETCH_MSN_WORKAROUNDS = 0x100,
+ IMAPC_FEATURE_FETCH_FIX_BROKEN_MAILS = 0x200,
+ IMAPC_FEATURE_MODSEQ = 0x400,
+ IMAPC_FEATURE_DELAY_LOGIN = 0x800,
+ IMAPC_FEATURE_FETCH_BODYSTRUCTURE = 0x1000,
+ IMAPC_FEATURE_SEND_ID = 0x2000,
+ IMAPC_FEATURE_FETCH_EMPTY_IS_EXPUNGED = 0x4000,
+ IMAPC_FEATURE_NO_MSN_UPDATES = 0x8000,
+ IMAPC_FEATURE_ACL = 0x10000,
+};
+/* </settings checks> */
+
+/*
+ * NOTE: Any additions here should be reflected in imapc_storage_create's
+ * serialization of settings.
+ */
+struct imapc_settings {
+ const char *imapc_host;
+ in_port_t imapc_port;
+
+ const char *imapc_user;
+ const char *imapc_master_user;
+ const char *imapc_password;
+ const char *imapc_sasl_mechanisms;
+
+ const char *imapc_ssl;
+ bool imapc_ssl_verify;
+
+ const char *imapc_features;
+ const char *imapc_rawlog_dir;
+ const char *imapc_list_prefix;
+ unsigned int imapc_cmd_timeout;
+ unsigned int imapc_max_idle_time;
+ unsigned int imapc_connection_retry_count;
+ unsigned int imapc_connection_retry_interval;
+ uoff_t imapc_max_line_length;
+
+ const char *pop3_deleted_flag;
+
+ enum imapc_features parsed_features;
+ unsigned int throttle_init_msecs;
+ unsigned int throttle_max_msecs;
+ unsigned int throttle_shrink_min_msecs;
+};
+
+const struct setting_parser_info *imapc_get_setting_parser_info(void);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-storage.c b/src/lib-storage/index/imapc/imapc-storage.c
new file mode 100644
index 0000000..2c9fcdf
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-storage.c
@@ -0,0 +1,1353 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "imap-arg.h"
+#include "imap-resp-code.h"
+#include "mailbox-tree.h"
+#include "imapc-connection.h"
+#include "imapc-msgmap.h"
+#include "imapc-mail.h"
+#include "imapc-list.h"
+#include "imapc-search.h"
+#include "imapc-sync.h"
+#include "imapc-settings.h"
+#include "imapc-storage.h"
+
+#define DNS_CLIENT_SOCKET_NAME "dns-client"
+
+struct imapc_open_context {
+ struct imapc_mailbox *mbox;
+ int ret;
+};
+
+struct imapc_resp_code_map {
+ const char *code;
+ enum mail_error error;
+};
+
+extern struct mail_storage imapc_storage;
+extern struct mailbox imapc_mailbox;
+
+static struct event_category event_category_imapc = {
+ .name = "imapc",
+ .parent = &event_category_storage,
+};
+
+static struct imapc_resp_code_map imapc_resp_code_map[] = {
+ { IMAP_RESP_CODE_UNAVAILABLE, MAIL_ERROR_TEMP },
+ { IMAP_RESP_CODE_AUTHFAILED, MAIL_ERROR_PERM },
+ { IMAP_RESP_CODE_AUTHZFAILED, MAIL_ERROR_PERM },
+ { IMAP_RESP_CODE_EXPIRED, MAIL_ERROR_PERM },
+ { IMAP_RESP_CODE_PRIVACYREQUIRED, MAIL_ERROR_PERM },
+ { IMAP_RESP_CODE_CONTACTADMIN, MAIL_ERROR_PERM },
+ { IMAP_RESP_CODE_NOPERM, MAIL_ERROR_PERM },
+ { IMAP_RESP_CODE_INUSE, MAIL_ERROR_INUSE },
+ { IMAP_RESP_CODE_EXPUNGEISSUED, MAIL_ERROR_EXPUNGED },
+ { IMAP_RESP_CODE_CORRUPTION, MAIL_ERROR_TEMP },
+ { IMAP_RESP_CODE_SERVERBUG, MAIL_ERROR_TEMP },
+ /* { IMAP_RESP_CODE_CLIENTBUG, 0 }, */
+ { IMAP_RESP_CODE_CANNOT, MAIL_ERROR_NOTPOSSIBLE },
+ { IMAP_RESP_CODE_LIMIT, MAIL_ERROR_LIMIT },
+ { IMAP_RESP_CODE_OVERQUOTA, MAIL_ERROR_NOQUOTA },
+ { IMAP_RESP_CODE_ALREADYEXISTS, MAIL_ERROR_EXISTS },
+ { IMAP_RESP_CODE_NONEXISTENT, MAIL_ERROR_NOTFOUND }
+};
+
+static void imapc_untagged_status(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client);
+static void imapc_untagged_namespace(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client);
+static int imapc_mailbox_run_status(struct mailbox *box,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r);
+
+bool imapc_resp_text_code_parse(const char *str, enum mail_error *error_r)
+{
+ unsigned int i;
+
+ if (str == NULL)
+ return FALSE;
+
+ for (i = 0; i < N_ELEMENTS(imapc_resp_code_map); i++) {
+ if (strcmp(imapc_resp_code_map[i].code, str) == 0) {
+ *error_r = imapc_resp_code_map[i].error;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+bool imapc_mail_error_to_resp_text_code(enum mail_error error, const char **str_r)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(imapc_resp_code_map); i++) {
+ if (imapc_resp_code_map[i].error == error) {
+ *str_r = imapc_resp_code_map[i].code;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+bool imapc_mailbox_has_modseqs(struct imapc_mailbox *mbox)
+{
+ return (mbox->capabilities & (IMAPC_CAPABILITY_CONDSTORE |
+ IMAPC_CAPABILITY_QRESYNC)) != 0 &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_MODSEQ);
+}
+
+static struct mail_storage *imapc_storage_alloc(void)
+{
+ struct imapc_storage *storage;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imapc storage", 2048);
+ storage = p_new(pool, struct imapc_storage, 1);
+ storage->storage = imapc_storage;
+ storage->storage.pool = pool;
+ storage->root_ioloop = current_ioloop;
+ return &storage->storage;
+}
+
+void imapc_copy_error_from_reply(struct imapc_storage *storage,
+ enum mail_error default_error,
+ const struct imapc_command_reply *reply)
+{
+ enum mail_error error;
+
+ if (imapc_resp_text_code_parse(reply->resp_text_key, &error)) {
+ mail_storage_set_error(&storage->storage, error,
+ reply->text_without_resp);
+ } else {
+ mail_storage_set_error(&storage->storage, default_error,
+ reply->text_without_resp);
+ }
+}
+
+void imapc_simple_context_init(struct imapc_simple_context *sctx,
+ struct imapc_storage_client *client)
+{
+ i_zero(sctx);
+ sctx->client = client;
+ sctx->ret = -2;
+}
+
+void imapc_simple_run(struct imapc_simple_context *sctx,
+ struct imapc_command **cmd)
+{
+ if (imapc_storage_client_handle_auth_failure(sctx->client)) {
+ imapc_command_abort(cmd);
+ imapc_client_logout(sctx->client->client);
+ sctx->ret = -1;
+ }
+ *cmd = NULL;
+ while (sctx->ret == -2)
+ imapc_client_run(sctx->client->client);
+}
+
+void imapc_mailbox_run(struct imapc_mailbox *mbox)
+{
+ imapc_mail_fetch_flush(mbox);
+ imapc_mailbox_run_nofetch(mbox);
+}
+
+void imapc_mailbox_run_nofetch(struct imapc_mailbox *mbox)
+{
+ do {
+ imapc_client_run(mbox->storage->client->client);
+ } while (mbox->storage->reopen_count > 0 ||
+ mbox->state_fetching_uid1);
+}
+
+void imapc_simple_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_simple_context *ctx = context;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ ctx->ret = 0;
+ else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_copy_error_from_reply(ctx->client->_storage,
+ MAIL_ERROR_PARAMS, reply);
+ ctx->ret = -1;
+ } else if (imapc_storage_client_handle_auth_failure(ctx->client)) {
+ ctx->ret = -1;
+ } else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED) {
+ mail_storage_set_internal_error(&ctx->client->_storage->storage);
+ ctx->ret = -1;
+ } else {
+ mail_storage_set_critical(&ctx->client->_storage->storage,
+ "imapc: Command failed: %s", reply->text_full);
+ ctx->ret = -1;
+ }
+ imapc_client_stop(ctx->client->client);
+}
+
+void imapc_mailbox_noop(struct imapc_mailbox *mbox)
+{
+ struct imapc_command *cmd;
+ struct imapc_simple_context sctx;
+
+ if (mbox->client_box == NULL) {
+ /* mailbox opening hasn't finished yet */
+ return;
+ }
+
+ imapc_simple_context_init(&sctx, mbox->storage->client);
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_simple_callback, &sctx);
+ imapc_command_send(cmd, "NOOP");
+ imapc_simple_run(&sctx, &cmd);
+}
+
+static void
+imapc_storage_client_untagged_cb(const struct imapc_untagged_reply *reply,
+ void *context)
+{
+ struct imapc_storage_client *client = context;
+ struct imapc_mailbox *mbox = reply->untagged_box_context;
+ const struct imapc_storage_event_callback *cb;
+ const struct imapc_mailbox_event_callback *mcb;
+
+ array_foreach(&client->untagged_callbacks, cb) {
+ if (strcasecmp(reply->name, cb->name) == 0)
+ cb->callback(reply, client);
+ }
+
+ if (mbox == NULL)
+ return;
+
+ array_foreach(&mbox->untagged_callbacks, mcb) {
+ if (strcasecmp(reply->name, mcb->name) == 0)
+ mcb->callback(reply, mbox);
+ }
+
+ if (reply->resp_text_key != NULL) {
+ array_foreach(&mbox->resp_text_callbacks, mcb) {
+ if (strcasecmp(reply->resp_text_key, mcb->name) == 0)
+ mcb->callback(reply, mbox);
+ }
+ }
+}
+
+static void
+imapc_storage_client_login_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_storage_client *client = context;
+
+ client->auth_returned = TRUE;
+ imapc_client_stop(client->client);
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ return;
+ if (client->destroying &&
+ reply->state == IMAPC_COMMAND_STATE_DISCONNECTED) {
+ /* user's work was finished before imapc login finished -
+ it's not an error */
+ return;
+ }
+
+ client->auth_failed_state = reply->state;
+ client->auth_failed_reason = i_strdup(reply->text_full);
+ if (!imapc_storage_client_handle_auth_failure(client))
+ i_unreached();
+}
+
+bool imapc_storage_client_handle_auth_failure(struct imapc_storage_client *client)
+{
+ if (client->auth_failed_state == IMAPC_COMMAND_STATE_OK)
+ return FALSE;
+
+ /* We need to set the error to either storage or to list, depending on
+ whether the caller is from mail-storage.h API or mailbox-list.h API.
+ We don't know here what the caller is though, so just set the error
+ to both of them. */
+ if (client->_storage != NULL) {
+ if (client->auth_failed_state == IMAPC_COMMAND_STATE_DISCONNECTED)
+ mail_storage_set_internal_error(&client->_storage->storage);
+ else {
+ mail_storage_set_error(&client->_storage->storage,
+ MAIL_ERROR_PERM, client->auth_failed_reason);
+ }
+ }
+ if (client->_list != NULL) {
+ if (client->auth_failed_state == IMAPC_COMMAND_STATE_DISCONNECTED)
+ mailbox_list_set_internal_error(&client->_list->list);
+ else {
+ mailbox_list_set_error(&client->_list->list,
+ MAIL_ERROR_PERM, client->auth_failed_reason);
+ }
+ }
+ return TRUE;
+}
+
+static void imapc_storage_client_login(struct imapc_storage_client *client,
+ struct mail_user *user, const char *host)
+{
+ imapc_client_login(client->client);
+ if (!user->namespaces_created) {
+ /* we're still initializing the user. wait for the
+ login to finish, so we can fail the user creation
+ if it fails. */
+ while (!client->auth_returned)
+ imapc_client_run(client->client);
+ if (imapc_storage_client_handle_auth_failure(client)) {
+ user->error = p_strdup_printf(user->pool,
+ "imapc: Login to %s failed: %s",
+ host, client->auth_failed_reason);
+ }
+ }
+}
+
+int imapc_storage_client_create(struct mail_namespace *ns,
+ const struct imapc_settings *imapc_set,
+ const struct mail_storage_settings *mail_set,
+ struct imapc_storage_client **client_r,
+ const char **error_r)
+{
+ struct imapc_storage_client *client;
+ struct imapc_client_settings set;
+ string_t *str;
+
+ i_zero(&set);
+ set.host = imapc_set->imapc_host;
+ if (*set.host == '\0') {
+ *error_r = "missing imapc_host";
+ return -1;
+ }
+ set.port = imapc_set->imapc_port;
+ if (imapc_set->imapc_user[0] != '\0')
+ set.username = imapc_set->imapc_user;
+ else if (ns->owner != NULL)
+ set.username = ns->owner->username;
+ else
+ set.username = ns->user->username;
+ set.master_user = imapc_set->imapc_master_user;
+ set.password = imapc_set->imapc_password;
+ if (*set.password == '\0') {
+ *error_r = "missing imapc_password";
+ return -1;
+ }
+ set.sasl_mechanisms = imapc_set->imapc_sasl_mechanisms;
+ set.use_proxyauth = (imapc_set->parsed_features & IMAPC_FEATURE_PROXYAUTH) != 0;
+ set.cmd_timeout_msecs = imapc_set->imapc_cmd_timeout * 1000;
+ set.connect_retry_count = imapc_set->imapc_connection_retry_count;
+ set.connect_retry_interval_msecs = imapc_set->imapc_connection_retry_interval;
+ set.max_idle_time = imapc_set->imapc_max_idle_time;
+ set.max_line_length = imapc_set->imapc_max_line_length;
+ set.dns_client_socket_path = *ns->user->set->base_dir == '\0' ? "" :
+ t_strconcat(ns->user->set->base_dir, "/",
+ DNS_CLIENT_SOCKET_NAME, NULL);
+ set.debug = mail_set->mail_debug;
+ set.rawlog_dir = mail_user_home_expand(ns->user,
+ imapc_set->imapc_rawlog_dir);
+ if ((imapc_set->parsed_features & IMAPC_FEATURE_SEND_ID) != 0)
+ set.session_id_prefix = ns->user->session_id;
+
+ str = t_str_new(128);
+ mail_user_set_get_temp_prefix(str, ns->user->set);
+ set.temp_path_prefix = str_c(str);
+
+ mail_user_init_ssl_client_settings(ns->user, &set.ssl_set);
+ if (!imapc_set->imapc_ssl_verify)
+ set.ssl_set.allow_invalid_cert = TRUE;
+
+ if (strcmp(imapc_set->imapc_ssl, "imaps") == 0)
+ set.ssl_mode = IMAPC_CLIENT_SSL_MODE_IMMEDIATE;
+ else if (strcmp(imapc_set->imapc_ssl, "starttls") == 0)
+ set.ssl_mode = IMAPC_CLIENT_SSL_MODE_STARTTLS;
+ else
+ set.ssl_mode = IMAPC_CLIENT_SSL_MODE_NONE;
+
+ set.throttle_set.init_msecs = imapc_set->throttle_init_msecs;
+ set.throttle_set.max_msecs = imapc_set->throttle_max_msecs;
+ set.throttle_set.shrink_min_msecs = imapc_set->throttle_shrink_min_msecs;
+
+ client = i_new(struct imapc_storage_client, 1);
+ client->refcount = 1;
+ i_array_init(&client->untagged_callbacks, 16);
+ /* FIXME: storage->event would be better, but we first get here when
+ creating mailbox_list, and storage doesn't even exist yet. */
+ client->client = imapc_client_init(&set, ns->user->event);
+ imapc_client_register_untagged(client->client,
+ imapc_storage_client_untagged_cb, client);
+
+ imapc_client_set_login_callback(client->client, imapc_storage_client_login_callback, client);
+
+ if ((ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0 &&
+ (imapc_set->parsed_features & IMAPC_FEATURE_DELAY_LOGIN) == 0) {
+ /* start logging in immediately */
+ imapc_storage_client_login(client, ns->user, set.host);
+ }
+
+ *client_r = client;
+ return 0;
+}
+
+void imapc_storage_client_unref(struct imapc_storage_client **_client)
+{
+ struct imapc_storage_client *client = *_client;
+ struct imapc_storage_event_callback *cb;
+
+ *_client = NULL;
+
+ i_assert(client->refcount > 0);
+ if (--client->refcount > 0)
+ return;
+ imapc_client_deinit(&client->client);
+ array_foreach_modifiable(&client->untagged_callbacks, cb)
+ i_free(cb->name);
+ array_free(&client->untagged_callbacks);
+ i_free(client->auth_failed_reason);
+ i_free(client);
+}
+
+static int
+imapc_storage_create(struct mail_storage *_storage,
+ struct mail_namespace *ns,
+ const char **error_r)
+{
+ struct imapc_storage *storage = IMAPC_STORAGE(_storage);
+ struct imapc_mailbox_list *imapc_list = NULL;
+
+ storage->set = mail_namespace_get_driver_settings(ns, _storage);
+
+ /* serialize all the settings */
+ _storage->unique_root_dir = p_strdup_printf(_storage->pool,
+ "%s%s://(%s|%s):%s@%s:%u/%s mechs:%s features:%s "
+ "rawlog:%s cmd_timeout:%u maxidle:%u maxline:%zuu "
+ "pop3delflg:%s root_dir:%s",
+ storage->set->imapc_ssl,
+ storage->set->imapc_ssl_verify ? "(verify)" : "",
+ storage->set->imapc_user,
+ storage->set->imapc_master_user,
+ storage->set->imapc_password,
+ storage->set->imapc_host,
+ storage->set->imapc_port,
+ storage->set->imapc_list_prefix,
+ storage->set->imapc_sasl_mechanisms,
+ storage->set->imapc_features,
+ storage->set->imapc_rawlog_dir,
+ storage->set->imapc_cmd_timeout,
+ storage->set->imapc_max_idle_time,
+ (size_t) storage->set->imapc_max_line_length,
+ storage->set->pop3_deleted_flag,
+ ns->list->set.root_dir);
+
+ if (strcmp(ns->list->name, MAILBOX_LIST_NAME_IMAPC) == 0) {
+ imapc_list = (struct imapc_mailbox_list *)ns->list;
+ storage->client = imapc_list->client;
+ storage->client->refcount++;
+ } else {
+ if (imapc_storage_client_create(ns, storage->set, _storage->set,
+ &storage->client, error_r) < 0)
+ return -1;
+ }
+ storage->client->_storage = storage;
+ p_array_init(&storage->remote_namespaces, _storage->pool, 4);
+ if (IMAPC_HAS_FEATURE(storage, IMAPC_FEATURE_FETCH_BODYSTRUCTURE)) {
+ _storage->nonbody_access_fields |=
+ MAIL_FETCH_IMAP_BODY | MAIL_FETCH_IMAP_BODYSTRUCTURE;
+ }
+
+ imapc_storage_client_register_untagged(storage->client, "STATUS",
+ imapc_untagged_status);
+ imapc_storage_client_register_untagged(storage->client, "NAMESPACE",
+ imapc_untagged_namespace);
+
+ return 0;
+}
+
+static void imapc_storage_destroy(struct mail_storage *_storage)
+{
+ struct imapc_storage *storage = IMAPC_STORAGE(_storage);
+
+ storage->client->destroying = TRUE;
+
+ /* make sure all pending commands are aborted before anything is
+ deinitialized */
+ imapc_client_logout(storage->client->client);
+
+ imapc_storage_client_unref(&storage->client);
+ index_storage_destroy(_storage);
+}
+
+void imapc_storage_client_register_untagged(struct imapc_storage_client *client,
+ const char *name,
+ imapc_storage_callback_t *callback)
+{
+ struct imapc_storage_event_callback *cb;
+
+ cb = array_append_space(&client->untagged_callbacks);
+ cb->name = i_strdup(name);
+ cb->callback = callback;
+}
+
+void imapc_storage_client_unregister_untagged(struct imapc_storage_client *client,
+ const char *name)
+{
+ struct imapc_storage_event_callback *cb;
+ unsigned int idx;
+ array_foreach_modifiable(&client->untagged_callbacks, cb) {
+ if (strcmp(cb->name, name) == 0) {
+ idx = array_foreach_idx(&client->untagged_callbacks, cb);
+ i_free(cb->name);
+ array_delete(&client->untagged_callbacks, idx, 1);
+ return;
+ }
+ }
+ i_unreached();
+}
+
+static void
+imapc_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_IMAPC;
+ set->storage_name_escape_char = IMAPC_LIST_STORAGE_NAME_ESCAPE_CHAR;
+ /* We want to have all imapc mailboxes accessible, so escape them if
+ necessary. */
+ if (set->vname_escape_char == '\0')
+ set->vname_escape_char = IMAPC_LIST_VNAME_ESCAPE_CHAR;
+}
+
+static struct mailbox *
+imapc_mailbox_alloc(struct mail_storage *storage, struct mailbox_list *list,
+ const char *vname, enum mailbox_flags flags)
+{
+ struct imapc_mailbox *mbox;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imapc mailbox", 1024*4);
+ mbox = p_new(pool, struct imapc_mailbox, 1);
+ mbox->box = imapc_mailbox;
+ mbox->box.pool = pool;
+ mbox->box.storage = storage;
+ mbox->box.list = list;
+ mbox->box.mail_vfuncs = &imapc_mail_vfuncs;
+
+ index_storage_mailbox_alloc(&mbox->box, vname, flags, MAIL_INDEX_PREFIX);
+
+ mbox->storage = IMAPC_STORAGE(storage);
+
+ p_array_init(&mbox->untagged_callbacks, pool, 16);
+ p_array_init(&mbox->resp_text_callbacks, pool, 16);
+ p_array_init(&mbox->fetch_requests, pool, 16);
+ p_array_init(&mbox->untagged_fetch_contexts, pool, 16);
+ p_array_init(&mbox->delayed_expunged_uids, pool, 16);
+ p_array_init(&mbox->copy_rollback_expunge_uids, pool, 16);
+ mbox->pending_fetch_cmd = str_new(pool, 128);
+ mbox->pending_copy_cmd = str_new(pool, 128);
+ mbox->prev_mail_cache.fd = -1;
+ imapc_mailbox_register_callbacks(mbox);
+ return &mbox->box;
+}
+
+const char *imapc_mailbox_get_remote_name(struct imapc_mailbox *mbox)
+{
+ struct imapc_mailbox_list *list =
+ container_of(mbox->box.list, struct imapc_mailbox_list, list);
+
+ if (strcmp(mbox->box.list->name, MAILBOX_LIST_NAME_IMAPC) != 0)
+ return mbox->box.name;
+ return imapc_list_storage_to_remote_name(list, mbox->box.name);
+}
+
+static int
+imapc_mailbox_exists(struct mailbox *box, bool auto_boxes,
+ enum mailbox_existence *existence_r)
+{
+ if (auto_boxes && mailbox_is_autocreated(box)) {
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ return 0;
+ }
+
+ if (strcmp(box->list->name, MAILBOX_LIST_NAME_IMAPC) != 0) {
+ if (box->inbox_any)
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ else
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+ return 0;
+ }
+
+ enum mailbox_info_flags flags;
+
+ struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)box->list;
+
+ if (imapc_storage_client_handle_auth_failure(list->client)) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ if (imapc_list_get_mailbox_flags(box->list, box->name, &flags) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ if ((flags & MAILBOX_NONEXISTENT) != 0)
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+ else if ((flags & MAILBOX_NOSELECT) != 0)
+ *existence_r = MAILBOX_EXISTENCE_NOSELECT;
+ else
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ return 0;
+}
+
+static bool imapc_mailbox_want_examine(struct imapc_mailbox *mbox)
+{
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_NO_EXAMINE)) {
+ /* mainly a Courier-workaround: With POP3-only Maildir that
+ doesn't have UIDVALIDITY set, EXAMINE won't generate a
+ permanent UIDVALIDITY while SELECT will. */
+ return FALSE;
+ }
+ return (mbox->box.flags & MAILBOX_FLAG_DROP_RECENT) == 0 &&
+ ((mbox->box.flags & MAILBOX_FLAG_READONLY) != 0 ||
+ (mbox->box.flags & MAILBOX_FLAG_SAVEONLY) != 0);
+}
+
+static bool
+imapc_mailbox_verify_select(struct imapc_mailbox *mbox, const char **error_r)
+{
+ if (!mbox->exists_received)
+ *error_r = "EXISTS not received";
+ else if (mbox->sync_uid_validity == 0)
+ *error_r = "UIDVALIDITY not received";
+ else
+ return TRUE;
+ return FALSE;
+}
+
+static void
+imapc_mailbox_reopen_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_mailbox *mbox = context;
+ const char *errmsg;
+
+ i_assert(mbox->storage->reopen_count > 0);
+ mbox->storage->reopen_count--;
+ mbox->selecting = FALSE;
+ if (reply->state != IMAPC_COMMAND_STATE_OK)
+ errmsg = reply->text_full;
+ else if (imapc_mailbox_verify_select(mbox, &errmsg)) {
+ imap_mailbox_select_finish(mbox);
+ errmsg = NULL;
+ }
+
+ if (errmsg != NULL) {
+ imapc_client_mailbox_reconnect(mbox->client_box,
+ t_strdup_printf("Reopening mailbox '%s' failed: %s",
+ mbox->box.name, errmsg));
+ }
+
+ imapc_client_stop(mbox->storage->client->client);
+}
+
+static void imapc_mailbox_reopen(void *context)
+{
+ struct imapc_mailbox *mbox = context;
+ struct imapc_command *cmd;
+
+ /* we're reconnecting and need to reopen the mailbox */
+ mbox->prev_skipped_rseq = 0;
+ mbox->prev_skipped_uid = 0;
+ imapc_msgmap_reset(imapc_client_mailbox_get_msgmap(mbox->client_box));
+
+ if (mbox->selecting) {
+ /* We reconnected during the initial SELECT/EXAMINE. It'll be
+ automatically resent by lib-imap-client, so we don't need to
+ send it again here. */
+ i_assert(!mbox->initial_sync_done);
+ return;
+ }
+ if (!mbox->initial_sync_done) {
+ /* Initial FETCH 1:* didn't fully succeed. We're reconnecting
+ and lib-imap-client is automatically resending it. But we
+ need to reset the sync_next_* state so that if any of the
+ mails are now expunged we won't get confused and crash. */
+ mbox->sync_next_lseq = 1;
+ mbox->sync_next_rseq = 1;
+ }
+
+ mbox->state_fetched_success = FALSE;
+ mbox->initial_sync_done = FALSE;
+ mbox->selecting = TRUE;
+ mbox->selected = FALSE;
+ mbox->exists_received = FALSE;
+
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_mailbox_reopen_callback, mbox);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT);
+ if (imapc_mailbox_want_examine(mbox)) {
+ imapc_command_sendf(cmd, "EXAMINE %s",
+ imapc_mailbox_get_remote_name(mbox));
+ } else {
+ imapc_command_sendf(cmd, "SELECT %s",
+ imapc_mailbox_get_remote_name(mbox));
+ }
+ mbox->storage->reopen_count++;
+}
+
+static void
+imapc_mailbox_open_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_open_context *ctx = context;
+ const char *error;
+
+ ctx->mbox->selecting = FALSE;
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ if (!imapc_mailbox_verify_select(ctx->mbox, &error)) {
+ mailbox_set_critical(&ctx->mbox->box,
+ "imapc: Opening mailbox failed: %s", error);
+ ctx->ret = -1;
+ } else {
+ imap_mailbox_select_finish(ctx->mbox);
+ ctx->ret = 0;
+ }
+ } else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ /* Unless the remote IMAP server supports sending
+ resp-text-code, we don't know if the NO reply is because
+ the mailbox doesn't exist or because of some internal error.
+ We'll default to assuming it doesn't exist, so e.g.
+ mailbox { auto=create } will auto-create missing mailboxes.
+ However, INBOX is a special mailbox, which is always
+ autocreated if it doesn't exist. This is true in both the
+ local Dovecot and the remote IMAP server. This means that
+ there's no point in trying to send CREATE INBOX to the
+ remote server. We'll avoid that by defaulting to temporary
+ failure with INBOX. */
+ enum mail_error default_error =
+ ctx->mbox->box.inbox_any ?
+ MAIL_ERROR_TEMP : MAIL_ERROR_NOTFOUND;
+ imapc_copy_error_from_reply(ctx->mbox->storage,
+ default_error, reply);
+ ctx->ret = -1;
+ } else if (imapc_storage_client_handle_auth_failure(ctx->mbox->storage->client)) {
+ ctx->ret = -1;
+ } else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED) {
+ ctx->ret = -1;
+ mail_storage_set_internal_error(ctx->mbox->box.storage);
+ } else {
+ mailbox_set_critical(&ctx->mbox->box,
+ "imapc: Opening mailbox failed: %s", reply->text_full);
+ ctx->ret = -1;
+ }
+ imapc_client_stop(ctx->mbox->storage->client->client);
+}
+
+static int imapc_mailbox_get_capabilities(struct imapc_mailbox *mbox)
+{
+ /* If authentication failed, don't check again. */
+ if (imapc_storage_client_handle_auth_failure(mbox->storage->client))
+ return -1;
+
+ return imapc_client_get_capabilities(mbox->storage->client->client,
+ &mbox->capabilities);
+
+}
+
+static void imapc_mailbox_get_extensions(struct imapc_mailbox *mbox)
+{
+ if (mbox->guid_fetch_field_name == NULL) {
+ /* see if we can get message GUIDs somehow */
+ if ((mbox->capabilities & IMAPC_CAPABILITY_X_GM_EXT_1) != 0) {
+ /* GMail */
+ mbox->guid_fetch_field_name = "X-GM-MSGID";
+ }
+ }
+}
+
+int imapc_mailbox_select(struct imapc_mailbox *mbox)
+{
+ struct imapc_command *cmd;
+ struct imapc_open_context ctx;
+
+ i_assert(mbox->client_box == NULL);
+
+ if (imapc_mailbox_get_capabilities(mbox) < 0)
+ return -1;
+
+ if (imapc_mailbox_has_modseqs(mbox)) {
+ if (!array_is_created(&mbox->rseq_modseqs))
+ i_array_init(&mbox->rseq_modseqs, 32);
+ else
+ array_clear(&mbox->rseq_modseqs);
+ }
+
+ mbox->client_box =
+ imapc_client_mailbox_open(mbox->storage->client->client, mbox);
+ imapc_client_mailbox_set_reopen_cb(mbox->client_box,
+ imapc_mailbox_reopen, mbox);
+
+ imapc_mailbox_get_extensions(mbox);
+
+ mbox->selecting = TRUE;
+ mbox->exists_received = FALSE;
+ ctx.mbox = mbox;
+ ctx.ret = -2;
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_mailbox_open_callback, &ctx);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT |
+ IMAPC_COMMAND_FLAG_RETRIABLE);
+ if (imapc_mailbox_want_examine(mbox)) {
+ imapc_command_sendf(cmd, "EXAMINE %s",
+ imapc_mailbox_get_remote_name(mbox));
+ } else {
+ imapc_command_sendf(cmd, "SELECT %s",
+ imapc_mailbox_get_remote_name(mbox));
+ }
+
+ while (ctx.ret == -2 || mbox->state_fetching_uid1)
+ imapc_mailbox_run(mbox);
+ if (!mbox->state_fetched_success)
+ ctx.ret = -1;
+ return ctx.ret;
+}
+
+static int imapc_mailbox_open(struct mailbox *box)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+
+ if (index_storage_mailbox_open(box, FALSE) < 0)
+ return -1;
+
+ if (box->deleting || (box->flags & MAILBOX_FLAG_SAVEONLY) != 0) {
+ /* We don't actually want to SELECT the mailbox. */
+ return 0;
+ }
+
+ if (*box->name == '\0' &&
+ (box->list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) {
+ /* trying to open INBOX as the namespace prefix.
+ Don't allow this. */
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ "Mailbox isn't selectable");
+ mailbox_close(box);
+ return -1;
+ }
+
+ if (imapc_mailbox_select(mbox) < 0) {
+ mailbox_close(box);
+ return -1;
+ }
+ return 0;
+}
+
+void imapc_mail_cache_free(struct imapc_mail_cache *cache)
+{
+ i_close_fd(&cache->fd);
+ buffer_free(&cache->buf);
+ cache->uid = 0;
+}
+
+static void imapc_mailbox_close(struct mailbox *box)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+ bool changes;
+
+ (void)imapc_mailbox_commit_delayed_trans(mbox, FALSE, &changes);
+ imapc_mail_fetch_flush(mbox);
+
+ /* Arriving here we may have fetch contexts still unprocessed,
+ if there have been no mailbox_sync() after receiving the untagged replies.
+ Losing these changes isn't a problem, since the same changes will be found
+ out after connecting to the server the next time. */
+ struct imapc_untagged_fetch_ctx *untagged_fetch_context;
+ array_foreach_elem(&mbox->untagged_fetch_contexts, untagged_fetch_context)
+ imapc_untagged_fetch_ctx_free(&untagged_fetch_context);
+ array_clear(&mbox->untagged_fetch_contexts);
+
+ if (mbox->client_box != NULL)
+ imapc_client_mailbox_close(&mbox->client_box);
+ if (array_is_created(&mbox->rseq_modseqs))
+ array_free(&mbox->rseq_modseqs);
+ if (mbox->sync_view != NULL)
+ mail_index_view_close(&mbox->sync_view);
+ timeout_remove(&mbox->to_idle_delay);
+ timeout_remove(&mbox->to_idle_check);
+ imapc_mail_cache_free(&mbox->prev_mail_cache);
+ index_storage_mailbox_close(box);
+}
+
+static int
+imapc_mailbox_create(struct mailbox *box,
+ const struct mailbox_update *update ATTR_UNUSED,
+ bool directory)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+ struct imapc_command *cmd;
+ struct imapc_simple_context sctx;
+ const char *remote_name = imapc_mailbox_get_remote_name(mbox);
+
+ if (!directory)
+ ;
+ else if (strcmp(box->list->name, MAILBOX_LIST_NAME_IMAPC) == 0) {
+ struct imapc_mailbox_list *imapc_list =
+ (struct imapc_mailbox_list *)box->list;
+ remote_name = t_strdup_printf("%s%c", remote_name,
+ imapc_list->root_sep);
+ } else {
+ remote_name = t_strdup_printf("%s%c", remote_name,
+ mailbox_list_get_hierarchy_sep(box->list));
+ }
+ imapc_simple_context_init(&sctx, mbox->storage->client);
+ cmd = imapc_client_cmd(mbox->storage->client->client,
+ imapc_simple_callback, &sctx);
+ imapc_command_sendf(cmd, "CREATE %s", remote_name);
+ imapc_simple_run(&sctx, &cmd);
+ return sctx.ret;
+}
+
+static int imapc_mailbox_update(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ if (!guid_128_is_empty(update->mailbox_guid) ||
+ update->uid_validity != 0 || update->min_next_uid != 0 ||
+ update->min_first_recent_uid != 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Not supported");
+ }
+ return index_storage_mailbox_update(box, update);
+}
+
+static void imapc_untagged_status(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ struct imapc_storage *storage = client->_storage;
+ struct mailbox_status *status;
+ const struct imap_arg *list;
+ const char *remote_name, *key, *value;
+ uint32_t num;
+ unsigned int i;
+
+ if (!imap_arg_get_astring(&reply->args[0], &remote_name) ||
+ !imap_arg_get_list(&reply->args[1], &list))
+ return;
+
+ if (storage->cur_status_box == NULL)
+ return;
+
+ if (!imapc_mailbox_name_equals(storage->cur_status_box,
+ remote_name))
+ return;
+
+ status = storage->cur_status;
+ for (i = 0; list[i].type != IMAP_ARG_EOL; i += 2) {
+ if (!imap_arg_get_atom(&list[i], &key) ||
+ !imap_arg_get_atom(&list[i+1], &value) ||
+ str_to_uint32(value, &num) < 0)
+ return;
+
+ if (strcasecmp(key, "MESSAGES") == 0)
+ status->messages = num;
+ else if (strcasecmp(key, "RECENT") == 0)
+ status->recent = num;
+ else if (strcasecmp(key, "UIDNEXT") == 0)
+ status->uidnext = num;
+ else if (strcasecmp(key, "UIDVALIDITY") == 0)
+ status->uidvalidity = num;
+ else if (strcasecmp(key, "UNSEEN") == 0)
+ status->unseen = num;
+ else if (strcasecmp(key, "HIGHESTMODSEQ") == 0 &&
+ imapc_mailbox_has_modseqs(storage->cur_status_box))
+ status->highest_modseq = num;
+ }
+}
+
+static void imapc_untagged_namespace(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ struct imapc_storage *storage = client->_storage;
+ static enum mail_namespace_type ns_types[] = {
+ MAIL_NAMESPACE_TYPE_PRIVATE,
+ MAIL_NAMESPACE_TYPE_SHARED,
+ MAIL_NAMESPACE_TYPE_PUBLIC
+ };
+ struct imapc_namespace *ns;
+ const struct imap_arg *list, *list2;
+ const char *prefix, *sep;
+ unsigned int i;
+
+ array_clear(&storage->remote_namespaces);
+ for (i = 0; i < N_ELEMENTS(ns_types); i++) {
+ if (reply->args[i].type == IMAP_ARG_NIL)
+ continue;
+ if (!imap_arg_get_list(&reply->args[i], &list))
+ break;
+
+ for (; list->type != IMAP_ARG_EOL; list++) {
+ if (!imap_arg_get_list(list, &list2) ||
+ !imap_arg_get_astring(&list2[0], &prefix) ||
+ !imap_arg_get_nstring(&list2[1], &sep))
+ break;
+
+ ns = array_append_space(&storage->remote_namespaces);
+ ns->prefix = p_strdup(storage->storage.pool, prefix);
+ ns->separator = sep == NULL ? '\0' : sep[0];
+ ns->type = ns_types[i];
+ }
+ }
+}
+
+static void imapc_mailbox_get_selected_status(struct imapc_mailbox *mbox,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ index_storage_get_open_status(&mbox->box, items, status_r);
+ if ((items & STATUS_PERMANENT_FLAGS) != 0)
+ status_r->permanent_flags = mbox->permanent_flags;
+ if ((items & STATUS_FIRST_RECENT_UID) != 0)
+ status_r->first_recent_uid = mbox->highest_nonrecent_uid + 1;
+ if ((items & STATUS_HIGHESTMODSEQ) != 0) {
+ /* FIXME: this doesn't work perfectly. we're now just returning
+ the HIGHESTMODSEQ from the current index, which may or may
+ not be correct. with QRESYNC enabled we could be returning
+ sync_highestmodseq, but that would require implementing
+ VANISHED replies. and without QRESYNC we'd have to issue
+ STATUS (HIGHESTMODSEQ), which isn't efficient since we get
+ here constantly (after every IMAP command). */
+ }
+ if (imapc_mailbox_has_modseqs(mbox)) {
+ /* even if local indexes are only in memory, we still
+ have modseqs on the IMAP server itself. */
+ status_r->nonpermanent_modseqs = FALSE;
+ }
+}
+
+static int imapc_mailbox_delete(struct mailbox *box)
+{
+ box->delete_skip_empty_check = TRUE;
+ return index_storage_mailbox_delete(box);
+}
+
+static int imapc_mailbox_run_status(struct mailbox *box,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+ struct imapc_command *cmd;
+ struct imapc_simple_context sctx;
+ string_t *str;
+
+ if (imapc_mailbox_get_capabilities(mbox) < 0)
+ return -1;
+
+ str = t_str_new(256);
+ if ((items & STATUS_MESSAGES) != 0)
+ str_append(str, " MESSAGES");
+ if ((items & STATUS_RECENT) != 0)
+ str_append(str, " RECENT");
+ if ((items & STATUS_UIDNEXT) != 0)
+ str_append(str, " UIDNEXT");
+ if ((items & STATUS_UIDVALIDITY) != 0)
+ str_append(str, " UIDVALIDITY");
+ if ((items & STATUS_UNSEEN) != 0)
+ str_append(str, " UNSEEN");
+ if ((items & STATUS_HIGHESTMODSEQ) != 0 &&
+ imapc_mailbox_has_modseqs(mbox))
+ str_append(str, " HIGHESTMODSEQ");
+
+ if (str_len(str) == 0) {
+ /* nothing requested */
+ return 0;
+ }
+
+ imapc_simple_context_init(&sctx, mbox->storage->client);
+ mbox->storage->cur_status_box = mbox;
+ mbox->storage->cur_status = status_r;
+ cmd = imapc_client_cmd(mbox->storage->client->client,
+ imapc_simple_callback, &sctx);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_sendf(cmd, "STATUS %s (%1s)",
+ imapc_mailbox_get_remote_name(mbox), str_c(str)+1);
+ imapc_simple_run(&sctx, &cmd);
+ mbox->storage->cur_status_box = NULL;
+ mbox->storage->cur_status = NULL;
+ return sctx.ret;
+}
+
+static int imapc_mailbox_get_status(struct mailbox *box,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+
+ if (mbox->guid_fetch_field_name != NULL ||
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_GUID_FORCED))
+ status_r->have_guids = TRUE;
+
+ if (box->opened) {
+ imapc_mailbox_get_selected_status(mbox, items, status_r);
+ } else if ((items & (STATUS_FIRST_UNSEEN_SEQ | STATUS_KEYWORDS |
+ STATUS_PERMANENT_FLAGS |
+ STATUS_FIRST_RECENT_UID)) != 0) {
+ /* getting these requires opening the mailbox */
+ if (mailbox_open(box) < 0)
+ return -1;
+ imapc_mailbox_get_selected_status(mbox, items, status_r);
+ } else {
+ if (imapc_mailbox_run_status(box, items, status_r) < 0)
+ return -1;
+ /* If this mailbox has private indexes make sure to check
+ STATUS_UNSEEN from there. */
+ if (box->list->set.index_pvt_dir != NULL &&
+ (items & (STATUS_UNSEEN)) != 0) {
+ struct mailbox_status pvt_idx_status;
+ index_storage_get_status(box, STATUS_UNSEEN,
+ &pvt_idx_status);
+ status_r->unseen = pvt_idx_status.unseen;
+ }
+ }
+
+ if (box->opened && !box->deleting && (items & STATUS_UIDNEXT) != 0 &&
+ mbox->sync_uid_next == 0) {
+ /* Courier-workaround, it doesn't send UIDNEXT on SELECT */
+ if (imapc_mailbox_run_status(box, STATUS_UIDNEXT, status_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int imapc_mailbox_get_namespaces(struct imapc_mailbox *mbox)
+{
+ struct imapc_storage *storage = mbox->storage;
+ struct imapc_command *cmd;
+ struct imapc_simple_context sctx;
+
+ if (storage->namespaces_requested)
+ return 0;
+
+ if (imapc_mailbox_get_capabilities(mbox) < 0)
+ return -1;
+ if ((mbox->capabilities & IMAPC_CAPABILITY_NAMESPACE) == 0) {
+ /* NAMESPACE capability not supported */
+ return 0;
+ }
+
+ imapc_simple_context_init(&sctx, storage->client);
+ cmd = imapc_client_cmd(storage->client->client,
+ imapc_simple_callback, &sctx);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, "NAMESPACE");
+ imapc_simple_run(&sctx, &cmd);
+
+ if (sctx.ret < 0)
+ return -1;
+ storage->namespaces_requested = TRUE;
+ return 0;
+}
+
+static const struct imapc_namespace *
+imapc_namespace_find_mailbox(struct imapc_storage *storage,
+ const char *remote_name)
+{
+ const struct imapc_namespace *ns, *best_ns = NULL;
+ size_t best_len = UINT_MAX, len;
+
+ array_foreach(&storage->remote_namespaces, ns) {
+ len = strlen(ns->prefix);
+ if (str_begins(remote_name, ns->prefix)) {
+ if (best_len > len) {
+ best_ns = ns;
+ best_len = len;
+ }
+ }
+ }
+ return best_ns;
+}
+
+static int imapc_mailbox_get_metadata(struct mailbox *box,
+ enum mailbox_metadata_items items,
+ struct mailbox_metadata *metadata_r)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+ const struct imapc_namespace *ns;
+
+ if ((items & MAILBOX_METADATA_GUID) != 0) {
+ /* a bit ugly way to do this, but better than nothing for now.
+ FIXME: if indexes are enabled, keep this there. */
+ mail_generate_guid_128_hash(box->name, metadata_r->guid);
+ items &= ENUM_NEGATE(MAILBOX_METADATA_GUID);
+ }
+ if ((items & MAILBOX_METADATA_BACKEND_NAMESPACE) != 0) {
+ if (imapc_mailbox_get_namespaces(mbox) < 0)
+ return -1;
+
+ const char *remote_name = imapc_mailbox_get_remote_name(mbox);
+ ns = imapc_namespace_find_mailbox(mbox->storage, remote_name);
+ if (ns != NULL) {
+ metadata_r->backend_ns_prefix = ns->prefix;
+ metadata_r->backend_ns_type = ns->type;
+ }
+ items &= ENUM_NEGATE(MAILBOX_METADATA_BACKEND_NAMESPACE);
+ }
+ if (items != 0) {
+ if (index_mailbox_get_metadata(box, items, metadata_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void imapc_noop_callback(const struct imapc_command_reply *reply,
+ void *context)
+
+{
+ struct imapc_storage *storage = context;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ ;
+ else if (reply->state == IMAPC_COMMAND_STATE_NO)
+ imapc_copy_error_from_reply(storage, MAIL_ERROR_PARAMS, reply);
+ else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED)
+ mail_storage_set_internal_error(&storage->storage);
+ else {
+ mail_storage_set_critical(&storage->storage,
+ "imapc: NOOP failed: %s", reply->text_full);
+ }
+}
+
+static void imapc_idle_timeout(struct imapc_mailbox *mbox)
+{
+ struct imapc_command *cmd;
+
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_noop_callback, mbox->storage);
+ imapc_command_send(cmd, "NOOP");
+}
+
+static void imapc_idle_noop_callback(const struct imapc_command_reply *reply,
+ void *context)
+
+{
+ struct imapc_mailbox *mbox = context;
+
+ imapc_noop_callback(reply, mbox->box.storage);
+ if (mbox->client_box != NULL)
+ imapc_client_mailbox_idle(mbox->client_box);
+}
+
+static void imapc_notify_changes(struct mailbox *box)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+ const struct mail_storage_settings *set = box->storage->set;
+ struct imapc_command *cmd;
+
+ if (box->notify_callback == NULL) {
+ timeout_remove(&mbox->to_idle_check);
+ return;
+ }
+
+ if ((mbox->capabilities & IMAPC_CAPABILITY_IDLE) != 0) {
+ /* remote server is already in IDLE. but since some servers
+ don't notice changes immediately, we'll force them to check
+ here by sending a NOOP. this helps with clients that break
+ IDLE when clicking "get mail". */
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_idle_noop_callback, mbox);
+ imapc_command_send(cmd, "NOOP");
+ } else {
+ /* remote server doesn't support IDLE.
+ check for changes with NOOP every once in a while. */
+ i_assert(!imapc_client_is_running(mbox->storage->client->client));
+ mbox->to_idle_check =
+ timeout_add(set->mailbox_idle_check_interval * 1000,
+ imapc_idle_timeout, mbox);
+ }
+}
+
+static bool imapc_is_inconsistent(struct mailbox *box)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+
+ if (box->view != NULL &&
+ mail_index_view_is_inconsistent(box->view))
+ return TRUE;
+
+ return mbox->client_box == NULL ? FALSE :
+ !imapc_client_mailbox_is_opened(mbox->client_box);
+}
+
+struct mail_storage imapc_storage = {
+ .name = IMAPC_STORAGE_NAME,
+ .class_flags = MAIL_STORAGE_CLASS_FLAG_NO_ROOT |
+ MAIL_STORAGE_CLASS_FLAG_UNIQUE_ROOT |
+ MAIL_STORAGE_CLASS_FLAG_SECONDARY_INDEX,
+ .event_category = &event_category_imapc,
+
+ .v = {
+ imapc_get_setting_parser_info,
+ imapc_storage_alloc,
+ imapc_storage_create,
+ imapc_storage_destroy,
+ NULL,
+ imapc_storage_get_list_settings,
+ NULL,
+ imapc_mailbox_alloc,
+ NULL,
+ NULL,
+ }
+};
+
+static int
+imapc_mailbox_transaction_commit(struct mailbox_transaction_context *t,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ int ret = imapc_transaction_save_commit(t);
+ int ret2 = index_transaction_commit(t, changes_r);
+ return ret >= 0 && ret2 >= 0 ? 0 : -1;
+}
+
+struct mailbox imapc_mailbox = {
+ .v = {
+ index_storage_is_readonly,
+ index_storage_mailbox_enable,
+ imapc_mailbox_exists,
+ imapc_mailbox_open,
+ imapc_mailbox_close,
+ index_storage_mailbox_free,
+ imapc_mailbox_create,
+ imapc_mailbox_update,
+ imapc_mailbox_delete,
+ index_storage_mailbox_rename,
+ imapc_mailbox_get_status,
+ imapc_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,
+ NULL,
+ NULL,
+ imapc_mailbox_sync_init,
+ index_mailbox_sync_next,
+ imapc_mailbox_sync_deinit,
+ NULL,
+ imapc_notify_changes,
+ index_transaction_begin,
+ imapc_mailbox_transaction_commit,
+ index_transaction_rollback,
+ NULL,
+ imapc_mail_alloc,
+ imapc_search_init,
+ imapc_search_deinit,
+ index_storage_search_next_nonblock,
+ imapc_search_next_update_seq,
+ index_storage_search_next_match_mail,
+ imapc_save_alloc,
+ imapc_save_begin,
+ imapc_save_continue,
+ imapc_save_finish,
+ imapc_save_cancel,
+ imapc_copy,
+ imapc_transaction_save_commit_pre,
+ imapc_transaction_save_commit_post,
+ imapc_transaction_save_rollback,
+ imapc_is_inconsistent
+ }
+};
diff --git a/src/lib-storage/index/imapc/imapc-storage.h b/src/lib-storage/index/imapc/imapc-storage.h
new file mode 100644
index 0000000..6624a0d
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-storage.h
@@ -0,0 +1,274 @@
+#ifndef IMAPC_STORAGE_H
+#define IMAPC_STORAGE_H
+
+#include "index-storage.h"
+#include "imapc-settings.h"
+#include "imapc-client.h"
+
+#define IMAPC_STORAGE_NAME "imapc"
+/* storage_name separator */
+#define IMAPC_LIST_STORAGE_NAME_ESCAPE_CHAR '%'
+/* fs_name separator */
+#define IMAPC_LIST_FS_NAME_ESCAPE_CHAR '%'
+/* vname separator */
+#define IMAPC_LIST_VNAME_ESCAPE_CHAR '~'
+
+struct imap_arg;
+struct imapc_untagged_reply;
+struct imapc_command_reply;
+struct imapc_mailbox;
+struct imapc_storage_client;
+
+typedef void imapc_storage_callback_t(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client);
+typedef void imapc_mailbox_callback_t(const struct imapc_untagged_reply *reply,
+ struct imapc_mailbox *mbox);
+
+struct imapc_storage_event_callback {
+ char *name;
+ imapc_storage_callback_t *callback;
+};
+
+struct imapc_mailbox_event_callback {
+ const char *name;
+ imapc_mailbox_callback_t *callback;
+};
+
+#define IMAPC_HAS_FEATURE(mstorage, feature) \
+ (((mstorage)->set->parsed_features & feature) != 0)
+#define IMAPC_BOX_HAS_FEATURE(mbox, feature) \
+ (((mbox)->storage->set->parsed_features & feature) != 0)
+
+/* Returns TRUE if we can assume from now on that untagged EXPUNGE, FETCH, etc.
+ replies belong to this mailbox instead of to the previously selected
+ mailbox. */
+#define IMAPC_MAILBOX_IS_FULLY_SELECTED(mbox) \
+ ((mbox)->sync_uid_validity != 0)
+
+struct imapc_namespace {
+ const char *prefix;
+ char separator;
+ enum mail_namespace_type type;
+};
+
+struct imapc_storage_client {
+ int refcount;
+
+ /* either one of these may not be available: */
+ struct imapc_storage *_storage;
+ struct imapc_mailbox_list *_list;
+
+ struct imapc_client *client;
+
+ ARRAY(struct imapc_storage_event_callback) untagged_callbacks;
+
+ /* IMAPC_COMMAND_STATE_OK if no auth failure (yet), otherwise result to
+ the LOGIN/AUTHENTICATE command. */
+ enum imapc_command_state auth_failed_state;
+ char *auth_failed_reason;
+
+ /* Authentication reply was received (success or failure) */
+ bool auth_returned:1;
+ bool destroying:1;
+};
+
+struct imapc_storage {
+ struct mail_storage storage;
+ const struct imapc_settings *set;
+
+ struct ioloop *root_ioloop;
+ struct imapc_storage_client *client;
+
+ struct imapc_mailbox *cur_status_box;
+ struct mailbox_status *cur_status;
+ unsigned int reopen_count;
+
+ ARRAY(struct imapc_namespace) remote_namespaces;
+
+ bool namespaces_requested:1;
+};
+
+struct imapc_mail_cache {
+ uint32_t uid;
+
+ /* either fd != -1 or buf != NULL */
+ int fd;
+ buffer_t *buf;
+};
+
+struct imapc_fetch_request {
+ ARRAY(struct imapc_mail *) mails;
+};
+
+struct imapc_untagged_fetch_ctx {
+ pool_t pool;
+
+ /* keywords, flags, guid, modseq and fetch_uid may or may not be
+ received with an untagged fetch response */
+ ARRAY_TYPE(const_string) keywords;
+ /* Is set if have_flags is TRUE */
+ enum mail_flags flags;
+ const char *guid;
+ uint64_t modseq;
+ uint32_t fetch_uid;
+
+ /* uid is generated locally based on the remote MSN or fetch_uid */
+ uint32_t uid;
+
+ bool have_gmail_labels:1;
+ bool have_flags:1;
+};
+
+struct imapc_copy_request {
+ struct imapc_save_context *sctx;
+ struct seqset_builder *uidset_builder;
+};
+
+struct imapc_mailbox {
+ struct mailbox box;
+ struct imapc_storage *storage;
+ struct imapc_client_mailbox *client_box;
+ enum imapc_capability capabilities;
+
+ struct mail_index_transaction *delayed_sync_trans;
+ struct mail_index_view *sync_view, *delayed_sync_view;
+ struct mail_cache_view *delayed_sync_cache_view;
+ struct mail_cache_transaction_ctx *delayed_sync_cache_trans;
+ struct timeout *to_idle_check, *to_idle_delay;
+
+ ARRAY(struct imapc_fetch_request *) fetch_requests;
+ ARRAY(struct imapc_untagged_fetch_ctx *) untagged_fetch_contexts;
+ /* if non-empty, contains the latest FETCH command we're going to be
+ sending soon (but still waiting to see if we can increase its
+ UID range) */
+ string_t *pending_fetch_cmd;
+ /* if non-empty, contains the latest COPY command we're going to be
+ sending soon. */
+ string_t *pending_copy_cmd;
+ char *copy_dest_box;
+ struct imapc_fetch_request *pending_fetch_request;
+ struct imapc_copy_request *pending_copy_request;
+ struct timeout *to_pending_fetch_send;
+
+ ARRAY(struct imapc_mailbox_event_callback) untagged_callbacks;
+ ARRAY(struct imapc_mailbox_event_callback) resp_text_callbacks;
+
+ enum mail_flags permanent_flags;
+ uint32_t highest_nonrecent_uid;
+
+ ARRAY(uint64_t) rseq_modseqs;
+ ARRAY_TYPE(seq_range) delayed_expunged_uids;
+ ARRAY_TYPE(seq_range) copy_rollback_expunge_uids;
+ uint32_t sync_uid_validity;
+ uint32_t sync_uid_next;
+ uint64_t sync_highestmodseq;
+ uint32_t sync_fetch_first_uid;
+ uint32_t sync_next_lseq;
+ uint32_t sync_next_rseq;
+ uint32_t exists_count;
+ uint32_t min_append_uid;
+ char *sync_gmail_pop3_search_tag;
+
+ /* keep the previous fetched message body cached,
+ mainly for partial IMAP fetches */
+ struct imapc_mail_cache prev_mail_cache;
+
+ uint32_t prev_skipped_rseq, prev_skipped_uid;
+ struct imapc_sync_context *sync_ctx;
+
+ const char *guid_fetch_field_name;
+ struct imapc_search_context *search_ctx;
+
+ bool selecting:1;
+ bool syncing:1;
+ bool initial_sync_done:1;
+ bool selected:1;
+ bool exists_received:1;
+ bool state_fetching_uid1:1;
+ bool state_fetched_success:1;
+ bool rollback_pending:1;
+ bool delayed_untagged_exists:1;
+};
+
+struct imapc_simple_context {
+ struct imapc_storage_client *client;
+ int ret;
+};
+
+#define IMAPC_STORAGE(s) container_of(s, struct imapc_storage, storage)
+#define IMAPC_MAILBOX(s) container_of(s, struct imapc_mailbox, box)
+
+int imapc_storage_client_create(struct mail_namespace *ns,
+ const struct imapc_settings *imapc_set,
+ const struct mail_storage_settings *mail_set,
+ struct imapc_storage_client **client_r,
+ const char **error_r);
+void imapc_storage_client_unref(struct imapc_storage_client **client);
+bool imapc_storage_client_handle_auth_failure(struct imapc_storage_client *client);
+
+struct mail_save_context *
+imapc_save_alloc(struct mailbox_transaction_context *_t);
+int imapc_save_begin(struct mail_save_context *ctx, struct istream *input);
+int imapc_save_continue(struct mail_save_context *ctx);
+int imapc_save_finish(struct mail_save_context *ctx);
+void imapc_save_cancel(struct mail_save_context *ctx);
+int imapc_copy(struct mail_save_context *ctx, struct mail *mail);
+
+int imapc_transaction_save_commit(struct mailbox_transaction_context *t);
+int imapc_transaction_save_commit_pre(struct mail_save_context *ctx);
+void imapc_transaction_save_commit_post(struct mail_save_context *ctx,
+ struct mail_index_transaction_commit_result *result);
+void imapc_transaction_save_rollback(struct mail_save_context *ctx);
+
+void imapc_mailbox_run(struct imapc_mailbox *mbox);
+void imapc_mailbox_run_nofetch(struct imapc_mailbox *mbox);
+void imapc_mail_cache_free(struct imapc_mail_cache *cache);
+int imapc_mailbox_select(struct imapc_mailbox *mbox);
+void imap_mailbox_select_finish(struct imapc_mailbox *mbox);
+
+bool imapc_mailbox_has_modseqs(struct imapc_mailbox *mbox);
+bool imapc_resp_text_code_parse(const char *str, enum mail_error *error_r);
+bool imapc_mail_error_to_resp_text_code(enum mail_error error, const char **str_r);
+void imapc_copy_error_from_reply(struct imapc_storage *storage,
+ enum mail_error default_error,
+ const struct imapc_command_reply *reply);
+void imapc_simple_context_init(struct imapc_simple_context *sctx,
+ struct imapc_storage_client *client);
+void imapc_simple_run(struct imapc_simple_context *sctx,
+ struct imapc_command **cmd);
+void imapc_simple_callback(const struct imapc_command_reply *reply,
+ void *context);
+int imapc_mailbox_commit_delayed_trans(struct imapc_mailbox *mbox,
+ bool force, bool *changes_r);
+bool imapc_mailbox_name_equals(struct imapc_mailbox *mbox,
+ const char *remote_name);
+void imapc_mailbox_noop(struct imapc_mailbox *mbox);
+void imapc_mailbox_set_corrupted(struct imapc_mailbox *mbox,
+ const char *reason, ...) ATTR_FORMAT(2, 3);
+const char *imapc_mailbox_get_remote_name(struct imapc_mailbox *mbox);
+
+void imapc_storage_client_register_untagged(struct imapc_storage_client *client,
+ const char *name,
+ imapc_storage_callback_t *callback);
+void imapc_storage_client_unregister_untagged(struct imapc_storage_client *client,
+ const char *name);
+void imapc_mailbox_register_untagged(struct imapc_mailbox *mbox,
+ const char *name,
+ imapc_mailbox_callback_t *callback);
+void imapc_mailbox_register_resp_text(struct imapc_mailbox *mbox,
+ const char *key,
+ imapc_mailbox_callback_t *callback);
+
+void imapc_mailbox_register_callbacks(struct imapc_mailbox *mbox);
+
+struct mail_index_view *
+imapc_mailbox_get_sync_view(struct imapc_mailbox *mbox);
+
+void imapc_untagged_fetch_ctx_free(struct imapc_untagged_fetch_ctx **_ctx);
+void imapc_untagged_fetch_update_flags(struct imapc_mailbox *mbox,
+ struct imapc_untagged_fetch_ctx *ctx,
+ struct mail_index_view *view,
+ uint32_t lseq);
+bool imapc_mailbox_fetch_state(struct imapc_mailbox *mbox, uint32_t first_uid);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-sync.c b/src/lib-storage/index/imapc/imapc-sync.c
new file mode 100644
index 0000000..3130121
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-sync.c
@@ -0,0 +1,702 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "hash.h"
+#include "str.h"
+#include "sort.h"
+#include "imap-util.h"
+#include "mail-cache.h"
+#include "mail-index-modseq.h"
+#include "index-sync-private.h"
+#include "imapc-msgmap.h"
+#include "imapc-list.h"
+#include "imapc-storage.h"
+#include "imapc-sync.h"
+
+struct imapc_sync_command {
+ struct imapc_sync_context *ctx;
+ char *cmd_str;
+ bool ignore_no;
+};
+
+static void imapc_sync_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_sync_command *cmd = context;
+ struct imapc_sync_context *ctx = cmd->ctx;
+
+ i_assert(ctx->sync_command_count > 0);
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ ;
+ else if (reply->state == IMAPC_COMMAND_STATE_NO && cmd->ignore_no) {
+ /* maybe the message was expunged already.
+ some servers fail STOREs with NO in such situation. */
+ } else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED) {
+ /* the disconnection is already logged, don't flood
+ the logs unnecessarily */
+ mail_storage_set_internal_error(&ctx->mbox->storage->storage);
+ ctx->failed = TRUE;
+ } else {
+ mailbox_set_critical(&ctx->mbox->box,
+ "imapc: Sync command '%s' failed: %s",
+ cmd->cmd_str, reply->text_full);
+ ctx->failed = TRUE;
+ }
+
+ if (--ctx->sync_command_count == 0)
+ imapc_client_stop(ctx->mbox->storage->client->client);
+ i_free(cmd->cmd_str);
+ i_free(cmd);
+}
+
+static struct imapc_command *
+imapc_sync_cmd_full(struct imapc_sync_context *ctx, const char *cmd_str,
+ bool ignore_no)
+{
+ struct imapc_sync_command *sync_cmd;
+ struct imapc_command *cmd;
+
+ sync_cmd = i_new(struct imapc_sync_command, 1);
+ sync_cmd->ctx = ctx;
+ sync_cmd->cmd_str = i_strdup(cmd_str);
+ sync_cmd->ignore_no = ignore_no;
+
+ ctx->sync_command_count++;
+ cmd = imapc_client_mailbox_cmd(ctx->mbox->client_box,
+ imapc_sync_callback, sync_cmd);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, cmd_str);
+ return cmd;
+}
+
+static struct imapc_command *
+imapc_sync_cmd(struct imapc_sync_context *ctx, const char *cmd_str)
+{
+ return imapc_sync_cmd_full(ctx, cmd_str, FALSE);
+}
+
+static unsigned int imapc_sync_store_hash(const struct imapc_sync_store *store)
+{
+ return str_hash(store->flags) ^ store->modify_type;
+}
+
+static int imapc_sync_store_cmp(const struct imapc_sync_store *store1,
+ const struct imapc_sync_store *store2)
+{
+ if (store1->modify_type != store2->modify_type)
+ return 1;
+ return strcmp(store1->flags, store2->flags);
+}
+
+static const char *imapc_sync_flags_sort(const char *flags)
+{
+ if (strchr(flags, ' ') == NULL)
+ return flags;
+
+ const char **str = t_strsplit(flags, " ");
+ i_qsort(str, str_array_length(str), sizeof(const char *),
+ i_strcasecmp_p);
+ return t_strarray_join(str, " ");
+}
+
+static void
+imapc_sync_store_flush(struct imapc_sync_context *ctx)
+{
+ struct imapc_sync_store *store;
+ const char *sorted_flags;
+
+ if (ctx->prev_uid1 == 0)
+ return;
+
+ sorted_flags = imapc_sync_flags_sort(str_c(ctx->prev_flags));
+ struct imapc_sync_store store_lookup = {
+ .modify_type = ctx->prev_modify_type,
+ .flags = sorted_flags,
+ };
+ store = hash_table_lookup(ctx->stores, &store_lookup);
+ if (store == NULL) {
+ store = p_new(ctx->pool, struct imapc_sync_store, 1);
+ store->modify_type = ctx->prev_modify_type;
+ store->flags = p_strdup(ctx->pool, sorted_flags);
+ p_array_init(&store->uids, ctx->pool, 4);
+ hash_table_insert(ctx->stores, store, store);
+ }
+ seq_range_array_add_range(&store->uids, ctx->prev_uid1, ctx->prev_uid2);
+}
+
+static void
+imapc_sync_store(struct imapc_sync_context *ctx,
+ enum modify_type modify_type, uint32_t uid1, uint32_t uid2,
+ const char *flags)
+{
+ if (ctx->prev_flags == NULL) {
+ ctx->prev_flags = str_new(ctx->pool, 128);
+ hash_table_create(&ctx->stores, ctx->pool, 0,
+ imapc_sync_store_hash, imapc_sync_store_cmp);
+ }
+
+ if (ctx->prev_uid1 != uid1 || ctx->prev_uid2 != uid2 ||
+ ctx->prev_modify_type != modify_type) {
+ imapc_sync_store_flush(ctx);
+ ctx->prev_uid1 = uid1;
+ ctx->prev_uid2 = uid2;
+ ctx->prev_modify_type = modify_type;
+ str_truncate(ctx->prev_flags, 0);
+ }
+ if (str_len(ctx->prev_flags) > 0)
+ str_append_c(ctx->prev_flags, ' ');
+ str_append(ctx->prev_flags, flags);
+}
+
+static void
+imapc_sync_finish_store(struct imapc_sync_context *ctx)
+{
+ struct hash_iterate_context *iter;
+ struct imapc_sync_store *store;
+ string_t *cmd = t_str_new(128);
+
+ imapc_sync_store_flush(ctx);
+
+ if (!hash_table_is_created(ctx->stores))
+ return;
+
+ iter = hash_table_iterate_init(ctx->stores);
+ while (hash_table_iterate(iter, ctx->stores, &store, &store)) {
+ str_truncate(cmd, 0);
+ str_append(cmd, "UID STORE ");
+ imap_write_seq_range(cmd, &store->uids);
+ str_printfa(cmd, " %cFLAGS (%s)",
+ store->modify_type == MODIFY_ADD ? '+' : '-',
+ store->flags);
+ imapc_sync_cmd_full(ctx, str_c(cmd), TRUE);
+ }
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&ctx->stores);
+}
+
+static void
+imapc_sync_add_missing_deleted_flags(struct imapc_sync_context *ctx,
+ uint32_t seq1, uint32_t seq2)
+{
+ const struct mail_index_record *rec;
+ uint32_t seq, uid1, uid2;
+
+ /* if any of them has a missing \Deleted flag,
+ just add it to all of them. */
+ for (seq = seq1; seq <= seq2; seq++) {
+ rec = mail_index_lookup(ctx->sync_view, seq);
+ if ((rec->flags & MAIL_DELETED) == 0)
+ break;
+ }
+
+ if (seq <= seq2) {
+ mail_index_lookup_uid(ctx->sync_view, seq1, &uid1);
+ mail_index_lookup_uid(ctx->sync_view, seq2, &uid2);
+
+ imapc_sync_store(ctx, MODIFY_ADD, uid1, uid2, "\\Deleted");
+ }
+}
+
+static void imapc_sync_index_flags(struct imapc_sync_context *ctx,
+ const struct mail_index_sync_rec *sync_rec)
+{
+ string_t *str = t_str_new(128);
+
+ i_assert(sync_rec->type == MAIL_INDEX_SYNC_TYPE_FLAGS);
+
+ if (sync_rec->add_flags != 0) {
+ i_assert((sync_rec->add_flags & MAIL_RECENT) == 0);
+
+ imap_write_flags(str, sync_rec->add_flags, NULL);
+ imapc_sync_store(ctx, MODIFY_ADD, sync_rec->uid1,
+ sync_rec->uid2, str_c(str));
+ }
+
+ if (sync_rec->remove_flags != 0) {
+ i_assert((sync_rec->remove_flags & MAIL_RECENT) == 0);
+ str_truncate(str, 0);
+ imap_write_flags(str, sync_rec->remove_flags, NULL);
+ imapc_sync_store(ctx, MODIFY_REMOVE, sync_rec->uid1,
+ sync_rec->uid2, str_c(str));
+ }
+}
+
+static void
+imapc_sync_index_keyword(struct imapc_sync_context *ctx,
+ const struct mail_index_sync_rec *sync_rec)
+{
+ const char *kw_str;
+ enum modify_type modify_type;
+
+ switch (sync_rec->type) {
+ case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
+ modify_type = MODIFY_ADD;
+ break;
+ case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
+ modify_type = MODIFY_REMOVE;
+ break;
+ default:
+ i_unreached();
+ }
+
+ kw_str = array_idx_elem(ctx->keywords, sync_rec->keyword_idx);
+ imapc_sync_store(ctx, modify_type, sync_rec->uid1,
+ sync_rec->uid2, kw_str);
+}
+
+static void imapc_sync_expunge_finish(struct imapc_sync_context *ctx)
+{
+ string_t *str;
+
+ if (array_count(&ctx->expunged_uids) == 0)
+ return;
+
+ if ((ctx->mbox->capabilities & IMAPC_CAPABILITY_UIDPLUS) == 0) {
+ /* just expunge everything */
+ imapc_sync_cmd(ctx, "EXPUNGE");
+ return;
+ }
+
+ /* build a list of UIDs to expunge */
+ str = t_str_new(128);
+ str_append(str, "UID EXPUNGE ");
+ imap_write_seq_range(str, &ctx->expunged_uids);
+ imapc_sync_cmd(ctx, str_c(str));
+}
+
+static void imapc_sync_uid_next(struct imapc_sync_context *ctx)
+{
+ struct imapc_mailbox *mbox = ctx->mbox;
+ const struct mail_index_header *hdr;
+ uint32_t uid_next = mbox->sync_uid_next;
+
+ if (uid_next < mbox->min_append_uid)
+ uid_next = mbox->min_append_uid;
+
+ hdr = mail_index_get_header(ctx->sync_view);
+ if (hdr->next_uid < uid_next) {
+ mail_index_update_header(ctx->trans,
+ offsetof(struct mail_index_header, next_uid),
+ &uid_next, sizeof(uid_next), FALSE);
+ }
+}
+
+static void imapc_sync_highestmodseq(struct imapc_sync_context *ctx)
+{
+ if (imapc_mailbox_has_modseqs(ctx->mbox) &&
+ mail_index_modseq_get_highest(ctx->sync_view) < ctx->mbox->sync_highestmodseq)
+ mail_index_update_highest_modseq(ctx->trans, ctx->mbox->sync_highestmodseq);
+}
+
+static void
+imapc_initial_sync_check(struct imapc_sync_context *ctx, bool nooped)
+{
+ struct imapc_msgmap *msgmap =
+ imapc_client_mailbox_get_msgmap(ctx->mbox->client_box);
+ struct mail_index_view *view = ctx->mbox->delayed_sync_view;
+ const struct mail_index_header *hdr = mail_index_get_header(view);
+ uint32_t rseq, lseq, ruid, luid, rcount, lcount;
+
+ rseq = lseq = 1;
+ rcount = imapc_msgmap_count(msgmap);
+ lcount = mail_index_view_get_messages_count(view);
+ while (rseq <= rcount || lseq <= lcount) {
+ if (rseq <= rcount)
+ ruid = imapc_msgmap_rseq_to_uid(msgmap, rseq);
+ else
+ ruid = (uint32_t)-1;
+ if (lseq <= lcount)
+ mail_index_lookup_uid(view, lseq, &luid);
+ else
+ luid = (uint32_t)-1;
+
+ if (ruid == luid) {
+ /* message exists in index and in remote server */
+ lseq++; rseq++;
+ } else if (luid < ruid) {
+ /* message exists in index but not in remote server */
+ if (luid >= ctx->mbox->sync_uid_next) {
+ /* the message was added to index by another
+ imapc session, and it's not visible yet
+ in this session */
+ break;
+ }
+ /* it's already expunged and we should have marked it */
+ i_assert(mail_index_is_expunged(view, lseq) ||
+ seq_range_exists(&ctx->mbox->delayed_expunged_uids, luid));
+ lseq++;
+ } else {
+ /* message doesn't exist in index, but exists in
+ remote server */
+ if (lseq > lcount && ruid >= hdr->next_uid) {
+ /* the message hasn't been yet added to index */
+ break;
+ }
+
+ /* another imapc session expunged it =>
+ NOOP should send us an EXPUNGE event */
+ if (!nooped) {
+ imapc_mailbox_noop(ctx->mbox);
+ imapc_initial_sync_check(ctx, TRUE);
+ return;
+ }
+ /* already nooped => index is corrupted */
+ imapc_mailbox_set_corrupted(ctx->mbox,
+ "Expunged message uid=%u reappeared", ruid);
+ ctx->failed = TRUE;
+ return;
+ }
+ }
+}
+
+static void
+imapc_sync_send_commands(struct imapc_sync_context *ctx)
+{
+ if (ctx->mbox->exists_count == 0) {
+ /* empty mailbox - no point in fetching anything */
+ return;
+ }
+
+ if (IMAPC_BOX_HAS_FEATURE(ctx->mbox, IMAPC_FEATURE_GMAIL_MIGRATION) &&
+ ctx->mbox->storage->set->pop3_deleted_flag[0] != '\0') {
+ struct imapc_command *cmd;
+
+ cmd = imapc_sync_cmd(ctx, "SEARCH RETURN (ALL) X-GM-RAW \"in:^pop\"");
+ i_free(ctx->mbox->sync_gmail_pop3_search_tag);
+ ctx->mbox->sync_gmail_pop3_search_tag =
+ i_strdup(imapc_command_get_tag(cmd));
+ }
+}
+
+static void imapc_sync_index(struct imapc_sync_context *ctx)
+{
+ struct imapc_mailbox *mbox = ctx->mbox;
+ struct mail_index_sync_rec sync_rec;
+ uint32_t seq1, seq2;
+
+ i_array_init(&ctx->expunged_uids, 64);
+ ctx->keywords = mail_index_get_keywords(mbox->box.index);
+ ctx->pool = pool_alloconly_create("imapc sync pool", 1024);
+
+ while (mail_index_sync_next(ctx->index_sync_ctx, &sync_rec)) T_BEGIN {
+ if (!mail_index_lookup_seq_range(ctx->sync_view,
+ sync_rec.uid1, sync_rec.uid2,
+ &seq1, &seq2)) {
+ /* already expunged, nothing to do. */
+ } else switch (sync_rec.type) {
+ case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
+ imapc_sync_add_missing_deleted_flags(ctx, seq1, seq2);
+ seq_range_array_add_range(&ctx->expunged_uids,
+ sync_rec.uid1, sync_rec.uid2);
+ break;
+ case MAIL_INDEX_SYNC_TYPE_FLAGS:
+ imapc_sync_index_flags(ctx, &sync_rec);
+ break;
+ case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
+ case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
+ imapc_sync_index_keyword(ctx, &sync_rec);
+ break;
+ }
+ } T_END;
+ imapc_sync_finish_store(ctx);
+ pool_unref(&ctx->pool);
+
+ if (!mbox->initial_sync_done)
+ imapc_sync_send_commands(ctx);
+
+ imapc_sync_expunge_finish(ctx);
+ while (ctx->sync_command_count > 0)
+ imapc_mailbox_run(mbox);
+ array_free(&ctx->expunged_uids);
+
+ if (!mbox->state_fetched_success) {
+ /* All the sync commands succeeded, but we got disconnected.
+ imapc_initial_sync_check() will crash if we go there. */
+ ctx->failed = TRUE;
+ }
+
+ /* add uidnext & highestmodseq after all appends */
+ imapc_sync_uid_next(ctx);
+ imapc_sync_highestmodseq(ctx);
+
+ mailbox_sync_notify(&mbox->box, 0, 0);
+
+ if (!ctx->failed) {
+ /* reset only after a successful sync */
+ mbox->sync_fetch_first_uid = 0;
+ }
+ if (!mbox->initial_sync_done && !ctx->failed) {
+ imapc_initial_sync_check(ctx, FALSE);
+ mbox->initial_sync_done = TRUE;
+ }
+}
+
+static int
+imapc_sync_begin(struct imapc_mailbox *mbox,
+ struct imapc_sync_context **ctx_r, bool force)
+{
+ struct imapc_sync_context *ctx;
+ enum mail_index_sync_flags sync_flags;
+ int ret;
+
+ i_assert(!mbox->syncing);
+
+ ctx = i_new(struct imapc_sync_context, 1);
+ ctx->mbox = mbox;
+
+ sync_flags = index_storage_get_sync_flags(&mbox->box) |
+ MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY;
+ if (!force)
+ sync_flags |= MAIL_INDEX_SYNC_FLAG_REQUIRE_CHANGES;
+
+ ret = mail_index_sync_begin(mbox->box.index, &ctx->index_sync_ctx,
+ &ctx->sync_view, &ctx->trans,
+ sync_flags);
+ if (ret <= 0) {
+ if (ret < 0)
+ mailbox_set_index_error(&mbox->box);
+ i_free(ctx);
+ *ctx_r = NULL;
+ return ret;
+ }
+
+ i_assert(mbox->sync_view == NULL);
+ i_assert(mbox->delayed_sync_trans == NULL);
+ mbox->sync_view = ctx->sync_view;
+ mbox->delayed_sync_view =
+ mail_index_transaction_open_updated_view(ctx->trans);
+ mbox->delayed_sync_trans = ctx->trans;
+ mbox->delayed_sync_cache_view =
+ mail_cache_view_open(mbox->box.cache, mbox->delayed_sync_view);
+ mbox->delayed_sync_cache_trans =
+ mail_cache_get_transaction(mbox->delayed_sync_cache_view,
+ mbox->delayed_sync_trans);
+ mbox->min_append_uid = mail_index_get_header(ctx->sync_view)->next_uid;
+
+ mbox->syncing = TRUE;
+ mbox->sync_ctx = ctx;
+
+ if (mbox->delayed_untagged_exists) {
+ bool fetch_send = imapc_mailbox_fetch_state(mbox,
+ mbox->min_append_uid);
+ while (fetch_send && mbox->delayed_untagged_exists)
+ imapc_mailbox_run(mbox);
+ }
+
+ if (!mbox->box.deleting)
+ imapc_sync_index(ctx);
+
+ mail_index_view_close(&mbox->delayed_sync_view);
+ mbox->delayed_sync_trans = NULL;
+ mbox->sync_view = NULL;
+
+ *ctx_r = ctx;
+ return 0;
+}
+
+static int imapc_sync_finish(struct imapc_sync_context **_ctx)
+{
+ struct imapc_sync_context *ctx = *_ctx;
+ bool changes;
+ int ret = ctx->failed ? -1 : 0;
+
+ *_ctx = NULL;
+ /* Commit the transaction even if we failed. This is important, because
+ during the sync delayed_sync_trans points to the sync transaction.
+ Even if the syncing doesn't fully succeed, we don't want to lose
+ changes in delayed_sync_trans. */
+ if (mail_index_sync_commit(&ctx->index_sync_ctx) < 0) {
+ mailbox_set_index_error(&ctx->mbox->box);
+ ret = -1;
+ }
+ if (ctx->mbox->sync_gmail_pop3_search_tag != NULL) {
+ mailbox_set_critical(&ctx->mbox->box,
+ "gmail-pop3 search not successful");
+ i_free_and_null(ctx->mbox->sync_gmail_pop3_search_tag);
+ ret = -1;
+ }
+ mail_cache_view_close(&ctx->mbox->delayed_sync_cache_view);
+ ctx->mbox->delayed_sync_cache_trans = NULL;
+
+ ctx->mbox->syncing = FALSE;
+ ctx->mbox->sync_ctx = NULL;
+
+ /* this is done simply to commit delayed expunges if there are any
+ (has to be done after sync is committed) */
+ if (imapc_mailbox_commit_delayed_trans(ctx->mbox, FALSE, &changes) < 0)
+ ctx->failed = TRUE;
+
+ i_free(ctx);
+ return ret;
+}
+
+static int imapc_untagged_fetch_uid_cmp(struct imapc_untagged_fetch_ctx *const *ctx1,
+ struct imapc_untagged_fetch_ctx *const *ctx2)
+{
+ return (*ctx1)->uid < (*ctx2)->uid ? -1 :
+ (*ctx1)->uid > (*ctx2)->uid ? 1 : 0;
+}
+
+static void imapc_sync_handle_untagged_fetches(struct imapc_mailbox *mbox)
+{
+ struct imapc_untagged_fetch_ctx *untagged_fetch_context;
+ struct mail_index_view *updated_view;
+ uint32_t lseq;
+
+ i_assert(array_count(&mbox->untagged_fetch_contexts) > 0);
+ i_assert(mbox->delayed_sync_trans == NULL);
+
+ array_sort(&mbox->untagged_fetch_contexts, imapc_untagged_fetch_uid_cmp);
+
+ mbox->delayed_sync_trans =
+ mail_index_transaction_begin(imapc_mailbox_get_sync_view(mbox),
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+
+ array_foreach_elem(&mbox->untagged_fetch_contexts, untagged_fetch_context) {
+ if (untagged_fetch_context->uid < mbox->min_append_uid ||
+ untagged_fetch_context->uid < mail_index_get_header(mbox->sync_view)->next_uid) {
+ /* The message was already added */
+ continue;
+ }
+
+ mail_index_append(mbox->delayed_sync_trans,
+ untagged_fetch_context->uid,
+ &lseq);
+ mbox->min_append_uid = untagged_fetch_context->uid + 1;
+ }
+
+ updated_view = mail_index_transaction_open_updated_view(mbox->delayed_sync_trans);
+
+ array_foreach_elem(&mbox->untagged_fetch_contexts, untagged_fetch_context) {
+ if (!untagged_fetch_context->have_flags) {
+ imapc_untagged_fetch_ctx_free(&untagged_fetch_context);
+ continue;
+ }
+
+ /* Lookup the mail belonging to this context using the
+ context->uid */
+ if (!mail_index_lookup_seq(updated_view,
+ untagged_fetch_context->uid,
+ &lseq)) {
+ /* mail is expunged already */
+ imapc_untagged_fetch_ctx_free(&untagged_fetch_context);
+ continue;
+ }
+
+ imapc_untagged_fetch_update_flags(mbox, untagged_fetch_context,
+ updated_view, lseq);
+ imapc_untagged_fetch_ctx_free(&untagged_fetch_context);
+ }
+
+ mail_index_view_close(&updated_view);
+ if (mail_index_transaction_commit(&mbox->delayed_sync_trans) < 0)
+ mailbox_set_index_error(&mbox->box);
+ array_clear(&mbox->untagged_fetch_contexts);
+}
+
+static int imapc_sync(struct imapc_mailbox *mbox)
+{
+ struct imapc_sync_context *sync_ctx;
+ bool force = mbox->sync_fetch_first_uid != 0;
+
+ if ((mbox->box.flags & MAILBOX_FLAG_SAVEONLY) != 0) {
+ /* we're only saving mails here - no syncing actually wanted */
+ return 0;
+ }
+
+ if (imapc_sync_begin(mbox, &sync_ctx, force) < 0)
+ return -1;
+
+ if (!array_is_empty(&mbox->untagged_fetch_contexts))
+ imapc_sync_handle_untagged_fetches(mbox);
+
+ if (sync_ctx == NULL)
+ return 0;
+ if (imapc_sync_finish(&sync_ctx) < 0)
+ return -1;
+ return 0;
+}
+
+static void
+imapc_noop_if_needed(struct imapc_mailbox *mbox, enum mailbox_sync_flags flags)
+{
+ if (!mbox->initial_sync_done) {
+ /* we just SELECTed/EXAMINEd the mailbox, don't do another
+ NOOP. */
+ } else if ((flags & MAILBOX_SYNC_FLAG_FAST) == 0 &&
+ ((mbox->capabilities & IMAPC_CAPABILITY_IDLE) == 0 ||
+ (flags & MAILBOX_SYNC_FLAG_FULL_READ) != 0)) {
+ /* do NOOP to make sure we have the latest changes before
+ starting sync. this is necessary either because se don't
+ support IDLE at all, or because we want to be sure that we
+ have the latest changes (IDLE is started with a small delay,
+ so we might not actually even be in IDLE right not) */
+ imapc_mailbox_noop(mbox);
+ }
+}
+
+static bool imapc_mailbox_need_initial_fetch(struct imapc_mailbox *mbox)
+{
+ if (mbox->box.deleting) {
+ /* If the mailbox is about to be deleted there is no need to
+ expect initial fetch to be done */
+ return FALSE;
+ }
+ if ((mbox->box.flags & MAILBOX_FLAG_SAVEONLY) != 0) {
+ /* The mailbox is opened only for saving there is no need to
+ expect initial fetchting do be done. */
+ return FALSE;
+ }
+ return TRUE;
+}
+
+struct mailbox_sync_context *
+imapc_mailbox_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(box);
+ struct imapc_mailbox_list *list = mbox->storage->client->_list;
+ bool changes;
+ int ret = 0;
+
+ if (list != NULL) {
+ if (!list->refreshed_mailboxes &&
+ list->last_refreshed_mailboxes < ioloop_time)
+ list->refreshed_mailboxes_recently = FALSE;
+ }
+
+ imapc_noop_if_needed(mbox, flags);
+
+ if (imapc_storage_client_handle_auth_failure(mbox->storage->client))
+ ret = -1;
+ else if (!mbox->state_fetched_success && !mbox->state_fetching_uid1 &&
+ imapc_mailbox_need_initial_fetch(mbox)) {
+ /* initial FETCH failed already */
+ ret = -1;
+ }
+ if (imapc_mailbox_commit_delayed_trans(mbox, FALSE, &changes) < 0)
+ ret = -1;
+ if ((changes || mbox->sync_fetch_first_uid != 0 ||
+ index_mailbox_want_full_sync(&mbox->box, flags)) &&
+ ret == 0)
+ ret = imapc_sync(mbox);
+
+ return index_mailbox_sync_init(box, flags, ret < 0);
+}
+
+int imapc_mailbox_sync_deinit(struct mailbox_sync_context *ctx,
+ struct mailbox_sync_status *status_r)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(ctx->box);
+ int ret;
+
+ ret = index_mailbox_sync_deinit(ctx, status_r);
+ ctx = NULL;
+
+ if (mbox->client_box == NULL)
+ return ret;
+
+ imapc_client_mailbox_idle(mbox->client_box);
+ return ret;
+}
diff --git a/src/lib-storage/index/imapc/imapc-sync.h b/src/lib-storage/index/imapc/imapc-sync.h
new file mode 100644
index 0000000..54a0368
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-sync.h
@@ -0,0 +1,39 @@
+#ifndef IMAPC_SYNC_H
+#define IMAPC_SYNC_H
+
+struct mailbox;
+struct mailbox_sync_status;
+
+struct imapc_sync_store {
+ enum modify_type modify_type;
+ const char *flags;
+
+ ARRAY_TYPE(seq_range) uids;
+};
+
+struct imapc_sync_context {
+ struct imapc_mailbox *mbox;
+ struct mail_index_sync_ctx *index_sync_ctx;
+ struct mail_index_view *sync_view;
+ struct mail_index_transaction *trans;
+
+ const ARRAY_TYPE(keywords) *keywords;
+ ARRAY_TYPE(seq_range) expunged_uids;
+ unsigned int sync_command_count;
+
+ pool_t pool;
+ HASH_TABLE(struct imapc_sync_store *, struct imapc_sync_store *) stores;
+
+ uint32_t prev_uid1, prev_uid2;
+ enum modify_type prev_modify_type;
+ string_t *prev_flags;
+
+ bool failed:1;
+};
+
+struct mailbox_sync_context *
+imapc_mailbox_sync_init(struct mailbox *box, enum mailbox_sync_flags flags);
+int imapc_mailbox_sync_deinit(struct mailbox_sync_context *ctx,
+ struct mailbox_sync_status *status_r);
+
+#endif