summaryrefslogtreecommitdiffstats
path: root/src/imap
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap')
-rw-r--r--src/imap/Makefile.am130
-rw-r--r--src/imap/Makefile.in1213
-rw-r--r--src/imap/cmd-append.c957
-rw-r--r--src/imap/cmd-cancelupdate.c45
-rw-r--r--src/imap/cmd-capability.c14
-rw-r--r--src/imap/cmd-check.c14
-rw-r--r--src/imap/cmd-close.c36
-rw-r--r--src/imap/cmd-copy.c407
-rw-r--r--src/imap/cmd-create.c48
-rw-r--r--src/imap/cmd-delete.c59
-rw-r--r--src/imap/cmd-enable.c35
-rw-r--r--src/imap/cmd-examine.c9
-rw-r--r--src/imap/cmd-expunge.c68
-rw-r--r--src/imap/cmd-fetch.c391
-rw-r--r--src/imap/cmd-genurlauth.c54
-rw-r--r--src/imap/cmd-getmetadata.c559
-rw-r--r--src/imap/cmd-id.c27
-rw-r--r--src/imap/cmd-idle.c308
-rw-r--r--src/imap/cmd-list.c484
-rw-r--r--src/imap/cmd-logout.c24
-rw-r--r--src/imap/cmd-lsub.c9
-rw-r--r--src/imap/cmd-namespace.c99
-rw-r--r--src/imap/cmd-noop.c15
-rw-r--r--src/imap/cmd-notify.c597
-rw-r--r--src/imap/cmd-rename.c51
-rw-r--r--src/imap/cmd-resetkey.c97
-rw-r--r--src/imap/cmd-search.c49
-rw-r--r--src/imap/cmd-select.c426
-rw-r--r--src/imap/cmd-setmetadata.c378
-rw-r--r--src/imap/cmd-sort.c142
-rw-r--r--src/imap/cmd-status.c54
-rw-r--r--src/imap/cmd-store.c261
-rw-r--r--src/imap/cmd-subscribe.c87
-rw-r--r--src/imap/cmd-thread.c294
-rw-r--r--src/imap/cmd-unselect.c18
-rw-r--r--src/imap/cmd-unsubscribe.c9
-rw-r--r--src/imap/cmd-urlfetch.c410
-rw-r--r--src/imap/cmd-x-cancel.c28
-rw-r--r--src/imap/cmd-x-state.c68
-rw-r--r--src/imap/imap-client-hibernate.c294
-rw-r--r--src/imap/imap-client.c1681
-rw-r--r--src/imap/imap-client.h362
-rw-r--r--src/imap/imap-commands-util.c402
-rw-r--r--src/imap/imap-commands-util.h81
-rw-r--r--src/imap/imap-commands.c247
-rw-r--r--src/imap/imap-commands.h137
-rw-r--r--src/imap/imap-common.h40
-rw-r--r--src/imap/imap-expunge.c111
-rw-r--r--src/imap/imap-expunge.h10
-rw-r--r--src/imap/imap-feature.c46
-rw-r--r--src/imap/imap-feature.h24
-rw-r--r--src/imap/imap-fetch-body.c722
-rw-r--r--src/imap/imap-fetch.c1040
-rw-r--r--src/imap/imap-fetch.h166
-rw-r--r--src/imap/imap-list.c35
-rw-r--r--src/imap/imap-list.h7
-rw-r--r--src/imap/imap-master-client.c448
-rw-r--r--src/imap/imap-master-client.h10
-rw-r--r--src/imap/imap-notify.c523
-rw-r--r--src/imap/imap-notify.h75
-rw-r--r--src/imap/imap-search-args.c353
-rw-r--r--src/imap/imap-search-args.h47
-rw-r--r--src/imap/imap-search.c612
-rw-r--r--src/imap/imap-search.h61
-rw-r--r--src/imap/imap-settings.c197
-rw-r--r--src/imap/imap-settings.h49
-rw-r--r--src/imap/imap-state.c897
-rw-r--r--src/imap/imap-state.h30
-rw-r--r--src/imap/imap-status.c172
-rw-r--r--src/imap/imap-status.h51
-rw-r--r--src/imap/imap-sync-private.h47
-rw-r--r--src/imap/imap-sync.c841
-rw-r--r--src/imap/imap-sync.h25
-rw-r--r--src/imap/mail-storage-callbacks.c45
-rw-r--r--src/imap/main.c591
-rw-r--r--src/imap/test-imap-client-hibernate.c292
76 files changed, 18745 insertions, 0 deletions
diff --git a/src/imap/Makefile.am b/src/imap/Makefile.am
new file mode 100644
index 0000000..0a45fd3
--- /dev/null
+++ b/src/imap/Makefile.am
@@ -0,0 +1,130 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = imap
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-smtp \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-urlauth \
+ -I$(top_srcdir)/src/lib-imap-storage \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ $(BINARY_CFLAGS)
+
+imap_LDFLAGS = -export-dynamic \
+ $(BINARY_LDFLAGS)
+
+imap_LDADD = \
+ ../lib-imap-urlauth/libimap-urlauth.la \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+imap_DEPENDENCIES = \
+ ../lib-imap-urlauth/libimap-urlauth.la \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+cmds = \
+ cmd-append.c \
+ cmd-capability.c \
+ cmd-cancelupdate.c \
+ cmd-check.c \
+ cmd-close.c \
+ cmd-copy.c \
+ cmd-create.c \
+ cmd-delete.c \
+ cmd-enable.c \
+ cmd-examine.c \
+ cmd-expunge.c \
+ cmd-fetch.c \
+ cmd-genurlauth.c \
+ cmd-getmetadata.c \
+ cmd-id.c \
+ cmd-idle.c \
+ cmd-list.c \
+ cmd-logout.c \
+ cmd-lsub.c \
+ cmd-namespace.c \
+ cmd-noop.c \
+ cmd-notify.c \
+ cmd-rename.c \
+ cmd-resetkey.c \
+ cmd-search.c \
+ cmd-select.c \
+ cmd-setmetadata.c \
+ cmd-sort.c \
+ cmd-status.c \
+ cmd-store.c \
+ cmd-subscribe.c \
+ cmd-thread.c \
+ cmd-unselect.c \
+ cmd-unsubscribe.c \
+ cmd-urlfetch.c \
+ cmd-x-cancel.c \
+ cmd-x-state.c
+
+common_sources = \
+ $(cmds) \
+ imap-client.c \
+ imap-client-hibernate.c \
+ imap-commands.c \
+ imap-commands-util.c \
+ imap-expunge.c \
+ imap-feature.c \
+ imap-fetch.c \
+ imap-fetch-body.c \
+ imap-list.c \
+ imap-master-client.c \
+ imap-notify.c \
+ imap-search.c \
+ imap-search-args.c \
+ imap-settings.c \
+ imap-status.c \
+ imap-state.c \
+ imap-sync.c \
+ mail-storage-callbacks.c
+
+imap_SOURCES = \
+ $(common_sources) \
+ main.c
+
+headers = \
+ imap-client.h \
+ imap-commands.h \
+ imap-commands-util.h \
+ imap-common.h \
+ imap-expunge.h \
+ imap-feature.h \
+ imap-fetch.h \
+ imap-list.h \
+ imap-master-client.h \
+ imap-notify.h \
+ imap-search.h \
+ imap-search-args.h \
+ imap-settings.h \
+ imap-status.h \
+ imap-state.h \
+ imap-sync.h \
+ imap-sync-private.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+ test-imap-client-hibernate
+noinst_PROGRAMS = $(test_programs)
+
+test_imap_client_hibernate_SOURCES = \
+ test-imap-client-hibernate.c $(common_sources)
+test_imap_client_hibernate_LDADD = $(imap_LDADD)
+test_imap_client_hibernate_DEPENDENCIES = $(imap_DEPENDENCIES)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/imap/Makefile.in b/src/imap/Makefile.in
new file mode 100644
index 0000000..f6211e2
--- /dev/null
+++ b/src/imap/Makefile.in
@@ -0,0 +1,1213 @@
+# 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@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+pkglibexec_PROGRAMS = imap$(EXEEXT)
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/imap
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-imap-client-hibernate$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS)
+am__objects_1 = cmd-append.$(OBJEXT) cmd-capability.$(OBJEXT) \
+ cmd-cancelupdate.$(OBJEXT) cmd-check.$(OBJEXT) \
+ cmd-close.$(OBJEXT) cmd-copy.$(OBJEXT) cmd-create.$(OBJEXT) \
+ cmd-delete.$(OBJEXT) cmd-enable.$(OBJEXT) \
+ cmd-examine.$(OBJEXT) cmd-expunge.$(OBJEXT) \
+ cmd-fetch.$(OBJEXT) cmd-genurlauth.$(OBJEXT) \
+ cmd-getmetadata.$(OBJEXT) cmd-id.$(OBJEXT) cmd-idle.$(OBJEXT) \
+ cmd-list.$(OBJEXT) cmd-logout.$(OBJEXT) cmd-lsub.$(OBJEXT) \
+ cmd-namespace.$(OBJEXT) cmd-noop.$(OBJEXT) \
+ cmd-notify.$(OBJEXT) cmd-rename.$(OBJEXT) \
+ cmd-resetkey.$(OBJEXT) cmd-search.$(OBJEXT) \
+ cmd-select.$(OBJEXT) cmd-setmetadata.$(OBJEXT) \
+ cmd-sort.$(OBJEXT) cmd-status.$(OBJEXT) cmd-store.$(OBJEXT) \
+ cmd-subscribe.$(OBJEXT) cmd-thread.$(OBJEXT) \
+ cmd-unselect.$(OBJEXT) cmd-unsubscribe.$(OBJEXT) \
+ cmd-urlfetch.$(OBJEXT) cmd-x-cancel.$(OBJEXT) \
+ cmd-x-state.$(OBJEXT)
+am__objects_2 = $(am__objects_1) imap-client.$(OBJEXT) \
+ imap-client-hibernate.$(OBJEXT) imap-commands.$(OBJEXT) \
+ imap-commands-util.$(OBJEXT) imap-expunge.$(OBJEXT) \
+ imap-feature.$(OBJEXT) imap-fetch.$(OBJEXT) \
+ imap-fetch-body.$(OBJEXT) imap-list.$(OBJEXT) \
+ imap-master-client.$(OBJEXT) imap-notify.$(OBJEXT) \
+ imap-search.$(OBJEXT) imap-search-args.$(OBJEXT) \
+ imap-settings.$(OBJEXT) imap-status.$(OBJEXT) \
+ imap-state.$(OBJEXT) imap-sync.$(OBJEXT) \
+ mail-storage-callbacks.$(OBJEXT)
+am_imap_OBJECTS = $(am__objects_2) main.$(OBJEXT)
+imap_OBJECTS = $(am_imap_OBJECTS)
+am__DEPENDENCIES_1 =
+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 =
+imap_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(imap_LDFLAGS) $(LDFLAGS) -o $@
+am_test_imap_client_hibernate_OBJECTS = \
+ test-imap-client-hibernate.$(OBJEXT) $(am__objects_2)
+test_imap_client_hibernate_OBJECTS = \
+ $(am_test_imap_client_hibernate_OBJECTS)
+am__DEPENDENCIES_2 = ../lib-imap-urlauth/libimap-urlauth.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-append.Po \
+ ./$(DEPDIR)/cmd-cancelupdate.Po ./$(DEPDIR)/cmd-capability.Po \
+ ./$(DEPDIR)/cmd-check.Po ./$(DEPDIR)/cmd-close.Po \
+ ./$(DEPDIR)/cmd-copy.Po ./$(DEPDIR)/cmd-create.Po \
+ ./$(DEPDIR)/cmd-delete.Po ./$(DEPDIR)/cmd-enable.Po \
+ ./$(DEPDIR)/cmd-examine.Po ./$(DEPDIR)/cmd-expunge.Po \
+ ./$(DEPDIR)/cmd-fetch.Po ./$(DEPDIR)/cmd-genurlauth.Po \
+ ./$(DEPDIR)/cmd-getmetadata.Po ./$(DEPDIR)/cmd-id.Po \
+ ./$(DEPDIR)/cmd-idle.Po ./$(DEPDIR)/cmd-list.Po \
+ ./$(DEPDIR)/cmd-logout.Po ./$(DEPDIR)/cmd-lsub.Po \
+ ./$(DEPDIR)/cmd-namespace.Po ./$(DEPDIR)/cmd-noop.Po \
+ ./$(DEPDIR)/cmd-notify.Po ./$(DEPDIR)/cmd-rename.Po \
+ ./$(DEPDIR)/cmd-resetkey.Po ./$(DEPDIR)/cmd-search.Po \
+ ./$(DEPDIR)/cmd-select.Po ./$(DEPDIR)/cmd-setmetadata.Po \
+ ./$(DEPDIR)/cmd-sort.Po ./$(DEPDIR)/cmd-status.Po \
+ ./$(DEPDIR)/cmd-store.Po ./$(DEPDIR)/cmd-subscribe.Po \
+ ./$(DEPDIR)/cmd-thread.Po ./$(DEPDIR)/cmd-unselect.Po \
+ ./$(DEPDIR)/cmd-unsubscribe.Po ./$(DEPDIR)/cmd-urlfetch.Po \
+ ./$(DEPDIR)/cmd-x-cancel.Po ./$(DEPDIR)/cmd-x-state.Po \
+ ./$(DEPDIR)/imap-client-hibernate.Po \
+ ./$(DEPDIR)/imap-client.Po ./$(DEPDIR)/imap-commands-util.Po \
+ ./$(DEPDIR)/imap-commands.Po ./$(DEPDIR)/imap-expunge.Po \
+ ./$(DEPDIR)/imap-feature.Po ./$(DEPDIR)/imap-fetch-body.Po \
+ ./$(DEPDIR)/imap-fetch.Po ./$(DEPDIR)/imap-list.Po \
+ ./$(DEPDIR)/imap-master-client.Po ./$(DEPDIR)/imap-notify.Po \
+ ./$(DEPDIR)/imap-search-args.Po ./$(DEPDIR)/imap-search.Po \
+ ./$(DEPDIR)/imap-settings.Po ./$(DEPDIR)/imap-state.Po \
+ ./$(DEPDIR)/imap-status.Po ./$(DEPDIR)/imap-sync.Po \
+ ./$(DEPDIR)/mail-storage-callbacks.Po ./$(DEPDIR)/main.Po \
+ ./$(DEPDIR)/test-imap-client-hibernate.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(imap_SOURCES) $(test_imap_client_hibernate_SOURCES)
+DIST_SOURCES = $(imap_SOURCES) $(test_imap_client_hibernate_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; }; \
+ }
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+pkglibexecdir = $(libexecdir)/dovecot
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-smtp \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-urlauth \
+ -I$(top_srcdir)/src/lib-imap-storage \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ $(BINARY_CFLAGS)
+
+imap_LDFLAGS = -export-dynamic \
+ $(BINARY_LDFLAGS)
+
+imap_LDADD = \
+ ../lib-imap-urlauth/libimap-urlauth.la \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+
+imap_DEPENDENCIES = \
+ ../lib-imap-urlauth/libimap-urlauth.la \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+cmds = \
+ cmd-append.c \
+ cmd-capability.c \
+ cmd-cancelupdate.c \
+ cmd-check.c \
+ cmd-close.c \
+ cmd-copy.c \
+ cmd-create.c \
+ cmd-delete.c \
+ cmd-enable.c \
+ cmd-examine.c \
+ cmd-expunge.c \
+ cmd-fetch.c \
+ cmd-genurlauth.c \
+ cmd-getmetadata.c \
+ cmd-id.c \
+ cmd-idle.c \
+ cmd-list.c \
+ cmd-logout.c \
+ cmd-lsub.c \
+ cmd-namespace.c \
+ cmd-noop.c \
+ cmd-notify.c \
+ cmd-rename.c \
+ cmd-resetkey.c \
+ cmd-search.c \
+ cmd-select.c \
+ cmd-setmetadata.c \
+ cmd-sort.c \
+ cmd-status.c \
+ cmd-store.c \
+ cmd-subscribe.c \
+ cmd-thread.c \
+ cmd-unselect.c \
+ cmd-unsubscribe.c \
+ cmd-urlfetch.c \
+ cmd-x-cancel.c \
+ cmd-x-state.c
+
+common_sources = \
+ $(cmds) \
+ imap-client.c \
+ imap-client-hibernate.c \
+ imap-commands.c \
+ imap-commands-util.c \
+ imap-expunge.c \
+ imap-feature.c \
+ imap-fetch.c \
+ imap-fetch-body.c \
+ imap-list.c \
+ imap-master-client.c \
+ imap-notify.c \
+ imap-search.c \
+ imap-search-args.c \
+ imap-settings.c \
+ imap-status.c \
+ imap-state.c \
+ imap-sync.c \
+ mail-storage-callbacks.c
+
+imap_SOURCES = \
+ $(common_sources) \
+ main.c
+
+headers = \
+ imap-client.h \
+ imap-commands.h \
+ imap-commands-util.h \
+ imap-common.h \
+ imap-expunge.h \
+ imap-feature.h \
+ imap-fetch.h \
+ imap-list.h \
+ imap-master-client.h \
+ imap-notify.h \
+ imap-search.h \
+ imap-search-args.h \
+ imap-settings.h \
+ imap-status.h \
+ imap-state.h \
+ imap-sync.h \
+ imap-sync-private.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+test_programs = \
+ test-imap-client-hibernate
+
+test_imap_client_hibernate_SOURCES = \
+ test-imap-client-hibernate.c $(common_sources)
+
+test_imap_client_hibernate_LDADD = $(imap_LDADD)
+test_imap_client_hibernate_DEPENDENCIES = $(imap_DEPENDENCIES)
+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/imap/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/imap/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files
+
+clean-pkglibexecPROGRAMS:
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+imap$(EXEEXT): $(imap_OBJECTS) $(imap_DEPENDENCIES) $(EXTRA_imap_DEPENDENCIES)
+ @rm -f imap$(EXEEXT)
+ $(AM_V_CCLD)$(imap_LINK) $(imap_OBJECTS) $(imap_LDADD) $(LIBS)
+
+test-imap-client-hibernate$(EXEEXT): $(test_imap_client_hibernate_OBJECTS) $(test_imap_client_hibernate_DEPENDENCIES) $(EXTRA_test_imap_client_hibernate_DEPENDENCIES)
+ @rm -f test-imap-client-hibernate$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_client_hibernate_OBJECTS) $(test_imap_client_hibernate_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-append.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-cancelupdate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-capability.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-check.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-close.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-copy.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-create.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-delete.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-enable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-examine.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-expunge.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-fetch.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-genurlauth.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-getmetadata.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-id.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-idle.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-list.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-logout.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-lsub.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-namespace.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-noop.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-notify.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-rename.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-resetkey.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-search.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-select.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-setmetadata.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-sort.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-status.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-store.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-subscribe.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-thread.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-unselect.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-unsubscribe.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-urlfetch.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-x-cancel.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-x-state.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-client-hibernate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-client.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-commands-util.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-commands.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-expunge.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-feature.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-fetch-body.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-fetch.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-list.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-master-client.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-notify.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-search-args.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-search.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-state.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-status.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-sync.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-storage-callbacks.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-client-hibernate.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ clean-pkglibexecPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-append.Po
+ -rm -f ./$(DEPDIR)/cmd-cancelupdate.Po
+ -rm -f ./$(DEPDIR)/cmd-capability.Po
+ -rm -f ./$(DEPDIR)/cmd-check.Po
+ -rm -f ./$(DEPDIR)/cmd-close.Po
+ -rm -f ./$(DEPDIR)/cmd-copy.Po
+ -rm -f ./$(DEPDIR)/cmd-create.Po
+ -rm -f ./$(DEPDIR)/cmd-delete.Po
+ -rm -f ./$(DEPDIR)/cmd-enable.Po
+ -rm -f ./$(DEPDIR)/cmd-examine.Po
+ -rm -f ./$(DEPDIR)/cmd-expunge.Po
+ -rm -f ./$(DEPDIR)/cmd-fetch.Po
+ -rm -f ./$(DEPDIR)/cmd-genurlauth.Po
+ -rm -f ./$(DEPDIR)/cmd-getmetadata.Po
+ -rm -f ./$(DEPDIR)/cmd-id.Po
+ -rm -f ./$(DEPDIR)/cmd-idle.Po
+ -rm -f ./$(DEPDIR)/cmd-list.Po
+ -rm -f ./$(DEPDIR)/cmd-logout.Po
+ -rm -f ./$(DEPDIR)/cmd-lsub.Po
+ -rm -f ./$(DEPDIR)/cmd-namespace.Po
+ -rm -f ./$(DEPDIR)/cmd-noop.Po
+ -rm -f ./$(DEPDIR)/cmd-notify.Po
+ -rm -f ./$(DEPDIR)/cmd-rename.Po
+ -rm -f ./$(DEPDIR)/cmd-resetkey.Po
+ -rm -f ./$(DEPDIR)/cmd-search.Po
+ -rm -f ./$(DEPDIR)/cmd-select.Po
+ -rm -f ./$(DEPDIR)/cmd-setmetadata.Po
+ -rm -f ./$(DEPDIR)/cmd-sort.Po
+ -rm -f ./$(DEPDIR)/cmd-status.Po
+ -rm -f ./$(DEPDIR)/cmd-store.Po
+ -rm -f ./$(DEPDIR)/cmd-subscribe.Po
+ -rm -f ./$(DEPDIR)/cmd-thread.Po
+ -rm -f ./$(DEPDIR)/cmd-unselect.Po
+ -rm -f ./$(DEPDIR)/cmd-unsubscribe.Po
+ -rm -f ./$(DEPDIR)/cmd-urlfetch.Po
+ -rm -f ./$(DEPDIR)/cmd-x-cancel.Po
+ -rm -f ./$(DEPDIR)/cmd-x-state.Po
+ -rm -f ./$(DEPDIR)/imap-client-hibernate.Po
+ -rm -f ./$(DEPDIR)/imap-client.Po
+ -rm -f ./$(DEPDIR)/imap-commands-util.Po
+ -rm -f ./$(DEPDIR)/imap-commands.Po
+ -rm -f ./$(DEPDIR)/imap-expunge.Po
+ -rm -f ./$(DEPDIR)/imap-feature.Po
+ -rm -f ./$(DEPDIR)/imap-fetch-body.Po
+ -rm -f ./$(DEPDIR)/imap-fetch.Po
+ -rm -f ./$(DEPDIR)/imap-list.Po
+ -rm -f ./$(DEPDIR)/imap-master-client.Po
+ -rm -f ./$(DEPDIR)/imap-notify.Po
+ -rm -f ./$(DEPDIR)/imap-search-args.Po
+ -rm -f ./$(DEPDIR)/imap-search.Po
+ -rm -f ./$(DEPDIR)/imap-settings.Po
+ -rm -f ./$(DEPDIR)/imap-state.Po
+ -rm -f ./$(DEPDIR)/imap-status.Po
+ -rm -f ./$(DEPDIR)/imap-sync.Po
+ -rm -f ./$(DEPDIR)/mail-storage-callbacks.Po
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/test-imap-client-hibernate.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibexecPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-append.Po
+ -rm -f ./$(DEPDIR)/cmd-cancelupdate.Po
+ -rm -f ./$(DEPDIR)/cmd-capability.Po
+ -rm -f ./$(DEPDIR)/cmd-check.Po
+ -rm -f ./$(DEPDIR)/cmd-close.Po
+ -rm -f ./$(DEPDIR)/cmd-copy.Po
+ -rm -f ./$(DEPDIR)/cmd-create.Po
+ -rm -f ./$(DEPDIR)/cmd-delete.Po
+ -rm -f ./$(DEPDIR)/cmd-enable.Po
+ -rm -f ./$(DEPDIR)/cmd-examine.Po
+ -rm -f ./$(DEPDIR)/cmd-expunge.Po
+ -rm -f ./$(DEPDIR)/cmd-fetch.Po
+ -rm -f ./$(DEPDIR)/cmd-genurlauth.Po
+ -rm -f ./$(DEPDIR)/cmd-getmetadata.Po
+ -rm -f ./$(DEPDIR)/cmd-id.Po
+ -rm -f ./$(DEPDIR)/cmd-idle.Po
+ -rm -f ./$(DEPDIR)/cmd-list.Po
+ -rm -f ./$(DEPDIR)/cmd-logout.Po
+ -rm -f ./$(DEPDIR)/cmd-lsub.Po
+ -rm -f ./$(DEPDIR)/cmd-namespace.Po
+ -rm -f ./$(DEPDIR)/cmd-noop.Po
+ -rm -f ./$(DEPDIR)/cmd-notify.Po
+ -rm -f ./$(DEPDIR)/cmd-rename.Po
+ -rm -f ./$(DEPDIR)/cmd-resetkey.Po
+ -rm -f ./$(DEPDIR)/cmd-search.Po
+ -rm -f ./$(DEPDIR)/cmd-select.Po
+ -rm -f ./$(DEPDIR)/cmd-setmetadata.Po
+ -rm -f ./$(DEPDIR)/cmd-sort.Po
+ -rm -f ./$(DEPDIR)/cmd-status.Po
+ -rm -f ./$(DEPDIR)/cmd-store.Po
+ -rm -f ./$(DEPDIR)/cmd-subscribe.Po
+ -rm -f ./$(DEPDIR)/cmd-thread.Po
+ -rm -f ./$(DEPDIR)/cmd-unselect.Po
+ -rm -f ./$(DEPDIR)/cmd-unsubscribe.Po
+ -rm -f ./$(DEPDIR)/cmd-urlfetch.Po
+ -rm -f ./$(DEPDIR)/cmd-x-cancel.Po
+ -rm -f ./$(DEPDIR)/cmd-x-state.Po
+ -rm -f ./$(DEPDIR)/imap-client-hibernate.Po
+ -rm -f ./$(DEPDIR)/imap-client.Po
+ -rm -f ./$(DEPDIR)/imap-commands-util.Po
+ -rm -f ./$(DEPDIR)/imap-commands.Po
+ -rm -f ./$(DEPDIR)/imap-expunge.Po
+ -rm -f ./$(DEPDIR)/imap-feature.Po
+ -rm -f ./$(DEPDIR)/imap-fetch-body.Po
+ -rm -f ./$(DEPDIR)/imap-fetch.Po
+ -rm -f ./$(DEPDIR)/imap-list.Po
+ -rm -f ./$(DEPDIR)/imap-master-client.Po
+ -rm -f ./$(DEPDIR)/imap-notify.Po
+ -rm -f ./$(DEPDIR)/imap-search-args.Po
+ -rm -f ./$(DEPDIR)/imap-search.Po
+ -rm -f ./$(DEPDIR)/imap-settings.Po
+ -rm -f ./$(DEPDIR)/imap-state.Po
+ -rm -f ./$(DEPDIR)/imap-status.Po
+ -rm -f ./$(DEPDIR)/imap-sync.Po
+ -rm -f ./$(DEPDIR)/mail-storage-callbacks.Po
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/test-imap-client-hibernate.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-noinstPROGRAMS clean-pkglibexecPROGRAMS cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-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-pkglibexecPROGRAMS \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/imap/cmd-append.c b/src/imap/cmd-append.c
new file mode 100644
index 0000000..0d0c1c3
--- /dev/null
+++ b/src/imap/cmd-append.c
@@ -0,0 +1,957 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "istream-chain.h"
+#include "ostream.h"
+#include "str.h"
+#include "imap-resp-code.h"
+#include "istream-binary-converter.h"
+#include "mail-storage-private.h"
+#include "imap-parser.h"
+#include "imap-date.h"
+#include "imap-util.h"
+#include "imap-commands.h"
+#include "imap-msgpart-url.h"
+
+#include <sys/time.h>
+
+/* Don't allow internaldates to be too far in the future. At least with Maildir
+ they can cause problems with incremental backups since internaldate is
+ stored in file's mtime. But perhaps there are also some other reasons why
+ it might not be wanted. */
+#define INTERNALDATE_MAX_FUTURE_SECS (2*3600)
+
+struct cmd_append_context {
+ struct client *client;
+ struct client_command_context *cmd;
+ struct mailbox *box;
+ struct mailbox_transaction_context *t;
+ time_t started;
+
+ struct istream_chain *catchain;
+ uoff_t cat_msg_size;
+
+ struct istream *input;
+ struct istream *litinput;
+ uoff_t literal_size;
+
+ struct imap_parser *save_parser;
+ struct mail_save_context *save_ctx;
+ unsigned int count;
+
+ bool message_input:1;
+ bool binary_input:1;
+ bool catenate:1;
+ bool cmd_args_set:1;
+ bool failed:1;
+};
+
+static void cmd_append_finish(struct cmd_append_context *ctx);
+static bool cmd_append_continue_message(struct client_command_context *cmd);
+static bool cmd_append_parse_new_msg(struct client_command_context *cmd);
+
+static const char *
+get_disconnect_reason(struct cmd_append_context *ctx, uoff_t lit_offset)
+{
+ string_t *str = t_str_new(128);
+ unsigned int secs = ioloop_time - ctx->started;
+
+ str_printfa(str, "%s (While APPENDing: %u msgs, %u secs",
+ i_stream_get_disconnect_reason(ctx->input),
+ ctx->count, secs);
+ if (ctx->literal_size > 0) {
+ str_printfa(str, ", %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes",
+ lit_offset, ctx->literal_size);
+ }
+ str_append_c(str, ')');
+ return str_c(str);
+}
+
+static void client_input_append(struct client_command_context *cmd)
+{
+ struct cmd_append_context *ctx = cmd->context;
+ struct client *client = cmd->client;
+ const char *reason;
+ bool finished;
+ uoff_t lit_offset;
+
+ i_assert(!client->destroyed);
+
+ client->last_input = ioloop_time;
+ timeout_reset(client->to_idle);
+
+ switch (i_stream_read(client->input)) {
+ case -1:
+ /* disconnected */
+ lit_offset = ctx->litinput == NULL ? 0 :
+ ctx->litinput->v_offset;
+ reason = get_disconnect_reason(ctx, lit_offset);
+ cmd_append_finish(cmd->context);
+ /* Reset command so that client_destroy() doesn't try to call
+ cmd_append_continue_message() anymore. */
+ client_command_free(&cmd);
+ client_destroy(client, reason);
+ return;
+ case -2:
+ if (ctx->message_input) {
+ /* message data, this is handled internally by
+ mailbox_save_continue() */
+ break;
+ }
+ cmd_append_finish(cmd->context);
+
+ /* parameter word is longer than max. input buffer size.
+ this is most likely an error, so skip the new data
+ until newline is found. */
+ client->input_skip_line = TRUE;
+
+ if (!ctx->failed)
+ client_send_command_error(cmd, "Too long argument.");
+ cmd->param_error = TRUE;
+ client_command_free(&cmd);
+ return;
+ }
+
+ o_stream_cork(client->output);
+ finished = command_exec(cmd);
+ if (!finished)
+ (void)client_handle_unfinished_cmd(cmd);
+ else
+ client_command_free(&cmd);
+ cmd_sync_delayed(client);
+ o_stream_uncork(client->output);
+
+ client_continue_pending_input(client);
+}
+
+static void cmd_append_finish(struct cmd_append_context *ctx)
+{
+ if (ctx->save_parser != NULL)
+ imap_parser_unref(&ctx->save_parser);
+
+ i_assert(ctx->client->input_lock == ctx->cmd);
+
+ io_remove(&ctx->client->io);
+ /* we must put back the original flush callback before beginning to
+ sync (the command is still unfinished at that point) */
+ o_stream_set_flush_callback(ctx->client->output,
+ client_output, ctx->client);
+
+ i_stream_unref(&ctx->litinput);
+ i_stream_unref(&ctx->input);
+ if (ctx->save_ctx != NULL)
+ mailbox_save_cancel(&ctx->save_ctx);
+ if (ctx->t != NULL)
+ mailbox_transaction_rollback(&ctx->t);
+ if (ctx->box != ctx->cmd->client->mailbox && ctx->box != NULL)
+ mailbox_free(&ctx->box);
+}
+
+static bool cmd_append_send_literal_continue(struct cmd_append_context *ctx)
+{
+ if (ctx->failed) {
+ /* tagline was already sent, we can abort here */
+ return FALSE;
+ }
+
+ o_stream_nsend(ctx->client->output, "+ OK\r\n", 6);
+ o_stream_uncork(ctx->client->output);
+ o_stream_cork(ctx->client->output);
+ return TRUE;
+}
+
+static int
+cmd_append_catenate_mpurl(struct client_command_context *cmd,
+ const char *caturl, struct imap_msgpart_url *mpurl)
+{
+ struct cmd_append_context *ctx = cmd->context;
+ struct imap_msgpart_open_result mpresult;
+ uoff_t newsize;
+ const char *client_error;
+ int ret;
+
+ /* catenate URL */
+ ret = imap_msgpart_url_read_part(mpurl, &mpresult, &client_error);
+ if (ret < 0) {
+ client_send_box_error(cmd, ctx->box);
+ return -1;
+ }
+ if (ret == 0) {
+ /* invalid url, abort */
+ client_send_tagline(cmd,
+ t_strdup_printf("NO [BADURL %s] %s.",
+ caturl, client_error));
+ return -1;
+ }
+ if (mpresult.size == 0) {
+ /* empty input */
+ return 0;
+ }
+
+ newsize = ctx->cat_msg_size + mpresult.size;
+ if (newsize < ctx->cat_msg_size) {
+ client_send_tagline(cmd,
+ "NO [TOOBIG] Composed message grows too big.");
+ return -1;
+ }
+
+ ctx->cat_msg_size = newsize;
+ /* add this input stream to chain */
+ i_stream_chain_append(ctx->catchain, mpresult.input);
+ /* save by reading the chain stream */
+ do {
+ ret = i_stream_read(mpresult.input);
+ i_assert(ret != 0); /* we can handle only blocking input here */
+ } while (mailbox_save_continue(ctx->save_ctx) == 0 && ret != -1);
+
+ if (mpresult.input->stream_errno != 0) {
+ mailbox_set_critical(ctx->box,
+ "read(%s) failed: %s (for CATENATE URL %s)",
+ i_stream_get_name(mpresult.input),
+ i_stream_get_error(mpresult.input), caturl);
+ client_send_box_error(cmd, ctx->box);
+ ret = -1;
+ } else if (!mpresult.input->eof) {
+ /* save failed */
+ client_send_box_error(cmd, ctx->box);
+ ret = -1;
+ } else {
+ /* all the input must be consumed, so istream-chain's read()
+ unreferences the stream and we can free its parent mail */
+ i_assert(!i_stream_have_bytes_left(mpresult.input));
+ ret = 0;
+ }
+ return ret;
+}
+
+static int
+cmd_append_catenate_url(struct client_command_context *cmd, const char *caturl)
+{
+ struct cmd_append_context *ctx = cmd->context;
+ struct imap_msgpart_url *mpurl;
+ const char *client_error;
+ int ret;
+
+ if (ctx->failed)
+ return -1;
+
+ ret = imap_msgpart_url_parse(cmd->client->user, cmd->client->mailbox,
+ caturl, &mpurl, &client_error);
+ if (ret < 0) {
+ client_send_box_error(cmd, ctx->box);
+ return -1;
+ }
+ if (ret == 0) {
+ /* invalid url, abort */
+ client_send_tagline(cmd,
+ t_strdup_printf("NO [BADURL %s] %s.",
+ caturl, client_error));
+ return -1;
+ }
+ ret = cmd_append_catenate_mpurl(cmd, caturl, mpurl);
+ imap_msgpart_url_free(&mpurl);
+ return ret;
+}
+
+static void cmd_append_catenate_text(struct client_command_context *cmd)
+{
+ struct cmd_append_context *ctx = cmd->context;
+
+ if (ctx->literal_size > UOFF_T_MAX - ctx->cat_msg_size &&
+ !ctx->failed) {
+ client_send_tagline(cmd,
+ "NO [TOOBIG] Composed message grows too big.");
+ ctx->failed = TRUE;
+ }
+
+ /* save the mail */
+ ctx->cat_msg_size += ctx->literal_size;
+ if (ctx->literal_size == 0) {
+ /* zero length literal. RFC doesn't explicitly specify
+ what should be done with this, so we'll simply
+ handle it by skipping the empty text part. */
+ ctx->litinput = i_stream_create_from_data("", 0);
+ ctx->litinput->eof = TRUE;
+ } else {
+ ctx->litinput = i_stream_create_limit(cmd->client->input,
+ ctx->literal_size);
+ i_stream_chain_append(ctx->catchain, ctx->litinput);
+ }
+}
+
+static int
+cmd_append_catenate(struct client_command_context *cmd,
+ const struct imap_arg *args, bool *nonsync_r)
+{
+ struct cmd_append_context *ctx = cmd->context;
+ const char *catpart;
+
+ *nonsync_r = FALSE;
+
+ /* Handle URLs until a TEXT literal is encountered */
+ while (imap_arg_get_atom(args, &catpart)) {
+ const char *caturl;
+
+ if (strcasecmp(catpart, "URL") == 0 ) {
+ /* URL <url> */
+ args++;
+ if (!imap_arg_get_astring(args, &caturl))
+ break;
+ if (cmd_append_catenate_url(cmd, caturl) < 0) {
+ /* delay failure until we can stop
+ parsing input */
+ ctx->failed = TRUE;
+ }
+ } else if (strcasecmp(catpart, "TEXT") == 0) {
+ /* TEXT <literal> */
+ args++;
+ if (!imap_arg_get_literal_size(args, &ctx->literal_size))
+ break;
+ if (args->literal8 && !ctx->binary_input &&
+ !ctx->failed) {
+ client_send_tagline(cmd,
+ "NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] "
+ "Binary input allowed only when the first part is binary.");
+ ctx->failed = TRUE;
+ }
+ *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
+ cmd_append_catenate_text(cmd);
+ return 1;
+ } else {
+ break;
+ }
+ args++;
+ }
+
+ if (IMAP_ARG_IS_EOL(args)) {
+ /* ")" */
+ return 0;
+ }
+ if (!ctx->failed)
+ client_send_command_error(cmd, "Invalid arguments.");
+ return -1;
+}
+
+static void cmd_append_finish_catenate(struct client_command_context *cmd)
+{
+ struct cmd_append_context *ctx = cmd->context;
+
+ i_stream_chain_append_eof(ctx->catchain);
+ i_stream_unref(&ctx->input);
+ ctx->catenate = FALSE;
+ ctx->catchain = NULL;
+
+ if (ctx->failed) {
+ /* APPEND has already failed */
+ if (ctx->save_ctx != NULL)
+ mailbox_save_cancel(&ctx->save_ctx);
+ } else {
+ if (mailbox_save_finish(&ctx->save_ctx) < 0) {
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ }
+ }
+}
+
+static bool catenate_args_can_stop(struct cmd_append_context *ctx,
+ const struct imap_arg *args)
+{
+ /* eat away literal_sizes from URLs */
+ while (args->type != IMAP_ARG_EOL) {
+ if (imap_arg_atom_equals(args, "TEXT"))
+ return TRUE;
+ if (!imap_arg_atom_equals(args, "URL")) {
+ /* error - handle it later */
+ return TRUE;
+ }
+ args++;
+ if (args->type == IMAP_ARG_LITERAL_SIZE ||
+ args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC) {
+ if (args->type == IMAP_ARG_LITERAL_SIZE) {
+ if (!cmd_append_send_literal_continue(ctx))
+ return TRUE;
+ }
+ imap_parser_read_last_literal(ctx->save_parser);
+ return FALSE;
+ }
+ args++;
+ }
+ return TRUE;
+}
+
+static bool cmd_append_continue_catenate(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_append_context *ctx = cmd->context;
+ const struct imap_arg *args;
+ const char *client_error;
+ enum imap_parser_error parse_error;
+ bool nonsync = FALSE;
+ int ret;
+
+ if (cmd->cancel) {
+ /* cancel the command immediately (disconnection) */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ /* we're parsing inside CATENATE (..) list after handling a TEXT part.
+ it's fine that this would need to fully fit into input buffer
+ (although clients attempting to DoS could simply insert an extra
+ {1+} between the URLs) */
+ do {
+ ret = imap_parser_read_args(ctx->save_parser, 0,
+ IMAP_PARSE_FLAG_LITERAL_SIZE |
+ IMAP_PARSE_FLAG_LITERAL8 |
+ IMAP_PARSE_FLAG_INSIDE_LIST, &args);
+ } while (ret > 0 && !catenate_args_can_stop(ctx, args));
+ if (ret == -1) {
+ client_error = imap_parser_get_error(ctx->save_parser,
+ &parse_error);
+ switch (parse_error) {
+ case IMAP_PARSE_ERROR_NONE:
+ i_unreached();
+ case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+ client_send_line(client, t_strconcat("* BYE ",
+ (client->set->imap_literal_minus ? "[TOOBIG] " : ""),
+ client_error, NULL));
+ client_disconnect(client, client_error);
+ break;
+ default:
+ if (!ctx->failed)
+ client_send_command_error(cmd, client_error);
+ }
+ client->input_skip_line = TRUE;
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ if (ret < 0) {
+ /* need more data */
+ return FALSE;
+ }
+
+ if ((ret = cmd_append_catenate(cmd, args, &nonsync)) < 0) {
+ /* invalid parameters, abort immediately */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ if (ret == 0) {
+ /* ")" */
+ cmd_append_finish_catenate(cmd);
+
+ /* last catenate part */
+ imap_parser_reset(ctx->save_parser);
+ cmd->func = cmd_append_parse_new_msg;
+ return cmd_append_parse_new_msg(cmd);
+ }
+
+ /* TEXT <literal> */
+
+ if (!nonsync) {
+ if (!cmd_append_send_literal_continue(ctx)) {
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ }
+
+ i_assert(ctx->litinput != NULL);
+ ctx->message_input = TRUE;
+ cmd->func = cmd_append_continue_message;
+ return cmd_append_continue_message(cmd);
+}
+
+static int
+cmd_append_handle_args(struct client_command_context *cmd,
+ const struct imap_arg *args, bool *nonsync_r)
+{
+ struct client *client = cmd->client;
+ struct cmd_append_context *ctx = cmd->context;
+ const struct imap_arg *flags_list;
+ const struct imap_arg *cat_list = NULL;
+ enum mail_flags flags;
+ const char *const *keywords_list;
+ struct mail_keywords *keywords;
+ struct istream *input;
+ const char *internal_date_str;
+ time_t internal_date;
+ int ret, timezone_offset;
+ bool valid;
+
+ if (!ctx->cmd_args_set) {
+ ctx->cmd_args_set = TRUE;
+ client_args_finished(cmd, args);
+ }
+
+ /* [<flags>] */
+ if (!imap_arg_get_list(args, &flags_list))
+ flags_list = NULL;
+ else
+ args++;
+
+ /* [<internal date>] */
+ if (args->type != IMAP_ARG_STRING)
+ internal_date_str = NULL;
+ else {
+ internal_date_str = imap_arg_as_astring(args);
+ args++;
+ }
+
+ /* <message literal> | CATENATE (..) */
+ valid = FALSE;
+ *nonsync_r = FALSE;
+ ctx->catenate = FALSE;
+ if (imap_arg_get_literal_size(args, &ctx->literal_size)) {
+ *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
+ ctx->binary_input = args->literal8;
+ valid = TRUE;
+ } else if (!imap_arg_atom_equals(args, "CATENATE")) {
+ /* invalid */
+ } else if (!imap_arg_get_list(++args, &cat_list)) {
+ /* invalid */
+ } else {
+ valid = TRUE;
+ ctx->catenate = TRUE;
+ /* We'll do BINARY conversion only if the CATENATE's first
+ part is a literal8. If it doesn't and a literal8 is seen
+ later we'll abort the append with UNKNOWN-CTE. */
+ ctx->binary_input = imap_arg_atom_equals(&cat_list[0], "TEXT") &&
+ cat_list[1].literal8;
+
+ }
+ if (!IMAP_ARG_IS_EOL(&args[1]))
+ valid = FALSE;
+ if (!valid) {
+ client->input_skip_line = TRUE;
+ if (!ctx->failed)
+ client_send_command_error(cmd, "Invalid arguments.");
+ return -1;
+ }
+
+ if (flags_list == NULL || ctx->failed) {
+ flags = 0;
+ keywords = NULL;
+ } else {
+ if (!client_parse_mail_flags(cmd, flags_list,
+ &flags, &keywords_list))
+ return -1;
+ if (keywords_list == NULL)
+ keywords = NULL;
+ else if (mailbox_keywords_create(ctx->box, keywords_list,
+ &keywords) < 0) {
+ /* invalid keywords - delay failure */
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ keywords = NULL;
+ }
+ }
+
+ if (internal_date_str == NULL || ctx->failed) {
+ /* no time given, default to now. */
+ internal_date = (time_t)-1;
+ timezone_offset = 0;
+ } else if (!imap_parse_datetime(internal_date_str,
+ &internal_date, &timezone_offset)) {
+ client_send_command_error(cmd, "Invalid internal date.");
+ if (keywords != NULL)
+ mailbox_keywords_unref(&keywords);
+ return -1;
+ }
+
+ if (internal_date != (time_t)-1 &&
+ internal_date > ioloop_time + INTERNALDATE_MAX_FUTURE_SECS) {
+ /* the client specified a time in the future, set it to now. */
+ internal_date = (time_t)-1;
+ timezone_offset = 0;
+ }
+
+ if (cat_list != NULL) {
+ ctx->cat_msg_size = 0;
+ ctx->input = i_stream_create_chain(&ctx->catchain,
+ IO_BLOCK_SIZE);
+ } else {
+ if (ctx->literal_size == 0) {
+ /* no message data, abort */
+ if (!ctx->failed) {
+ client_send_tagline(cmd,
+ "NO Can't save a zero byte message.");
+ ctx->failed = TRUE;
+ }
+ if (!*nonsync_r) {
+ if (keywords != NULL)
+ mailbox_keywords_unref(&keywords);
+ return -1;
+ }
+ /* {0+} used. although there isn't any point in using
+ MULTIAPPEND here and adding more messages, it is
+ technically valid so we'll continue parsing.. */
+ }
+ ctx->litinput = i_stream_create_limit(client->input, ctx->literal_size);
+ ctx->input = ctx->litinput;
+ i_stream_ref(ctx->input);
+ }
+ if (ctx->binary_input) {
+ input = i_stream_create_binary_converter(ctx->input);
+ i_stream_unref(&ctx->input);
+ ctx->input = input;
+ }
+
+ if (!ctx->failed) {
+ /* save the mail */
+ ctx->save_ctx = mailbox_save_alloc(ctx->t);
+ mailbox_save_set_flags(ctx->save_ctx, flags, keywords);
+ mailbox_save_set_received_date(ctx->save_ctx,
+ internal_date, timezone_offset);
+ if (mailbox_save_begin(&ctx->save_ctx, ctx->input) < 0) {
+ /* save initialization failed */
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ }
+ }
+ if (keywords != NULL)
+ mailbox_keywords_unref(&keywords);
+ ctx->count++;
+
+ if (cat_list == NULL) {
+ /* normal APPEND */
+ return 1;
+ } else if (cat_list->type == IMAP_ARG_EOL) {
+ /* zero parts */
+ if (!ctx->failed)
+ client_send_command_error(cmd, "Empty CATENATE list.");
+ client->input_skip_line = TRUE;
+ return -1;
+ } else if ((ret = cmd_append_catenate(cmd, cat_list, nonsync_r)) < 0) {
+ /* invalid parameters, abort immediately */
+ return -1;
+ } else if (ret == 0) {
+ /* CATENATE consisted only of URLs */
+ return 0;
+ } else {
+ /* TEXT part found from CATENATE */
+ return 1;
+ }
+}
+
+static bool cmd_append_finish_parsing(struct client_command_context *cmd)
+{
+ struct cmd_append_context *ctx = cmd->context;
+ enum mailbox_sync_flags sync_flags;
+ enum imap_sync_flags imap_flags;
+ struct mail_transaction_commit_changes changes;
+ unsigned int save_count;
+ string_t *msg;
+ int ret;
+
+ /* eat away the trailing CRLF */
+ cmd->client->input_skip_line = TRUE;
+
+ if (ctx->failed) {
+ /* we failed earlier, error message is sent */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ if (ctx->count == 0) {
+ client_send_command_error(cmd, "Missing message size.");
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ ret = mailbox_transaction_commit_get_changes(&ctx->t, &changes);
+ if (ret < 0) {
+ client_send_box_error(cmd, ctx->box);
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ msg = t_str_new(256);
+ save_count = seq_range_count(&changes.saved_uids);
+ if (save_count == 0 || changes.no_read_perm) {
+ /* not supported by backend (virtual) */
+ str_append(msg, "OK Append completed.");
+ } else {
+ i_assert(ctx->count == save_count);
+ str_printfa(msg, "OK [APPENDUID %u ",
+ changes.uid_validity);
+ imap_write_seq_range(msg, &changes.saved_uids);
+ str_append(msg, "] Append completed.");
+ }
+ ctx->client->append_count += save_count;
+ pool_unref(&changes.pool);
+
+ if (ctx->box == cmd->client->mailbox) {
+ sync_flags = 0;
+ imap_flags = IMAP_SYNC_FLAG_SAFE;
+ } else {
+ sync_flags = MAILBOX_SYNC_FLAG_FAST;
+ imap_flags = 0;
+ }
+
+ cmd_append_finish(ctx);
+ return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg));
+}
+
+static bool cmd_append_args_can_stop(struct cmd_append_context *ctx,
+ const struct imap_arg *args,
+ bool *last_literal_r)
+{
+ const struct imap_arg *cat_list;
+
+ *last_literal_r = FALSE;
+ if (args->type == IMAP_ARG_EOL)
+ return TRUE;
+
+ /* [(flags)] ["internal date"] <message literal> | CATENATE (..) */
+ if (args->type == IMAP_ARG_LIST)
+ args++;
+ if (args->type == IMAP_ARG_STRING)
+ args++;
+
+ if (args->type == IMAP_ARG_LITERAL_SIZE ||
+ args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC)
+ return TRUE;
+ if (imap_arg_atom_equals(args, "CATENATE") &&
+ imap_arg_get_list(&args[1], &cat_list)) {
+ if (catenate_args_can_stop(ctx, cat_list))
+ return TRUE;
+ *last_literal_r = TRUE;
+ }
+ return FALSE;
+}
+
+static bool cmd_append_parse_new_msg(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_append_context *ctx = cmd->context;
+ const struct imap_arg *args;
+ const char *client_error;
+ enum imap_parser_error parse_error;
+ unsigned int arg_min_count;
+ bool nonsync, last_literal;
+ int ret;
+
+ /* this function gets called 1) after parsing APPEND <mailbox> and
+ 2) with MULTIAPPEND extension after already saving one or more
+ mails. */
+ if (cmd->cancel) {
+ /* cancel the command immediately (disconnection) */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ /* if error occurs, the CRLF is already read. */
+ client->input_skip_line = FALSE;
+
+ /* parse the entire line up to the first message literal, or in case
+ the input buffer is full of MULTIAPPEND CATENATE URLs, parse at
+ least until the beginning of the next message */
+ arg_min_count = 0; last_literal = FALSE;
+ do {
+ if (!last_literal)
+ arg_min_count++;
+ else {
+ /* we only read the literal size. now we read the
+ literal itself. */
+ }
+ ret = imap_parser_read_args(ctx->save_parser, arg_min_count,
+ IMAP_PARSE_FLAG_LITERAL_SIZE |
+ IMAP_PARSE_FLAG_LITERAL8, &args);
+ } while (ret >= (int)arg_min_count &&
+ !cmd_append_args_can_stop(ctx, args, &last_literal));
+ if (ret == -1) {
+ if (!ctx->failed) {
+ client_error = imap_parser_get_error(ctx->save_parser, &parse_error);
+ switch (parse_error) {
+ case IMAP_PARSE_ERROR_NONE:
+ i_unreached();
+ case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+ client_send_line(client, t_strconcat("* BYE ",
+ (client->set->imap_literal_minus ? "[TOOBIG] " : ""),
+ client_error, NULL));
+ client_disconnect(client, client_error);
+ break;
+ default:
+ client_send_command_error(cmd, client_error);
+ }
+ }
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ if (ret < 0) {
+ /* need more data */
+ return FALSE;
+ }
+
+ if (IMAP_ARG_IS_EOL(args)) {
+ /* last message */
+ return cmd_append_finish_parsing(cmd);
+ }
+
+ ret = cmd_append_handle_args(cmd, args, &nonsync);
+ if (ret < 0) {
+ /* invalid parameters, abort immediately */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ if (ret == 0) {
+ /* CATENATE contained only URLs. Finish it and see if there
+ are more messages. */
+ cmd_append_finish_catenate(cmd);
+ imap_parser_reset(ctx->save_parser);
+ return cmd_append_parse_new_msg(cmd);
+ }
+
+ if (!nonsync) {
+ if (!cmd_append_send_literal_continue(ctx)) {
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+ }
+
+ i_assert(ctx->litinput != NULL);
+ ctx->message_input = TRUE;
+ cmd->func = cmd_append_continue_message;
+ return cmd_append_continue_message(cmd);
+}
+
+static bool cmd_append_continue_message(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_append_context *ctx = cmd->context;
+ int ret = 0;
+
+ if (cmd->cancel) {
+ /* cancel the command immediately (disconnection) */
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ if (ctx->save_ctx != NULL) {
+ while (ctx->litinput->v_offset != ctx->literal_size) {
+ ret = i_stream_read(ctx->litinput);
+ if (mailbox_save_continue(ctx->save_ctx) < 0) {
+ /* we still have to finish reading the message
+ from client */
+ mailbox_save_cancel(&ctx->save_ctx);
+ break;
+ }
+ if (ret == -1 || ret == 0)
+ break;
+ }
+ }
+
+ if (ctx->save_ctx == NULL) {
+ /* saving has already failed, we're just eating away the
+ literal */
+ (void)i_stream_read(ctx->litinput);
+ i_stream_skip(ctx->litinput,
+ i_stream_get_data_size(ctx->litinput));
+ }
+
+ if (ctx->litinput->eof || client->input->closed) {
+ uoff_t lit_offset = ctx->litinput->v_offset;
+
+ /* finished - do one more read, to make sure istream-chain
+ unreferences its stream, which is needed for litinput's
+ unreferencing to seek the client->input to correct
+ position. the seek is needed to avoid trying to seek
+ backwards in the ctx->input's parent stream. */
+ i_stream_seek(ctx->input, ctx->input->v_offset);
+ (void)i_stream_read(ctx->input);
+ i_stream_unref(&ctx->litinput);
+
+ if (ctx->failed) {
+ if (ctx->save_ctx != NULL)
+ mailbox_save_cancel(&ctx->save_ctx);
+ } else if (ctx->save_ctx == NULL) {
+ /* failed above */
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ } else if (lit_offset != ctx->literal_size) {
+ /* client disconnected before it finished sending the
+ whole message. */
+ ctx->failed = TRUE;
+ mailbox_save_cancel(&ctx->save_ctx);
+ client_disconnect(client,
+ get_disconnect_reason(ctx, lit_offset));
+ } else if (ctx->catenate) {
+ /* CATENATE isn't finished yet */
+ } else if (mailbox_save_finish(&ctx->save_ctx) < 0) {
+ client_send_box_error(cmd, ctx->box);
+ ctx->failed = TRUE;
+ }
+
+ if (client->input->closed) {
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+
+ /* prepare for the next message (or its part with catenate) */
+ ctx->message_input = FALSE;
+ imap_parser_reset(ctx->save_parser);
+
+ if (ctx->catenate) {
+ cmd->func = cmd_append_continue_catenate;
+ return cmd_append_continue_catenate(cmd);
+ }
+
+ i_stream_unref(&ctx->input);
+ cmd->func = cmd_append_parse_new_msg;
+ return cmd_append_parse_new_msg(cmd);
+ }
+ return FALSE;
+}
+
+bool cmd_append(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_append_context *ctx;
+ const char *mailbox;
+
+ if (client->syncing) {
+ /* if transaction is created while its view is synced,
+ appends aren't allowed for it. */
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
+ return FALSE;
+ }
+
+ /* <mailbox> */
+ if (!client_read_string_args(cmd, 1, &mailbox))
+ return FALSE;
+
+ /* we keep the input locked all the time */
+ client->input_lock = cmd;
+
+ ctx = p_new(cmd->pool, struct cmd_append_context, 1);
+ ctx->cmd = cmd;
+ ctx->client = client;
+ ctx->started = ioloop_time;
+ if (client_open_save_dest_box(cmd, mailbox, &ctx->box) < 0)
+ ctx->failed = TRUE;
+ else {
+ event_add_str(cmd->global_event, "mailbox",
+ mailbox_get_vname(ctx->box));
+ ctx->t = mailbox_transaction_begin(ctx->box,
+ MAILBOX_TRANSACTION_FLAG_EXTERNAL |
+ MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS,
+ imap_client_command_get_reason(cmd));
+ }
+
+ io_remove(&client->io);
+ client->io = io_add_istream(client->input, client_input_append, cmd);
+ /* append is special because we're only waiting on client input, not
+ client output, so disable the standard output handler until we're
+ finished */
+ o_stream_unset_flush_callback(client->output);
+
+ ctx->save_parser = imap_parser_create(client->input, client->output,
+ client->set->imap_max_line_length);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(ctx->save_parser);
+
+ cmd->func = cmd_append_parse_new_msg;
+ cmd->context = ctx;
+ return cmd_append_parse_new_msg(cmd);
+}
diff --git a/src/imap/cmd-cancelupdate.c b/src/imap/cmd-cancelupdate.c
new file mode 100644
index 0000000..8676567
--- /dev/null
+++ b/src/imap/cmd-cancelupdate.c
@@ -0,0 +1,45 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-search.h"
+#include "imap-commands.h"
+
+static bool client_search_update_cancel(struct client *client, const char *tag)
+{
+ struct imap_search_update *update;
+ unsigned int idx;
+
+ update = client_search_update_lookup(client, tag, &idx);
+ if (update == NULL)
+ return FALSE;
+
+ imap_search_update_free(update);
+ array_delete(&client->search_updates, idx, 1);
+ return TRUE;
+}
+
+bool cmd_cancelupdate(struct client_command_context *cmd)
+{
+ const struct imap_arg *args;
+ const char *tag;
+ unsigned int i;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ for (i = 0; args[i].type == IMAP_ARG_STRING; i++) ;
+ if (!IMAP_ARG_IS_EOL(&args[i]) || i == 0) {
+ client_send_command_error(cmd, "Invalid parameters.");
+ return TRUE;
+ }
+
+ while (imap_arg_get_quoted(args, &tag)) {
+ if (!client_search_update_cancel(cmd->client, tag)) {
+ client_send_tagline(cmd, "NO Unknown tag.");
+ return TRUE;
+ }
+ args++;
+ }
+ client_send_tagline(cmd, "OK Updates cancelled.");
+ return TRUE;
+}
diff --git a/src/imap/cmd-capability.c b/src/imap/cmd-capability.c
new file mode 100644
index 0000000..3433c89
--- /dev/null
+++ b/src/imap/cmd-capability.c
@@ -0,0 +1,14 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+#include "str.h"
+
+bool cmd_capability(struct client_command_context *cmd)
+{
+ client_send_line(cmd->client, t_strconcat(
+ "* CAPABILITY ", str_c(cmd->client->capability_string), NULL));
+
+ client_send_tagline(cmd, "OK Capability completed.");
+ return TRUE;
+}
diff --git a/src/imap/cmd-check.c b/src/imap/cmd-check.c
new file mode 100644
index 0000000..a456f7c
--- /dev/null
+++ b/src/imap/cmd-check.c
@@ -0,0 +1,14 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+
+bool cmd_check(struct client_command_context *cmd)
+{
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FULL_READ |
+ MAILBOX_SYNC_FLAG_FULL_WRITE, IMAP_SYNC_FLAG_SAFE,
+ "OK Check completed.");
+}
diff --git a/src/imap/cmd-close.c b/src/imap/cmd-close.c
new file mode 100644
index 0000000..1f4dfeb
--- /dev/null
+++ b/src/imap/cmd-close.c
@@ -0,0 +1,36 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+#include "imap-expunge.h"
+
+bool cmd_close(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct mailbox *mailbox = client->mailbox;
+ struct mail_storage *storage;
+ const char *errstr, *tagged_reply = "OK Close completed.";
+ enum mail_error error = MAIL_ERROR_NONE;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ i_assert(client->mailbox_change_lock == NULL);
+
+ storage = mailbox_get_storage(mailbox);
+ if (imap_expunge(mailbox, NULL, &client->expunged_count) < 0) {
+ errstr = mailbox_get_last_error(mailbox, &error);
+ if (error != MAIL_ERROR_PERM)
+ client_send_untagged_storage_error(client, storage);
+ else {
+ tagged_reply = t_strdup_printf(
+ "OK Closed without expunging: %s", errstr);
+ }
+ }
+ if (mailbox_sync(mailbox, 0) < 0)
+ client_send_untagged_storage_error(client, storage);
+
+ imap_client_close_mailbox(client);
+ client_send_tagline(cmd, tagged_reply);
+ return TRUE;
+}
diff --git a/src/imap/cmd-copy.c b/src/imap/cmd-copy.c
new file mode 100644
index 0000000..1ce018b
--- /dev/null
+++ b/src/imap/cmd-copy.c
@@ -0,0 +1,407 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "imap-resp-code.h"
+#include "imap-util.h"
+#include "imap-commands.h"
+#include "imap-search-args.h"
+
+#include <time.h>
+
+#define COPY_CHECK_INTERVAL 100
+#define MOVE_COMMIT_INTERVAL 1000
+
+struct cmd_copy_context {
+ struct client_command_context *cmd;
+ struct mailbox *srcbox;
+ struct mailbox *destbox;
+ bool move;
+
+ unsigned int copy_count;
+
+ uint32_t uid_validity;
+ ARRAY_TYPE(seq_range) src_uids;
+ ARRAY_TYPE(seq_range) saved_uids;
+ bool hide_saved_uids;
+
+ const char *error_string;
+ enum mail_error mail_error;
+};
+
+static int client_send_sendalive_if_needed(struct client *client)
+{
+ time_t now, last_io;
+ int ret = 0;
+
+ if (o_stream_get_buffer_used_size(client->output) != 0)
+ return 0;
+
+ now = time(NULL);
+ last_io = I_MAX(client->last_input, client->last_output);
+ if (now - last_io > MAIL_STORAGE_STAYALIVE_SECS) {
+ o_stream_nsend_str(client->output, "* OK Hang in there..\r\n");
+ /* make sure it doesn't get stuck on the corked stream */
+ if (o_stream_uncork_flush(client->output) < 0)
+ ret = -1;
+ o_stream_cork(client->output);
+ client->last_output = now;
+ }
+ return ret;
+}
+
+static void copy_update_trashed(struct client *client, struct mailbox *box,
+ unsigned int count)
+{
+ const struct mailbox_settings *set;
+
+ set = mailbox_settings_find(mailbox_get_namespace(box),
+ mailbox_get_vname(box));
+ if (set != NULL && set->special_use[0] != '\0' &&
+ str_array_icase_find(t_strsplit_spaces(set->special_use, " "),
+ "\\Trash"))
+ client->trashed_count += count;
+}
+
+static bool client_is_disconnected(struct client *client)
+{
+ if (client->fd_in == STDIN_FILENO) {
+ /* Skip this check for stdio clients. It's often used in
+ testing where the test expects that all commands will be
+ run even though stdin already has reached EOF. */
+ return FALSE;
+ }
+ ssize_t bytes = i_stream_read(client->input);
+ if (bytes == -1)
+ return TRUE;
+ if (bytes != 0)
+ i_stream_set_input_pending(client->input, TRUE);
+ return FALSE;
+}
+
+static int fetch_and_copy(struct cmd_copy_context *copy_ctx,
+ const struct mail_search_args *uid_search_args)
+{
+ struct client *client = copy_ctx->cmd->client;
+ struct mailbox_transaction_context *t, *src_trans;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mail_save_context *save_ctx;
+ struct mail *mail;
+ const char *cmd_reason;
+ struct mail_transaction_commit_changes changes;
+ ARRAY_TYPE(seq_range) src_uids;
+ int ret;
+
+ /* convert uidset to seqset */
+ search_args = mail_search_args_dup(uid_search_args);
+ mail_search_args_init(search_args, copy_ctx->srcbox, TRUE, NULL);
+ /* make sure the number of messages didn't already change */
+ i_assert(uid_search_args->args->type == SEARCH_UIDSET);
+ i_assert(search_args->args->type == SEARCH_SEQSET ||
+ (search_args->args->type == SEARCH_ALL &&
+ search_args->args->match_not));
+ if (search_args->args->type != SEARCH_SEQSET ||
+ seq_range_count(&search_args->args->value.seqset) !=
+ seq_range_count(&uid_search_args->args->value.seqset)) {
+ mail_search_args_unref(&search_args);
+ return 0;
+ }
+
+ i_assert(o_stream_is_corked(client->output) ||
+ client->output->stream_errno != 0);
+
+ cmd_reason = imap_client_command_get_reason(copy_ctx->cmd);
+ t = mailbox_transaction_begin(copy_ctx->destbox,
+ MAILBOX_TRANSACTION_FLAG_EXTERNAL |
+ MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS,
+ cmd_reason);
+ /* Refresh source index so expunged mails will be noticed */
+ src_trans = mailbox_transaction_begin(copy_ctx->srcbox,
+ MAILBOX_TRANSACTION_FLAG_REFRESH,
+ cmd_reason);
+ search_ctx = mailbox_search_init(src_trans, search_args,
+ NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ t_array_init(&src_uids, 64);
+ ret = 1;
+ while (mailbox_search_next(search_ctx, &mail) && ret > 0) {
+ if (mail->expunged) {
+ ret = 0;
+ break;
+ }
+
+ if ((++copy_ctx->copy_count % COPY_CHECK_INTERVAL) == 0) {
+ /* If we're COPYing (not MOVEing), check if client has
+ already disconnected. If yes, abort the COPY to
+ avoid client duplicating the COPY again later.
+ We can detect this as long as the client doesn't
+ fill the input buffer full. */
+ if (client_send_sendalive_if_needed(client) < 0 ||
+ (!copy_ctx->move &&
+ client_is_disconnected(client))) {
+ /* Client disconnected. Use the same failure
+ code path as if some messages were
+ expunged. */
+ ret = 0;
+ break;
+ }
+ }
+
+ save_ctx = mailbox_save_alloc(t);
+ mailbox_save_copy_flags(save_ctx, mail);
+
+ if (copy_ctx->move) {
+ if (mailbox_move(&save_ctx, mail) < 0)
+ ret = -1;
+ } else {
+ if (mailbox_copy(&save_ctx, mail) < 0)
+ ret = -1;
+ }
+ if (ret < 0 && mail->expunged)
+ ret = 0;
+
+ if (ret > 0)
+ seq_range_array_add(&src_uids, mail->uid);
+ }
+
+ if (ret < 0) {
+ copy_ctx->error_string =
+ mailbox_get_last_error(copy_ctx->destbox, &copy_ctx->mail_error);
+ }
+ if (mailbox_search_deinit(&search_ctx) < 0 && ret >= 0) {
+ copy_ctx->error_string =
+ mailbox_get_last_error(copy_ctx->srcbox, &copy_ctx->mail_error);
+ ret = -1;
+ }
+
+ /* Do a final check before committing COPY to see if the client has
+ already disconnected. */
+ if (!copy_ctx->move && client_is_disconnected(client))
+ ret = 0;
+
+ if (ret <= 0)
+ mailbox_transaction_rollback(&t);
+ else if (mailbox_transaction_commit_get_changes(&t, &changes) < 0) {
+ if (mailbox_get_last_mail_error(copy_ctx->destbox) == MAIL_ERROR_EXPUNGED) {
+ /* storage backend didn't notice the expunge until
+ at commit time. */
+ ret = 0;
+ } else {
+ ret = -1;
+ copy_ctx->error_string =
+ mailbox_get_last_error(copy_ctx->destbox, &copy_ctx->mail_error);
+ }
+ } else {
+ if (changes.no_read_perm)
+ copy_ctx->hide_saved_uids = TRUE;
+
+ if (seq_range_count(&changes.saved_uids) == 0) {
+ /* storage doesn't support returning UIDs */
+ copy_ctx->hide_saved_uids = TRUE;
+ }
+
+ if (copy_ctx->uid_validity == 0)
+ copy_ctx->uid_validity = changes.uid_validity;
+ else if (copy_ctx->uid_validity != changes.uid_validity) {
+ /* UIDVALIDITY unexpectedly changed */
+ copy_ctx->hide_saved_uids = TRUE;
+ }
+ seq_range_array_merge(&copy_ctx->src_uids, &src_uids);
+ seq_range_array_merge(&copy_ctx->saved_uids, &changes.saved_uids);
+
+ i_assert(copy_ctx->copy_count == seq_range_count(&copy_ctx->saved_uids) ||
+ copy_ctx->hide_saved_uids);
+ copy_update_trashed(client, copy_ctx->destbox, copy_ctx->copy_count);
+ pool_unref(&changes.pool);
+ }
+
+ if (!copy_ctx->move ||
+ copy_ctx->srcbox == copy_ctx->destbox) {
+ /* copying or moving within the same mailbox
+ succeeded or failed */
+ if (mailbox_transaction_commit(&src_trans) < 0 && ret >= 0) {
+ copy_ctx->error_string =
+ mailbox_get_last_error(copy_ctx->srcbox, &copy_ctx->mail_error);
+ ret = -1;
+ }
+ } else if (ret <= 0) {
+ /* move failed, don't expunge anything */
+ mailbox_transaction_rollback(&src_trans);
+ } else {
+ /* move succeeded */
+ if (mailbox_transaction_commit(&src_trans) < 0 ||
+ mailbox_sync(copy_ctx->srcbox,
+ MAILBOX_SYNC_FLAG_EXPUNGE) < 0) {
+ copy_ctx->error_string =
+ mailbox_get_last_error(copy_ctx->srcbox, &copy_ctx->mail_error);
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+static void cmd_move_send_untagged(struct cmd_copy_context *copy_ctx,
+ string_t *msg, string_t *src_uidset)
+{
+ if (array_count(&copy_ctx->saved_uids) == 0)
+ return;
+ str_printfa(msg, "* OK [COPYUID %u %s ",
+ copy_ctx->uid_validity, str_c(src_uidset));
+ imap_write_seq_range(msg, &copy_ctx->saved_uids);
+ str_append(msg, "] Moved UIDs.");
+ client_send_line(copy_ctx->cmd->client, str_c(msg));
+}
+
+static bool cmd_copy_full(struct client_command_context *cmd, bool move)
+{
+ struct client *client = cmd->client;
+ struct mailbox *destbox;
+ struct mail_search_args *search_args;
+ struct imap_search_seqset_iter *seqset_iter = NULL;
+ const char *messageset, *mailbox;
+ enum mailbox_sync_flags sync_flags = 0;
+ enum imap_sync_flags imap_flags = 0;
+ struct cmd_copy_context copy_ctx;
+ string_t *msg, *src_uidset;
+ int ret;
+
+ /* <message set> <mailbox> */
+ if (!client_read_string_args(cmd, 2, &messageset, &mailbox))
+ return FALSE;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ /* First convert the message set to sequences. This way nonexistent
+ UIDs are dropped. */
+ ret = imap_search_get_seqset(cmd, messageset, cmd->uid, &search_args);
+ if (ret <= 0)
+ return ret < 0;
+ if (search_args->args->type == SEARCH_ALL) {
+ i_assert(search_args->args->match_not);
+ mail_search_args_unref(&search_args);
+ return cmd_sync(cmd, sync_flags, imap_flags,
+ "OK No messages found.");
+ }
+ /* Convert seqset to uidset. This is required for MOVE to work
+ correctly, since it opens another view for the source mailbox
+ that can have different sequences. */
+ imap_search_anyset_to_uidset(cmd, search_args);
+
+ if (client_open_save_dest_box(cmd, mailbox, &destbox) < 0) {
+ mail_search_args_unref(&search_args);
+ return TRUE;
+ }
+
+ i_zero(&copy_ctx);
+ copy_ctx.cmd = cmd;
+ copy_ctx.destbox = destbox;
+ if (destbox == client->mailbox || !move)
+ copy_ctx.srcbox = client->mailbox;
+ else {
+ copy_ctx.srcbox = mailbox_alloc(mailbox_get_namespace(client->mailbox)->list,
+ mailbox_get_vname(client->mailbox), 0);
+ if (mailbox_sync(copy_ctx.srcbox, 0) < 0) {
+ mail_search_args_unref(&search_args);
+ client_send_box_error(cmd, copy_ctx.srcbox);
+ mailbox_free(&copy_ctx.srcbox);
+ mailbox_free(&destbox);
+ return TRUE;
+ }
+ }
+ copy_ctx.move = move;
+ i_array_init(&copy_ctx.src_uids, 8);
+ i_array_init(&copy_ctx.saved_uids, 8);
+
+ if (move) {
+ /* When moving mails, perform the work in batches of
+ MOVE_COMMIT_INTERVAL. Each such batch has its own
+ transaction and search query. */
+ seqset_iter = imap_search_seqset_iter_init(search_args,
+ client->messages_count, MOVE_COMMIT_INTERVAL);
+ }
+ do {
+ T_BEGIN {
+ ret = fetch_and_copy(&copy_ctx, search_args);
+ } T_END;
+ if (ret <= 0) {
+ /* failed */
+ break;
+ }
+ } while (seqset_iter != NULL &&
+ imap_search_seqset_iter_next(seqset_iter));
+ imap_search_seqset_iter_deinit(&seqset_iter);
+ mail_search_args_unref(&search_args);
+
+ src_uidset = t_str_new(256);
+ imap_write_seq_range(src_uidset, &copy_ctx.src_uids);
+
+ msg = t_str_new(256);
+ if (ret <= 0) {
+ if (move && array_count(&copy_ctx.src_uids) > 0) {
+ /* some of the messages were successfully moved */
+ cmd_move_send_untagged(&copy_ctx, msg, src_uidset);
+ }
+ } else if (copy_ctx.copy_count == 0) {
+ str_append(msg, "OK No messages found.");
+ } else if (seq_range_count(&copy_ctx.saved_uids) == 0 ||
+ copy_ctx.hide_saved_uids) {
+ /* not supported by backend (virtual) or no read permissions
+ for mailbox */
+ str_append(msg, move ? "OK Move completed." :
+ "OK Copy completed.");
+ } else if (move) {
+ cmd_move_send_untagged(&copy_ctx, msg, src_uidset);
+ str_truncate(msg, 0);
+ str_append(msg, "OK Move completed.");
+ } else {
+ str_printfa(msg, "OK [COPYUID %u %s ", copy_ctx.uid_validity,
+ str_c(src_uidset));
+ imap_write_seq_range(msg, &copy_ctx.saved_uids);
+ str_append(msg, "] Copy completed.");
+ }
+
+ array_free(&copy_ctx.src_uids);
+ array_free(&copy_ctx.saved_uids);
+
+ if (destbox != client->mailbox) {
+ if (move)
+ sync_flags |= MAILBOX_SYNC_FLAG_EXPUNGE;
+ else
+ sync_flags |= MAILBOX_SYNC_FLAG_FAST;
+ imap_flags |= IMAP_SYNC_FLAG_SAFE;
+ mailbox_free(&destbox);
+ } else if (move) {
+ sync_flags |= MAILBOX_SYNC_FLAG_EXPUNGE;
+ imap_flags |= IMAP_SYNC_FLAG_SAFE;
+ }
+ if (copy_ctx.srcbox != client->mailbox)
+ mailbox_free(&copy_ctx.srcbox);
+
+ if (ret > 0)
+ return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg));
+ else if (ret == 0) {
+ /* some messages were expunged, sync them */
+ return cmd_sync(cmd, 0, 0,
+ "NO ["IMAP_RESP_CODE_EXPUNGEISSUED"] "
+ "Some of the requested messages no longer exist.");
+ } else {
+ client_send_error(cmd, copy_ctx.error_string,
+ copy_ctx.mail_error);
+ return TRUE;
+ }
+}
+
+bool cmd_copy(struct client_command_context *cmd)
+{
+ return cmd_copy_full(cmd, FALSE);
+}
+
+bool cmd_move(struct client_command_context *cmd)
+{
+ return cmd_copy_full(cmd, TRUE);
+}
diff --git a/src/imap/cmd-create.c b/src/imap/cmd-create.c
new file mode 100644
index 0000000..2459173
--- /dev/null
+++ b/src/imap/cmd-create.c
@@ -0,0 +1,48 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-resp-code.h"
+#include "mail-namespace.h"
+#include "imap-commands.h"
+
+bool cmd_create(struct client_command_context *cmd)
+{
+ struct mail_namespace *ns;
+ const char *mailbox, *orig_mailbox;
+ struct mailbox *box;
+ bool directory;
+ size_t len;
+
+ /* <mailbox> */
+ if (!client_read_string_args(cmd, 1, &mailbox))
+ return FALSE;
+
+ orig_mailbox = mailbox;
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ len = strlen(orig_mailbox);
+ if (len == 0 || orig_mailbox[len-1] != mail_namespace_get_sep(ns))
+ directory = FALSE;
+ else {
+ /* name ends with hierarchy separator - client is just
+ informing us that it wants to create children under this
+ mailbox. */
+ directory = TRUE;
+
+ /* drop separator from mailbox. it's already dropped when
+ WORKAROUND_TB_EXTRA_MAILBOX_SEP is enabled */
+ if (len == strlen(mailbox))
+ mailbox = t_strndup(mailbox, len-1);
+ }
+
+ box = mailbox_alloc(ns->list, mailbox, 0);
+ event_add_str(cmd->global_event, "mailbox", mailbox_get_vname(box));
+ if (mailbox_create(box, NULL, directory) < 0)
+ client_send_box_error(cmd, box);
+ else
+ client_send_tagline(cmd, "OK Create completed.");
+ mailbox_free(&box);
+ return TRUE;
+}
diff --git a/src/imap/cmd-delete.c b/src/imap/cmd-delete.c
new file mode 100644
index 0000000..e095321
--- /dev/null
+++ b/src/imap/cmd-delete.c
@@ -0,0 +1,59 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+
+bool cmd_delete(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ const char *name, *client_error;
+ enum mail_error error;
+ bool disconnect = FALSE;
+
+ /* <mailbox> */
+ if (!client_read_string_args(cmd, 1, &name))
+ return FALSE;
+
+ ns = client_find_namespace(cmd, &name);
+ if (ns == NULL)
+ return TRUE;
+
+ box = mailbox_alloc(ns->list, name, 0);
+ event_add_str(cmd->global_event, "mailbox", mailbox_get_vname(box));
+ if (mailbox_is_any_inbox(box)) {
+ /* IMAP protocol allows this, but I think it's safer to
+ not allow it. */
+ mailbox_free(&box);
+ client_send_tagline(cmd, "NO INBOX can't be deleted.");
+ return TRUE;
+ }
+ if (client->mailbox != NULL &&
+ mailbox_backends_equal(box, client->mailbox)) {
+ /* deleting selected mailbox. close it first */
+ client_search_updates_free(client);
+ mailbox_free(&client->mailbox);
+ disconnect = TRUE;
+ }
+
+ if (mailbox_delete(box) == 0)
+ client_send_tagline(cmd, "OK Delete completed.");
+ else {
+ client_error = mailbox_get_last_error(box, &error);
+ if (error != MAIL_ERROR_EXISTS)
+ client_send_box_error(cmd, box);
+ else {
+ /* mailbox has children */
+ client_send_tagline(cmd, t_strdup_printf("NO %s",
+ client_error));
+ }
+ }
+ mailbox_free(&box);
+
+ if (disconnect) {
+ client_disconnect_with_error(cmd->client,
+ "Selected mailbox was deleted, have to disconnect.");
+ }
+ return TRUE;
+}
diff --git a/src/imap/cmd-enable.c b/src/imap/cmd-enable.c
new file mode 100644
index 0000000..904d6db
--- /dev/null
+++ b/src/imap/cmd-enable.c
@@ -0,0 +1,35 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "imap-feature.h"
+
+bool cmd_enable(struct client_command_context *cmd)
+{
+ const struct imap_arg *args;
+ const char *str;
+ string_t *reply;
+ unsigned int feature_idx;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ reply = t_str_new(64);
+ str_append(reply, "* ENABLED");
+ for (; !IMAP_ARG_IS_EOL(args); args++) {
+ if (!imap_arg_get_atom(args, &str)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+ if (imap_feature_lookup(str, &feature_idx)) {
+ client_enable(cmd->client, feature_idx);
+ str_append_c(reply, ' ');
+ str_append(reply, t_str_ucase(str));
+ }
+ }
+ if (str_len(reply) > 9)
+ client_send_line(cmd->client, str_c(reply));
+ client_send_tagline(cmd, "OK Enabled.");
+ return TRUE;
+}
+
diff --git a/src/imap/cmd-examine.c b/src/imap/cmd-examine.c
new file mode 100644
index 0000000..4000fa0
--- /dev/null
+++ b/src/imap/cmd-examine.c
@@ -0,0 +1,9 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+
+bool cmd_examine(struct client_command_context *cmd)
+{
+ return cmd_select_full(cmd, TRUE);
+}
diff --git a/src/imap/cmd-expunge.c b/src/imap/cmd-expunge.c
new file mode 100644
index 0000000..7388937
--- /dev/null
+++ b/src/imap/cmd-expunge.c
@@ -0,0 +1,68 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+#include "imap-search-args.h"
+#include "imap-expunge.h"
+
+static bool ATTR_NULL(2)
+cmd_expunge_finish(struct client_command_context *cmd,
+ struct mail_search_args *search_args)
+{
+ struct client *client = cmd->client;
+ const char *errstr;
+ enum mail_error error = MAIL_ERROR_NONE;
+ int ret;
+
+ ret = imap_expunge(client->mailbox, search_args == NULL ? NULL :
+ search_args->args, &client->expunged_count);
+ if (search_args != NULL)
+ mail_search_args_unref(&search_args);
+ if (ret < 0) {
+ errstr = mailbox_get_last_error(client->mailbox, &error);
+ if (error != MAIL_ERROR_PERM) {
+ client_send_box_error(cmd, client->mailbox);
+ return TRUE;
+ } else {
+ return cmd_sync(cmd, 0, IMAP_SYNC_FLAG_SAFE,
+ t_strdup_printf("OK Expunge ignored: %s.",
+ errstr));
+ }
+ }
+
+ client->sync_seen_deletes = FALSE;
+ return cmd_sync(cmd, MAILBOX_SYNC_FLAG_EXPUNGE,
+ IMAP_SYNC_FLAG_SAFE, "OK Expunge completed.");
+}
+
+bool cmd_uid_expunge(struct client_command_context *cmd)
+{
+ const struct imap_arg *args;
+ struct mail_search_args *search_args;
+ const char *uidset;
+ int ret;
+
+ if (!client_read_args(cmd, 1, 0, &args))
+ return FALSE;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ if (!imap_arg_get_astring(&args[0], &uidset)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ ret = imap_search_get_seqset(cmd, uidset, TRUE, &search_args);
+ if (ret <= 0)
+ return ret < 0;
+ return cmd_expunge_finish(cmd, search_args);
+}
+
+bool cmd_expunge(struct client_command_context *cmd)
+{
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ return cmd_expunge_finish(cmd, NULL);
+}
diff --git a/src/imap/cmd-fetch.c b/src/imap/cmd-fetch.c
new file mode 100644
index 0000000..35e85d1
--- /dev/null
+++ b/src/imap/cmd-fetch.c
@@ -0,0 +1,391 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ostream.h"
+#include "imap-resp-code.h"
+#include "imap-commands.h"
+#include "imap-fetch.h"
+#include "imap-search-args.h"
+#include "mail-search.h"
+
+
+static const char *all_macro[] = {
+ "FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", NULL
+};
+static const char *fast_macro[] = {
+ "FLAGS", "INTERNALDATE", "RFC822.SIZE", NULL
+};
+static const char *full_macro[] = {
+ "FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", "BODY", NULL
+};
+
+static bool
+imap_fetch_cmd_init_handler(struct imap_fetch_context *ctx,
+ struct client_command_context *cmd,
+ const char *name, const struct imap_arg **args)
+{
+ struct imap_fetch_init_context init_ctx;
+
+ i_zero(&init_ctx);
+ init_ctx.fetch_ctx = ctx;
+ init_ctx.pool = ctx->ctx_pool;
+ init_ctx.name = name;
+ init_ctx.args = *args;
+
+ if (!imap_fetch_init_handler(&init_ctx)) {
+ i_assert(init_ctx.error != NULL);
+ client_send_command_error(cmd, init_ctx.error);
+ return FALSE;
+ }
+ *args = init_ctx.args;
+ return TRUE;
+}
+
+static bool
+fetch_parse_args(struct imap_fetch_context *ctx,
+ struct client_command_context *cmd,
+ const struct imap_arg *arg, const struct imap_arg **next_arg_r)
+{
+ const char *str, *const *macro;
+
+ if (cmd->uid) {
+ if (!imap_fetch_cmd_init_handler(ctx, cmd, "UID", &arg))
+ return FALSE;
+ }
+ if (imap_arg_get_atom(arg, &str)) {
+ str = t_str_ucase(str);
+ arg++;
+
+ /* handle macros first */
+ if (strcmp(str, "ALL") == 0)
+ macro = all_macro;
+ else if (strcmp(str, "FAST") == 0)
+ macro = fast_macro;
+ else if (strcmp(str, "FULL") == 0)
+ macro = full_macro;
+ else {
+ macro = NULL;
+ if (!imap_fetch_cmd_init_handler(ctx, cmd, str, &arg))
+ return FALSE;
+ }
+ if (macro != NULL) {
+ while (*macro != NULL) {
+ if (!imap_fetch_cmd_init_handler(ctx, cmd, *macro, &arg))
+ return FALSE;
+ macro++;
+ }
+ }
+ *next_arg_r = arg;
+ } else {
+ *next_arg_r = arg + 1;
+ arg = imap_arg_as_list(arg);
+ if (IMAP_ARG_IS_EOL(arg)) {
+ client_send_command_error(cmd,
+ "FETCH list is empty.");
+ return FALSE;
+ }
+ while (imap_arg_get_atom(arg, &str)) {
+ str = t_str_ucase(str);
+ arg++;
+ if (!imap_fetch_cmd_init_handler(ctx, cmd, str, &arg))
+ return FALSE;
+ }
+ if (!IMAP_ARG_IS_EOL(arg)) {
+ client_send_command_error(cmd,
+ "FETCH list contains non-atoms.");
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static bool
+fetch_parse_modifier(struct imap_fetch_context *ctx,
+ struct client_command_context *cmd,
+ struct mail_search_args *search_args,
+ const char *name, const struct imap_arg **args,
+ bool *send_vanished)
+{
+ const char *str;
+ uint64_t modseq;
+
+ if (strcmp(name, "CHANGEDSINCE") == 0) {
+ if (cmd->client->nonpermanent_modseqs) {
+ client_send_command_error(cmd,
+ "FETCH CHANGEDSINCE can't be used with non-permanent modseqs");
+ return FALSE;
+ }
+ if (!imap_arg_get_atom(*args, &str) ||
+ str_to_uint64(str, &modseq) < 0) {
+ client_send_command_error(cmd,
+ "Invalid CHANGEDSINCE modseq.");
+ return FALSE;
+ }
+ *args += 1;
+ imap_search_add_changed_since(search_args, modseq);
+ imap_fetch_init_nofail_handler(ctx, imap_fetch_modseq_init);
+ return TRUE;
+ }
+ if (strcmp(name, "VANISHED") == 0 && cmd->uid) {
+ if (!client_has_enabled(ctx->client, imap_feature_qresync)) {
+ client_send_command_error(cmd, "QRESYNC not enabled");
+ return FALSE;
+ }
+ *send_vanished = TRUE;
+ return TRUE;
+ }
+
+ client_send_command_error(cmd, "Unknown FETCH modifier");
+ return FALSE;
+}
+
+static bool
+fetch_parse_modifiers(struct imap_fetch_context *ctx,
+ struct client_command_context *cmd,
+ struct mail_search_args *search_args,
+ const struct imap_arg *args, bool *send_vanished_r)
+{
+ const char *name;
+
+ *send_vanished_r = FALSE;
+
+ while (!IMAP_ARG_IS_EOL(args)) {
+ if (!imap_arg_get_atom(args, &name)) {
+ client_send_command_error(cmd,
+ "FETCH modifiers contain non-atoms.");
+ return FALSE;
+ }
+ args++;
+ if (!fetch_parse_modifier(ctx, cmd, search_args,
+ t_str_ucase(name),
+ &args, send_vanished_r))
+ return FALSE;
+ }
+ if (*send_vanished_r &&
+ (search_args->args->next == NULL ||
+ search_args->args->next->type != SEARCH_MODSEQ)) {
+ client_send_command_error(cmd,
+ "VANISHED used without CHANGEDSINCE");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool cmd_fetch_finished(struct client_command_context *cmd ATTR_UNUSED)
+{
+ return TRUE;
+}
+
+static bool imap_fetch_is_failed_retry(struct imap_fetch_context *ctx)
+{
+ if (!array_is_created(&ctx->client->fetch_failed_uids) ||
+ !array_is_created(&ctx->fetch_failed_uids))
+ return FALSE;
+ return seq_range_array_have_common(&ctx->client->fetch_failed_uids,
+ &ctx->fetch_failed_uids);
+
+}
+
+static void imap_fetch_add_failed_uids(struct imap_fetch_context *ctx)
+{
+ if (!array_is_created(&ctx->fetch_failed_uids))
+ return;
+ if (!array_is_created(&ctx->client->fetch_failed_uids)) {
+ p_array_init(&ctx->client->fetch_failed_uids, ctx->client->pool,
+ array_count(&ctx->fetch_failed_uids));
+ }
+ seq_range_array_merge(&ctx->client->fetch_failed_uids,
+ &ctx->fetch_failed_uids);
+}
+
+static bool cmd_fetch_finish(struct imap_fetch_context *ctx,
+ struct client_command_context *cmd)
+{
+ static const char *ok_message = "OK Fetch completed.";
+ const char *tagged_reply = ok_message;
+ enum mail_error error;
+ bool seen_flags_changed = ctx->state.seen_flags_changed;
+
+ if (ctx->state.skipped_expunged_msgs) {
+ tagged_reply = "OK ["IMAP_RESP_CODE_EXPUNGEISSUED"] "
+ "Some messages were already expunged.";
+ }
+
+ if (imap_fetch_end(ctx) < 0) {
+ const char *client_error;
+
+ if (cmd->client->output->closed) {
+ /* If we're canceling we need to finish this command
+ or we'll assert crash. But normally we want to
+ return FALSE so that the disconnect message logs
+ about this fetch command and that these latest
+ output bytes are included in it (which wouldn't
+ happen if we called client_disconnect() here
+ directly). */
+ cmd->func = cmd_fetch_finished;
+ imap_fetch_free(&ctx);
+ return cmd->cancel;
+ }
+
+ if (ctx->error == MAIL_ERROR_NONE)
+ client_error = mailbox_get_last_error(cmd->client->mailbox, &error);
+ else {
+ client_error = ctx->errstr;
+ error = ctx->error;
+ }
+ if (error == MAIL_ERROR_CONVERSION) {
+ /* BINARY found unsupported Content-Transfer-Encoding */
+ tagged_reply = t_strdup_printf(
+ "NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] %s", client_error);
+ } else if (error == MAIL_ERROR_INVALIDDATA) {
+ /* Content was invalid */
+ tagged_reply = t_strdup_printf(
+ "NO ["IMAP_RESP_CODE_PARSE"] %s", client_error);
+ } else if (cmd->client->set->parsed_fetch_failure != IMAP_CLIENT_FETCH_FAILURE_NO_AFTER ||
+ imap_fetch_is_failed_retry(ctx)) {
+ /* By default we never want to reply NO to FETCH
+ requests, because many IMAP clients become confused
+ about what they should on NO. A disconnection causes
+ less confusion. */
+ const char *internal_error =
+ mailbox_get_last_internal_error(cmd->client->mailbox, NULL);
+ client_send_line(cmd->client, t_strconcat(
+ "* BYE FETCH failed: ", client_error, NULL));
+ client_disconnect(cmd->client, t_strconcat(
+ "FETCH failed: ", internal_error, NULL));
+ imap_fetch_free(&ctx);
+ return TRUE;
+ } else {
+ /* Use a tagged NO to FETCH failure, but only if client
+ hasn't repeated the FETCH to the same email (so that
+ we avoid infinitely retries from client.) */
+ imap_fetch_add_failed_uids(ctx);
+ tagged_reply = t_strdup_printf(
+ "NO ["IMAP_RESP_CODE_SERVERBUG"] %s", client_error);
+ }
+ }
+ imap_fetch_free(&ctx);
+
+ return cmd_sync(cmd,
+ (seen_flags_changed ? 0 : MAILBOX_SYNC_FLAG_FAST) |
+ (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES), 0,
+ tagged_reply);
+}
+
+static bool cmd_fetch_continue(struct client_command_context *cmd)
+{
+ struct imap_fetch_context *ctx = cmd->context;
+
+ if (imap_fetch_more(ctx, cmd) == 0) {
+ /* unfinished */
+ return FALSE;
+ }
+ return cmd_fetch_finish(ctx, cmd);
+}
+
+static void cmd_fetch_set_reason_codes(struct client_command_context *cmd,
+ struct imap_fetch_context *ctx)
+{
+ /* Fetching body or header always causes the message to be opened.
+ Use them as the primary reason. */
+ if ((ctx->fetch_data & MAIL_FETCH_STREAM_BODY) != 0) {
+ event_strlist_append(cmd->global_event, EVENT_REASON_CODE,
+ "imap:fetch_body");
+ return;
+ }
+ if ((ctx->fetch_data & MAIL_FETCH_STREAM_HEADER) != 0) {
+ event_strlist_append(cmd->global_event, EVENT_REASON_CODE,
+ "imap:fetch_header");
+ return;
+ }
+
+ /* The rest of these can come from cache. Since especially with
+ prefetching we can't really know which one of them specifically
+ triggered opening the mail, just use all of them as the reasons. */
+ if (ctx->fetch_header_fields ||
+ HAS_ANY_BITS(ctx->fetch_data,
+ MAIL_FETCH_IMAP_ENVELOPE |
+ MAIL_FETCH_DATE)) {
+ event_strlist_append(cmd->global_event, EVENT_REASON_CODE,
+ "imap:fetch_header_fields");
+ }
+ if (HAS_ANY_BITS(ctx->fetch_data,
+ MAIL_FETCH_IMAP_BODY |
+ MAIL_FETCH_IMAP_BODYSTRUCTURE)) {
+ event_strlist_append(cmd->global_event, EVENT_REASON_CODE,
+ "imap:fetch_bodystructure");
+ }
+ if (HAS_ANY_BITS(ctx->fetch_data,
+ MAIL_FETCH_PHYSICAL_SIZE |
+ MAIL_FETCH_VIRTUAL_SIZE)) {
+ event_strlist_append(cmd->global_event, EVENT_REASON_CODE,
+ "imap:fetch_size");
+ }
+}
+
+bool cmd_fetch(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct imap_fetch_context *ctx;
+ const struct imap_arg *args, *next_arg, *list_arg;
+ struct mail_search_args *search_args;
+ struct imap_fetch_qresync_args qresync_args;
+ const char *messageset;
+ bool send_vanished = FALSE;
+ int ret;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ /* <messageset> <field(s)> [(modifiers)] */
+ if (!imap_arg_get_atom(&args[0], &messageset) ||
+ (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM) ||
+ (!IMAP_ARG_IS_EOL(&args[2]) && args[2].type != IMAP_ARG_LIST)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ /* UID FETCH VANISHED needs the uidset, so convert it to
+ sequence set later */
+ ret = imap_search_get_anyset(cmd, messageset, cmd->uid, &search_args);
+ if (ret <= 0)
+ return ret < 0;
+
+ ctx = imap_fetch_alloc(client, cmd->pool,
+ imap_client_command_get_reason(cmd));
+
+ if (!fetch_parse_args(ctx, cmd, &args[1], &next_arg) ||
+ (imap_arg_get_list(next_arg, &list_arg) &&
+ !fetch_parse_modifiers(ctx, cmd, search_args, list_arg,
+ &send_vanished))) {
+ imap_fetch_free(&ctx);
+ mail_search_args_unref(&search_args);
+ return TRUE;
+ }
+
+ if (send_vanished) {
+ i_zero(&qresync_args);
+ if (imap_fetch_send_vanished(client, client->mailbox,
+ search_args, &qresync_args) < 0) {
+ mail_search_args_unref(&search_args);
+ return cmd_fetch_finish(ctx, cmd);
+ }
+ }
+
+ cmd_fetch_set_reason_codes(cmd, ctx);
+ imap_fetch_begin(ctx, client->mailbox, search_args);
+ mail_search_args_unref(&search_args);
+
+ if (imap_fetch_more(ctx, cmd) == 0) {
+ /* unfinished */
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+
+ cmd->func = cmd_fetch_continue;
+ cmd->context = ctx;
+ return FALSE;
+ }
+ return cmd_fetch_finish(ctx, cmd);
+}
diff --git a/src/imap/cmd-genurlauth.c b/src/imap/cmd-genurlauth.c
new file mode 100644
index 0000000..2cdbdd1
--- /dev/null
+++ b/src/imap/cmd-genurlauth.c
@@ -0,0 +1,54 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "imap-commands.h"
+#include "imap-quote.h"
+#include "imap-urlauth.h"
+
+bool cmd_genurlauth(struct client_command_context *cmd)
+{
+ const struct imap_arg *args;
+ string_t *response;
+ int ret;
+
+ if (cmd->client->urlauth_ctx == NULL) {
+ client_send_command_error(cmd, "URLAUTH disabled.");
+ return TRUE;
+ }
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ response = t_str_new(1024);
+ str_append(response, "* GENURLAUTH");
+ for (;;) {
+ const char *url_rump, *mechanism, *url, *client_error;
+
+ if (IMAP_ARG_IS_EOL(args))
+ break;
+ if (!imap_arg_get_astring(args++, &url_rump) ||
+ !imap_arg_get_atom(args++, &mechanism)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return FALSE;
+ }
+
+ ret = imap_urlauth_generate(cmd->client->urlauth_ctx,
+ mechanism, url_rump, &url,
+ &client_error);
+ if (ret <= 0) {
+ if (ret < 0)
+ client_send_internal_error(cmd);
+ else
+ client_send_command_error(cmd, client_error);
+ return TRUE;
+ }
+
+ str_append_c(response, ' ');
+ imap_append_astring(response, url);
+ }
+
+ client_send_line(cmd->client, str_c(response));
+ client_send_tagline(cmd, "OK GENURLAUTH completed.");
+ return TRUE;
+}
diff --git a/src/imap/cmd-getmetadata.c b/src/imap/cmd-getmetadata.c
new file mode 100644
index 0000000..ec23135
--- /dev/null
+++ b/src/imap/cmd-getmetadata.c
@@ -0,0 +1,559 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "mail-storage-private.h"
+#include "str.h"
+#include "istream.h"
+#include "istream-sized.h"
+#include "ostream.h"
+#include "mailbox-list-iter.h"
+#include "imap-utf7.h"
+#include "imap-quote.h"
+#include "imap-metadata.h"
+
+struct imap_getmetadata_context {
+ struct client_command_context *cmd;
+
+ struct mailbox *box;
+ struct imap_metadata_transaction *trans;
+ struct mailbox_list_iterate_context *list_iter;
+
+ ARRAY_TYPE(const_string) entries;
+ uint32_t maxsize;
+ uoff_t largest_seen_size;
+ unsigned int depth;
+
+ struct istream *cur_stream;
+
+ struct imap_metadata_iter *iter;
+ string_t *iter_entry_prefix;
+
+ string_t *delayed_errors;
+ string_t *last_error_str;
+ enum mail_error last_error;
+
+ unsigned int entry_idx;
+ bool first_entry_sent;
+};
+
+static bool
+cmd_getmetadata_mailbox_iter_next(struct imap_getmetadata_context *ctx);
+
+static bool
+cmd_getmetadata_parse_options(struct imap_getmetadata_context *ctx,
+ const struct imap_arg *options)
+{
+ const char *value;
+
+ while (!IMAP_ARG_IS_EOL(options)) {
+ if (imap_arg_atom_equals(options, "MAXSIZE")) {
+ options++;
+ if (!imap_arg_get_atom(options, &value) ||
+ str_to_uint32(value, &ctx->maxsize) < 0) {
+ client_send_command_error(ctx->cmd,
+ "Invalid value for MAXSIZE option");
+ return FALSE;
+ }
+ } else if (imap_arg_atom_equals(options, "DEPTH")) {
+ options++;
+ if (!imap_arg_get_atom(options, &value)) {
+ client_send_command_error(ctx->cmd,
+ "Invalid value for DEPTH option");
+ return FALSE;
+ }
+ if (strcmp(value, "0") == 0)
+ ctx->depth = 0;
+ else if (strcmp(value, "1") == 0)
+ ctx->depth = 1;
+ else if (strcmp(value, "infinity") == 0)
+ ctx->depth = UINT_MAX;
+ else {
+ client_send_command_error(ctx->cmd,
+ "Invalid value for DEPTH option");
+ return FALSE;
+ }
+ } else {
+ client_send_command_error(ctx->cmd, "Unknown option");
+ return FALSE;
+ }
+ options++;
+ }
+ return TRUE;
+}
+
+static bool
+imap_metadata_parse_entry_names(struct imap_getmetadata_context *ctx,
+ const struct imap_arg *entries)
+{
+ const char *value, *client_error;
+
+ p_array_init(&ctx->entries, ctx->cmd->pool, 4);
+ for (; !IMAP_ARG_IS_EOL(entries); entries++) {
+ if (!imap_arg_get_astring(entries, &value)) {
+ client_send_command_error(ctx->cmd, "Entry isn't astring");
+ return FALSE;
+ }
+ if (!imap_metadata_verify_entry_name(value, &client_error)) {
+ client_send_command_error(ctx->cmd, client_error);
+ return FALSE;
+ }
+
+ /* names are case-insensitive so we'll always lowercase them */
+ value = p_strdup(ctx->cmd->pool, t_str_lcase(value));
+ array_push_back(&ctx->entries, &value);
+ }
+ return TRUE;
+}
+
+static string_t *
+metadata_add_entry(struct imap_getmetadata_context *ctx, const char *entry)
+{
+ string_t *str;
+
+ str = t_str_new(64);
+ if (!ctx->first_entry_sent) {
+ string_t *mailbox_mutf7 = t_str_new(64);
+
+ ctx->first_entry_sent = TRUE;
+ str_append(str, "* METADATA ");
+ if (ctx->box == NULL) {
+ /* server metadata reply */
+ str_append(str, "\"\"");
+ } else {
+ if (imap_utf8_to_utf7(mailbox_get_vname(ctx->box), mailbox_mutf7) < 0)
+ i_unreached();
+ imap_append_astring(str, str_c(mailbox_mutf7));
+ }
+ str_append(str, " (");
+
+ /* nothing can be sent until untagged METADATA is finished */
+ ctx->cmd->client->output_cmd_lock = ctx->cmd;
+ } else {
+ str_append_c(str, ' ');
+ }
+ imap_append_astring(str, entry);
+ return str;
+}
+
+static void
+cmd_getmetadata_send_nil_reply(struct imap_getmetadata_context *ctx,
+ const char *entry)
+{
+ string_t *str;
+
+ /* client requested a specific entry that didn't exist.
+ we must return it as NIL. */
+ str = metadata_add_entry(ctx, entry);
+ str_append(str, " NIL");
+ o_stream_nsend(ctx->cmd->client->output, str_data(str), str_len(str));
+}
+
+static void
+cmd_getmetadata_handle_error_str(struct imap_getmetadata_context *ctx,
+ const char *error_string,
+ enum mail_error error)
+{
+ if (str_len(ctx->last_error_str) > 0) {
+ str_append(ctx->delayed_errors, "* NO ");
+ str_append_str(ctx->delayed_errors, ctx->last_error_str);
+ str_append(ctx->delayed_errors, "\r\n");
+ str_truncate(ctx->last_error_str, 0);
+ }
+ str_append(ctx->last_error_str, error_string);
+ ctx->last_error = error;
+}
+
+static bool
+cmd_getmetadata_handle_error(struct imap_getmetadata_context *ctx)
+{
+ const char *error_string;
+ enum mail_error error;
+
+ error_string = imap_metadata_transaction_get_last_error(ctx->trans, &error);
+ if (error == MAIL_ERROR_NOTPOSSIBLE && ctx->depth > 0) {
+ /* Using DEPTH to iterate children with imap_metadata=no.
+ Don't return an error, since some of the entries could be
+ returned successfully. */
+ return FALSE;
+ }
+
+ cmd_getmetadata_handle_error_str(ctx, error_string, error);
+ return TRUE;
+}
+
+static void cmd_getmetadata_send_entry(struct imap_getmetadata_context *ctx,
+ const char *entry, bool require_reply)
+{
+ struct client *client = ctx->cmd->client;
+ struct mail_attribute_value value;
+ uoff_t value_len;
+ string_t *str;
+
+ if (imap_metadata_get_stream(ctx->trans, entry, &value) < 0) {
+ if (cmd_getmetadata_handle_error(ctx))
+ return;
+ }
+
+ if (value.value != NULL)
+ value_len = strlen(value.value);
+ else if (value.value_stream != NULL) {
+ if (i_stream_get_size(value.value_stream, TRUE, &value_len) < 0) {
+ e_error(client->event,
+ "GETMETADATA %s: i_stream_get_size(%s) failed: %s", entry,
+ i_stream_get_name(value.value_stream),
+ i_stream_get_error(value.value_stream));
+ i_stream_unref(&value.value_stream);
+ cmd_getmetadata_handle_error_str(ctx,
+ MAIL_ERRSTR_CRITICAL_MSG, MAIL_ERROR_TEMP);
+ return;
+ }
+ } else {
+ /* skip nonexistent entries */
+ if (require_reply)
+ cmd_getmetadata_send_nil_reply(ctx, entry);
+ return;
+ }
+
+ if (value_len > ctx->maxsize) {
+ /* value length is larger than specified MAXSIZE,
+ skip this entry */
+ if (ctx->largest_seen_size < value_len)
+ ctx->largest_seen_size = value_len;
+ i_stream_unref(&value.value_stream);
+ return;
+ }
+
+ str = metadata_add_entry(ctx, entry);
+ if (value.value != NULL) {
+ str_printfa(str, " {%"PRIuUOFF_T"}\r\n%s", value_len, value.value);
+ o_stream_nsend(client->output, str_data(str), str_len(str));
+ } else {
+ str_printfa(str, " ~{%"PRIuUOFF_T"}\r\n", value_len);
+ o_stream_nsend(client->output, str_data(str), str_len(str));
+
+ ctx->cur_stream = i_stream_create_sized(value.value_stream, value_len);
+ i_stream_unref(&value.value_stream);
+ }
+}
+
+static bool
+cmd_getmetadata_stream_continue(struct imap_getmetadata_context *ctx)
+{
+ enum ostream_send_istream_result res;
+
+ o_stream_set_max_buffer_size(ctx->cmd->client->output, 0);
+ res = o_stream_send_istream(ctx->cmd->client->output, ctx->cur_stream);
+ o_stream_set_max_buffer_size(ctx->cmd->client->output, SIZE_MAX);
+
+ switch (res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ return TRUE;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ return FALSE;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ e_error(ctx->cmd->client->event, "read(%s) failed: %s",
+ i_stream_get_name(ctx->cur_stream),
+ i_stream_get_error(ctx->cur_stream));
+ client_disconnect(ctx->cmd->client,
+ "Internal GETMETADATA failure");
+ return TRUE;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ /* client disconnected */
+ return TRUE;
+ }
+ i_unreached();
+}
+
+static int
+cmd_getmetadata_send_entry_tree(struct imap_getmetadata_context *ctx,
+ const char *entry)
+{
+ struct client *client = ctx->cmd->client;
+
+ if (o_stream_get_buffer_used_size(client->output) >=
+ CLIENT_OUTPUT_OPTIMAL_SIZE) {
+ if (o_stream_flush(client->output) <= 0) {
+ o_stream_set_flush_pending(client->output, TRUE);
+ return 0;
+ }
+ }
+
+ if (ctx->iter != NULL) {
+ const char *subentry;
+
+ /* DEPTH iteration */
+ do {
+ subentry = imap_metadata_iter_next(ctx->iter);
+ if (subentry == NULL) {
+ /* iteration finished, get to the next entry */
+ if (imap_metadata_iter_deinit(&ctx->iter) < 0) {
+ if (!cmd_getmetadata_handle_error(ctx))
+ i_unreached();
+ }
+ return -1;
+ }
+ } while (ctx->depth == 1 && strchr(subentry, '/') != NULL);
+ entry = t_strconcat(str_c(ctx->iter_entry_prefix), subentry, NULL);
+ }
+ /* send NIL only on depth 0 query */
+ cmd_getmetadata_send_entry(ctx, entry, ctx->depth == 0);
+
+ if (ctx->cur_stream != NULL) {
+ if (!cmd_getmetadata_stream_continue(ctx))
+ return 0;
+ i_stream_unref(&ctx->cur_stream);
+ }
+
+ if (ctx->iter != NULL) {
+ /* already iterating the entry */
+ return 1;
+ } else if (ctx->depth == 0) {
+ /* no iteration for the entry */
+ return -1;
+ } else {
+ /* we just sent the entry root. iterate its children. */
+ str_truncate(ctx->iter_entry_prefix, 0);
+ str_append(ctx->iter_entry_prefix, entry);
+ str_append_c(ctx->iter_entry_prefix, '/');
+
+ ctx->iter = imap_metadata_iter_init(ctx->trans, entry);
+ return 1;
+ }
+}
+
+static void cmd_getmetadata_iter_deinit(struct imap_getmetadata_context *ctx)
+{
+ if (ctx->iter != NULL)
+ (void)imap_metadata_iter_deinit(&ctx->iter);
+ if (ctx->trans != NULL)
+ (void)imap_metadata_transaction_commit(&ctx->trans, NULL, NULL);
+ if (ctx->box != NULL)
+ mailbox_free(&ctx->box);
+ ctx->first_entry_sent = FALSE;
+ ctx->entry_idx = 0;
+}
+
+static void cmd_getmetadata_deinit(struct imap_getmetadata_context *ctx)
+{
+ struct client_command_context *cmd = ctx->cmd;
+
+ cmd_getmetadata_iter_deinit(ctx);
+ cmd->client->output_cmd_lock = NULL;
+
+ if (ctx->list_iter != NULL &&
+ mailbox_list_iter_deinit(&ctx->list_iter) < 0)
+ client_send_list_error(cmd, cmd->client->user->namespaces->list);
+ else if (ctx->last_error != 0) {
+ i_assert(str_len(ctx->last_error_str) > 0);
+ const char *tagline =
+ imap_get_error_string(cmd, str_c(ctx->last_error_str),
+ ctx->last_error);
+ client_send_tagline(cmd, tagline);
+ } else if (ctx->largest_seen_size != 0) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "OK [METADATA LONGENTRIES %"PRIuUOFF_T"] "
+ "Getmetadata completed.", ctx->largest_seen_size));
+ } else {
+ client_send_tagline(cmd, "OK Getmetadata completed.");
+ }
+}
+
+static bool cmd_getmetadata_continue(struct client_command_context *cmd)
+{
+ struct imap_getmetadata_context *ctx = cmd->context;
+ const char *const *entries;
+ unsigned int count;
+ int ret;
+
+ if (cmd->cancel) {
+ cmd_getmetadata_deinit(ctx);
+ return TRUE;
+ }
+
+ if (ctx->cur_stream != NULL) {
+ if (!cmd_getmetadata_stream_continue(ctx))
+ return FALSE;
+ i_stream_unref(&ctx->cur_stream);
+ }
+
+ entries = array_get(&ctx->entries, &count);
+ for (; ctx->entry_idx < count; ctx->entry_idx++) {
+ do {
+ T_BEGIN {
+ ret = cmd_getmetadata_send_entry_tree(ctx, entries[ctx->entry_idx]);
+ } T_END;
+ if (ret == 0)
+ return FALSE;
+ } while (ret > 0);
+ }
+ if (ctx->first_entry_sent)
+ o_stream_nsend_str(cmd->client->output, ")\r\n");
+
+ if (str_len(ctx->delayed_errors) > 0) {
+ o_stream_nsend(cmd->client->output,
+ str_data(ctx->delayed_errors),
+ str_len(ctx->delayed_errors));
+ str_truncate(ctx->delayed_errors, 0);
+ }
+
+ cmd_getmetadata_iter_deinit(ctx);
+ if (ctx->list_iter != NULL)
+ return cmd_getmetadata_mailbox_iter_next(ctx);
+ cmd_getmetadata_deinit(ctx);
+ return TRUE;
+}
+
+static bool
+cmd_getmetadata_start(struct imap_getmetadata_context *ctx)
+{
+ struct client_command_context *cmd = ctx->cmd;
+
+ if (ctx->depth > 0)
+ ctx->iter_entry_prefix = str_new(cmd->pool, 128);
+ imap_metadata_transaction_validated_only(ctx->trans,
+ !cmd->client->set->imap_metadata);
+
+ if (!cmd_getmetadata_continue(cmd)) {
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+ cmd->func = cmd_getmetadata_continue;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+cmd_getmetadata_server(struct imap_getmetadata_context *ctx)
+{
+ ctx->trans = imap_metadata_transaction_begin_server(ctx->cmd->client->user);
+ return cmd_getmetadata_start(ctx);
+}
+
+static int
+cmd_getmetadata_try_mailbox(struct imap_getmetadata_context *ctx,
+ struct mail_namespace *ns, const char *mailbox)
+{
+ ctx->box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY);
+ event_add_str(ctx->cmd->global_event, "mailbox",
+ mailbox_get_vname(ctx->box));
+
+ enum mailbox_existence existence;
+ if (mailbox_exists(ctx->box, TRUE, &existence) < 0) {
+ return -1;
+ } else if (existence == MAILBOX_EXISTENCE_NONE) {
+ const char *err = t_strdup_printf(MAIL_ERRSTR_MAILBOX_NOT_FOUND,
+ mailbox_get_vname(ctx->box));
+ mail_storage_set_error(ctx->box->storage, MAIL_ERROR_NOTFOUND, err);
+ return -1;
+ }
+
+ ctx->trans = imap_metadata_transaction_begin(ctx->box);
+ return cmd_getmetadata_start(ctx) ? 1 : 0;
+}
+
+static bool
+cmd_getmetadata_mailbox(struct imap_getmetadata_context *ctx,
+ struct mail_namespace *ns, const char *mailbox)
+{
+ int ret;
+
+ ret = cmd_getmetadata_try_mailbox(ctx, ns, mailbox);
+ if (ret < 0) {
+ client_send_box_error(ctx->cmd, ctx->box);
+ mailbox_free(&ctx->box);
+ }
+ return ret != 0;
+}
+
+static bool
+cmd_getmetadata_mailbox_iter_next(struct imap_getmetadata_context *ctx)
+{
+ const struct mailbox_info *info;
+ int ret;
+
+ while ((info = mailbox_list_iter_next(ctx->list_iter)) != NULL) {
+ if ((info->flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) != 0)
+ continue;
+ ret = cmd_getmetadata_try_mailbox(ctx, info->ns, info->vname);
+ if (ret > 0) {
+ /* we'll already recursively went through
+ all the mailboxes (FIXME: ugly and potentially
+ stack consuming) */
+ return TRUE;
+ } else if (ret == 0) {
+ /* need to send more data later */
+ return FALSE;
+ }
+ T_BEGIN {
+ client_send_line(ctx->cmd->client, t_strdup_printf(
+ "* NO Failed to open mailbox %s: %s",
+ info->vname, mailbox_get_last_error(ctx->box, NULL)));
+ } T_END;
+ mailbox_free(&ctx->box);
+ }
+ cmd_getmetadata_deinit(ctx);
+ return TRUE;
+}
+
+bool cmd_getmetadata(struct client_command_context *cmd)
+{
+ struct imap_getmetadata_context *ctx;
+ struct mail_namespace *ns;
+ const struct imap_arg *args, *options, *entries;
+ const char *mailbox, *entry_name;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ ctx = p_new(cmd->pool, struct imap_getmetadata_context, 1);
+ ctx->cmd = cmd;
+ ctx->maxsize = (uint32_t)-1;
+ ctx->cmd->context = ctx;
+ ctx->delayed_errors = str_new(cmd->pool, 128);
+ ctx->last_error_str = str_new(cmd->pool, 128);
+
+ if (imap_arg_get_list(&args[0], &options)) {
+ if (!cmd_getmetadata_parse_options(ctx, options))
+ return TRUE;
+ args++;
+ }
+ if (!imap_arg_get_astring(&args[0], &mailbox)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+ if (!imap_arg_get_list(&args[1], &entries)) {
+ if (!imap_arg_get_astring(&args[1], &entry_name) ||
+ !IMAP_ARG_IS_EOL(&args[2])) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+ entries = args+1;
+ }
+ if (!imap_metadata_parse_entry_names(ctx, entries))
+ return TRUE;
+
+ if (mailbox[0] == '\0') {
+ /* server attribute */
+ return cmd_getmetadata_server(ctx);
+ } else if (strchr(mailbox, '*') == NULL &&
+ strchr(mailbox, '%') == NULL) {
+ /* mailbox attribute */
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+ return cmd_getmetadata_mailbox(ctx, ns, mailbox);
+ } else {
+ /* wildcards in mailbox name. this isn't supported by RFC 5464,
+ but it was in the earlier drafts and is already used by
+ some software (Horde). */
+ const char *patterns[2];
+ patterns[0] = mailbox; patterns[1] = NULL;
+
+ ctx->list_iter =
+ mailbox_list_iter_init_namespaces(
+ cmd->client->user->namespaces,
+ patterns, MAIL_NAMESPACE_TYPE_MASK_ALL, 0);
+ return cmd_getmetadata_mailbox_iter_next(ctx);
+ }
+}
diff --git a/src/imap/cmd-id.c b/src/imap/cmd-id.c
new file mode 100644
index 0000000..98355d4
--- /dev/null
+++ b/src/imap/cmd-id.c
@@ -0,0 +1,27 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-id.h"
+
+bool cmd_id(struct client_command_context *cmd)
+{
+ const struct imap_settings *set = cmd->client->set;
+ const struct imap_arg *args;
+ const char *value;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!cmd->client->id_logged) {
+ cmd->client->id_logged = TRUE;
+ value = imap_id_args_get_log_reply(args, set->imap_id_log);
+ if (value != NULL)
+ e_info(cmd->client->event, "ID sent: %s", value);
+ }
+
+ client_send_line(cmd->client, t_strdup_printf(
+ "* ID %s", imap_id_reply_generate(set->imap_id_send)));
+ client_send_tagline(cmd, "OK ID completed.");
+ return TRUE;
+}
+
diff --git a/src/imap/cmd-idle.c b/src/imap/cmd-idle.c
new file mode 100644
index 0000000..2b31dc7
--- /dev/null
+++ b/src/imap/cmd-idle.c
@@ -0,0 +1,308 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "istream.h"
+#include "ostream.h"
+#include "crc32.h"
+#include "mail-storage-settings.h"
+#include "imap-commands.h"
+#include "imap-keepalive.h"
+#include "imap-sync.h"
+
+struct cmd_idle_context {
+ struct client *client;
+ struct client_command_context *cmd;
+
+ struct imap_sync_context *sync_ctx;
+ struct timeout *keepalive_to, *to_hibernate;
+
+ bool manual_cork:1;
+ bool sync_pending:1;
+};
+
+static void idle_add_keepalive_timeout(struct cmd_idle_context *ctx);
+static bool cmd_idle_continue(struct client_command_context *cmd);
+
+static void
+idle_finish(struct cmd_idle_context *ctx, bool done_ok, bool free_cmd)
+{
+ struct client *client = ctx->client;
+
+ timeout_remove(&ctx->keepalive_to);
+ timeout_remove(&ctx->to_hibernate);
+
+ if (ctx->sync_ctx != NULL) {
+ /* we're here only in connection failure cases */
+ (void)imap_sync_deinit(ctx->sync_ctx, ctx->cmd);
+ }
+
+ o_stream_cork(client->output);
+ io_remove(&client->io);
+
+ if (client->mailbox != NULL)
+ mailbox_notify_changes_stop(client->mailbox);
+
+ if (done_ok)
+ client_send_tagline(ctx->cmd, "OK Idle completed.");
+ else
+ client_send_tagline(ctx->cmd, "BAD Expected DONE.");
+
+ o_stream_uncork(client->output);
+ if (free_cmd)
+ client_command_free(&ctx->cmd);
+}
+
+static bool
+idle_client_handle_input(struct cmd_idle_context *ctx, bool free_cmd)
+{
+ const char *line;
+
+ while ((line = i_stream_next_line(ctx->client->input)) != NULL) {
+ if (ctx->client->input_skip_line)
+ ctx->client->input_skip_line = FALSE;
+ else {
+ idle_finish(ctx, strcasecmp(line, "DONE") == 0,
+ free_cmd);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool idle_client_input_more(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+
+ client->last_input = ioloop_time;
+ timeout_reset(client->to_idle);
+
+ switch (i_stream_read(client->input)) {
+ case -1:
+ /* disconnected */
+ client_disconnect(client, NULL);
+ return TRUE;
+ case -2:
+ client->input_skip_line = TRUE;
+ idle_finish(ctx, FALSE, TRUE);
+ return TRUE;
+ }
+
+ if (ctx->sync_ctx != NULL) {
+ /* we're still sending output to client. wait until it's all
+ sent so we don't lose any changes. */
+ io_remove(&client->io);
+ return FALSE;
+ }
+
+ return idle_client_handle_input(ctx, TRUE);
+}
+
+static void idle_client_input(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+
+ if (idle_client_input_more(ctx))
+ client_continue_pending_input(client);
+}
+
+static void keepalive_timeout(struct cmd_idle_context *ctx)
+{
+ if (ctx->client->output_cmd_lock != NULL) {
+ /* it's busy sending output */
+ return;
+ }
+
+ if (o_stream_get_buffer_used_size(ctx->client->output) == 0) {
+ /* Sending this keeps NATs/stateful firewalls alive.
+ Sending this also catches dead connections. Don't send
+ anything if there is already data waiting in output
+ buffer. */
+ o_stream_cork(ctx->client->output);
+ client_send_line(ctx->client, "* OK Still here");
+ o_stream_uncork(ctx->client->output);
+ }
+ /* Make sure idling connections don't get disconnected. There are
+ several clients that really want to IDLE forever and there's not
+ much harm in letting them do so. */
+ timeout_reset(ctx->client->to_idle);
+ /* recalculate time for the next keepalive timeout */
+ idle_add_keepalive_timeout(ctx);
+}
+
+static bool idle_sync_now(struct mailbox *box, struct cmd_idle_context *ctx)
+{
+ i_assert(ctx->sync_ctx == NULL);
+
+ /* hibernation can't happen while sync is running.
+ the timeout is added back afterwards. */
+ timeout_remove(&ctx->to_hibernate);
+
+ ctx->sync_pending = FALSE;
+ ctx->sync_ctx = imap_sync_init(ctx->client, box, 0, 0);
+ return cmd_idle_continue(ctx->cmd);
+}
+
+static void idle_callback(struct mailbox *box, struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+
+ if (ctx->sync_ctx != NULL)
+ ctx->sync_pending = TRUE;
+ else {
+ ctx->manual_cork = TRUE;
+ (void)idle_sync_now(box, ctx);
+ if (client->disconnected)
+ client_destroy(client, NULL);
+ }
+}
+
+static void idle_add_keepalive_timeout(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+ unsigned int interval = client->set->imap_idle_notify_interval;
+
+ if (interval == 0)
+ return;
+
+ interval = imap_keepalive_interval_msecs(client->user->username,
+ client->user->conn.remote_ip,
+ interval);
+
+ timeout_remove(&ctx->keepalive_to);
+ ctx->keepalive_to = timeout_add(interval, keepalive_timeout, ctx);
+}
+
+static void idle_hibernate_timeout(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+ const char *reason;
+
+ i_assert(ctx->sync_ctx == NULL);
+ i_assert(!ctx->sync_pending);
+
+ if (imap_client_hibernate(&client, &reason)) {
+ /* client may be destroyed now */
+ } else {
+ /* failed - don't bother retrying */
+ timeout_remove(&ctx->to_hibernate);
+ }
+}
+
+static void idle_add_hibernate_timeout(struct cmd_idle_context *ctx)
+{
+ unsigned int secs = ctx->client->set->imap_hibernate_timeout;
+
+ i_assert(ctx->to_hibernate == NULL);
+
+ if (secs == 0)
+ return;
+
+ ctx->to_hibernate =
+ timeout_add(secs * 1000, idle_hibernate_timeout, ctx);
+}
+
+static bool cmd_idle_continue(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_idle_context *ctx = cmd->context;
+ uoff_t orig_offset = client->output->offset;
+
+ if (cmd->cancel) {
+ idle_finish(ctx, FALSE, FALSE);
+ return TRUE;
+ }
+
+ if (ctx->to_hibernate != NULL)
+ timeout_reset(ctx->to_hibernate);
+
+ if (ctx->manual_cork) {
+ /* we're coming from idle_callback instead of a normal
+ I/O handler, so we'll have to do corking manually */
+ o_stream_cork(client->output);
+ }
+
+ if (ctx->sync_ctx != NULL) {
+ if (imap_sync_more(ctx->sync_ctx) == 0) {
+ /* unfinished */
+ if (ctx->manual_cork) {
+ ctx->manual_cork = FALSE;
+ o_stream_uncork(client->output);
+ }
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+ return FALSE;
+ }
+
+ if (imap_sync_deinit(ctx->sync_ctx, ctx->cmd) < 0) {
+ client_send_untagged_storage_error(client,
+ mailbox_get_storage(client->mailbox));
+ mailbox_notify_changes_stop(client->mailbox);
+ }
+ ctx->sync_ctx = NULL;
+ }
+ if (client->output->offset != orig_offset &&
+ ctx->keepalive_to != NULL)
+ idle_add_keepalive_timeout(ctx);
+
+ if (ctx->sync_pending) {
+ /* more changes occurred while we were sending changes to
+ client.
+
+ NOTE: this recurses back to this function,
+ so we return here instead of doing everything twice. */
+ return idle_sync_now(client->mailbox, ctx);
+ }
+ if (ctx->to_hibernate == NULL)
+ idle_add_hibernate_timeout(ctx);
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
+
+ if (ctx->manual_cork) {
+ ctx->manual_cork = FALSE;
+ o_stream_uncork(client->output);
+ }
+
+ if (client->output->closed) {
+ idle_finish(ctx, FALSE, FALSE);
+ return TRUE;
+ }
+ if (client->io == NULL) {
+ /* input is pending. add the io back and mark the input as
+ pending. we can't safely read more input immediately here. */
+ client->io = io_add_istream(client->input,
+ idle_client_input, ctx);
+ i_stream_set_input_pending(client->input, TRUE);
+ }
+ return FALSE;
+}
+
+bool cmd_idle(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_idle_context *ctx;
+
+ ctx = p_new(cmd->pool, struct cmd_idle_context, 1);
+ ctx->cmd = cmd;
+ ctx->client = client;
+ idle_add_keepalive_timeout(ctx);
+ idle_add_hibernate_timeout(ctx);
+
+ if (client->mailbox != NULL)
+ mailbox_notify_changes(client->mailbox, idle_callback, ctx);
+ if (!client->state_import_idle_continue)
+ client_send_line(client, "+ idling");
+ else {
+ /* continuing an IDLE after hibernation */
+ client->state_import_idle_continue = FALSE;
+ }
+
+ io_remove(&client->io);
+ client->io = io_add_istream(client->input, idle_client_input, ctx);
+
+ cmd->func = cmd_idle_continue;
+ cmd->context = ctx;
+
+ /* check immediately if there are changes. if they came before we
+ added mailbox-notifier, we wouldn't see them otherwise. */
+ if (client->mailbox != NULL)
+ idle_sync_now(client->mailbox, ctx);
+ return idle_client_handle_input(ctx, FALSE);
+}
diff --git a/src/imap/cmd-list.c b/src/imap/cmd-list.c
new file mode 100644
index 0000000..dd6c00e
--- /dev/null
+++ b/src/imap/cmd-list.c
@@ -0,0 +1,484 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "mailbox-list-iter.h"
+#include "imap-utf7.h"
+#include "imap-quote.h"
+#include "imap-match.h"
+#include "imap-status.h"
+#include "imap-commands.h"
+#include "imap-list.h"
+
+struct cmd_list_context {
+ struct client_command_context *cmd;
+ struct mail_user *user;
+
+ enum mailbox_list_iter_flags list_flags;
+ struct imap_status_items status_items;
+
+ struct mailbox_list_iterate_context *list_iter;
+
+ bool lsub:1;
+ bool lsub_no_unsubscribed:1;
+ bool used_listext:1;
+ bool used_status:1;
+};
+
+static void
+mailbox_flags2str(struct cmd_list_context *ctx, string_t *str,
+ const char *special_use, enum mailbox_info_flags flags)
+{
+ size_t orig_len = str_len(str);
+
+ if ((flags & MAILBOX_NONEXISTENT) != 0 && !ctx->used_listext) {
+ flags |= MAILBOX_NOSELECT;
+ flags &= ENUM_NEGATE(MAILBOX_NONEXISTENT);
+ }
+
+ if ((ctx->list_flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0)
+ flags &= ENUM_NEGATE(MAILBOX_CHILDREN | MAILBOX_NOCHILDREN);
+
+ if ((flags & MAILBOX_CHILD_SUBSCRIBED) != 0 &&
+ (flags & MAILBOX_SUBSCRIBED) == 0 && !ctx->used_listext) {
+ /* LSUB uses \Noselect for this */
+ flags |= MAILBOX_NOSELECT;
+ } else if ((ctx->list_flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) == 0)
+ flags &= ENUM_NEGATE(MAILBOX_SUBSCRIBED);
+ imap_mailbox_flags2str(str, flags);
+
+ if ((ctx->list_flags & MAILBOX_LIST_ITER_RETURN_SPECIALUSE) != 0 &&
+ special_use != NULL) {
+ if (str_len(str) != orig_len)
+ str_append_c(str, ' ');
+ str_append(str, special_use);
+ }
+}
+
+static void
+mailbox_childinfo2str(struct cmd_list_context *ctx, string_t *str,
+ enum mailbox_info_flags flags)
+{
+ if (!ctx->used_listext)
+ return;
+
+ if ((flags & MAILBOX_CHILD_SUBSCRIBED) != 0 &&
+ (ctx->list_flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0)
+ str_append(str, " (CHILDINFO (\"SUBSCRIBED\"))");
+ if ((flags & MAILBOX_CHILD_SPECIALUSE) != 0 &&
+ (ctx->list_flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0)
+ str_append(str, " (CHILDINFO (\"SPECIAL-USE\"))");
+}
+
+static bool
+parse_select_flags(struct cmd_list_context *ctx, const struct imap_arg *args)
+{
+ enum mailbox_list_iter_flags list_flags = 0;
+ const char *str;
+
+ while (!IMAP_ARG_IS_EOL(args)) {
+ if (!imap_arg_get_atom(args, &str)) {
+ client_send_command_error(ctx->cmd,
+ "List options contains non-atoms.");
+ return FALSE;
+ }
+
+ if (strcasecmp(str, "SUBSCRIBED") == 0) {
+ list_flags |= MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
+ MAILBOX_LIST_ITER_RETURN_SUBSCRIBED;
+ } else if (strcasecmp(str, "RECURSIVEMATCH") == 0)
+ list_flags |= MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH;
+ else if (strcasecmp(str, "SPECIAL-USE") == 0) {
+ list_flags |= MAILBOX_LIST_ITER_SELECT_SPECIALUSE |
+ MAILBOX_LIST_ITER_RETURN_SPECIALUSE;
+ } else if (strcasecmp(str, "REMOTE") == 0) {
+ /* not supported, ignore */
+ } else {
+ /* skip also optional list value */
+ client_send_command_error(ctx->cmd,
+ "Unknown select options");
+ return FALSE;
+ }
+ args++;
+ }
+
+ if ((list_flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0 &&
+ (list_flags & (MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
+ MAILBOX_LIST_ITER_SELECT_SPECIALUSE)) == 0) {
+ client_send_command_error(ctx->cmd,
+ "RECURSIVEMATCH must not be the only selection.");
+ return FALSE;
+ }
+
+ ctx->list_flags = list_flags;
+ return TRUE;
+}
+
+static bool
+parse_return_flags(struct cmd_list_context *ctx, const struct imap_arg *args)
+{
+ enum mailbox_list_iter_flags list_flags = 0;
+ const struct imap_arg *list_args;
+ const char *str;
+
+ while (!IMAP_ARG_IS_EOL(args)) {
+ if (!imap_arg_get_atom(args, &str)) {
+ client_send_command_error(ctx->cmd,
+ "List options contains non-atoms.");
+ return FALSE;
+ }
+
+ if (strcasecmp(str, "SUBSCRIBED") == 0)
+ list_flags |= MAILBOX_LIST_ITER_RETURN_SUBSCRIBED;
+ else if (strcasecmp(str, "CHILDREN") == 0)
+ list_flags |= MAILBOX_LIST_ITER_RETURN_CHILDREN;
+ else if (strcasecmp(str, "SPECIAL-USE") == 0)
+ list_flags |= MAILBOX_LIST_ITER_RETURN_SPECIALUSE;
+ else if (strcasecmp(str, "STATUS") == 0 &&
+ imap_arg_get_list(&args[1], &list_args)) {
+ if (imap_status_parse_items(ctx->cmd, list_args,
+ &ctx->status_items) < 0)
+ return FALSE;
+ ctx->used_status = TRUE;
+ args++;
+ } else {
+ /* skip also optional list value */
+ client_send_command_error(ctx->cmd,
+ "Unknown return options");
+ return FALSE;
+ }
+ args++;
+ }
+
+ ctx->list_flags |= list_flags;
+ return TRUE;
+}
+
+static const char *ns_prefix_mutf7(struct mail_namespace *ns)
+{
+ string_t *str;
+
+ if (*ns->prefix == '\0')
+ return "";
+
+ str = t_str_new(64);
+ if (imap_utf8_to_utf7(ns->prefix, str) < 0)
+ i_panic("Namespace prefix not UTF-8: %s", ns->prefix);
+ return str_c(str);
+}
+
+static void list_reply_append_ns_sep_param(string_t *str, char sep)
+{
+ str_append_c(str, '"');
+ if (sep == '\\')
+ str_append(str, "\\\\");
+ else
+ str_append_c(str, sep);
+ str_append_c(str, '"');
+}
+
+static void
+list_send_status(struct cmd_list_context *ctx, const char *name,
+ const char *mutf7_name, enum mailbox_info_flags flags)
+{
+ struct imap_status_result result;
+ struct mail_namespace *ns;
+
+ if ((flags & (MAILBOX_NONEXISTENT | MAILBOX_NOSELECT)) != 0) {
+ /* doesn't exist, don't even try to get STATUS */
+ return;
+ }
+ if ((flags & MAILBOX_SUBSCRIBED) == 0 &&
+ (ctx->list_flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ /* listing subscriptions, but only child is subscribed */
+ return;
+ }
+
+ /* if we're listing subscriptions and there are subscriptions=no
+ namespaces, ctx->ns may not point to correct one */
+ ns = mail_namespace_find(ctx->user->namespaces, name);
+ if (imap_status_get(ctx->cmd, ns, name,
+ &ctx->status_items, &result) < 0) {
+ client_send_line(ctx->cmd->client,
+ t_strconcat("* ", result.errstr, NULL));
+ return;
+ }
+
+ imap_status_send(ctx->cmd->client, mutf7_name,
+ &ctx->status_items, &result);
+}
+
+static bool cmd_list_continue(struct client_command_context *cmd)
+{
+ struct cmd_list_context *ctx = cmd->context;
+ const struct mailbox_info *info;
+ enum mailbox_info_flags flags;
+ string_t *str, *mutf7_name;
+ const char *name;
+ int ret = 0;
+
+ if (cmd->cancel) {
+ if (ctx->list_iter != NULL)
+ (void)mailbox_list_iter_deinit(&ctx->list_iter);
+ return TRUE;
+ }
+ str = t_str_new(256);
+ mutf7_name = t_str_new(128);
+ while ((info = mailbox_list_iter_next(ctx->list_iter)) != NULL) {
+ name = info->vname;
+ flags = info->flags;
+
+ if ((flags & MAILBOX_CHILD_SUBSCRIBED) != 0 &&
+ (flags & MAILBOX_SUBSCRIBED) == 0 &&
+ ctx->lsub_no_unsubscribed) {
+ /* mask doesn't end with %. we don't want to show
+ any extra mailboxes. */
+ continue;
+ }
+
+ str_truncate(mutf7_name, 0);
+ if (imap_utf8_to_utf7(name, mutf7_name) < 0)
+ i_panic("LIST: Mailbox name not UTF-8: %s", name);
+
+ str_truncate(str, 0);
+ str_printfa(str, "* %s (", ctx->lsub ? "LSUB" : "LIST");
+ mailbox_flags2str(ctx, str, info->special_use, flags);
+ str_append(str, ") ");
+ list_reply_append_ns_sep_param(str,
+ mail_namespace_get_sep(info->ns));
+ str_append_c(str, ' ');
+ imap_append_astring(str, str_c(mutf7_name));
+ mailbox_childinfo2str(ctx, str, flags);
+
+ ret = client_send_line_next(ctx->cmd->client, str_c(str));
+ if (ctx->used_status) T_BEGIN {
+ list_send_status(ctx, name, str_c(mutf7_name), flags);
+ } T_END;
+ if (ret == 0) {
+ /* buffer is full, continue later */
+ return FALSE;
+ }
+ }
+
+ if (mailbox_list_iter_deinit(&ctx->list_iter) < 0) {
+ client_send_list_error(cmd, ctx->user->namespaces->list);
+ return TRUE;
+ }
+ client_send_tagline(cmd, !ctx->lsub ?
+ "OK List completed." :
+ "OK Lsub completed.");
+ return TRUE;
+}
+
+static const char *const *
+list_get_ref_patterns(struct cmd_list_context *ctx, const char *ref,
+ const char *const *patterns)
+{
+ struct mail_namespace *ns;
+ const char *const *pat, *pattern;
+ ARRAY(const char *) full_patterns;
+
+ if (*ref == '\0')
+ return patterns;
+
+ ns = mail_namespace_find(ctx->user->namespaces, ref);
+
+ t_array_init(&full_patterns, 16);
+ for (pat = patterns; *pat != NULL; pat++) {
+ pattern = mailbox_list_join_refpattern(ns->list, ref, *pat);
+ array_push_back(&full_patterns, &pattern);
+ }
+ array_append_zero(&full_patterns); /* NULL-terminate */
+ return array_front(&full_patterns);
+}
+
+static void cmd_list_init(struct cmd_list_context *ctx,
+ const char *const *patterns)
+{
+ enum mail_namespace_type type_mask = MAIL_NAMESPACE_TYPE_MASK_ALL;
+
+ ctx->list_iter =
+ mailbox_list_iter_init_namespaces(ctx->user->namespaces,
+ patterns, type_mask,
+ ctx->list_flags);
+}
+
+static void cmd_list_ref_root(struct client *client, const char *ref)
+{
+ struct mail_namespace *ns;
+ const char *ns_prefix;
+ char ns_sep;
+ string_t *str;
+
+ /* Special request to return the hierarchy delimiter and mailbox root
+ name. If namespace has a prefix, it's returned as the mailbox root.
+ Otherwise we'll emulate UW-IMAP behavior. */
+ ns = mail_namespace_find_visible(client->user->namespaces, ref);
+ if (ns != NULL) {
+ ns_prefix = ns_prefix_mutf7(ns);
+ ns_sep = mail_namespace_get_sep(ns);
+ } else {
+ ns_prefix = "";
+ ns_sep = mail_namespaces_get_root_sep(client->user->namespaces);
+ }
+
+ str = t_str_new(64);
+ str_append(str, "* LIST (\\Noselect) \"");
+ if (ns_sep == '\\' || ns_sep == '"')
+ str_append_c(str, '\\');
+ str_printfa(str, "%c\" ", ns_sep);
+ if (*ns_prefix != '\0') {
+ /* non-hidden namespace, use it as the root name */
+ imap_append_astring(str, ns_prefix);
+ } else {
+ /* Hidden namespace or empty namespace prefix. We could just
+ return an empty root name, but it's safer to emulate what
+ UW-IMAP does. With full filesystem access this might even
+ matter (root of "~user/mail/" is "~user/", not "") */
+ const char *p = strchr(ref, ns_sep);
+
+ if (p == NULL)
+ str_append(str, "\"\"");
+ else
+ imap_append_astring(str, t_strdup_until(ref, p + 1));
+ }
+ client_send_line(client, str_c(str));
+}
+
+bool cmd_list_full(struct client_command_context *cmd, bool lsub)
+{
+ struct client *client = cmd->client;
+ const struct imap_arg *args, *list_args;
+ unsigned int arg_count;
+ struct cmd_list_context *ctx;
+ ARRAY(const char *) patterns = ARRAY_INIT;
+ const char *ref, *pattern, *const *patterns_strarr;
+ string_t *str;
+
+ /* [(<selection options>)] <reference> <pattern>|(<pattern list>)
+ [RETURN (<return options>)] */
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ ctx = p_new(cmd->pool, struct cmd_list_context, 1);
+ ctx->cmd = cmd;
+ ctx->lsub = lsub;
+ ctx->user = client->user;
+
+ cmd->context = ctx;
+
+ if (!lsub && imap_arg_get_list(&args[0], &list_args)) {
+ /* LIST-EXTENDED selection options */
+ ctx->used_listext = TRUE;
+ if (!parse_select_flags(ctx, list_args))
+ return TRUE;
+ args++;
+ }
+
+ if (!imap_arg_get_astring(&args[0], &ref)) {
+ client_send_command_error(cmd, "Invalid reference.");
+ return TRUE;
+ }
+ str = t_str_new(64);
+ if (imap_utf7_to_utf8(ref, str) == 0)
+ ref = p_strdup(cmd->pool, str_c(str));
+ str_truncate(str, 0);
+
+ if (imap_arg_get_list_full(&args[1], &list_args, &arg_count)) {
+ ctx->used_listext = TRUE;
+ /* convert pattern list to string array */
+ p_array_init(&patterns, cmd->pool, arg_count);
+ for (; !IMAP_ARG_IS_EOL(list_args); list_args++) {
+ if (!imap_arg_get_astring(list_args, &pattern)) {
+ client_send_command_error(cmd,
+ "Invalid pattern list.");
+ return TRUE;
+ }
+ if (imap_utf7_to_utf8(pattern, str) == 0)
+ pattern = p_strdup(cmd->pool, str_c(str));
+ array_push_back(&patterns, &pattern);
+ str_truncate(str, 0);
+ }
+ args += 2;
+ } else {
+ if (!imap_arg_get_astring(&args[1], &pattern)) {
+ client_send_command_error(cmd, "Invalid pattern.");
+ return TRUE;
+ }
+ if (imap_utf7_to_utf8(pattern, str) == 0)
+ pattern = p_strdup(cmd->pool, str_c(str));
+
+ p_array_init(&patterns, cmd->pool, 1);
+ array_push_back(&patterns, &pattern);
+ args += 2;
+
+ if (lsub) {
+ size_t len = strlen(pattern);
+ ctx->lsub_no_unsubscribed = len == 0 ||
+ pattern[len-1] != '%';
+ }
+ }
+
+ if (imap_arg_atom_equals(&args[0], "RETURN") &&
+ imap_arg_get_list(&args[1], &list_args)) {
+ /* LIST-EXTENDED return options */
+ ctx->used_listext = TRUE;
+ if (!parse_return_flags(ctx, list_args))
+ return TRUE;
+ args += 2;
+ }
+
+ if (lsub) {
+ /* LSUB - we don't care about flags except if
+ tb-lsub-flags workaround is explicitly set */
+ ctx->list_flags |= MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
+ MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH;
+ /* Return SPECIAL-USE flags for LSUB anyway. Outlook 2013
+ does this and since it's not expensive for us to return
+ them, it's not worth the trouble of adding an explicit
+ workaround setting. */
+ ctx->list_flags |= MAILBOX_LIST_ITER_RETURN_SPECIALUSE;
+ if ((cmd->client->set->parsed_workarounds &
+ WORKAROUND_TB_LSUB_FLAGS) == 0)
+ ctx->list_flags |= MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
+ } else if (!ctx->used_listext) {
+ /* non-extended LIST: use default flags */
+ ctx->list_flags |= MAILBOX_LIST_ITER_RETURN_CHILDREN |
+ MAILBOX_LIST_ITER_RETURN_SPECIALUSE;
+ }
+
+ if (!IMAP_ARG_IS_EOL(args)) {
+ client_send_command_error(cmd, "Extra arguments.");
+ return TRUE;
+ }
+
+ array_append_zero(&patterns); /* NULL-terminate */
+ patterns_strarr = array_front(&patterns);
+ if (!ctx->used_listext && !lsub && *patterns_strarr[0] == '\0') {
+ /* Only LIST ref "" gets us here */
+ cmd_list_ref_root(client, ref);
+ client_send_tagline(cmd, "OK List completed.");
+ } else {
+ patterns_strarr =
+ list_get_ref_patterns(ctx, ref, patterns_strarr);
+ cmd_list_init(ctx, patterns_strarr);
+
+ if (!cmd_list_continue(cmd)) {
+ /* unfinished */
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+ cmd->func = cmd_list_continue;
+ return FALSE;
+ }
+
+ cmd->context = NULL;
+ return TRUE;
+ }
+ return TRUE;
+}
+
+bool cmd_list(struct client_command_context *cmd)
+{
+ return cmd_list_full(cmd, FALSE);
+}
diff --git a/src/imap/cmd-logout.c b/src/imap/cmd-logout.c
new file mode 100644
index 0000000..fbdd416
--- /dev/null
+++ b/src/imap/cmd-logout.c
@@ -0,0 +1,24 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ostream.h"
+#include "imap-commands.h"
+
+bool cmd_logout(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+
+ client->logged_out = TRUE;
+ client_send_line(client, "* BYE Logging out");
+
+ if (client->mailbox != NULL) {
+ /* this could be done at client_disconnect() as well,
+ but eg. mbox rewrite takes a while so the waiting is
+ better to happen before "OK" message. */
+ imap_client_close_mailbox(client);
+ }
+
+ client_send_tagline(cmd, "OK Logout completed.");
+ client_disconnect(client, "Logged out");
+ return TRUE;
+}
diff --git a/src/imap/cmd-lsub.c b/src/imap/cmd-lsub.c
new file mode 100644
index 0000000..55d9b68
--- /dev/null
+++ b/src/imap/cmd-lsub.c
@@ -0,0 +1,9 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+
+bool cmd_lsub(struct client_command_context *cmd)
+{
+ return cmd_list_full(cmd, TRUE);
+}
diff --git a/src/imap/cmd-namespace.c b/src/imap/cmd-namespace.c
new file mode 100644
index 0000000..23d2286
--- /dev/null
+++ b/src/imap/cmd-namespace.c
@@ -0,0 +1,99 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "imap-utf7.h"
+#include "imap-quote.h"
+#include "imap-commands.h"
+#include "mail-namespace.h"
+
+struct namespace_order {
+ int secondary_order;
+ struct mail_namespace *ns;
+};
+
+static int namespace_order_cmp(const struct namespace_order *no1,
+ const struct namespace_order *no2)
+{
+ if (no1->ns->set->order < no2->ns->set->order)
+ return -1;
+ if (no1->ns->set->order > no2->ns->set->order)
+ return 1;
+
+ if (no1->secondary_order < no2->secondary_order)
+ return -1;
+ if (no1->secondary_order > no2->secondary_order)
+ return 1;
+ return 0;
+}
+
+static void list_namespaces(struct mail_namespace *ns,
+ enum mail_namespace_type type, string_t *str)
+{
+ ARRAY(struct namespace_order) ns_order;
+ struct namespace_order *no;
+ unsigned int count = 0;
+ string_t *mutf7_prefix;
+ char ns_sep;
+
+ t_array_init(&ns_order, 4);
+
+ while (ns != NULL) {
+ if (ns->type == type &&
+ (ns->flags & NAMESPACE_FLAG_HIDDEN) == 0) {
+ no = array_append_space(&ns_order);
+ no->ns = ns;
+ no->secondary_order = ++count;
+ }
+ ns = ns->next;
+ }
+
+ if (array_count(&ns_order) == 0) {
+ str_append(str, "NIL");
+ return;
+ }
+ array_sort(&ns_order, namespace_order_cmp);
+
+ mutf7_prefix = t_str_new(64);
+ str_append_c(str, '(');
+ array_foreach_modifiable(&ns_order, no) {
+ ns_sep = mail_namespace_get_sep(no->ns);
+ str_append_c(str, '(');
+
+ str_truncate(mutf7_prefix, 0);
+ if (imap_utf8_to_utf7(no->ns->prefix, mutf7_prefix) < 0) {
+ i_panic("LIST: Namespace prefix not UTF-8: %s",
+ no->ns->prefix);
+ }
+
+ imap_append_string(str, str_c(mutf7_prefix));
+ str_append(str, " \"");
+ if (ns_sep == '\\')
+ str_append_c(str, '\\');
+ str_append_c(str, ns_sep);
+ str_append(str, "\")");
+ }
+ str_append_c(str, ')');
+}
+
+bool cmd_namespace(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ string_t *str;
+
+ str = t_str_new(256);
+ str_append(str, "* NAMESPACE ");
+
+ list_namespaces(client->user->namespaces,
+ MAIL_NAMESPACE_TYPE_PRIVATE, str);
+ str_append_c(str, ' ');
+ list_namespaces(client->user->namespaces,
+ MAIL_NAMESPACE_TYPE_SHARED, str);
+ str_append_c(str, ' ');
+ list_namespaces(client->user->namespaces,
+ MAIL_NAMESPACE_TYPE_PUBLIC, str);
+
+ client_send_line(client, str_c(str));
+ client_send_tagline(cmd, "OK Namespace completed.");
+ return TRUE;
+}
diff --git a/src/imap/cmd-noop.c b/src/imap/cmd-noop.c
new file mode 100644
index 0000000..3fd2444
--- /dev/null
+++ b/src/imap/cmd-noop.c
@@ -0,0 +1,15 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-notify.h"
+#include "imap-commands.h"
+
+bool cmd_noop(struct client_command_context *cmd)
+{
+ if (cmd->client->notify_ctx != NULL) {
+ /* flush any delayed notifications now. this is mainly useful
+ for testing. */
+ imap_notify_flush(cmd->client->notify_ctx);
+ }
+ return cmd_sync(cmd, 0, IMAP_SYNC_FLAG_SAFE, "OK NOOP completed.");
+}
diff --git a/src/imap/cmd-notify.c b/src/imap/cmd-notify.c
new file mode 100644
index 0000000..e043e97
--- /dev/null
+++ b/src/imap/cmd-notify.c
@@ -0,0 +1,597 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "mailbox-list-iter.h"
+#include "imap-quote.h"
+#include "imap-commands.h"
+#include "imap-fetch.h"
+#include "imap-list.h"
+#include "imap-status.h"
+#include "imap-notify.h"
+
+#define IMAP_NOTIFY_MAX_NAMES_PER_NS 100
+
+static const char *imap_notify_event_names[] = {
+ "MessageNew", "MessageExpunge", "FlagChange", "AnnotationChange",
+ "MailboxName", "SubscriptionChange", "MailboxMetadataChange",
+ "ServerMetadataChange"
+};
+
+static int
+cmd_notify_parse_event(const struct imap_arg *arg,
+ enum imap_notify_event *event_r)
+{
+ const char *str;
+ unsigned int i;
+
+ if (!imap_arg_get_atom(arg, &str))
+ return -1;
+
+ for (i = 0; i < N_ELEMENTS(imap_notify_event_names); i++) {
+ if (strcasecmp(str, imap_notify_event_names[i]) == 0) {
+ *event_r = (enum imap_notify_event)(1 << i);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int
+cmd_notify_parse_fetch(struct imap_notify_context *ctx,
+ const struct imap_arg *list)
+{
+ if (list->type == IMAP_ARG_EOL)
+ return -1; /* at least one attribute must be set */
+ return imap_fetch_att_list_parse(ctx->client, ctx->pool, list,
+ &ctx->fetch_ctx, &ctx->error);
+}
+
+static int
+cmd_notify_set_selected(struct imap_notify_context *ctx,
+ const struct imap_arg *events)
+{
+#define EV_NEW_OR_EXPUNGE \
+ (IMAP_NOTIFY_EVENT_MESSAGE_NEW | IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE)
+ const struct imap_arg *list, *fetch_att_list;
+ const char *str;
+ enum imap_notify_event event;
+
+ if (imap_arg_get_atom(events, &str) &&
+ strcasecmp(str, "NONE") == 0) {
+ /* no events for selected mailbox. this is also the default
+ when NOTIFY command doesn't specify it explicitly */
+ if (events[1].type != IMAP_ARG_EOL)
+ return -1; /* no extra parameters */
+ return 0;
+ }
+
+ if (!imap_arg_get_list(events, &list))
+ return -1;
+ if (events[1].type != IMAP_ARG_EOL)
+ return -1; /* no extra parameters */
+ if (list->type == IMAP_ARG_EOL)
+ return -1; /* at least one event */
+
+ for (; list->type != IMAP_ARG_EOL; list++) {
+ if (cmd_notify_parse_event(list, &event) < 0)
+ return -1;
+ ctx->selected_events |= event;
+ ctx->global_used_events |= event;
+
+ if (event == IMAP_NOTIFY_EVENT_MESSAGE_NEW &&
+ imap_arg_get_list(&list[1], &fetch_att_list)) {
+ /* MessageNew: list of fetch-att */
+ if (cmd_notify_parse_fetch(ctx, fetch_att_list) < 0)
+ return -1;
+ list++;
+ }
+ }
+
+ /* if MessageNew or MessageExpunge is specified, both of them must */
+ if ((ctx->selected_events & EV_NEW_OR_EXPUNGE) != 0 &&
+ (ctx->selected_events & EV_NEW_OR_EXPUNGE) != EV_NEW_OR_EXPUNGE) {
+ ctx->error = "MessageNew and MessageExpunge must be together";
+ return -1;
+ }
+
+ /* if FlagChange or AnnotationChange is specified,
+ MessageNew and MessageExpunge must also be specified */
+ if ((ctx->selected_events &
+ (IMAP_NOTIFY_EVENT_FLAG_CHANGE |
+ IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE)) != 0 &&
+ (ctx->selected_events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) == 0) {
+ ctx->error = "FlagChange requires MessageNew and MessageExpunge";
+ return -1;
+ }
+ return 0;
+}
+
+static struct imap_notify_namespace *
+imap_notify_namespace_get(struct imap_notify_context *ctx,
+ struct mail_namespace *ns)
+{
+ struct imap_notify_namespace *notify_ns;
+
+ array_foreach_modifiable(&ctx->namespaces, notify_ns) {
+ if (notify_ns->ns == ns)
+ return notify_ns;
+ }
+ notify_ns = array_append_space(&ctx->namespaces);
+ notify_ns->ctx = ctx;
+ notify_ns->ns = ns;
+ p_array_init(&notify_ns->mailboxes, ctx->pool, 4);
+ return notify_ns;
+}
+
+static struct imap_notify_mailboxes *
+imap_notify_mailboxes_get(struct imap_notify_namespace *notify_ns,
+ enum imap_notify_type type,
+ enum imap_notify_event events)
+{
+ struct imap_notify_mailboxes *notify_boxes;
+
+ array_foreach_modifiable(&notify_ns->mailboxes, notify_boxes) {
+ if (notify_boxes->type == type &&
+ notify_boxes->events == events)
+ return notify_boxes;
+ }
+ notify_boxes = array_append_space(&notify_ns->mailboxes);
+ notify_boxes->type = type;
+ notify_boxes->events = events;
+ p_array_init(&notify_boxes->names, notify_ns->ctx->pool, 4);
+ return notify_boxes;
+}
+
+static void
+cmd_notify_add_mailbox(struct imap_notify_context *ctx,
+ struct mail_namespace *ns, const char *name,
+ enum imap_notify_type type,
+ enum imap_notify_event events)
+{
+ struct imap_notify_namespace *notify_ns;
+ struct imap_notify_mailboxes *notify_boxes;
+ const char *const *names;
+ unsigned int i, count;
+ size_t cur_len, name_len = strlen(name);
+ char ns_sep = mail_namespace_get_sep(ns);
+
+ if (mail_namespace_is_removable(ns)) {
+ /* exclude removable namespaces */
+ return;
+ }
+
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
+ !str_begins(name, "INBOX") &&
+ strncasecmp(name, "INBOX", 5) == 0 &&
+ (name[5] == '\0' || name[5] == ns_sep)) {
+ /* we'll do only case-sensitive comparisons later,
+ so sanitize INBOX to be uppercase */
+ name = t_strconcat("INBOX", name + 5, NULL);
+ }
+
+ notify_ns = imap_notify_namespace_get(ctx, ns);
+ notify_boxes = imap_notify_mailboxes_get(notify_ns, type, events);
+
+ names = array_get(&notify_boxes->names, &count);
+ for (i = 0; i < count; ) {
+ if (strcmp(names[i], name) == 0) {
+ /* exact duplicate, already added */
+ return;
+ }
+ if (type != IMAP_NOTIFY_TYPE_SUBTREE)
+ i++;
+ else {
+ /* see if one is a subtree of the other */
+ cur_len = strlen(names[i]);
+ if (str_begins(name, names[i]) &&
+ names[i][cur_len] == ns_sep) {
+ /* already matched in this subtree */
+ return;
+ }
+ if (strncmp(names[i], name, name_len) == 0 &&
+ names[i][name_len] == ns_sep) {
+ /* we're adding a parent, remove the child */
+ array_delete(&notify_boxes->names, i, 1);
+ names = array_get(&notify_boxes->names, &count);
+ } else {
+ i++;
+ }
+ }
+ }
+ name = p_strdup(ctx->pool, name);
+ array_push_back(&notify_boxes->names, &name);
+
+ ctx->global_max_mailbox_names =
+ I_MAX(ctx->global_max_mailbox_names,
+ array_count(&notify_boxes->names));
+}
+
+static void cmd_notify_add_personal(struct imap_notify_context *ctx,
+ enum imap_notify_event events)
+{
+ struct mail_namespace *ns;
+
+ for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) {
+ if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) {
+ cmd_notify_add_mailbox(ctx, ns, "",
+ IMAP_NOTIFY_TYPE_SUBTREE, events);
+ }
+ }
+}
+
+static int
+imap_notify_refresh_subscriptions(struct client_command_context *cmd,
+ struct imap_notify_context *ctx)
+{
+ struct mailbox_list_iterate_context *iter;
+ struct mail_namespace *ns;
+
+ if (!ctx->have_subscriptions)
+ return 0;
+
+ /* make sure subscriptions are refreshed at least once */
+ for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) {
+ iter = mailbox_list_iter_init(ns->list, "*", MAILBOX_LIST_ITER_SELECT_SUBSCRIBED);
+ (void)mailbox_list_iter_next(iter);
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ client_send_list_error(cmd, ns->list);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void cmd_notify_add_subscribed(struct imap_notify_context *ctx,
+ enum imap_notify_event events)
+{
+ struct mail_namespace *ns;
+
+ ctx->have_subscriptions = TRUE;
+ for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) {
+ cmd_notify_add_mailbox(ctx, ns, "",
+ IMAP_NOTIFY_TYPE_SUBSCRIBED, events);
+ }
+}
+
+static void
+cmd_notify_add_mailbox_namespaces(struct imap_notify_context *ctx,
+ const char *name,
+ enum imap_notify_type type,
+ enum imap_notify_event events)
+{
+ struct mail_namespace *ns;
+
+ ns = mail_namespace_find(ctx->client->user->namespaces, name);
+ cmd_notify_add_mailbox(ctx, ns, name, type, events);
+}
+
+static int
+cmd_notify_add_mailboxes(struct imap_notify_context *ctx,
+ const struct imap_arg *arg,
+ enum imap_notify_type type,
+ enum imap_notify_event events)
+{
+ const struct imap_arg *list;
+ const char *name;
+
+ if (imap_arg_get_astring(arg, &name)) {
+ cmd_notify_add_mailbox_namespaces(ctx, name, type, events);
+ return 0;
+ }
+ if (!imap_arg_get_list(arg, &list))
+ return -1;
+
+ for (; list->type != IMAP_ARG_EOL; list++) {
+ if (!imap_arg_get_astring(list, &name))
+ return -1;
+
+ cmd_notify_add_mailbox_namespaces(ctx, name, type, events);
+ }
+ return 0;
+}
+
+static int
+cmd_notify_set(struct imap_notify_context *ctx, const struct imap_arg *args)
+{
+ const struct imap_arg *event_group, *mailboxes, *list;
+ const char *str, *filter_mailboxes;
+ enum imap_notify_event event, event_mask;
+
+ if (imap_arg_get_atom(args, &str) &&
+ strcasecmp(str, "STATUS") == 0) {
+ /* send STATUS replies for all matched mailboxes before
+ NOTIFY's OK reply */
+ ctx->send_immediate_status = TRUE;
+ args++;
+ }
+ for (; args->type != IMAP_ARG_EOL; args++) {
+ if (!imap_arg_get_list(args, &event_group))
+ return -1;
+
+ /* filter-mailboxes */
+ if (!imap_arg_get_atom(event_group, &filter_mailboxes))
+ return -1;
+ event_group++;
+
+ if (strcasecmp(filter_mailboxes, "selected") == 0 ||
+ strcasecmp(filter_mailboxes, "selected-delayed") == 0) {
+ /* setting events for selected mailbox.
+ handle specially. */
+ if (ctx->selected_set) {
+ ctx->error = "Duplicate selected filter";
+ return -1;
+ }
+ ctx->selected_set = TRUE;
+ if (strcasecmp(filter_mailboxes, "selected") == 0)
+ ctx->selected_immediate_expunges = TRUE;
+ if (cmd_notify_set_selected(ctx, event_group) < 0)
+ return -1;
+ continue;
+ }
+
+ if (strcasecmp(filter_mailboxes, "subtree") == 0 ||
+ strcasecmp(filter_mailboxes, "mailboxes") == 0) {
+ if (event_group->type == IMAP_ARG_EOL)
+ return -1;
+ mailboxes = event_group++;
+ /* check that the mailboxes parameter is valid */
+ if (IMAP_ARG_IS_ASTRING(mailboxes))
+ ;
+ else if (!imap_arg_get_list(mailboxes, &list))
+ return -1;
+ else if (list->type == IMAP_ARG_EOL) {
+ /* should have at least one mailbox */
+ return -1;
+ }
+ } else {
+ mailboxes = NULL;
+ }
+
+ /* parse events */
+ if (imap_arg_get_atom(event_group, &str) &&
+ strcasecmp(str, "NONE") == 0) {
+ /* NONE is the default, ignore this */
+ continue;
+ }
+ if (!imap_arg_get_list(event_group, &list) ||
+ list[0].type == IMAP_ARG_EOL)
+ return -1;
+
+ event_mask = 0;
+ for (; list->type != IMAP_ARG_EOL; list++) {
+ if (cmd_notify_parse_event(list, &event) < 0)
+ return -1;
+ event_mask |= event;
+ ctx->global_used_events |= event;
+ }
+
+ /* we can't currently know inboxes, so treat it the
+ same as personal */
+ if (strcasecmp(filter_mailboxes, "inboxes") == 0 ||
+ strcasecmp(filter_mailboxes, "personal") == 0)
+ cmd_notify_add_personal(ctx, event_mask);
+ else if (strcasecmp(filter_mailboxes, "subscribed") == 0)
+ cmd_notify_add_subscribed(ctx, event_mask);
+ else if (strcasecmp(filter_mailboxes, "subtree") == 0) {
+ if (cmd_notify_add_mailboxes(ctx, mailboxes,
+ IMAP_NOTIFY_TYPE_SUBTREE,
+ event_mask) < 0)
+ return -1;
+ } else if (strcasecmp(filter_mailboxes, "mailboxes") == 0) {
+ if (cmd_notify_add_mailboxes(ctx, mailboxes,
+ IMAP_NOTIFY_TYPE_MAILBOX,
+ event_mask) < 0)
+ return -1;
+ } else {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void
+imap_notify_box_list_noperm(struct client *client, struct mailbox *box)
+{
+ string_t *str = t_str_new(128);
+ char ns_sep = mail_namespace_get_sep(mailbox_get_namespace(box));
+ enum mailbox_info_flags mailbox_flags;
+
+ if (mailbox_list_mailbox(mailbox_get_namespace(box)->list,
+ mailbox_get_name(box), &mailbox_flags) < 0)
+ mailbox_flags = 0;
+
+ str_append(str, "* LIST (");
+ if (imap_mailbox_flags2str(str, mailbox_flags))
+ str_append_c(str, ' ');
+ str_append(str, "\\NoAccess) \"");
+ if (ns_sep == '\\')
+ str_append_c(str, '\\');
+ str_append_c(str, ns_sep);
+ str_append(str, "\" ");
+
+ imap_append_astring(str, mailbox_get_vname(box));
+ client_send_line(client, str_c(str));
+}
+
+static void
+imap_notify_box_send_status(struct client_command_context *cmd,
+ struct imap_notify_context *ctx,
+ const struct mailbox_info *info)
+{
+ struct mailbox *box;
+ struct imap_status_items items;
+ struct imap_status_result result;
+
+ if ((info->flags & (MAILBOX_NONEXISTENT | MAILBOX_NOSELECT)) != 0)
+ return;
+
+ /* don't send STATUS to selected mailbox */
+ if (cmd->client->mailbox != NULL &&
+ mailbox_equals(cmd->client->mailbox, info->ns, info->vname))
+ return;
+
+ i_zero(&items);
+ i_zero(&result);
+
+ items.flags = IMAP_STATUS_ITEM_UIDVALIDITY | IMAP_STATUS_ITEM_UIDNEXT |
+ IMAP_STATUS_ITEM_MESSAGES | IMAP_STATUS_ITEM_UNSEEN;
+ if ((ctx->global_used_events & (IMAP_NOTIFY_EVENT_FLAG_CHANGE |
+ IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE)) != 0)
+ items.flags |= IMAP_STATUS_ITEM_HIGHESTMODSEQ;
+
+ box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY);
+ (void)mailbox_enable(box, client_enabled_mailbox_features(ctx->client));
+
+ if (imap_status_get(cmd, info->ns, info->vname, &items, &result) < 0) {
+ if (result.error == MAIL_ERROR_PERM)
+ imap_notify_box_list_noperm(ctx->client, box);
+ else if (result.error != MAIL_ERROR_NOTFOUND) {
+ client_send_line(ctx->client,
+ t_strconcat("* ", result.errstr, NULL));
+ }
+ } else {
+ imap_status_send(ctx->client, info->vname, &items, &result);
+ }
+ mailbox_free(&box);
+}
+
+static bool imap_notify_ns_want_status(struct imap_notify_namespace *notify_ns)
+{
+ const struct imap_notify_mailboxes *notify_boxes;
+
+ array_foreach(&notify_ns->mailboxes, notify_boxes) {
+ if ((notify_boxes->events &
+ (IMAP_NOTIFY_EVENT_MESSAGE_NEW |
+ IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE |
+ IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE |
+ IMAP_NOTIFY_EVENT_FLAG_CHANGE)) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+imap_notify_ns_send_status(struct client_command_context *cmd,
+ struct imap_notify_context *ctx,
+ struct imap_notify_namespace *notify_ns)
+{
+ struct mailbox_list_iterate_context *iter;
+ const struct imap_notify_mailboxes *notify_boxes;
+ const struct mailbox_info *info;
+
+ if (!imap_notify_ns_want_status(notify_ns))
+ return;
+
+ /* set _RETURN_SUBSCRIBED flag just in case IMAP_NOTIFY_TYPE_SUBSCRIBED
+ is used, which requires refreshing subscriptions */
+ iter = mailbox_list_iter_init(notify_ns->ns->list, "*",
+ MAILBOX_LIST_ITER_RETURN_SUBSCRIBED |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ array_foreach(&notify_ns->mailboxes, notify_boxes) {
+ if (imap_notify_match_mailbox(notify_ns, notify_boxes,
+ info->vname)) {
+ imap_notify_box_send_status(cmd, ctx, info);
+ break;
+ }
+ }
+ }
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ client_send_line(notify_ns->ctx->client,
+ "* NO Mailbox listing failed");
+ }
+}
+
+static void cmd_notify_send_status(struct client_command_context *cmd,
+ struct imap_notify_context *ctx)
+{
+ struct imap_notify_namespace *notify_ns;
+
+ array_foreach_modifiable(&ctx->namespaces, notify_ns)
+ imap_notify_ns_send_status(cmd, ctx, notify_ns);
+}
+
+bool cmd_notify(struct client_command_context *cmd)
+{
+ struct imap_notify_context *ctx;
+ const struct imap_arg *args;
+ const char *str;
+ int ret = 0;
+ pool_t pool;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ pool = pool_alloconly_create("imap notify context", 1024);
+ ctx = p_new(pool, struct imap_notify_context, 1);
+ ctx->pool = pool;
+ ctx->client = cmd->client;
+ p_array_init(&ctx->namespaces, pool, 4);
+
+ if (!imap_arg_get_atom(&args[0], &str))
+ ret = -1;
+ else if (strcasecmp(str, "NONE") == 0)
+ ;
+ else if (strcasecmp(str, "SET") == 0)
+ ret = cmd_notify_set(ctx, args+1);
+ else
+ ret = -1;
+
+ if (ret < 0) {
+ client_send_command_error(cmd, ctx->error != NULL ? ctx->error :
+ "Invalid arguments.");
+ pool_unref(&pool);
+ return TRUE;
+ }
+
+ if ((ctx->global_used_events & UNSUPPORTED_EVENTS) != 0) {
+ string_t *client_error = t_str_new(128);
+ unsigned int i;
+
+ str_append(client_error, "NO [BADEVENT");
+ for (i = 0; i < N_ELEMENTS(imap_notify_event_names); i++) {
+ if ((ctx->global_used_events & (1 << i)) != 0 &&
+ ((1 << i) & UNSUPPORTED_EVENTS) != 0) {
+ str_append_c(client_error, ' ');
+ str_append(client_error, imap_notify_event_names[i]);
+ }
+ }
+ str_append(client_error, "] Unsupported NOTIFY events.");
+ client_send_tagline(cmd, str_c(client_error));
+ pool_unref(&pool);
+ return TRUE;
+ }
+
+ if (array_count(&ctx->namespaces) == 0) {
+ /* selected mailbox only */
+ } else if (ctx->global_max_mailbox_names > IMAP_NOTIFY_MAX_NAMES_PER_NS) {
+ client_send_tagline(cmd,
+ "NO [NOTIFICATIONOVERFLOW] Too many mailbox names");
+ pool_unref(&pool);
+ return TRUE;
+ } else if (imap_notify_refresh_subscriptions(cmd, ctx) < 0) {
+ /* tagline already sent */
+ pool_unref(&pool);
+ return TRUE;
+ } else if (imap_notify_begin(ctx) < 0) {
+ client_send_tagline(cmd,
+ "NO [NOTIFICATIONOVERFLOW] NOTIFY not supported for these mailboxes.");
+ pool_unref(&pool);
+ return TRUE;
+ }
+ if (cmd->client->notify_ctx != NULL)
+ imap_notify_deinit(&cmd->client->notify_ctx);
+
+ if (ctx->send_immediate_status)
+ cmd_notify_send_status(cmd, ctx);
+ cmd->client->notify_immediate_expunges =
+ ctx->selected_immediate_expunges;
+ cmd->client->notify_count_changes =
+ (ctx->selected_events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) != 0;
+ cmd->client->notify_flag_changes =
+ (ctx->selected_events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) != 0;
+
+ cmd->client->notify_ctx = ctx;
+ return cmd_sync(cmd, 0, IMAP_SYNC_FLAG_SAFE, "OK NOTIFY completed.");
+}
diff --git a/src/imap/cmd-rename.c b/src/imap/cmd-rename.c
new file mode 100644
index 0000000..45b44fd
--- /dev/null
+++ b/src/imap/cmd-rename.c
@@ -0,0 +1,51 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "mail-namespace.h"
+#include "imap-commands.h"
+
+bool cmd_rename(struct client_command_context *cmd)
+{
+ struct mail_namespace *old_ns, *new_ns;
+ struct mailbox *old_box, *new_box;
+ const char *oldname, *newname;
+ size_t oldlen;
+
+ /* <old name> <new name> */
+ if (!client_read_string_args(cmd, 2, &oldname, &newname))
+ return FALSE;
+
+ old_ns = client_find_namespace(cmd, &oldname);
+ if (old_ns == NULL)
+ return TRUE;
+ new_ns = client_find_namespace(cmd, &newname);
+ if (new_ns == NULL)
+ return TRUE;
+
+ if (old_ns == new_ns) {
+ /* disallow box -> box/child, because it may break clients and
+ there's really no point in doing it anyway. */
+ old_ns = mailbox_list_get_namespace(old_ns->list);
+ oldlen = strlen(oldname);
+ if (str_begins(newname, oldname) &&
+ newname[oldlen] == mail_namespace_get_sep(old_ns)) {
+ client_send_tagline(cmd,
+ "NO Can't rename mailbox under its own child.");
+ return TRUE;
+ }
+ }
+
+ old_box = mailbox_alloc(old_ns->list, oldname, 0);
+ new_box = mailbox_alloc(new_ns->list, newname, 0);
+ event_add_str(cmd->global_event, "old_mailbox",
+ mailbox_get_vname(old_box));
+ event_add_str(cmd->global_event, "new_mailbox",
+ mailbox_get_vname(new_box));
+ if (mailbox_rename(old_box, new_box) < 0)
+ client_send_box_error(cmd, old_box);
+ else
+ client_send_tagline(cmd, "OK Rename completed.");
+ mailbox_free(&old_box);
+ mailbox_free(&new_box);
+ return TRUE;
+}
diff --git a/src/imap/cmd-resetkey.c b/src/imap/cmd-resetkey.c
new file mode 100644
index 0000000..3475994
--- /dev/null
+++ b/src/imap/cmd-resetkey.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-resp-code.h"
+#include "imap-commands.h"
+#include "imap-urlauth.h"
+
+static bool cmd_resetkey_all(struct client_command_context *cmd)
+{
+ if (imap_urlauth_reset_all_keys(cmd->client->urlauth_ctx) < 0) {
+ client_send_internal_error(cmd);
+ return TRUE;
+ }
+
+ client_send_tagline(cmd, "OK All keys removed.");
+ return TRUE;
+}
+
+static bool
+cmd_resetkey_mailbox(struct client_command_context *cmd,
+ const char *mailbox, const struct imap_arg *mech_args)
+{
+ struct mail_namespace *ns;
+ enum mailbox_flags flags = MAILBOX_FLAG_READONLY;
+ struct mailbox *box;
+
+ /* check mechanism arguments (we support only INTERNAL mechanism) */
+ while (!IMAP_ARG_IS_EOL(mech_args)) {
+ const char *mechanism;
+
+ if (imap_arg_get_astring(mech_args, &mechanism)) {
+ if (strcasecmp(mechanism, "INTERNAL") != 0) {
+ client_send_tagline(cmd,
+ "NO Unsupported URLAUTH mechanism.");
+ return TRUE;
+ }
+ } else {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ mech_args++;
+ }
+
+ /* find mailbox namespace */
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ /* open mailbox */
+ box = mailbox_alloc(ns->list, mailbox, flags);
+ event_add_str(cmd->global_event, "mailbox", mailbox_get_vname(box));
+ if (mailbox_open(box) < 0) {
+ client_send_box_error(cmd, box);
+ mailbox_free(&box);
+ return TRUE;
+ }
+
+ /* check urlauth environment and reset requested key */
+ if (imap_urlauth_reset_mailbox_key(cmd->client->urlauth_ctx, box) < 0) {
+ client_send_internal_error(cmd);
+ mailbox_free(&box);
+ return TRUE;
+ }
+
+ /* confirm success */
+ /* FIXME: RFC Says: `Any current IMAP session logged in as the user
+ that has the mailbox selected will receive an untagged OK response
+ with the URLMECH status response code'. We currently don't do that
+ at all. We could probably do it by communicating via mailbox list
+ index. */
+ client_send_tagline(cmd, "OK [URLMECH INTERNAL] Key removed.");
+ mailbox_free(&box);
+ return TRUE;
+}
+
+bool cmd_resetkey(struct client_command_context *cmd)
+{
+ const struct imap_arg *args;
+ const char *mailbox;
+
+ if (cmd->client->urlauth_ctx == NULL) {
+ client_send_command_error(cmd, "URLAUTH disabled.");
+ return TRUE;
+ }
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (IMAP_ARG_IS_EOL(&args[0]))
+ return cmd_resetkey_all(cmd);
+ else if (imap_arg_get_astring(&args[0], &mailbox))
+ return cmd_resetkey_mailbox(cmd, mailbox, &args[1]);
+
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+}
diff --git a/src/imap/cmd-search.c b/src/imap/cmd-search.c
new file mode 100644
index 0000000..52be6f6
--- /dev/null
+++ b/src/imap/cmd-search.c
@@ -0,0 +1,49 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-search-args.h"
+#include "imap-search.h"
+
+bool cmd_search(struct client_command_context *cmd)
+{
+ struct imap_search_context *ctx;
+ struct mail_search_args *sargs;
+ const struct imap_arg *args;
+ const char *charset;
+ int ret;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ ctx = p_new(cmd->pool, struct imap_search_context, 1);
+ ctx->cmd = cmd;
+
+ if ((ret = cmd_search_parse_return_if_found(ctx, &args)) <= 0) {
+ /* error / waiting for unambiguity */
+ return ret < 0;
+ }
+
+ if (imap_arg_atom_equals(args, "CHARSET")) {
+ /* CHARSET specified */
+ if (!imap_arg_get_astring(&args[1], &charset)) {
+ client_send_command_error(cmd,
+ "Invalid charset argument.");
+ imap_search_context_free(ctx);
+ return TRUE;
+ }
+ args += 2;
+ } else {
+ charset = "UTF-8";
+ }
+
+ ret = imap_search_args_build(cmd, args, charset, &sargs);
+ if (ret <= 0) {
+ imap_search_context_free(ctx);
+ return ret < 0;
+ }
+
+ return imap_search_start(ctx, sargs, NULL);
+}
diff --git a/src/imap/cmd-select.c b/src/imap/cmd-select.c
new file mode 100644
index 0000000..c9af870
--- /dev/null
+++ b/src/imap/cmd-select.c
@@ -0,0 +1,426 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "seq-range-array.h"
+#include "time-util.h"
+#include "imap-commands.h"
+#include "mail-search-build.h"
+#include "imap-search-args.h"
+#include "imap-seqset.h"
+#include "imap-fetch.h"
+#include "imap-sync.h"
+
+
+struct imap_select_context {
+ struct client_command_context *cmd;
+ struct mail_namespace *ns;
+ struct mailbox *box;
+
+ struct imap_fetch_context *fetch_ctx;
+
+ uint32_t qresync_uid_validity;
+ uint64_t qresync_modseq;
+ ARRAY_TYPE(seq_range) qresync_known_uids;
+ ARRAY_TYPE(uint32_t) qresync_sample_seqset;
+ ARRAY_TYPE(uint32_t) qresync_sample_uidset;
+
+ bool condstore:1;
+};
+
+static int select_qresync_get_uids(struct imap_select_context *ctx,
+ const ARRAY_TYPE(seq_range) *seqset,
+ const ARRAY_TYPE(seq_range) *uidset)
+{
+ const struct seq_range *uid_range;
+ struct seq_range_iter seq_iter;
+ unsigned int i, uid_count, diff, n = 0;
+ uint32_t seq;
+
+ /* change all n:m ranges to n,m and store the results */
+ uid_range = array_get(uidset, &uid_count);
+
+ seq_range_array_iter_init(&seq_iter, seqset);
+ i_array_init(&ctx->qresync_sample_uidset, uid_count);
+ i_array_init(&ctx->qresync_sample_seqset, uid_count);
+ for (i = 0; i < uid_count; i++) {
+ if (!seq_range_array_iter_nth(&seq_iter, n++, &seq))
+ return -1;
+ array_push_back(&ctx->qresync_sample_uidset,
+ &uid_range[i].seq1);
+ array_push_back(&ctx->qresync_sample_seqset, &seq);
+
+ diff = uid_range[i].seq2 - uid_range[i].seq1;
+ if (diff > 0) {
+ n += diff - 1;
+ if (!seq_range_array_iter_nth(&seq_iter, n++, &seq))
+ return -1;
+
+ array_push_back(&ctx->qresync_sample_uidset,
+ &uid_range[i].seq2);
+ array_push_back(&ctx->qresync_sample_seqset, &seq);
+ }
+ }
+ if (seq_range_array_iter_nth(&seq_iter, n, &seq))
+ return -1;
+ return 0;
+}
+
+static bool
+select_parse_qresync_known_set(struct imap_select_context *ctx,
+ const struct imap_arg *args,
+ const char **error_r)
+{
+ ARRAY_TYPE(seq_range) seqset, uidset;
+ const char *str;
+
+ t_array_init(&seqset, 32);
+ if (!imap_arg_get_atom(args, &str) ||
+ imap_seq_set_nostar_parse(str, &seqset) < 0) {
+ *error_r = "Invalid QRESYNC known-sequence-set";
+ return FALSE;
+ }
+ args++;
+
+ t_array_init(&uidset, 32);
+ if (!imap_arg_get_atom(args, &str) ||
+ imap_seq_set_nostar_parse(str, &uidset) < 0) {
+ *error_r = "Invalid QRESYNC known-uid-set";
+ return FALSE;
+ }
+ args++;
+
+ if (select_qresync_get_uids(ctx, &seqset, &uidset) < 0) {
+ *error_r = "Invalid QRESYNC sets";
+ return FALSE;
+ }
+ if (!IMAP_ARG_IS_EOL(args)) {
+ *error_r = "Too many parameters to QRESYNC known set";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+select_parse_qresync(struct imap_select_context *ctx,
+ const struct imap_arg *args, const char **error_r)
+{
+ const struct imap_arg *list_args;
+ const char *str;
+ unsigned int count;
+
+ if (!client_has_enabled(ctx->cmd->client, imap_feature_qresync)) {
+ *error_r = "QRESYNC not enabled";
+ return FALSE;
+ }
+ if (!imap_arg_get_list_full(args, &args, &count)) {
+ *error_r = "QRESYNC parameters missing";
+ return FALSE;
+ }
+
+ if (!imap_arg_get_atom(&args[0], &str) ||
+ str_to_uint32(str, &ctx->qresync_uid_validity) < 0 ||
+ !imap_arg_get_atom(&args[1], &str) ||
+ str_to_uint64(str, &ctx->qresync_modseq) < 0) {
+ *error_r = "Invalid QRESYNC parameters";
+ return FALSE;
+ }
+ args += 2;
+
+ i_array_init(&ctx->qresync_known_uids, 64);
+ if (imap_arg_get_atom(args, &str)) {
+ if (imap_seq_set_nostar_parse(str, &ctx->qresync_known_uids) < 0) {
+ *error_r = "Invalid QRESYNC known-uids";
+ return FALSE;
+ }
+ args++;
+ } else {
+ seq_range_array_add_range(&ctx->qresync_known_uids,
+ 1, (uint32_t)-1);
+ }
+ if (imap_arg_get_list(args, &list_args)) {
+ if (!select_parse_qresync_known_set(ctx, list_args, error_r))
+ return FALSE;
+ args++;
+ }
+ if (!IMAP_ARG_IS_EOL(args)) {
+ *error_r = "Invalid QRESYNC parameters";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+select_parse_options(struct imap_select_context *ctx,
+ const struct imap_arg *args, const char **error_r)
+{
+ const char *name;
+
+ while (!IMAP_ARG_IS_EOL(args)) {
+ if (!imap_arg_get_atom(args, &name)) {
+ *error_r = "SELECT options contain non-atoms.";
+ return FALSE;
+ }
+ name = t_str_ucase(name);
+ args++;
+
+ if (strcmp(name, "CONDSTORE") == 0)
+ ctx->condstore = TRUE;
+ else if (strcmp(name, "QRESYNC") == 0) {
+ if (!select_parse_qresync(ctx, args, error_r))
+ return FALSE;
+ args++;
+ } else {
+ *error_r = "Unknown FETCH modifier";
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void select_context_free(struct imap_select_context *ctx)
+{
+ if (array_is_created(&ctx->qresync_known_uids))
+ array_free(&ctx->qresync_known_uids);
+ if (array_is_created(&ctx->qresync_sample_seqset))
+ array_free(&ctx->qresync_sample_seqset);
+ if (array_is_created(&ctx->qresync_sample_uidset))
+ array_free(&ctx->qresync_sample_uidset);
+}
+
+static void cmd_select_finish(struct imap_select_context *ctx, int ret)
+{
+ const char *resp_code;
+
+ if (ret < 0) {
+ if (ctx->box != NULL)
+ mailbox_free(&ctx->box);
+ ctx->cmd->client->mailbox = NULL;
+ } else {
+ resp_code = mailbox_is_readonly(ctx->box) ?
+ "READ-ONLY" : "READ-WRITE";
+ client_send_tagline(ctx->cmd, t_strdup_printf(
+ "OK [%s] %s completed", resp_code,
+ ctx->cmd->client->mailbox_examined ? "Examine" : "Select"));
+ }
+ select_context_free(ctx);
+}
+
+static bool cmd_select_continue(struct client_command_context *cmd)
+{
+ struct imap_select_context *ctx = cmd->context;
+ int ret;
+
+ if (imap_fetch_more(ctx->fetch_ctx, cmd) == 0) {
+ /* unfinished */
+ return FALSE;
+ }
+
+ ret = imap_fetch_end(ctx->fetch_ctx);
+ if (ret < 0)
+ client_send_box_error(ctx->cmd, ctx->box);
+ imap_fetch_free(&ctx->fetch_ctx);
+ cmd_select_finish(ctx, ret);
+ return TRUE;
+}
+
+static int select_qresync(struct imap_select_context *ctx)
+{
+ struct imap_fetch_context *fetch_ctx;
+ struct mail_search_args *search_args;
+ struct imap_fetch_qresync_args qresync_args;
+ int ret;
+
+ search_args = mail_search_build_init();
+ search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
+ search_args->args->type = SEARCH_UIDSET;
+ search_args->args->value.seqset = ctx->qresync_known_uids;
+ imap_search_add_changed_since(search_args, ctx->qresync_modseq);
+
+ i_zero(&qresync_args);
+ qresync_args.qresync_sample_seqset = &ctx->qresync_sample_seqset;
+ qresync_args.qresync_sample_uidset = &ctx->qresync_sample_uidset;
+
+ if (imap_fetch_send_vanished(ctx->cmd->client, ctx->box,
+ search_args, &qresync_args) < 0) {
+ mail_search_args_unref(&search_args);
+ return -1;
+ }
+
+ fetch_ctx = imap_fetch_alloc(ctx->cmd->client, ctx->cmd->pool,
+ t_strdup_printf("%s %s", ctx->cmd->name, ctx->cmd->args));
+
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_uid_init);
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_flags_init);
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_modseq_init);
+
+ imap_fetch_begin(fetch_ctx, ctx->box, search_args);
+ mail_search_args_unref(&search_args);
+
+ if (imap_fetch_more(fetch_ctx, ctx->cmd) == 0) {
+ /* unfinished */
+ ctx->fetch_ctx = fetch_ctx;
+ ctx->cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+
+ ctx->cmd->func = cmd_select_continue;
+ ctx->cmd->context = ctx;
+ return 0;
+ }
+ ret = imap_fetch_end(fetch_ctx);
+ imap_fetch_free(&fetch_ctx);
+ return ret < 0 ? -1 : 1;
+}
+
+static int
+select_open(struct imap_select_context *ctx, const char *mailbox, bool readonly)
+{
+ struct client *client = ctx->cmd->client;
+ struct mailbox_status status;
+ enum mailbox_flags flags = 0;
+ int ret = 0;
+
+ if (readonly)
+ flags |= MAILBOX_FLAG_READONLY;
+ else
+ flags |= MAILBOX_FLAG_DROP_RECENT;
+ ctx->box = mailbox_alloc(ctx->ns->list, mailbox, flags);
+ event_add_str(ctx->cmd->global_event, "mailbox",
+ mailbox_get_vname(ctx->box));
+ if (mailbox_open(ctx->box) < 0) {
+ client_send_box_error(ctx->cmd, ctx->box);
+ mailbox_free(&ctx->box);
+ return -1;
+ }
+
+ ret = mailbox_enable(ctx->box, client_enabled_mailbox_features(client));
+ if (ret < 0 ||
+ mailbox_sync(ctx->box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) {
+ client_send_box_error(ctx->cmd, ctx->box);
+ return -1;
+ }
+ mailbox_get_open_status(ctx->box, STATUS_MESSAGES | STATUS_RECENT |
+ STATUS_FIRST_UNSEEN_SEQ | STATUS_UIDVALIDITY |
+ STATUS_UIDNEXT | STATUS_KEYWORDS |
+ STATUS_HIGHESTMODSEQ, &status);
+
+ client->mailbox = ctx->box;
+ client->mailbox_examined = readonly;
+ client->messages_count = status.messages;
+ client->recent_count = status.recent;
+ client->uidvalidity = status.uidvalidity;
+ client->notify_uidnext = status.uidnext;
+
+ client_update_mailbox_flags(client, status.keywords);
+ client_send_mailbox_flags(client, TRUE);
+
+ client_send_line(client,
+ t_strdup_printf("* %u EXISTS", status.messages));
+ client_send_line(client,
+ t_strdup_printf("* %u RECENT", status.recent));
+
+ if (status.first_unseen_seq != 0) {
+ client_send_line(client,
+ t_strdup_printf("* OK [UNSEEN %u] First unseen.",
+ status.first_unseen_seq));
+ }
+
+ client_send_line(client,
+ t_strdup_printf("* OK [UIDVALIDITY %u] UIDs valid",
+ status.uidvalidity));
+
+ client_send_line(client,
+ t_strdup_printf("* OK [UIDNEXT %u] Predicted next UID",
+ status.uidnext));
+
+ client->nonpermanent_modseqs = status.nonpermanent_modseqs;
+ if (status.nonpermanent_modseqs) {
+ client_send_line(client,
+ "* OK [NOMODSEQ] No permanent modsequences");
+ } else if (!status.no_modseq_tracking) {
+ client_send_line(client,
+ t_strdup_printf("* OK [HIGHESTMODSEQ %"PRIu64"] Highest",
+ status.highest_modseq));
+ client->sync_last_full_modseq = status.highest_modseq;
+ }
+
+ if (ctx->qresync_uid_validity == status.uidvalidity &&
+ status.uidvalidity != 0 && !client->nonpermanent_modseqs) {
+ if ((ret = select_qresync(ctx)) < 0) {
+ client_send_box_error(ctx->cmd, ctx->box);
+ return -1;
+ }
+ } else {
+ ret = 1;
+ }
+ return ret;
+}
+
+static void close_selected_mailbox(struct client *client)
+{
+ if (client->mailbox == NULL)
+ return;
+
+ imap_client_close_mailbox(client);
+ /* CLOSED response is required by QRESYNC */
+ client_send_line(client, "* OK [CLOSED] Previous mailbox closed.");
+}
+
+bool cmd_select_full(struct client_command_context *cmd, bool readonly)
+{
+ struct client *client = cmd->client;
+ struct imap_select_context *ctx;
+ const struct imap_arg *args, *list_args;
+ const char *mailbox, *client_error;
+ int ret;
+
+ /* <mailbox> [(optional parameters)] */
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!imap_arg_get_astring(args, &mailbox)) {
+ close_selected_mailbox(client);
+ client_send_command_error(cmd, "Invalid arguments.");
+ return FALSE;
+ }
+
+ ctx = p_new(cmd->pool, struct imap_select_context, 1);
+ ctx->cmd = cmd;
+ ctx->ns = client_find_namespace_full(cmd->client, &mailbox, &client_error);
+ if (ctx->ns == NULL) {
+ /* send * OK [CLOSED] before the tagged reply */
+ close_selected_mailbox(client);
+ client_send_tagline(cmd, client_error);
+ return TRUE;
+ }
+
+ if (imap_arg_get_list(&args[1], &list_args)) {
+ if (!select_parse_options(ctx, list_args, &client_error)) {
+ select_context_free(ctx);
+ /* send * OK [CLOSED] before the tagged reply */
+ close_selected_mailbox(client);
+ client_send_command_error(ctx->cmd, client_error);
+ return TRUE;
+ }
+ }
+
+ i_assert(client->mailbox_change_lock == NULL);
+ client->mailbox_change_lock = cmd;
+
+ close_selected_mailbox(client);
+
+ if (ctx->condstore) {
+ /* Enable while no mailbox is opened to avoid sending
+ HIGHESTMODSEQ for previously opened mailbox */
+ client_enable(client, imap_feature_condstore);
+ }
+
+ ret = select_open(ctx, mailbox, readonly);
+ if (ret == 0)
+ return FALSE;
+ cmd_select_finish(ctx, ret);
+ return TRUE;
+}
+
+bool cmd_select(struct client_command_context *cmd)
+{
+ return cmd_select_full(cmd, FALSE);
+}
diff --git a/src/imap/cmd-setmetadata.c b/src/imap/cmd-setmetadata.c
new file mode 100644
index 0000000..247afce
--- /dev/null
+++ b/src/imap/cmd-setmetadata.c
@@ -0,0 +1,378 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "istream-seekable.h"
+#include "ostream.h"
+#include "str.h"
+#include "imap-metadata.h"
+#include "mail-storage-private.h"
+
+#define METADATA_MAX_INMEM_SIZE (1024*128)
+
+struct imap_setmetadata_context {
+ struct client_command_context *cmd;
+ struct imap_parser *parser;
+
+ struct mailbox *box;
+ struct imap_metadata_transaction *trans;
+
+ char *entry_name;
+ uoff_t entry_value_len;
+ struct istream *input;
+ bool failed;
+ bool cmd_error_sent;
+ bool storage_failure;
+};
+
+static void cmd_setmetadata_deinit(struct imap_setmetadata_context *ctx)
+{
+ o_stream_set_flush_callback(ctx->cmd->client->output,
+ client_output, ctx->cmd->client);
+
+ ctx->cmd->client->input_lock = NULL;
+ imap_parser_unref(&ctx->parser);
+ if (ctx->trans != NULL)
+ imap_metadata_transaction_rollback(&ctx->trans);
+ if (ctx->box != NULL && ctx->box != ctx->cmd->client->mailbox)
+ mailbox_free(&ctx->box);
+ i_free(ctx->entry_name);
+}
+
+static int
+cmd_setmetadata_parse_entryvalue(struct imap_setmetadata_context *ctx,
+ const char **entry_r,
+ const struct imap_arg **value_r)
+{
+ const struct imap_arg *args;
+ const char *name, *client_error;
+ enum imap_parser_error parse_error;
+ int ret;
+
+ /* parse the entry name */
+ ret = imap_parser_read_args(ctx->parser, 1,
+ IMAP_PARSE_FLAG_INSIDE_LIST, &args);
+ if (ret >= 0) {
+ if (ret == 0) {
+ /* ')' found */
+ *entry_r = NULL;
+ return 1;
+ }
+ if (!imap_arg_get_astring(args, &name)) {
+ client_send_command_error(ctx->cmd,
+ "Entry name isn't astring");
+ return -1;
+ }
+
+ ret = imap_parser_read_args(ctx->parser, 2,
+ IMAP_PARSE_FLAG_INSIDE_LIST |
+ IMAP_PARSE_FLAG_LITERAL_SIZE |
+ IMAP_PARSE_FLAG_LITERAL8, &args);
+ }
+ if (ret < 0) {
+ if (ret == -2)
+ return 0;
+ client_error = imap_parser_get_error(ctx->parser, &parse_error);
+ switch (parse_error) {
+ case IMAP_PARSE_ERROR_NONE:
+ i_unreached();
+ case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+ client_disconnect_with_error(ctx->cmd->client,
+ client_error);
+ break;
+ default:
+ client_send_command_error(ctx->cmd, client_error);
+ break;
+ }
+ return -1;
+ }
+ if (args[1].type == IMAP_ARG_EOL) {
+ client_send_command_error(ctx->cmd, "Entry value missing");
+ return -1;
+ }
+ if (args[1].type == IMAP_ARG_LIST) {
+ client_send_command_error(ctx->cmd, "Entry value can't be a list");
+ return -1;
+ }
+ if (!ctx->cmd_error_sent &&
+ !imap_metadata_verify_entry_name(name, &client_error)) {
+ client_send_command_error(ctx->cmd, client_error);
+ ctx->cmd_error_sent = TRUE;
+ }
+ if (ctx->cmd_error_sent) {
+ ctx->cmd->param_error = FALSE;
+ ctx->cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
+
+ ctx->failed = TRUE;
+ if (args[1].type == IMAP_ARG_LITERAL_SIZE) {
+ /* client won't see "+ OK", so we can abort
+ immediately */
+ ctx->cmd->client->input_skip_line = FALSE;
+ return -1;
+ }
+ }
+
+ /* entry names are case-insensitive. handle this by using only
+ lowercase names. */
+ *entry_r = t_str_lcase(name);
+ *value_r = &args[1];
+ return 1;
+}
+
+static int
+cmd_setmetadata_entry_read_stream(struct imap_setmetadata_context *ctx)
+{
+ const unsigned char *data;
+ size_t size;
+ struct mail_attribute_value value;
+ int ret;
+
+ while ((ret = i_stream_read_more(ctx->input, &data, &size)) > 0)
+ i_stream_skip(ctx->input, size);
+ if (ret == 0)
+ return 0;
+
+ if (ctx->input->v_offset != ctx->entry_value_len) {
+ /* client disconnected */
+ i_assert(ctx->input->eof);
+ return -1;
+ }
+
+ /* finished reading the value */
+ i_stream_seek(ctx->input, 0);
+
+ if (ctx->failed) {
+ i_stream_unref(&ctx->input);
+ return 1;
+ }
+
+ i_zero(&value);
+ value.value_stream = ctx->input;
+ if (imap_metadata_set(ctx->trans, ctx->entry_name, &value) < 0) {
+ /* delay reporting the failure so we'll finish
+ reading the command input */
+ ctx->storage_failure = TRUE;
+ ctx->failed = TRUE;
+ }
+ i_stream_unref(&ctx->input);
+ return 1;
+}
+
+static int
+cmd_setmetadata_entry(struct imap_setmetadata_context *ctx,
+ const char *entry_name,
+ const struct imap_arg *entry_value)
+{
+ struct istream *inputs[2];
+ struct mail_attribute_value value;
+ string_t *path;
+ int ret;
+
+ switch (entry_value->type) {
+ case IMAP_ARG_NIL:
+ case IMAP_ARG_ATOM:
+ case IMAP_ARG_STRING:
+ /* we have the value already */
+ if (ctx->failed)
+ return 1;
+ i_zero(&value);
+ /* NOTE: The RFC doesn't allow atoms as value, but since
+ Dovecot has traditionally supported it this is kept for
+ backwards compatibility just in case some client is
+ using it. */
+ if (entry_value->type == IMAP_ARG_NIL)
+ ;
+ else if (!imap_arg_get_atom(entry_value, &value.value))
+ value.value = imap_arg_as_nstring(entry_value);
+ ret = imap_metadata_set(ctx->trans, entry_name, &value);
+ if (ret < 0) {
+ /* delay reporting the failure so we'll finish
+ reading the command input */
+ ctx->storage_failure = TRUE;
+ ctx->failed = TRUE;
+ }
+ return 1;
+ case IMAP_ARG_LITERAL_SIZE:
+ o_stream_nsend(ctx->cmd->client->output, "+ OK\r\n", 6);
+ o_stream_uncork(ctx->cmd->client->output);
+ o_stream_cork(ctx->cmd->client->output);
+ /* fall through */
+ case IMAP_ARG_LITERAL_SIZE_NONSYNC:
+ i_free(ctx->entry_name);
+ ctx->entry_name = i_strdup(entry_name);
+ ctx->entry_value_len = imap_arg_as_literal_size(entry_value);
+
+ inputs[0] = i_stream_create_limit(ctx->cmd->client->input,
+ ctx->entry_value_len);
+ inputs[1] = NULL;
+
+ path = t_str_new(128);
+ mail_user_set_get_temp_prefix(path, ctx->cmd->client->user->set);
+ ctx->input = i_stream_create_seekable_path(inputs,
+ METADATA_MAX_INMEM_SIZE, str_c(path));
+ i_stream_set_name(ctx->input, i_stream_get_name(inputs[0]));
+ i_stream_unref(&inputs[0]);
+ return cmd_setmetadata_entry_read_stream(ctx);
+ case IMAP_ARG_LITERAL:
+ case IMAP_ARG_LIST:
+ case IMAP_ARG_EOL:
+ break;
+ }
+ i_unreached();
+}
+
+static bool cmd_setmetadata_continue(struct client_command_context *cmd)
+{
+ struct imap_setmetadata_context *ctx = cmd->context;
+ const char *entry, *client_error;
+ enum mail_error error;
+ const struct imap_arg *value;
+ int ret;
+
+ if (cmd->cancel) {
+ cmd_setmetadata_deinit(ctx);
+ return TRUE;
+ }
+
+ if (ctx->input != NULL) {
+ if ((ret = cmd_setmetadata_entry_read_stream(ctx)) == 0)
+ return FALSE;
+ if (ret < 0) {
+ cmd_setmetadata_deinit(ctx);
+ return TRUE;
+ }
+ }
+
+ while ((ret = cmd_setmetadata_parse_entryvalue(ctx, &entry, &value)) > 0 &&
+ entry != NULL) {
+ ret = ctx->failed ? 1 :
+ cmd_setmetadata_entry(ctx, entry, value);
+ imap_parser_reset(ctx->parser);
+ if (ret <= 0)
+ break;
+ }
+ if (ret == 0)
+ return 0;
+
+ if (ret < 0 || ctx->cmd_error_sent) {
+ /* already sent the error to client */ ;
+ } else if (ctx->storage_failure) {
+ if (ctx->box == NULL)
+ client_disconnect_if_inconsistent(cmd->client);
+ client_error = imap_metadata_transaction_get_last_error
+ (ctx->trans, &error);
+ client_send_tagline(cmd,
+ imap_get_error_string(cmd, client_error, error));
+ } else if (imap_metadata_transaction_commit(&ctx->trans,
+ &error, &client_error) < 0) {
+ if (ctx->box == NULL)
+ client_disconnect_if_inconsistent(cmd->client);
+ client_send_tagline(cmd,
+ imap_get_error_string(cmd, client_error, error));
+ } else {
+ client_send_tagline(cmd, "OK Setmetadata completed.");
+ }
+ cmd_setmetadata_deinit(ctx);
+ return TRUE;
+}
+
+static bool
+cmd_setmetadata_start(struct imap_setmetadata_context *ctx)
+{
+ struct client_command_context *cmd = ctx->cmd;
+ struct client *client = cmd->client;
+
+ imap_metadata_transaction_validated_only(ctx->trans,
+ !cmd->client->set->imap_metadata);
+ /* we support large literals, so read the values from client
+ asynchronously the same way as APPEND does. */
+ client->input_lock = cmd;
+ ctx->parser = imap_parser_create(client->input, client->output,
+ client->set->imap_max_line_length);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(ctx->parser);
+ o_stream_unset_flush_callback(client->output);
+
+ cmd->func = cmd_setmetadata_continue;
+ cmd->context = ctx;
+ return cmd_setmetadata_continue(cmd);
+}
+
+static bool
+cmd_setmetadata_server(struct imap_setmetadata_context *ctx)
+{
+ ctx->trans = imap_metadata_transaction_begin_server(ctx->cmd->client->user);
+ return cmd_setmetadata_start(ctx);
+}
+
+static bool
+cmd_setmetadata_mailbox(struct imap_setmetadata_context *ctx,
+ const char *mailbox)
+{
+ struct client_command_context *cmd = ctx->cmd;
+ struct client *client = cmd->client;
+ struct mail_namespace *ns;
+
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ if (client->mailbox != NULL && !client->mailbox_examined &&
+ mailbox_equals(client->mailbox, ns, mailbox))
+ ctx->box = client->mailbox;
+ else {
+ ctx->box = mailbox_alloc(ns->list, mailbox,
+ MAILBOX_FLAG_ATTRIBUTE_SESSION);
+ enum mailbox_existence existence;
+ if (mailbox_exists(ctx->box, TRUE, &existence) < 0) {
+ client_send_box_error(cmd, ctx->box);
+ mailbox_free(&ctx->box);
+ return TRUE;
+ } else if (existence == MAILBOX_EXISTENCE_NONE) {
+ const char *err = t_strdup_printf(MAIL_ERRSTR_MAILBOX_NOT_FOUND,
+ mailbox_get_vname(ctx->box));
+ mail_storage_set_error(ctx->box->storage, MAIL_ERROR_NOTFOUND, err);
+ client_send_box_error(cmd, ctx->box);
+ mailbox_free(&ctx->box);
+ return TRUE;
+ }
+ }
+ event_add_str(ctx->cmd->global_event, "mailbox",
+ mailbox_get_vname(ctx->box));
+ ctx->trans = imap_metadata_transaction_begin(ctx->box);
+ return cmd_setmetadata_start(ctx);
+}
+
+bool cmd_setmetadata(struct client_command_context *cmd)
+{
+ struct imap_setmetadata_context *ctx;
+ const struct imap_arg *args;
+ const char *mailbox;
+ int ret;
+
+ ret = imap_parser_read_args(cmd->parser, 2,
+ IMAP_PARSE_FLAG_STOP_AT_LIST, &args);
+ if (ret == -1) {
+ client_send_command_error(cmd, NULL);
+ return TRUE;
+ }
+ if (ret == -2)
+ return FALSE;
+ if (!imap_arg_get_astring(&args[0], &mailbox) ||
+ args[1].type != IMAP_ARG_LIST) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ ctx = p_new(cmd->pool, struct imap_setmetadata_context, 1);
+ ctx->cmd = cmd;
+ ctx->cmd->context = ctx;
+
+ if (mailbox[0] == '\0') {
+ /* server attribute */
+ return cmd_setmetadata_server(ctx);
+ }
+
+ return cmd_setmetadata_mailbox(ctx, mailbox);
+}
diff --git a/src/imap/cmd-sort.c b/src/imap/cmd-sort.c
new file mode 100644
index 0000000..6515a67
--- /dev/null
+++ b/src/imap/cmd-sort.c
@@ -0,0 +1,142 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "buffer.h"
+#include "imap-commands.h"
+#include "imap-search-args.h"
+#include "imap-search.h"
+
+struct sort_name {
+ enum mail_sort_type type;
+ const char *name;
+};
+
+static struct sort_name sort_names[] = {
+ { MAIL_SORT_ARRIVAL, "arrival" },
+ { MAIL_SORT_CC, "cc" },
+ { MAIL_SORT_DATE, "date" },
+ { MAIL_SORT_FROM, "from" },
+ { MAIL_SORT_SIZE, "size" },
+ { MAIL_SORT_SUBJECT, "subject" },
+ { MAIL_SORT_TO, "to" },
+ { MAIL_SORT_RELEVANCY, "x-score" }, /* FIXME: obsolete */
+ { MAIL_SORT_RELEVANCY, "relevancy" },
+ { MAIL_SORT_DISPLAYFROM, "displayfrom" },
+ { MAIL_SORT_DISPLAYTO, "displayto" },
+
+ { MAIL_SORT_END, NULL }
+};
+
+static int
+get_sort_program(struct client_command_context *cmd,
+ const struct imap_arg *args,
+ enum mail_sort_type program[MAX_SORT_PROGRAM_SIZE])
+{
+ enum mail_sort_type mask = 0;
+ const char *arg;
+ unsigned int i, pos;
+ bool reverse, last_reverse;
+
+ if (IMAP_ARG_IS_EOL(args)) {
+ /* empty list */
+ client_send_command_error(cmd, "Empty sort program.");
+ return -1;
+ }
+
+ pos = 0; reverse = last_reverse = FALSE;
+ for (; imap_arg_get_astring(args, &arg); args++) {
+ last_reverse = strcasecmp(arg, "reverse") == 0;
+ if (last_reverse) {
+ reverse = !reverse;
+ continue;
+ }
+
+ for (i = 0; sort_names[i].type != MAIL_SORT_END; i++) {
+ if (strcasecmp(arg, sort_names[i].name) == 0)
+ break;
+ }
+
+ if (sort_names[i].type == MAIL_SORT_END) {
+ client_send_command_error(cmd, t_strconcat(
+ "Unknown sort argument: ", arg, NULL));
+ return -1;
+ }
+
+ if ((mask & sort_names[i].type) != 0)
+ continue;
+ mask |= sort_names[i].type;
+
+ /* @UNSAFE: mask check should prevent us from ever
+ overflowing */
+ i_assert(pos < MAX_SORT_PROGRAM_SIZE-1);
+ program[pos++] = sort_names[i].type |
+ (reverse ? MAIL_SORT_FLAG_REVERSE : 0);
+ reverse = FALSE;
+ }
+ if (last_reverse) {
+ client_send_command_error(cmd, "Sort list ends with REVERSE.");
+ return -1;
+ }
+ program[pos] = MAIL_SORT_END;
+
+ if (!IMAP_ARG_IS_EOL(args)) {
+ client_send_command_error(cmd,
+ "Invalid sort list argument.");
+ return -1;
+ }
+
+ return 0;
+}
+
+bool cmd_sort(struct client_command_context *cmd)
+{
+ struct imap_search_context *ctx;
+ struct mail_search_args *sargs;
+ enum mail_sort_type sort_program[MAX_SORT_PROGRAM_SIZE];
+ const struct imap_arg *args, *list_args;
+ const char *charset;
+ int ret;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ ctx = p_new(cmd->pool, struct imap_search_context, 1);
+ ctx->cmd = cmd;
+
+ if ((ret = cmd_search_parse_return_if_found(ctx, &args)) <= 0) {
+ /* error / waiting for unambiguity */
+ return ret < 0;
+ }
+
+ /* sort program */
+ if (!imap_arg_get_list(args, &list_args)) {
+ client_send_command_error(cmd, "Invalid sort argument.");
+ imap_search_context_free(ctx);
+ return TRUE;
+ }
+
+ if (get_sort_program(cmd, list_args, sort_program) < 0) {
+ imap_search_context_free(ctx);
+ return TRUE;
+ }
+ args++;
+
+ /* charset */
+ if (!imap_arg_get_astring(args, &charset)) {
+ client_send_command_error(cmd, "Invalid charset argument.");
+ imap_search_context_free(ctx);
+ return TRUE;
+ }
+ args++;
+
+ ret = imap_search_args_build(cmd, args, charset, &sargs);
+ if (ret <= 0) {
+ imap_search_context_free(ctx);
+ return ret < 0;
+ }
+
+ return imap_search_start(ctx, sargs, sort_program);
+}
diff --git a/src/imap/cmd-status.c b/src/imap/cmd-status.c
new file mode 100644
index 0000000..69c0a51
--- /dev/null
+++ b/src/imap/cmd-status.c
@@ -0,0 +1,54 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-resp-code.h"
+#include "imap-commands.h"
+#include "imap-sync.h"
+#include "imap-status.h"
+
+bool cmd_status(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ const struct imap_arg *args, *list_args;
+ struct imap_status_items items;
+ struct imap_status_result result;
+ struct mail_namespace *ns;
+ const char *mailbox, *orig_mailbox;
+ bool selected_mailbox;
+
+ /* <mailbox> <status items> */
+ if (!client_read_args(cmd, 2, 0, &args))
+ return FALSE;
+
+ if (!imap_arg_get_astring(&args[0], &mailbox) ||
+ !imap_arg_get_list(&args[1], &list_args)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ /* get the items client wants */
+ if (imap_status_parse_items(cmd, list_args, &items) < 0)
+ return TRUE;
+
+ orig_mailbox = mailbox;
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ event_add_str(cmd->global_event, "mailbox", mailbox);
+ selected_mailbox = client->mailbox != NULL &&
+ mailbox_equals(client->mailbox, ns, mailbox);
+ if (imap_status_get(cmd, ns, mailbox, &items, &result) < 0) {
+ client_send_tagline(cmd, result.errstr);
+ return TRUE;
+ }
+
+ imap_status_send(client, orig_mailbox, &items, &result);
+ if (!selected_mailbox)
+ client_send_tagline(cmd, "OK Status completed.");
+ else {
+ client_send_tagline(cmd, "OK ["IMAP_RESP_CODE_CLIENTBUG"] "
+ "Status on selected mailbox completed.");
+ }
+ return TRUE;
+}
diff --git a/src/imap/cmd-store.c b/src/imap/cmd-store.c
new file mode 100644
index 0000000..126c042
--- /dev/null
+++ b/src/imap/cmd-store.c
@@ -0,0 +1,261 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "seq-range-array.h"
+#include "str.h"
+#include "imap-commands.h"
+#include "imap-search-args.h"
+#include "imap-util.h"
+
+
+struct imap_store_context {
+ struct client_command_context *cmd;
+ uint64_t max_modseq;
+
+ enum mail_flags flags;
+ struct mail_keywords *keywords;
+
+ enum modify_type modify_type;
+ bool silent;
+};
+
+static bool
+get_modify_type(struct imap_store_context *ctx, const char *type)
+{
+ if (*type == '+') {
+ ctx->modify_type = MODIFY_ADD;
+ type++;
+ } else if (*type == '-') {
+ ctx->modify_type = MODIFY_REMOVE;
+ type++;
+ } else {
+ ctx->modify_type = MODIFY_REPLACE;
+ }
+
+ if (strncasecmp(type, "FLAGS", 5) != 0)
+ return FALSE;
+
+ ctx->silent = strcasecmp(type+5, ".SILENT") == 0;
+ if (!ctx->silent && type[5] != '\0')
+ return FALSE;
+ return TRUE;
+}
+
+static bool
+store_parse_modifiers(struct imap_store_context *ctx,
+ const struct imap_arg *args)
+{
+ const char *name, *value;
+
+ for (; !IMAP_ARG_IS_EOL(args); args += 2) {
+ if (!imap_arg_get_atom(&args[0], &name) ||
+ !imap_arg_get_atom(&args[1], &value)) {
+ client_send_command_error(ctx->cmd,
+ "Invalid STORE modifiers.");
+ return FALSE;
+ }
+
+ if (strcasecmp(name, "UNCHANGEDSINCE") == 0) {
+ if (ctx->cmd->client->nonpermanent_modseqs) {
+ client_send_command_error(ctx->cmd,
+ "STORE UNCHANGEDSINCE can't be used with non-permanent modseqs");
+ return FALSE;
+ }
+ if (str_to_uint64(value, &ctx->max_modseq) < 0) {
+ client_send_command_error(ctx->cmd,
+ "Invalid modseq");
+ return FALSE;
+ }
+ client_enable(ctx->cmd->client, imap_feature_condstore);
+ } else {
+ client_send_command_error(ctx->cmd,
+ "Unknown STORE modifier");
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static bool
+store_parse_args(struct imap_store_context *ctx, const struct imap_arg *args)
+{
+ struct client_command_context *cmd = ctx->cmd;
+ const struct imap_arg *list_args;
+ const char *type;
+ const char *const *keywords_list = NULL;
+
+ ctx->max_modseq = (uint64_t)-1;
+ if (imap_arg_get_list(args, &list_args)) {
+ if (!store_parse_modifiers(ctx, list_args))
+ return FALSE;
+ args++;
+ }
+
+ if (!imap_arg_get_astring(args, &type) ||
+ !get_modify_type(ctx, type)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return FALSE;
+ }
+ args++;
+
+ if (imap_arg_get_list(args, &list_args)) {
+ if (!client_parse_mail_flags(cmd, list_args,
+ &ctx->flags, &keywords_list))
+ return FALSE;
+ } else {
+ if (!client_parse_mail_flags(cmd, args,
+ &ctx->flags, &keywords_list))
+ return FALSE;
+ }
+
+ if (keywords_list != NULL || ctx->modify_type == MODIFY_REPLACE) {
+ if (mailbox_keywords_create(cmd->client->mailbox, keywords_list,
+ &ctx->keywords) < 0) {
+ /* invalid keywords */
+ client_send_box_error(cmd, cmd->client->mailbox);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+bool cmd_store(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ const struct imap_arg *args;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mailbox_transaction_context *t;
+ struct mail *mail;
+ struct imap_store_context ctx;
+ ARRAY_TYPE(seq_range) modified_set, uids;
+ enum mailbox_transaction_flags flags = 0;
+ enum imap_sync_flags imap_sync_flags = 0;
+ const char *set, *reply, *tagged_reply;
+ string_t *str;
+ int ret;
+ bool update_deletes;
+ unsigned int deleted_count;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ if (!imap_arg_get_atom(args, &set)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+ ret = imap_search_get_seqset(cmd, set, cmd->uid, &search_args);
+ if (ret <= 0)
+ return ret < 0;
+
+ i_zero(&ctx);
+ ctx.cmd = cmd;
+ if (!store_parse_args(&ctx, ++args)) {
+ mail_search_args_unref(&search_args);
+ return TRUE;
+ }
+
+ if (client->mailbox_examined) {
+ mail_search_args_unref(&search_args);
+ if (ctx.max_modseq < (uint64_t)-1)
+ reply = "NO CONDSTORE failed: Mailbox is read-only.";
+ else
+ reply = "OK Store ignored with read-only mailbox.";
+ return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
+ (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+ 0, reply);
+ }
+
+ if (ctx.silent)
+ flags |= MAILBOX_TRANSACTION_FLAG_HIDE;
+ if (ctx.max_modseq < (uint64_t)-1) {
+ /* update modseqs so we can check them early */
+ flags |= MAILBOX_TRANSACTION_FLAG_REFRESH;
+ }
+
+ t = mailbox_transaction_begin(client->mailbox, flags,
+ imap_client_command_get_reason(cmd));
+
+ search_ctx = mailbox_search_init(t, search_args, NULL,
+ MAIL_FETCH_FLAGS, NULL);
+ mail_search_args_unref(&search_args);
+
+ i_array_init(&modified_set, 64);
+ if (ctx.max_modseq < (uint64_t)-1) {
+ /* STORE UNCHANGEDSINCE is being used */
+ mailbox_transaction_set_max_modseq(t, ctx.max_modseq,
+ &modified_set);
+ }
+
+ update_deletes = (ctx.flags & MAIL_DELETED) != 0 &&
+ ctx.modify_type != MODIFY_REMOVE;
+ deleted_count = 0;
+ while (mailbox_search_next(search_ctx, &mail)) {
+ if (ctx.max_modseq < (uint64_t)-1) {
+ /* check early so there's less work for transaction
+ commit if something has to be cancelled */
+ if (mail_get_modseq(mail) > ctx.max_modseq) {
+ seq_range_array_add(&modified_set, mail->seq);
+ continue;
+ }
+ }
+ if (update_deletes) {
+ if ((mail_get_flags(mail) & MAIL_DELETED) == 0)
+ deleted_count++;
+ }
+ if (ctx.modify_type == MODIFY_REPLACE || ctx.flags != 0)
+ mail_update_flags(mail, ctx.modify_type, ctx.flags);
+ if (ctx.modify_type == MODIFY_REPLACE || ctx.keywords != NULL) {
+ mail_update_keywords(mail, ctx.modify_type,
+ ctx.keywords);
+ }
+ }
+
+ if (ctx.keywords != NULL)
+ mailbox_keywords_unref(&ctx.keywords);
+
+ ret = mailbox_search_deinit(&search_ctx);
+ if (ret < 0)
+ mailbox_transaction_rollback(&t);
+ else
+ ret = mailbox_transaction_commit(&t);
+ if (ret < 0) {
+ array_free(&modified_set);
+ client_send_box_error(cmd, client->mailbox);
+ return TRUE;
+ }
+ client->deleted_count += deleted_count;
+
+ if (array_count(&modified_set) == 0)
+ tagged_reply = "OK Store completed.";
+ else {
+ if (cmd->uid) {
+ i_array_init(&uids, array_count(&modified_set)*2);
+ mailbox_get_uid_range(client->mailbox, &modified_set,
+ &uids);
+ array_free(&modified_set);
+ modified_set = uids;
+ }
+ str = str_new(cmd->pool, 256);
+ str_append(str, "OK [MODIFIED ");
+ imap_write_seq_range(str, &modified_set);
+ str_append(str, "] Conditional store failed.");
+ tagged_reply = str_c(str);
+ }
+ array_free(&modified_set);
+
+ /* With UID STORE we have to return UID for the flags as well.
+ Unfortunately we don't have the ability to separate those
+ flag changes that were caused by UID STORE and those that
+ came externally, so we'll just send the UID for all flag
+ changes that we see. */
+ if (cmd->uid && (!ctx.silent ||
+ client_has_enabled(client, imap_feature_condstore)))
+ imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID;
+
+ return cmd_sync(cmd, (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+ imap_sync_flags, tagged_reply);
+}
diff --git a/src/imap/cmd-subscribe.c b/src/imap/cmd-subscribe.c
new file mode 100644
index 0000000..03c2684
--- /dev/null
+++ b/src/imap/cmd-subscribe.c
@@ -0,0 +1,87 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-utf7.h"
+#include "imap-commands.h"
+#include "mail-namespace.h"
+
+static bool
+subscribe_is_valid_name(struct client_command_context *cmd, struct mailbox *box)
+{
+ enum mailbox_existence existence;
+
+ if (mailbox_exists(box, TRUE, &existence) < 0) {
+ client_send_box_error(cmd, box);
+ return FALSE;
+ }
+ if (existence == MAILBOX_EXISTENCE_NONE) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO "MAIL_ERRSTR_MAILBOX_NOT_FOUND,
+ mailbox_get_vname(box)));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool str_ends_with_char(const char *str, char c)
+{
+ size_t len = strlen(str);
+
+ return len > 0 && str[len-1] == c;
+}
+
+bool cmd_subscribe_full(struct client_command_context *cmd, bool subscribe)
+{
+ struct mail_namespace *ns;
+ struct mailbox *box, *box2;
+ const char *mailbox, *orig_mailbox;
+ bool unsubscribed_mailbox2;
+ char sep;
+
+ /* <mailbox> */
+ if (!client_read_string_args(cmd, 1, &mailbox))
+ return FALSE;
+ orig_mailbox = mailbox;
+
+ ns = client_find_namespace(cmd, &mailbox);
+ if (ns == NULL)
+ return TRUE;
+
+ box = mailbox_alloc(ns->list, mailbox, 0);
+ event_add_str(cmd->global_event, "mailbox", mailbox_get_vname(box));
+ if (subscribe) {
+ if (!subscribe_is_valid_name(cmd, box)) {
+ mailbox_free(&box);
+ return TRUE;
+ }
+ }
+
+ sep = mail_namespace_get_sep(ns);
+ unsubscribed_mailbox2 = FALSE;
+ if (!subscribe &&
+ str_ends_with_char(orig_mailbox, sep) &&
+ !str_ends_with_char(mailbox, sep)) {
+ /* try to unsubscribe both "box" and "box/" */
+ const char *name2 = t_strdup_printf("%s%c", mailbox, sep);
+ box2 = mailbox_alloc(ns->list, name2, 0);
+ if (mailbox_set_subscribed(box2, FALSE) == 0)
+ unsubscribed_mailbox2 = TRUE;
+ mailbox_free(&box2);
+ }
+
+ if (mailbox_set_subscribed(box, subscribe) < 0 &&
+ !unsubscribed_mailbox2) {
+ client_send_box_error(cmd, box);
+ } else {
+ client_send_tagline(cmd, subscribe ?
+ "OK Subscribe completed." :
+ "OK Unsubscribe completed.");
+ }
+ mailbox_free(&box);
+ return TRUE;
+}
+
+bool cmd_subscribe(struct client_command_context *cmd)
+{
+ return cmd_subscribe_full(cmd, TRUE);
+}
diff --git a/src/imap/cmd-thread.c b/src/imap/cmd-thread.c
new file mode 100644
index 0000000..64388d5
--- /dev/null
+++ b/src/imap/cmd-thread.c
@@ -0,0 +1,294 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "ostream.h"
+#include "imap-base-subject.h"
+#include "imap-commands.h"
+#include "imap-search-args.h"
+#include "mail-thread.h"
+
+static int imap_thread_write(struct mail_thread_iterate_context *iter,
+ string_t *str, bool root)
+{
+ const struct mail_thread_child_node *node;
+ struct mail_thread_iterate_context *child_iter;
+ unsigned int count;
+ int ret = 0;
+
+ count = mail_thread_iterate_count(iter);
+ if (count == 0)
+ return 0;
+
+ if (count == 1 && !root) {
+ /* only one child - special case to avoid extra parenthesis */
+ node = mail_thread_iterate_next(iter, &child_iter);
+ str_printfa(str, "%u", node->uid);
+ if (child_iter != NULL) {
+ str_append_c(str, ' ');
+ T_BEGIN {
+ ret = imap_thread_write(child_iter, str, FALSE);
+ } T_END;
+ if (mail_thread_iterate_deinit(&child_iter) < 0)
+ return -1;
+ }
+ return ret;
+ }
+
+ while ((node = mail_thread_iterate_next(iter, &child_iter)) != NULL) {
+ if (child_iter == NULL) {
+ /* no children */
+ str_printfa(str, "(%u)", node->uid);
+ } else {
+ /* node with children */
+ str_append_c(str, '(');
+ if (node->uid != 0)
+ str_printfa(str, "%u ", node->uid);
+ T_BEGIN {
+ ret = imap_thread_write(child_iter, str, FALSE);
+ } T_END;
+ if (mail_thread_iterate_deinit(&child_iter) < 0 ||
+ ret < 0)
+ return -1;
+ str_append_c(str, ')');
+ }
+ }
+ return 0;
+}
+
+static int
+imap_thread_write_reply(struct mail_thread_context *ctx, string_t *str,
+ enum mail_thread_type thread_type, bool write_seqs)
+{
+ struct mail_thread_iterate_context *iter;
+ int ret;
+
+ iter = mail_thread_iterate_init(ctx, thread_type, write_seqs);
+ str_append(str, "* THREAD ");
+ T_BEGIN {
+ ret = imap_thread_write(iter, str, TRUE);
+ } T_END;
+ if (mail_thread_iterate_deinit(&iter) < 0)
+ ret = -1;
+
+ str_append(str, "\r\n");
+ return ret;
+}
+
+static int imap_thread(struct client_command_context *cmd,
+ struct mail_search_args *search_args,
+ enum mail_thread_type thread_type)
+{
+ struct mail_thread_context *ctx;
+ string_t *str;
+ int ret;
+
+ i_assert(thread_type == MAIL_THREAD_REFERENCES ||
+ thread_type == MAIL_THREAD_REFS);
+
+ str = str_new(default_pool, 1024);
+ ret = mail_thread_init(cmd->client->mailbox,
+ search_args, &ctx);
+ if (ret == 0) {
+ ret = imap_thread_write_reply(ctx, str, thread_type,
+ !cmd->uid);
+ mail_thread_deinit(&ctx);
+ }
+
+ if (ret == 0)
+ o_stream_nsend(cmd->client->output, str_data(str), str_len(str));
+ str_free(&str);
+ return ret;
+}
+
+struct orderedsubject_thread {
+ time_t timestamp;
+ ARRAY_TYPE(uint32_t) msgs;
+};
+
+static int orderedsubject_thread_cmp(const struct orderedsubject_thread *t1,
+ const struct orderedsubject_thread *t2)
+{
+ const uint32_t *m1, *m2;
+
+ if (t1->timestamp < t2->timestamp)
+ return -1;
+ if (t1->timestamp > t2->timestamp)
+ return 1;
+
+ m1 = array_front(&t1->msgs);
+ m2 = array_front(&t2->msgs);
+ if (*m1 < *m2)
+ return -1;
+ if (*m1 > *m2)
+ return 1;
+ i_unreached();
+}
+
+static void
+imap_orderedsubject_thread_write(struct ostream *output, string_t *reply,
+ const struct orderedsubject_thread *thread)
+{
+ const uint32_t *msgs;
+ unsigned int i, count;
+
+ if (str_len(reply) > 128-10) {
+ o_stream_nsend(output, str_data(reply), str_len(reply));
+ str_truncate(reply, 0);
+ }
+
+ msgs = array_get(&thread->msgs, &count);
+ switch (count) {
+ case 1:
+ str_printfa(reply, "(%u)", msgs[0]);
+ break;
+ case 2:
+ str_printfa(reply, "(%u %u)", msgs[0], msgs[1]);
+ break;
+ default:
+ /* (1 (2)(3)) */
+ str_printfa(reply, "(%u ", msgs[0]);
+ for (i = 1; i < count; i++) {
+ if (str_len(reply) > 128-10) {
+ o_stream_nsend(output, str_data(reply),
+ str_len(reply));
+ str_truncate(reply, 0);
+ }
+ str_printfa(reply, "(%u)", msgs[i]);
+ }
+ str_append_c(reply, ')');
+ }
+}
+
+static int imap_thread_orderedsubject(struct client_command_context *cmd,
+ struct mail_search_args *search_args)
+{
+ static const enum mail_sort_type sort_program[] = {
+ MAIL_SORT_SUBJECT,
+ MAIL_SORT_DATE,
+ 0
+ };
+ struct mailbox_transaction_context *trans;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ string_t *prev_subject, *reply;
+ const char *subject, *base_subject;
+ pool_t pool;
+ ARRAY(struct orderedsubject_thread) threads;
+ const struct orderedsubject_thread *thread;
+ struct orderedsubject_thread *cur_thread = NULL;
+ uint32_t num;
+ bool reply_or_fw;
+ int ret, tz;
+
+ prev_subject = str_new(default_pool, 128);
+
+ /* first read all of the threads into memory */
+ pool = pool_alloconly_create("orderedsubject thread", 1024);
+ i_array_init(&threads, 128);
+ trans = mailbox_transaction_begin(cmd->client->mailbox, 0,
+ imap_client_command_get_reason(cmd));
+ search_ctx = mailbox_search_init(trans, search_args, sort_program,
+ 0, NULL);
+ while (mailbox_search_next(search_ctx, &mail)) {
+ if (mail_get_first_header(mail, "Subject", &subject) <= 0)
+ subject = "";
+ T_BEGIN {
+ base_subject = imap_get_base_subject_cased(
+ pool_datastack_create(), subject,
+ &reply_or_fw);
+ if (strcmp(str_c(prev_subject), base_subject) != 0) {
+ /* thread changed */
+ cur_thread = NULL;
+ }
+ str_truncate(prev_subject, 0);
+ str_append(prev_subject, base_subject);
+ } T_END;
+
+ if (cur_thread == NULL) {
+ /* starting a new thread. get the first message's
+ date */
+ cur_thread = array_append_space(&threads);
+ if (mail_get_date(mail, &cur_thread->timestamp,
+ &tz) == 0 &&
+ cur_thread->timestamp == 0) {
+ (void)mail_get_received_date(mail,
+ &cur_thread->timestamp);
+ }
+ p_array_init(&cur_thread->msgs, pool, 4);
+ }
+ num = cmd->uid ? mail->uid : mail->seq;
+ array_push_back(&cur_thread->msgs, &num);
+ }
+ str_free(&prev_subject);
+ ret = mailbox_search_deinit(&search_ctx);
+ (void)mailbox_transaction_commit(&trans);
+ if (ret < 0) {
+ array_free(&threads);
+ pool_unref(&pool);
+ return -1;
+ }
+
+ /* sort the threads by their first message's timestamp */
+ array_sort(&threads, orderedsubject_thread_cmp);
+
+ /* write the threads to client */
+ reply = t_str_new(128);
+ str_append(reply, "* THREAD ");
+ array_foreach(&threads, thread) {
+ imap_orderedsubject_thread_write(cmd->client->output,
+ reply, thread);
+ }
+ str_append(reply, "\r\n");
+ o_stream_nsend(cmd->client->output, str_data(reply), str_len(reply));
+
+ array_free(&threads);
+ pool_unref(&pool);
+ return 0;
+}
+
+bool cmd_thread(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ enum mail_thread_type thread_type;
+ struct mail_search_args *sargs;
+ const struct imap_arg *args;
+ const char *charset, *str;
+ int ret;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ if (!imap_arg_get_astring(&args[0], &str) ||
+ !imap_arg_get_astring(&args[1], &charset)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+ args += 2;
+
+ if (!mail_thread_type_parse(str, &thread_type)) {
+ client_send_command_error(cmd, "Unknown thread algorithm.");
+ return TRUE;
+ }
+
+ ret = imap_search_args_build(cmd, args, charset, &sargs);
+ if (ret <= 0)
+ return ret < 0;
+
+ if (thread_type != MAIL_THREAD_ORDEREDSUBJECT)
+ ret = imap_thread(cmd, sargs, thread_type);
+ else
+ ret = imap_thread_orderedsubject(cmd, sargs);
+ mail_search_args_unref(&sargs);
+ if (ret < 0) {
+ client_send_box_error(cmd, client->mailbox);
+ return TRUE;
+ }
+
+ return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
+ (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+ 0, "OK Thread completed.");
+}
diff --git a/src/imap/cmd-unselect.c b/src/imap/cmd-unselect.c
new file mode 100644
index 0000000..7f9f547
--- /dev/null
+++ b/src/imap/cmd-unselect.c
@@ -0,0 +1,18 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+
+bool cmd_unselect(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+
+ if (!client_verify_open_mailbox(cmd))
+ return TRUE;
+
+ i_assert(client->mailbox_change_lock == NULL);
+
+ imap_client_close_mailbox(client);
+ client_send_tagline(cmd, "OK Unselect completed.");
+ return TRUE;
+}
diff --git a/src/imap/cmd-unsubscribe.c b/src/imap/cmd-unsubscribe.c
new file mode 100644
index 0000000..1d23624
--- /dev/null
+++ b/src/imap/cmd-unsubscribe.c
@@ -0,0 +1,9 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+
+bool cmd_unsubscribe(struct client_command_context *cmd)
+{
+ return cmd_subscribe_full(cmd, FALSE);
+}
diff --git a/src/imap/cmd-urlfetch.c b/src/imap/cmd-urlfetch.c
new file mode 100644
index 0000000..6ea5ad5
--- /dev/null
+++ b/src/imap/cmd-urlfetch.c
@@ -0,0 +1,410 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "strfuncs.h"
+#include "str.h"
+#include "array.h"
+#include "net.h"
+#include "istream.h"
+#include "istream-sized.h"
+#include "ostream.h"
+#include "imap-url.h"
+#include "imap-quote.h"
+#include "imap-common.h"
+#include "imap-commands.h"
+#include "imap-urlauth.h"
+#include "imap-urlauth-fetch.h"
+
+struct cmd_urlfetch_context {
+ struct imap_urlauth_fetch *ufetch;
+ struct istream *input;
+
+ bool failed:1;
+ bool finished:1;
+ bool extended:1;
+ bool in_io_handler:1;
+};
+
+struct cmd_urlfetch_url {
+ const char *url;
+
+ enum imap_urlauth_fetch_flags flags;
+};
+
+static void cmd_urlfetch_finish(struct client_command_context *cmd)
+{
+ struct cmd_urlfetch_context *ctx =
+ (struct cmd_urlfetch_context *)cmd->context;
+
+ if (ctx->finished)
+ return;
+ ctx->finished = TRUE;
+
+ i_stream_unref(&ctx->input);
+ if (ctx->ufetch != NULL)
+ imap_urlauth_fetch_deinit(&ctx->ufetch);
+
+ if (ctx->failed) {
+ if (cmd->client->output_cmd_lock == cmd) {
+ /* failed in the middle of a literal.
+ we need to disconnect. */
+ cmd->client->output_cmd_lock = NULL;
+ client_disconnect(cmd->client, "URLFETCH failed");
+ } else {
+ client_send_internal_error(cmd);
+ }
+ return;
+ }
+
+ client_send_tagline(cmd, "OK URLFETCH completed.");
+}
+
+static bool cmd_urlfetch_cancel(struct client_command_context *cmd)
+{
+ struct cmd_urlfetch_context *ctx =
+ (struct cmd_urlfetch_context *)cmd->context;
+
+ if (!cmd->cancel)
+ return FALSE;
+
+ if (ctx->ufetch != NULL) {
+ e_debug(cmd->client->event,
+ "URLFETCH: Canceling command; "
+ "aborting URLAUTH fetch requests prematurely");
+ imap_urlauth_fetch_deinit(&ctx->ufetch);
+ }
+ return TRUE;
+}
+
+static int cmd_urlfetch_transfer_literal(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_urlfetch_context *ctx =
+ (struct cmd_urlfetch_context *)cmd->context;
+ enum ostream_send_istream_result res;
+ int ret;
+
+ /* are we in the middle of an urlfetch literal? */
+ if (ctx->input == NULL)
+ return 1;
+
+ /* flush output to client if buffer is filled above optimum */
+ if (o_stream_get_buffer_used_size(client->output) >=
+ CLIENT_OUTPUT_OPTIMAL_SIZE) {
+ if ((ret = o_stream_flush(client->output)) <= 0)
+ return ret;
+ }
+
+ /* transfer literal to client */
+ o_stream_set_max_buffer_size(client->output, 0);
+ res = o_stream_send_istream(client->output, ctx->input);
+ o_stream_set_max_buffer_size(client->output, SIZE_MAX);
+
+ switch (res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ i_stream_unref(&ctx->input);
+ return 1;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ return 0;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ e_error(client->event, "read(%s) failed: %s (URLFETCH)",
+ i_stream_get_name(ctx->input),
+ i_stream_get_error(ctx->input));
+ client_disconnect(client, "URLFETCH failed");
+ return -1;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ /* client disconnected */
+ return -1;
+ }
+ i_unreached();
+}
+
+static bool cmd_urlfetch_continue(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_urlfetch_context *ctx =
+ (struct cmd_urlfetch_context *)cmd->context;
+ bool urls_pending;
+ int ret = 1;
+
+ if (cmd->cancel)
+ return cmd_urlfetch_cancel(cmd);
+
+ i_assert(client->output_cmd_lock == NULL ||
+ client->output_cmd_lock == cmd);
+
+ /* finish a pending literal transfer */
+ ret = cmd_urlfetch_transfer_literal(cmd);
+ if (ret < 0) {
+ ctx->failed = TRUE;
+ cmd_urlfetch_finish(cmd);
+ return TRUE;
+ }
+ if (ret == 0) {
+ /* not finished; apparently output blocked again */
+ return FALSE;
+ }
+
+ if (ctx->extended)
+ client_send_line(client, ")");
+ else
+ client_send_line(client, "");
+ client->output_cmd_lock = NULL;
+
+ ctx->in_io_handler = TRUE;
+ urls_pending = imap_urlauth_fetch_continue(ctx->ufetch);
+ ctx->in_io_handler = FALSE;
+
+ if (urls_pending) {
+ /* waiting for imap urlauth service */
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_EXTERNAL;
+ cmd->func = cmd_urlfetch_cancel;
+
+ /* retrieve next url */
+ return FALSE;
+ }
+
+ /* finished */
+ cmd_urlfetch_finish(cmd);
+ return TRUE;
+}
+
+static int cmd_urlfetch_url_success(struct client_command_context *cmd,
+ struct imap_urlauth_fetch_reply *reply)
+{
+ struct cmd_urlfetch_context *ctx = cmd->context;
+ string_t *response = t_str_new(256);
+ int ret;
+
+ str_append(response, "* URLFETCH ");
+ imap_append_astring(response, reply->url);
+
+ if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_EXTENDED) == 0) {
+ /* simple */
+ ctx->extended = FALSE;
+
+ str_printfa(response, " {%"PRIuUOFF_T"}", reply->size);
+ client_send_line(cmd->client, str_c(response));
+ i_assert(reply->size == 0 || reply->input != NULL);
+ } else {
+ bool metadata = FALSE;
+
+ /* extended */
+ ctx->extended = TRUE;
+
+ str_append(response, " (");
+ if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0 &&
+ reply->bodypartstruct != NULL) {
+ str_append(response, "BODYPARTSTRUCTURE (");
+ str_append(response, reply->bodypartstruct);
+ str_append_c(response, ')');
+ metadata = TRUE;
+ }
+ if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 ||
+ (reply->flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) {
+ if (metadata)
+ str_append_c(response, ' ');
+ if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0) {
+ str_append(response, "BODY ");
+ } else {
+ str_append(response, "BINARY ");
+ if (reply->binary_has_nuls)
+ str_append_c(response, '~');
+ }
+ str_printfa(response, "{%"PRIuUOFF_T"}", reply->size);
+ i_assert(reply->size == 0 || reply->input != NULL);
+ } else {
+ str_append_c(response, ')');
+ ctx->extended = FALSE;
+ }
+
+ client_send_line(cmd->client, str_c(response));
+ }
+
+ if (reply->input != NULL) {
+ ctx->input = i_stream_create_sized(reply->input, reply->size);
+
+ ret = cmd_urlfetch_transfer_literal(cmd);
+ if (ret < 0) {
+ ctx->failed = TRUE;
+ return -1;
+ }
+ if (ret == 0) {
+ /* not finished; apparently output blocked */
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+ cmd->func = cmd_urlfetch_continue;
+ cmd->client->output_cmd_lock = cmd;
+ return 0;
+ }
+
+ if (ctx->extended)
+ client_send_line(cmd->client, ")");
+ else
+ client_send_line(cmd->client, "");
+ }
+ return 1;
+}
+
+static int
+cmd_urlfetch_url_callback(struct imap_urlauth_fetch_reply *reply,
+ bool last, void *context)
+{
+ struct client_command_context *cmd = context;
+ struct client *client = cmd->client;
+ struct cmd_urlfetch_context *ctx = cmd->context;
+ bool in_io_handler = ctx->in_io_handler;
+ int ret;
+
+ if (!in_io_handler)
+ o_stream_cork(client->output);
+ if (reply == NULL) {
+ /* fatal failure */
+ ctx->failed = TRUE;
+ ret = -1;
+ } else if (reply->succeeded) {
+ /* URL fetch succeeded */
+ ret = cmd_urlfetch_url_success(cmd, reply);
+ } else {
+ /* URL fetch failed */
+ string_t *response = t_str_new(128);
+
+ str_append(response, "* URLFETCH ");
+ imap_append_astring(response, reply->url);
+ str_append(response, " NIL");
+ client_send_line(client, str_c(response));
+ if (reply->error != NULL) {
+ client_send_line(client, t_strdup_printf(
+ "* NO %s.", reply->error));
+ }
+ ret = 1;
+ }
+
+ if ((last && cmd->state == CLIENT_COMMAND_STATE_WAIT_EXTERNAL) ||
+ ret < 0) {
+ cmd_urlfetch_finish(cmd);
+ client_command_free(&cmd);
+ }
+ if (!in_io_handler)
+ o_stream_uncork(client->output);
+ return ret;
+}
+
+static int
+cmd_urlfetch_parse_arg(struct client_command_context *cmd,
+ const struct imap_arg *arg,
+ struct cmd_urlfetch_url *ufurl_r)
+{
+ enum imap_urlauth_fetch_flags url_flags = 0;
+ const struct imap_arg *params;
+ const char *url_text;
+
+ if (imap_arg_get_list(arg, &params))
+ url_flags |= IMAP_URLAUTH_FETCH_FLAG_EXTENDED;
+ else
+ params = arg;
+
+ /* read url */
+ if (!imap_arg_get_astring(params++, &url_text)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return -1;
+ }
+ ufurl_r->url = t_strdup(url_text);
+ if (url_flags == 0)
+ return 0;
+
+ while (!IMAP_ARG_IS_EOL(params)) {
+ const char *fetch_param;
+
+ if (!imap_arg_get_atom(params++, &fetch_param)) {
+ client_send_command_error(cmd,
+ "Invalid URL fetch parameter.");
+ return -1;
+ }
+
+ if (strcasecmp(fetch_param, "BODY") == 0)
+ url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODY;
+ else if (strcasecmp(fetch_param, "BINARY") == 0)
+ url_flags |= IMAP_URLAUTH_FETCH_FLAG_BINARY;
+ else if (strcasecmp(fetch_param, "BODYPARTSTRUCTURE") == 0)
+ url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE;
+ else {
+ client_send_command_error(cmd,
+ t_strdup_printf("Unknown URL fetch parameter: %s",
+ fetch_param));
+ return -1;
+ }
+ }
+
+ if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 &&
+ (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) {
+ client_send_command_error(cmd,
+ "URL cannot have both BODY and BINARY fetch parameters.");
+ return -1;
+ }
+
+ if (url_flags == IMAP_URLAUTH_FETCH_FLAG_EXTENDED)
+ url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODY;
+ ufurl_r->flags = url_flags;
+ return 0;
+}
+
+bool cmd_urlfetch(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_urlfetch_context *ctx;
+ ARRAY(struct cmd_urlfetch_url) urls;
+ const struct cmd_urlfetch_url *url;
+ const struct imap_arg *args;
+ struct cmd_urlfetch_url *ufurl;
+
+ if (client->urlauth_ctx == NULL) {
+ client_send_command_error(cmd, "URLAUTH disabled.");
+ return TRUE;
+ }
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ if (IMAP_ARG_IS_EOL(args)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+
+ t_array_init(&urls, 32);
+
+ /* parse url arguments and group them per userid */
+ for (; !IMAP_ARG_IS_EOL(args); args++) {
+ ufurl = array_append_space(&urls);
+ if (cmd_urlfetch_parse_arg(cmd, args, ufurl) < 0)
+ return TRUE;
+ }
+ cmd->context = ctx = p_new(cmd->pool, struct cmd_urlfetch_context, 1);
+ cmd->func = cmd_urlfetch_cancel;
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
+
+ ctx->ufetch = imap_urlauth_fetch_init(client->urlauth_ctx,
+ cmd_urlfetch_url_callback, cmd);
+
+ ctx->in_io_handler = TRUE;
+ array_foreach(&urls, url) {
+ if (imap_urlauth_fetch_url(ctx->ufetch, url->url, url->flags) < 0) {
+ /* fatal error */
+ ctx->failed = TRUE;
+ break;
+ }
+ }
+ ctx->in_io_handler = FALSE;
+
+ if ((ctx->failed || !imap_urlauth_fetch_is_pending(ctx->ufetch))
+ && cmd->client->output_cmd_lock != cmd) {
+ /* finished */
+ cmd_urlfetch_finish(cmd);
+ return TRUE;
+ }
+
+ if (cmd->client->output_cmd_lock != cmd)
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_EXTERNAL;
+ return FALSE;
+}
diff --git a/src/imap/cmd-x-cancel.c b/src/imap/cmd-x-cancel.c
new file mode 100644
index 0000000..3f49451
--- /dev/null
+++ b/src/imap/cmd-x-cancel.c
@@ -0,0 +1,28 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-commands.h"
+
+bool cmd_x_cancel(struct client_command_context *cmd)
+{
+ struct client_command_context *cancel_cmd;
+ const char *tag;
+
+ /* <tag> */
+ if (!client_read_string_args(cmd, 1, &tag))
+ return FALSE;
+
+ cancel_cmd = cmd->client->command_queue;
+ for (; cancel_cmd != NULL; cancel_cmd = cancel_cmd->next) {
+ if (cancel_cmd->tag != NULL && cancel_cmd != cmd &&
+ strcmp(cancel_cmd->tag, tag) == 0) {
+ client_command_cancel(&cancel_cmd);
+ client_send_tagline(cmd, "OK Command cancelled.");
+ return TRUE;
+ }
+ }
+
+ client_send_tagline(cmd, "NO Command tag not found.");
+ return TRUE;
+}
+
diff --git a/src/imap/cmd-x-state.c b/src/imap/cmd-x-state.c
new file mode 100644
index 0000000..8809998
--- /dev/null
+++ b/src/imap/cmd-x-state.c
@@ -0,0 +1,68 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "base64.h"
+#include "str.h"
+#include "imap-commands.h"
+#include "imap-state.h"
+
+bool cmd_x_state(struct client_command_context *cmd)
+{
+ /* FIXME: state importing can cause unnecessarily large memory usage
+ by specifying an old modseq, because the EXPUNGE/FETCH replies
+ aren't currently sent asynchronously. so this command is disabled
+ for now. */
+#if 0
+ const struct imap_arg *args;
+ const char *str, *error;
+ buffer_t *state, *state_encoded;
+ int ret;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ state = buffer_create_dynamic(cmd->pool, 256);
+ if (imap_arg_get_astring(&args[0], &str)) {
+ if (cmd->client->mailbox != NULL) {
+ client_send_tagline(cmd,
+ "BAD Can't be used in SELECTED state");
+ return TRUE;
+ }
+ if (base64_decode(str, strlen(str), NULL, state) < 0)
+ ret = 0;
+ else {
+ ret = imap_state_import_external(cmd->client,
+ state->data, state->used, &error);
+ }
+ if (ret < 0) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO Failed to restore state: %s", error));
+ } else if (ret == 0) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "BAD Broken state: %s", error));
+ } else {
+ client_send_tagline(cmd, "OK State imported.");
+ }
+ return TRUE;
+ } else if (args[0].type == IMAP_ARG_EOL) {
+ if (!imap_state_export_external(cmd->client, state, &error)) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO Can't save state: %s", error));
+ return TRUE;
+ }
+ state_encoded = buffer_create_dynamic(cmd->pool,
+ MAX_BASE64_ENCODED_SIZE(state->used)+10);
+ str_append(state_encoded, "* STATE ");
+ base64_encode(state->data, state->used, state_encoded);
+ client_send_line(cmd->client, str_c(state_encoded));
+ client_send_tagline(cmd, "OK State exported.");
+ return TRUE;
+ } else {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+#else
+ client_send_command_error(cmd, "Command is disabled for now.");
+ return TRUE;
+#endif
+}
diff --git a/src/imap/imap-client-hibernate.c b/src/imap/imap-client-hibernate.c
new file mode 100644
index 0000000..b29f500
--- /dev/null
+++ b/src/imap/imap-client-hibernate.c
@@ -0,0 +1,294 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "fdpass.h"
+#include "net.h"
+#include "ostream.h"
+#include "write-full.h"
+#include "base64.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "mailbox-watch.h"
+#include "imap-state.h"
+#include "imap-client.h"
+
+#include <sys/stat.h>
+
+#define IMAP_HIBERNATE_SOCKET_NAME "imap-hibernate"
+#define IMAP_HIBERNATE_SEND_TIMEOUT_SECS 10
+#define IMAP_HIBERNATE_HANDSHAKE "VERSION\timap-hibernate\t1\t0\n"
+
+static int
+imap_hibernate_handshake(int fd, const char *path, const char **error_r)
+{
+ char buf[1024];
+ ssize_t ret;
+
+ if (write_full(fd, IMAP_HIBERNATE_HANDSHAKE,
+ strlen(IMAP_HIBERNATE_HANDSHAKE)) < 0) {
+ *error_r = t_strdup_printf("write(%s) failed: %m", path);
+ return -1;
+ } else if ((ret = read(fd, buf, sizeof(buf)-1)) < 0) {
+ *error_r = t_strdup_printf("read(%s) failed: %m", path);
+ return -1;
+ } else if (ret > 0 && buf[ret-1] == '\n') {
+ buf[ret-1] = '\0';
+ if (version_string_verify(buf, "imap-hibernate", 1))
+ return 0;
+ } else {
+ buf[ret] = '\0';
+ }
+ *error_r = t_strdup_printf("%s sent invalid VERSION handshake: %s",
+ path, buf);
+ return -1;
+}
+
+static void imap_hibernate_write_cmd(struct client *client, string_t *cmd,
+ const buffer_t *state, int fd_notify)
+{
+ struct mail_user *user = client->user;
+ struct stat peer_st;
+ const char *tag;
+
+ tag = client->command_queue == NULL ? NULL : client->command_queue->tag;
+
+ str_append_tabescaped(cmd, user->username);
+ str_append_c(cmd, '\t');
+ str_append_tabescaped(cmd, user->set->mail_log_prefix);
+ str_printfa(cmd, "\tidle_notify_interval=%u",
+ client->set->imap_idle_notify_interval);
+ if (fstat(client->fd_in, &peer_st) == 0) {
+ str_printfa(cmd, "\tpeer_dev_major=%lu\tpeer_dev_minor=%lu\tpeer_ino=%llu",
+ (unsigned long)major(peer_st.st_dev),
+ (unsigned long)minor(peer_st.st_dev),
+ (unsigned long long)peer_st.st_ino);
+ }
+
+ str_append(cmd, "\tsession=");
+ str_append_tabescaped(cmd, user->session_id);
+ if (user->session_create_time != 0) {
+ str_printfa(cmd, "\tsession_created=%s",
+ dec2str(user->session_create_time));
+ }
+ if (user->conn.local_ip != NULL)
+ str_printfa(cmd, "\tlip=%s", net_ip2addr(user->conn.local_ip));
+ if (user->conn.local_port != 0)
+ str_printfa(cmd, "\tlport=%u", user->conn.local_port);
+ if (user->conn.remote_ip != NULL)
+ str_printfa(cmd, "\trip=%s", net_ip2addr(user->conn.remote_ip));
+ if (user->conn.remote_port != 0)
+ str_printfa(cmd, "\trport=%u", user->conn.remote_port);
+ if (client->userdb_fields != NULL) {
+ string_t *userdb_fields = t_str_new(256);
+ unsigned int i;
+
+ for (i = 0; client->userdb_fields[i] != NULL; i++) {
+ if (i > 0)
+ str_append_c(userdb_fields, '\t');
+ str_append_tabescaped(userdb_fields, client->userdb_fields[i]);
+ }
+ str_append(cmd, "\tuserdb_fields=");
+ str_append_tabescaped(cmd, str_c(userdb_fields));
+ }
+ if (user->uid != (uid_t)-1)
+ str_printfa(cmd, "\tuid=%s", dec2str(user->uid));
+ if (user->gid != (gid_t)-1)
+ str_printfa(cmd, "\tgid=%s", dec2str(user->gid));
+ if (client->mailbox != NULL) {
+ str_append(cmd, "\tmailbox=");
+ str_append_tabescaped(cmd, mailbox_get_vname(client->mailbox));
+ }
+ if (tag != NULL) {
+ str_append(cmd, "\ttag=");
+ str_append_tabescaped(cmd, tag);
+ }
+ str_append(cmd, "\tstats=");
+ str_append_tabescaped(cmd, client_stats(client));
+ if (client->command_queue != NULL &&
+ strcasecmp(client->command_queue->name, "IDLE") == 0)
+ str_append(cmd, "\tidle-cmd");
+ if (fd_notify != -1)
+ str_append(cmd, "\tnotify_fd");
+ str_append(cmd, "\tstate=");
+ base64_encode(state->data, state->used, cmd);
+ str_append_c(cmd, '\n');
+}
+
+static int
+imap_hibernate_process_send_cmd(int fd_socket, const char *path,
+ const string_t *cmd, int fd_client,
+ const char **error_r)
+{
+ ssize_t ret;
+
+ i_assert(fd_socket != -1);
+ i_assert(str_len(cmd) > 1);
+
+ if (imap_hibernate_handshake(fd_socket, path, error_r) < 0)
+ return -1;
+ if ((ret = fd_send(fd_socket, fd_client, str_data(cmd), 1)) < 0) {
+ *error_r = t_strdup_printf("fd_send(%s) failed: %m", path);
+ return -1;
+ }
+ i_assert(ret == 1);
+ if (write_full(fd_socket, str_data(cmd)+1, str_len(cmd)-1) < 0) {
+ *error_r = t_strdup_printf("write(%s) failed: %m", path);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+imap_hibernate_process_read(int fd, const char *path, const char **error_r)
+{
+ char buf[1024];
+ ssize_t ret;
+
+ if ((ret = read(fd, buf, sizeof(buf)-1)) < 0) {
+ *error_r = t_strdup_printf("read(%s) failed: %m", path);
+ return -1;
+ } else if (ret == 0) {
+ *error_r = t_strdup_printf("%s disconnected", path);
+ return -1;
+ } else if (buf[0] != '+') {
+ buf[ret] = '\0';
+ *error_r = t_strdup_printf("%s returned failure: %s", path,
+ ret > 0 && buf[0] == '-' ? buf+1 : buf);
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+static int
+imap_hibernate_process_send(struct client *client, const buffer_t *state,
+ int fd_notify, int *fd_r, const char **error_r)
+{
+ string_t *cmd = t_str_new(512);
+ const char *path;
+ ssize_t ret = 0;
+ int fd;
+
+ i_assert(state->used > 0);
+
+ *fd_r = -1;
+
+ path = t_strconcat(client->user->set->base_dir,
+ "/"IMAP_HIBERNATE_SOCKET_NAME, NULL);
+ fd = net_connect_unix_with_retries(path, 1000);
+ if (fd == -1) {
+ *error_r = t_strdup_printf(
+ "net_connect_unix(%s) failed: %m", path);
+ return -1;
+ }
+ net_set_nonblock(fd, FALSE);
+
+ imap_hibernate_write_cmd(client, cmd, state, fd_notify);
+
+ alarm(IMAP_HIBERNATE_SEND_TIMEOUT_SECS);
+ if (imap_hibernate_process_send_cmd(fd, path, cmd, client->fd_in, error_r) < 0 ||
+ imap_hibernate_process_read(fd, path, error_r) < 0)
+ ret = -1;
+ else if (fd_notify != -1) {
+ if ((ret = fd_send(fd, fd_notify, "\n", 1)) < 0)
+ *error_r = t_strdup_printf("fd_send(%s) failed: %m", path);
+ else
+ ret = imap_hibernate_process_read(fd, path, error_r);
+ }
+ alarm(0);
+ if (ret < 0) {
+ net_disconnect(fd);
+ return -1;
+ }
+ *fd_r = fd;
+ return 0;
+}
+
+bool imap_client_hibernate(struct client **_client, const char **reason_r)
+{
+ struct client *client = *_client;
+ buffer_t *state;
+ const char *error;
+ int ret, fd_notify = -1, fd_hibernate = -1;
+
+ *reason_r = NULL;
+
+ if (client->fd_in != client->fd_out) {
+ /* we won't try to hibernate stdio clients */
+ *reason_r = "stdio clients can't be hibernated";
+ return FALSE;
+ }
+ if (o_stream_get_buffer_used_size(client->output) > 0) {
+ /* wait until we've sent the pending output to client */
+ *reason_r = "output pending to client";
+ return FALSE;
+ }
+
+ struct event_passthrough *e =
+ event_create_passthrough(client->event)->
+ set_name("imap_client_hibernated");
+ if (client->mailbox != NULL)
+ e->add_str("mailbox", mailbox_get_vname(client->mailbox));
+
+ state = buffer_create_dynamic(default_pool, 1024);
+ ret = imap_state_export_internal(client, state, &error);
+ if (ret < 0) {
+ e->add_str("error", error);
+ e_error(e->event(), "Couldn't hibernate imap client: "
+ "Couldn't export state: %s (mailbox=%s)", error,
+ client->mailbox == NULL ? "" :
+ mailbox_get_vname(client->mailbox));
+ *reason_r = error;
+ } else if (ret == 0) {
+ e->add_str("error", error);
+ e_debug(e->event(), "Couldn't hibernate imap client: "
+ "Couldn't export state: %s (mailbox=%s)", error,
+ client->mailbox == NULL ? "" :
+ mailbox_get_vname(client->mailbox));
+ *reason_r = error;
+ }
+ if (ret > 0 && client->mailbox != NULL) {
+ fd_notify = mailbox_watch_extract_notify_fd(client->mailbox,
+ &error);
+ if (fd_notify == -1) {
+ e->add_str("error", error);
+ e_debug(e->event(), "Couldn't hibernate imap client: "
+ "Couldn't extract notifications fd: %s",
+ error);
+ *reason_r = error;
+ ret = -1;
+ }
+ }
+ if (ret > 0) {
+ if (imap_hibernate_process_send(client, state, fd_notify,
+ &fd_hibernate, &error) < 0) {
+ e->add_str("error", error);
+ e_error(e->event(),
+ "Couldn't hibernate imap client: %s", error);
+ *reason_r = error;
+ ret = -1;
+ }
+ }
+ i_close_fd(&fd_notify);
+ if (ret > 0) {
+ /* hide the disconnect log message, because the client didn't
+ actually log out */
+ e_debug(e->event(),
+ "Successfully hibernated imap client in mailbox %s",
+ client->mailbox == NULL ? "<none>" :
+ mailbox_get_vname(client->mailbox));
+ client->disconnected = TRUE;
+ client->hibernated = TRUE;
+ client_destroy(client, NULL);
+ *_client = NULL;
+ }
+ /* notify imap-hibernate that we're done by closing the connection.
+ do this only after client is destroyed. this way imap-hibernate
+ won't try to launch another imap process too early and cause
+ problems (like sending duplicate session ID to stats process) */
+ if (fd_hibernate != -1)
+ net_disconnect(fd_hibernate);
+ buffer_free(&state);
+ return ret > 0;
+}
diff --git a/src/imap/imap-client.c b/src/imap/imap-client.c
new file mode 100644
index 0000000..f3c1643
--- /dev/null
+++ b/src/imap/imap-client.c
@@ -0,0 +1,1681 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ioloop.h"
+#include "llist.h"
+#include "str.h"
+#include "hostpid.h"
+#include "net.h"
+#include "iostream.h"
+#include "iostream-rawlog.h"
+#include "istream.h"
+#include "istream-concat.h"
+#include "ostream.h"
+#include "time-util.h"
+#include "var-expand.h"
+#include "master-service.h"
+#include "imap-resp-code.h"
+#include "imap-util.h"
+#include "imap-urlauth.h"
+#include "mail-error.h"
+#include "mail-namespace.h"
+#include "mail-storage-service.h"
+#include "mail-autoexpunge.h"
+#include "imap-state.h"
+#include "imap-search.h"
+#include "imap-notify.h"
+#include "imap-commands.h"
+#include "imap-feature.h"
+
+#include <unistd.h>
+
+/* If the last command took longer than this to run, log statistics on
+ where the time was spent. */
+#define IMAP_CLIENT_DISCONNECT_LOG_STATS_CMD_MIN_RUNNING_MSECS 1000
+
+extern struct mail_storage_callbacks mail_storage_callbacks;
+extern struct imap_client_vfuncs imap_client_vfuncs;
+
+struct imap_module_register imap_module_register = { 0 };
+
+struct client *imap_clients = NULL;
+unsigned int imap_client_count = 0;
+
+unsigned int imap_feature_condstore = UINT_MAX;
+unsigned int imap_feature_qresync = UINT_MAX;
+
+static const char *client_command_state_names[CLIENT_COMMAND_STATE_DONE+1] = {
+ "wait-input",
+ "wait-output",
+ "wait-external",
+ "wait-unambiguity",
+ "wait-sync",
+ "done"
+};
+
+static void client_idle_timeout(struct client *client)
+{
+ if (client->output_cmd_lock == NULL)
+ client_send_line(client, "* BYE Disconnected for inactivity.");
+ client_destroy(client, t_strdup_printf(
+ "Inactivity - no input for %"PRIdTIME_T" secs",
+ ioloop_time - client->last_input));
+}
+
+static void client_init_urlauth(struct client *client)
+{
+ struct imap_urlauth_config config;
+
+ i_zero(&config);
+ config.url_host = client->set->imap_urlauth_host;
+ config.url_port = client->set->imap_urlauth_port;
+ config.socket_path = t_strconcat(client->user->set->base_dir,
+ "/"IMAP_URLAUTH_SOCKET_NAME, NULL);
+ config.session_id = client->user->session_id;
+ config.access_user = client->user->username;
+ config.access_service = "imap";
+ config.access_anonymous = client->user->anonymous;
+
+ client->urlauth_ctx = imap_urlauth_init(client->user, &config);
+}
+
+static bool user_has_special_use_mailboxes(struct mail_user *user)
+{
+ struct mail_namespace_settings *ns_set;
+
+ /*
+ * We have to iterate over namespace and mailbox *settings* since
+ * the namespaces haven't been set up yet. The namespaces haven't
+ * been set up so that we don't hold up the OK response to LOGIN
+ * when using slow lib-storage backends.
+ */
+
+ /* no namespaces => no special use flags */
+ if (!array_is_created(&user->set->namespaces))
+ return FALSE;
+
+ array_foreach_elem(&user->set->namespaces, ns_set) {
+ struct mailbox_settings *box_set;
+
+ /* no mailboxes => no special use flags */
+ if (!array_is_created(&ns_set->mailboxes))
+ continue;
+
+ array_foreach_elem(&ns_set->mailboxes, box_set) {
+ if (box_set->special_use != NULL)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct client *client_create(int fd_in, int fd_out, bool unhibernated,
+ struct event *event, struct mail_user *user,
+ struct mail_storage_service_user *service_user,
+ const struct imap_settings *set,
+ const struct smtp_submit_settings *smtp_set)
+{
+ const struct mail_storage_settings *mail_set;
+ struct client *client;
+ const char *ident;
+ pool_t pool;
+
+ /* always use nonblocking I/O */
+ net_set_nonblock(fd_in, TRUE);
+ net_set_nonblock(fd_out, TRUE);
+
+ pool = pool_alloconly_create("imap client", 2048);
+ client = p_new(pool, struct client, 1);
+ client->pool = pool;
+ client->v = imap_client_vfuncs;
+ client->event = event;
+ event_ref(client->event);
+ client->unhibernated = unhibernated;
+ client->set = set;
+ client->smtp_set = smtp_set;
+ client->service_user = service_user;
+ client->fd_in = fd_in;
+ client->fd_out = fd_out;
+ client->input = i_stream_create_fd(fd_in,
+ set->imap_max_line_length);
+ client->output = o_stream_create_fd(fd_out, SIZE_MAX);
+ o_stream_set_no_error_handling(client->output, TRUE);
+ i_stream_set_name(client->input, "<imap client>");
+ o_stream_set_name(client->output, "<imap client>");
+
+ o_stream_set_flush_callback(client->output, client_output, client);
+
+ p_array_init(&client->module_contexts, client->pool, 5);
+ client->last_input = ioloop_time;
+ client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
+ client_idle_timeout, client);
+
+ client->command_pool =
+ pool_alloconly_create(MEMPOOL_GROWING"client command", 1024*2);
+ client->user = user;
+ client->notify_count_changes = TRUE;
+ client->notify_flag_changes = TRUE;
+ p_array_init(&client->enabled_features, client->pool, 8);
+
+ client->capability_string =
+ str_new(client->pool, sizeof(CAPABILITY_STRING)+64);
+
+ if (*set->imap_capability == '\0')
+ str_append(client->capability_string, CAPABILITY_STRING);
+ else if (*set->imap_capability != '+') {
+ str_append(client->capability_string, set->imap_capability);
+ } else {
+ str_append(client->capability_string, CAPABILITY_STRING);
+ str_append_c(client->capability_string, ' ');
+ str_append(client->capability_string, set->imap_capability + 1);
+ }
+ if (client->set->imap_literal_minus)
+ client_add_capability(client, "LITERAL-");
+ else
+ client_add_capability(client, "LITERAL+");
+ if (user->fuzzy_search) {
+ /* Enable FUZZY capability only when it actually has
+ a chance of working */
+ client_add_capability(client, "SEARCH=FUZZY");
+ }
+
+ mail_set = mail_user_set_get_storage_set(user);
+ if (mail_set->mailbox_list_index) {
+ /* NOTIFY is enabled only when mailbox list indexes are
+ enabled, although even that doesn't necessarily guarantee
+ it always */
+ client_add_capability(client, "NOTIFY");
+ }
+
+ if (*set->imap_urlauth_host != '\0' &&
+ *mail_set->mail_attribute_dict != '\0') {
+ /* Enable URLAUTH capability only when dict is
+ configured correctly */
+ client_init_urlauth(client);
+ client_add_capability(client, "URLAUTH");
+ client_add_capability(client, "URLAUTH=BINARY");
+ }
+ if (set->imap_metadata && *mail_set->mail_attribute_dict != '\0')
+ client_add_capability(client, "METADATA");
+ if (user_has_special_use_mailboxes(user)) {
+ /* Advertise SPECIAL-USE only if there are actually some
+ SPECIAL-USE flags in mailbox configuration. */
+ client_add_capability(client, "SPECIAL-USE");
+ }
+
+ ident = mail_user_get_anvil_userip_ident(client->user);
+ if (ident != NULL) {
+ master_service_anvil_send(master_service, t_strconcat(
+ "CONNECT\t", my_pid, "\timap/", ident, "\n", NULL));
+ client->anvil_sent = TRUE;
+ }
+
+ imap_client_count++;
+ DLLIST_PREPEND(&imap_clients, client);
+ if (hook_client_created != NULL)
+ hook_client_created(&client);
+
+ imap_refresh_proctitle();
+ return client;
+}
+
+void client_create_finish_io(struct client *client)
+{
+ if (client->set->rawlog_dir[0] != '\0') {
+ (void)iostream_rawlog_create(client->set->rawlog_dir,
+ &client->input, &client->output);
+ }
+ client->io = io_add_istream(client->input, client_input, client);
+}
+
+int client_create_finish(struct client *client, const char **error_r)
+{
+ if (mail_namespaces_init(client->user, error_r) < 0)
+ return -1;
+ mail_namespaces_set_storage_callbacks(client->user->namespaces,
+ &mail_storage_callbacks, client);
+ client->v.init(client);
+ return 0;
+}
+
+void client_add_istream_prefix(struct client *client,
+ const unsigned char *data, size_t size)
+{
+ i_assert(client->io == NULL);
+
+ struct istream *inputs[] = {
+ i_stream_create_copy_from_data(data, size),
+ client->input,
+ NULL
+ };
+ client->input = i_stream_create_concat(inputs);
+ i_stream_copy_fd(client->input, inputs[1]);
+ i_stream_unref(&inputs[0]);
+ i_stream_unref(&inputs[1]);
+
+ i_stream_set_input_pending(client->input, TRUE);
+}
+
+static void client_default_init(struct client *client ATTR_UNUSED)
+{
+ /* nothing */
+}
+
+void client_command_cancel(struct client_command_context **_cmd)
+{
+ struct client_command_context *cmd = *_cmd;
+ bool cmd_ret;
+
+ switch (cmd->state) {
+ case CLIENT_COMMAND_STATE_WAIT_INPUT:
+ /* a bit kludgy check: cancel command only if it has context
+ set. currently only append command matches this check. all
+ other commands haven't even started the processing yet. */
+ if (cmd->context == NULL)
+ break;
+ /* fall through */
+ case CLIENT_COMMAND_STATE_WAIT_EXTERNAL:
+ case CLIENT_COMMAND_STATE_WAIT_OUTPUT:
+ cmd->cancel = TRUE;
+ break;
+ case CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY:
+ case CLIENT_COMMAND_STATE_WAIT_SYNC:
+ /* commands haven't started yet */
+ break;
+ case CLIENT_COMMAND_STATE_DONE:
+ i_unreached();
+ }
+
+ cmd_ret = !cmd->cancel || cmd->func == NULL ? TRUE :
+ command_exec(cmd);
+ if (!cmd_ret) {
+ if (cmd->client->output->closed)
+ i_panic("command didn't cancel itself: %s", cmd->name);
+ } else {
+ client_command_free(*_cmd != NULL ? _cmd : &cmd);
+ }
+}
+
+const char *client_stats(struct client *client)
+{
+ const struct var_expand_table logout_tab[] = {
+ { 'i', dec2str(i_stream_get_absolute_offset(client->input)), "input" },
+ { 'o', dec2str(client->output->offset), "output" },
+ { '\0', client->user->session_id, "session" },
+ { '\0', dec2str(client->fetch_hdr_count), "fetch_hdr_count" },
+ { '\0', dec2str(client->fetch_hdr_bytes), "fetch_hdr_bytes" },
+ { '\0', dec2str(client->fetch_body_count), "fetch_body_count" },
+ { '\0', dec2str(client->fetch_body_bytes), "fetch_body_bytes" },
+ { '\0', dec2str(client->deleted_count), "deleted" },
+ { '\0', dec2str(client->expunged_count), "expunged" },
+ { '\0', dec2str(client->trashed_count), "trashed" },
+ { '\0', dec2str(client->autoexpunged_count), "autoexpunged" },
+ { '\0', dec2str(client->append_count), "appended" },
+ { '\0', NULL, NULL }
+ };
+ const struct var_expand_table *user_tab =
+ mail_user_var_expand_table(client->user);
+ const struct var_expand_table *tab =
+ t_var_expand_merge_tables(logout_tab, user_tab);
+ string_t *str;
+ const char *error;
+
+ str = t_str_new(128);
+ if (var_expand_with_funcs(str, client->set->imap_logout_format,
+ tab, mail_user_var_expand_func_table,
+ client->user, &error) < 0) {
+ e_error(client->event,
+ "Failed to expand imap_logout_format=%s: %s",
+ client->set->imap_logout_format, error);
+ }
+ return str_c(str);
+}
+
+void client_destroy(struct client *client, const char *reason)
+{
+ client->v.destroy(client, reason);
+}
+
+static void
+client_command_stats_append(string_t *str,
+ const struct client_command_stats *stats,
+ const char *wait_condition,
+ size_t buffered_size)
+{
+ uint64_t ioloop_wait_usecs;
+ unsigned int msecs_in_ioloop;
+
+ ioloop_wait_usecs = io_loop_get_wait_usecs(current_ioloop);
+ msecs_in_ioloop = (ioloop_wait_usecs -
+ stats->start_ioloop_wait_usecs + 999) / 1000;
+ str_printfa(str, "running for %d.%03d + waiting ",
+ (int)((stats->running_usecs+999)/1000 / 1000),
+ (int)((stats->running_usecs+999)/1000 % 1000));
+ if (wait_condition[0] != '\0')
+ str_printfa(str, "%s ", wait_condition);
+ str_printfa(str, "for %d.%03d secs",
+ msecs_in_ioloop / 1000, msecs_in_ioloop % 1000);
+ if (stats->lock_wait_usecs > 0) {
+ int lock_wait_msecs = (stats->lock_wait_usecs+999)/1000;
+ str_printfa(str, ", %d.%03d in locks",
+ lock_wait_msecs/1000, lock_wait_msecs%1000);
+ }
+ str_printfa(str, ", %"PRIu64" B in + %"PRIu64,
+ stats->bytes_in, stats->bytes_out);
+ if (buffered_size > 0)
+ str_printfa(str, "+%zu", buffered_size);
+ str_append(str, " B out");
+}
+
+static const char *client_get_last_command_status(struct client *client)
+{
+ if (client->logged_out)
+ return "";
+ if (client->last_cmd_name == NULL) {
+ if (client->unhibernated)
+ return " (No commands sent after unhibernation)";
+ else
+ return " (No commands sent)";
+ }
+
+ /* client disconnected without sending LOGOUT. if the last command
+ took over 1 second to run, log it. */
+ const struct client_command_stats *stats = &client->last_cmd_stats;
+
+ string_t *str = t_str_new(128);
+ int last_run_secs = timeval_diff_msecs(&ioloop_timeval,
+ &stats->last_run_timeval);
+ str_printfa(str, " (%s finished %d.%03d secs ago",
+ client->last_cmd_name, last_run_secs/1000,
+ last_run_secs%1000);
+
+ if (timeval_diff_msecs(&stats->last_run_timeval, &stats->start_time) >=
+ IMAP_CLIENT_DISCONNECT_LOG_STATS_CMD_MIN_RUNNING_MSECS) {
+ str_append(str, " - ");
+ client_command_stats_append(str, stats, "", 0);
+ }
+ str_append_c(str, ')');
+ return str_c(str);
+}
+
+static const char *client_get_commands_status(struct client *client)
+{
+ struct client_command_context *cmd, *last_cmd = NULL;
+ struct client_command_stats all_stats;
+ string_t *str;
+ enum io_condition cond;
+ const char *cond_str;
+
+ if (client->command_queue == NULL)
+ return client_get_last_command_status(client);
+
+ i_zero(&all_stats);
+ str = t_str_new(128);
+ str_append(str, " (");
+ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+ if (cmd->name == NULL) {
+ /* (parts of a) tag were received, but not yet
+ the command name */
+ continue;
+ }
+ str_append(str, cmd->name);
+ if (cmd->next != NULL)
+ str_append_c(str, ',');
+ all_stats.running_usecs += cmd->stats.running_usecs;
+ all_stats.lock_wait_usecs += cmd->stats.lock_wait_usecs;
+ all_stats.bytes_in += cmd->stats.bytes_in;
+ all_stats.bytes_out += cmd->stats.bytes_out;
+ last_cmd = cmd;
+ }
+ if (last_cmd == NULL)
+ return client_get_last_command_status(client);
+
+ cond = io_loop_find_fd_conditions(current_ioloop, client->fd_out);
+ if ((cond & (IO_READ | IO_WRITE)) == (IO_READ | IO_WRITE))
+ cond_str = "input/output";
+ else if ((cond & IO_READ) != 0)
+ cond_str = "input";
+ else if ((cond & IO_WRITE) != 0)
+ cond_str = "output";
+ else
+ cond_str = "nothing";
+
+ all_stats.start_ioloop_wait_usecs =
+ last_cmd->stats.start_ioloop_wait_usecs;
+ str_append_c(str, ' ');
+ client_command_stats_append(str, &all_stats, cond_str,
+ o_stream_get_buffer_used_size(client->output));
+ str_printfa(str, ", state=%s)",
+ client_command_state_names[last_cmd->state]);
+ return str_c(str);
+}
+
+static void client_log_disconnect(struct client *client, const char *reason)
+{
+ e_info(client->event, "Disconnected: %s %s", reason, client_stats(client));
+}
+
+static void client_default_destroy(struct client *client, const char *reason)
+{
+ struct client_command_context *cmd;
+
+ i_assert(!client->destroyed);
+ client->destroyed = TRUE;
+ client->disconnected = TRUE;
+
+ if (client->disconnect_reason != NULL)
+ reason = client->disconnect_reason;
+ if (reason == NULL)
+ reason = t_strconcat(
+ io_stream_get_disconnect_reason(client->input,
+ client->output),
+ client_get_commands_status(client), NULL);
+
+ i_stream_close(client->input);
+ o_stream_close(client->output);
+
+ /* finish off all the queued commands. */
+ if (client->output_cmd_lock != NULL)
+ client_command_cancel(&client->output_cmd_lock);
+ while (client->command_queue != NULL) {
+ cmd = client->command_queue;
+ client_command_cancel(&cmd);
+ }
+ /* handle the input_lock command last. it might have been waiting on
+ other queued commands (although we probably should just drop the
+ command at that point since it hasn't started running. but this may
+ change in future). */
+ if (client->input_lock != NULL)
+ client_command_cancel(&client->input_lock);
+
+ if (client->notify_ctx != NULL)
+ imap_notify_deinit(&client->notify_ctx);
+ if (client->urlauth_ctx != NULL)
+ imap_urlauth_deinit(&client->urlauth_ctx);
+ /* Keep mailbox closing close to last, so anything that could
+ potentially have transactions open will close them first. */
+ if (client->mailbox != NULL)
+ imap_client_close_mailbox(client);
+ if (client->anvil_sent) {
+ master_service_anvil_send(master_service, t_strconcat(
+ "DISCONNECT\t", my_pid, "\timap/",
+ mail_user_get_anvil_userip_ident(client->user),
+ "\n", NULL));
+ }
+
+ if (client->free_parser != NULL)
+ imap_parser_unref(&client->free_parser);
+ io_remove(&client->io);
+ timeout_remove(&client->to_idle_output);
+ timeout_remove(&client->to_idle);
+
+ /* i/ostreams are already closed at this stage, so fd can be closed */
+ fd_close_maybe_stdio(&client->fd_in, &client->fd_out);
+
+ /* Autoexpunging might run for a long time. Disconnect the client
+ before it starts, and refresh proctitle so it's clear that it's
+ doing autoexpunging. We've also sent DISCONNECT to anvil already,
+ because this is background work and shouldn't really be counted
+ as an active IMAP session for the user.
+
+ Don't autoexpunge if the client is hibernated - it shouldn't be any
+ different from the non-hibernating IDLE case. For frequent
+ hibernations it could also be doing unnecessarily much work. */
+ imap_refresh_proctitle();
+ if (!client->hibernated) {
+ client->autoexpunged_count = mail_user_autoexpunge(client->user);
+ client_log_disconnect(client, reason);
+ }
+ mail_user_deinit(&client->user);
+
+ /* free the i/ostreams after mail_user_unref(), which could trigger
+ mail_storage_callbacks notifications that write to the ostream. */
+ i_stream_destroy(&client->input);
+ o_stream_destroy(&client->output);
+
+ if (array_is_created(&client->search_saved_uidset))
+ array_free(&client->search_saved_uidset);
+ if (array_is_created(&client->search_updates))
+ array_free(&client->search_updates);
+ pool_unref(&client->command_pool);
+ mail_storage_service_user_unref(&client->service_user);
+
+ imap_client_count--;
+ DLLIST_REMOVE(&imap_clients, client);
+
+ event_unref(&client->event);
+ i_free(client->last_cmd_name);
+ pool_unref(&client->pool);
+
+ master_service_client_connection_destroyed(master_service);
+ imap_refresh_proctitle();
+}
+
+static void client_destroy_timeout(struct client *client)
+{
+ client_destroy(client, NULL);
+}
+
+void client_disconnect(struct client *client, const char *reason)
+{
+ if (client->disconnected)
+ return;
+
+ client->disconnected = TRUE;
+ client->disconnect_reason = p_strdup(client->pool, reason);
+ /* Finish the ostream. With IMAP COMPRESS this sends the EOF marker. */
+ (void)o_stream_finish(client->output);
+ o_stream_uncork(client->output);
+
+ i_stream_close(client->input);
+ o_stream_close(client->output);
+
+ timeout_remove(&client->to_idle);
+ client->to_idle = timeout_add(0, client_destroy_timeout, client);
+}
+
+void client_disconnect_with_error(struct client *client,
+ const char *client_error)
+{
+ client_send_line(client, t_strconcat("* BYE ", client_error, NULL));
+ client_disconnect(client, client_error);
+}
+
+void client_add_capability(struct client *client, const char *capability)
+{
+ /* require a single capability at a time (feels cleaner) */
+ i_assert(strchr(capability, ' ') == NULL);
+
+ if (client->set->imap_capability[0] != '\0' &&
+ client->set->imap_capability[0] != '+') {
+ /* explicit capability - don't change it */
+ return;
+ }
+ str_append_c(client->capability_string, ' ');
+ str_append(client->capability_string, capability);
+}
+
+void client_send_line(struct client *client, const char *data)
+{
+ (void)client_send_line_next(client, data);
+}
+
+int client_send_line_next(struct client *client, const char *data)
+{
+ struct const_iovec iov[2];
+
+ if (client->output->closed)
+ return -1;
+
+ iov[0].iov_base = data;
+ iov[0].iov_len = strlen(data);
+ iov[1].iov_base = "\r\n";
+ iov[1].iov_len = 2;
+
+ if (o_stream_sendv(client->output, iov, 2) < 0)
+ return -1;
+ client->last_output = ioloop_time;
+
+ if (o_stream_get_buffer_used_size(client->output) >=
+ CLIENT_OUTPUT_OPTIMAL_SIZE) {
+ /* buffer full, try flushing */
+ return o_stream_flush(client->output);
+ }
+ return 1;
+}
+
+static void
+client_cmd_append_timing_stats(struct client_command_context *cmd,
+ string_t *str)
+{
+ unsigned int msecs_in_cmd, msecs_in_ioloop;
+ uint64_t ioloop_wait_usecs;
+ unsigned int msecs_since_cmd;
+
+ if (cmd->stats.start_time.tv_sec == 0)
+ return;
+ command_stats_flush(cmd);
+
+ ioloop_wait_usecs = io_loop_get_wait_usecs(current_ioloop);
+ msecs_in_cmd = (cmd->stats.running_usecs + 999) / 1000;
+ msecs_in_ioloop = (ioloop_wait_usecs -
+ cmd->stats.start_ioloop_wait_usecs + 999) / 1000;
+ msecs_since_cmd = timeval_diff_msecs(&ioloop_timeval,
+ &cmd->stats.last_run_timeval);
+
+ if (str_data(str)[str_len(str)-1] == '.')
+ str_truncate(str, str_len(str)-1);
+ str_printfa(str, " (%d.%03d + %d.%03d ",
+ msecs_in_cmd / 1000, msecs_in_cmd % 1000,
+ msecs_in_ioloop / 1000, msecs_in_ioloop % 1000);
+ if (msecs_since_cmd > 0) {
+ str_printfa(str, "+ %d.%03d ",
+ msecs_since_cmd / 1000, msecs_since_cmd % 1000);
+ }
+ str_append(str, "secs).");
+}
+
+void client_send_tagline(struct client_command_context *cmd, const char *data)
+{
+ cmd->client->v.send_tagline(cmd, data);
+}
+
+static void
+client_default_send_tagline(struct client_command_context *cmd, const char *data)
+{
+ struct client *client = cmd->client;
+ const char *tag = cmd->tag;
+
+ if (client->output->closed || cmd->cancel)
+ return;
+
+ i_assert(!cmd->tagline_sent);
+ cmd->tagline_sent = TRUE;
+ cmd->tagline_reply = p_strdup(cmd->pool, data);
+
+ if (tag == NULL || *tag == '\0')
+ tag = "*";
+
+ T_BEGIN {
+ string_t *str = t_str_new(256);
+ str_printfa(str, "%s %s", tag, data);
+ client_cmd_append_timing_stats(cmd, str);
+ str_append(str, "\r\n");
+ o_stream_nsend(client->output, str_data(str), str_len(str));
+ } T_END;
+
+ client->last_output = ioloop_time;
+}
+
+static int
+client_default_sync_notify_more(struct imap_sync_context *ctx ATTR_UNUSED)
+{
+ return 1;
+}
+
+void client_send_command_error(struct client_command_context *cmd,
+ const char *client_error)
+{
+ struct client *client = cmd->client;
+ const char *error, *cmd_name;
+ enum imap_parser_error parse_error;
+
+ if (client_error == NULL) {
+ client_error = imap_parser_get_error(cmd->parser, &parse_error);
+ switch (parse_error) {
+ case IMAP_PARSE_ERROR_NONE:
+ i_unreached();
+ case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+ client_disconnect_with_error(client, client_error);
+ return;
+ default:
+ break;
+ }
+ }
+
+ if (cmd->tag == NULL)
+ error = t_strconcat("BAD Error in IMAP tag: ", client_error, NULL);
+ else if (cmd->name == NULL)
+ error = t_strconcat("BAD Error in IMAP command: ", client_error, NULL);
+ else {
+ cmd_name = t_str_ucase(cmd->name);
+ error = t_strconcat("BAD Error in IMAP command ",
+ cmd_name, ": ", client_error, NULL);
+ }
+
+ client_send_tagline(cmd, error);
+
+ if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
+ client_disconnect_with_error(client,
+ "Too many invalid IMAP commands.");
+ }
+
+ cmd->param_error = TRUE;
+ /* client_read_args() failures rely on this being set, so that the
+ command processing is stopped even while command function returns
+ FALSE. */
+ cmd->state = CLIENT_COMMAND_STATE_DONE;
+}
+
+void client_send_internal_error(struct client_command_context *cmd)
+{
+ client_send_tagline(cmd,
+ t_strflocaltime("NO "MAIL_ERRSTR_CRITICAL_MSG_STAMP, ioloop_time));
+}
+
+bool client_read_args(struct client_command_context *cmd, unsigned int count,
+ unsigned int flags, const struct imap_arg **args_r)
+{
+ int ret;
+
+ i_assert(count <= INT_MAX);
+
+ ret = imap_parser_read_args(cmd->parser, count, flags, args_r);
+ if (ret >= (int)count) {
+ /* all parameters read successfully */
+ i_assert(cmd->client->input_lock == NULL ||
+ cmd->client->input_lock == cmd);
+
+ client_args_finished(cmd, *args_r);
+ cmd->client->input_lock = NULL;
+ return TRUE;
+ } else if (ret == -2) {
+ /* need more data */
+ if (cmd->client->input->closed) {
+ /* disconnected */
+ cmd->state = CLIENT_COMMAND_STATE_DONE;
+ }
+ return FALSE;
+ } else {
+ /* error, or missing arguments */
+ client_send_command_error(cmd, ret < 0 ? NULL :
+ "Missing arguments");
+ return FALSE;
+ }
+}
+
+bool client_read_string_args(struct client_command_context *cmd,
+ unsigned int count, ...)
+{
+ const struct imap_arg *imap_args;
+ va_list va;
+ const char *str;
+ unsigned int i;
+
+ if (!client_read_args(cmd, count, 0, &imap_args))
+ return FALSE;
+
+ va_start(va, count);
+ for (i = 0; i < count; i++) {
+ const char **ret = va_arg(va, const char **);
+
+ if (IMAP_ARG_IS_EOL(&imap_args[i])) {
+ client_send_command_error(cmd, "Missing arguments.");
+ break;
+ }
+
+ if (!imap_arg_get_astring(&imap_args[i], &str)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ break;
+ }
+
+ if (ret != NULL)
+ *ret = str;
+ }
+ va_end(va);
+
+ return i == count;
+}
+
+void client_args_finished(struct client_command_context *cmd,
+ const struct imap_arg *args)
+{
+ string_t *str = t_str_new(256);
+
+ if (cmd->args != NULL && cmd->args[0] != '\0') {
+ str_append(str, cmd->args);
+ str_append_c(str, ' ');
+ }
+ imap_write_args(str, args);
+ cmd->args = p_strdup(cmd->pool, str_c(str));
+ event_add_str(cmd->event, "cmd_args", cmd->args);
+
+ str_truncate(str, 0);
+ if (cmd->human_args != NULL && cmd->human_args[0] != '\0') {
+ str_append(str, cmd->human_args);
+ str_append_c(str, ' ');
+ }
+ imap_write_args_for_human(str, args);
+ cmd->human_args = p_strdup(cmd->pool, str_c(str));
+ event_add_str(cmd->event, "cmd_human_args", cmd->human_args);
+}
+
+static struct client_command_context *
+client_command_find_with_flags(struct client_command_context *new_cmd,
+ enum command_flags flags,
+ enum client_command_state max_state)
+{
+ struct client_command_context *cmd;
+
+ cmd = new_cmd->client->command_queue;
+ for (; cmd != NULL; cmd = cmd->next) {
+ /* The tagline_sent check is a bit kludgy here. Plugins may
+ hook into sync_notify_more() and send the tagline before
+ finishing the command. During this stage the state was been
+ dropped from _WAIT_SYNC to _WAIT_OUTPUT, so the <= max_state
+ check doesn't work correctly here. (Perhaps we should add
+ a new _WAIT_SYNC_OUTPUT?) */
+ if (cmd->state <= max_state && !cmd->tagline_sent &&
+ cmd != new_cmd && (cmd->cmd_flags & flags) != 0)
+ return cmd;
+ }
+ return NULL;
+}
+
+static bool client_command_is_ambiguous(struct client_command_context *cmd)
+{
+ enum command_flags flags;
+ enum client_command_state max_state =
+ CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
+ bool broken_client = FALSE;
+
+ if ((cmd->cmd_flags & COMMAND_FLAG_REQUIRES_SYNC) != 0 &&
+ !imap_sync_is_allowed(cmd->client))
+ return TRUE;
+
+ if (cmd->search_save_result_used) {
+ /* if there are pending commands that update the search
+ save result, wait */
+ struct client_command_context *old_cmd = cmd->next;
+
+ for (; old_cmd != NULL; old_cmd = old_cmd->next) {
+ if (old_cmd->search_save_result)
+ return TRUE;
+ }
+ }
+
+ if ((cmd->cmd_flags & COMMAND_FLAG_BREAKS_MAILBOX) ==
+ COMMAND_FLAG_BREAKS_MAILBOX) {
+ /* there must be no other command running that uses the
+ selected mailbox */
+ flags = COMMAND_FLAG_USES_MAILBOX;
+ max_state = CLIENT_COMMAND_STATE_DONE;
+ } else if ((cmd->cmd_flags & COMMAND_FLAG_USES_SEQS) != 0) {
+ /* no existing command must be breaking sequences */
+ flags = COMMAND_FLAG_BREAKS_SEQS;
+ broken_client = TRUE;
+ } else if ((cmd->cmd_flags & COMMAND_FLAG_BREAKS_SEQS) != 0) {
+ /* if existing command uses sequences, we'll have to block */
+ flags = COMMAND_FLAG_USES_SEQS;
+ } else {
+ return FALSE;
+ }
+
+ if (client_command_find_with_flags(cmd, flags, max_state) == NULL) {
+ if (cmd->client->syncing) {
+ /* don't do anything until syncing is finished */
+ return TRUE;
+ }
+ if (cmd->client->mailbox_change_lock != NULL &&
+ cmd->client->mailbox_change_lock != cmd) {
+ /* don't do anything until mailbox is fully
+ opened/closed */
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ if (broken_client) {
+ client_send_line(cmd->client,
+ "* BAD ["IMAP_RESP_CODE_CLIENTBUG"] "
+ "Command pipelining results in ambiguity.");
+ }
+
+ return TRUE;
+}
+
+struct client_command_context *client_command_alloc(struct client *client)
+{
+ struct client_command_context *cmd;
+
+ cmd = p_new(client->command_pool, struct client_command_context, 1);
+ cmd->client = client;
+ cmd->pool = client->command_pool;
+ cmd->global_event = event_create(client->event);
+ cmd->event = event_create(cmd->global_event);
+ cmd->stats.start_time = ioloop_timeval;
+ cmd->stats.last_run_timeval = ioloop_timeval;
+ cmd->stats.start_ioloop_wait_usecs =
+ io_loop_get_wait_usecs(current_ioloop);
+ p_array_init(&cmd->module_contexts, cmd->pool, 5);
+
+ DLLIST_PREPEND(&client->command_queue, cmd);
+ client->command_queue_size++;
+
+ imap_client_notify_command_allocated(client);
+ return cmd;
+}
+
+void client_command_init_finished(struct client_command_context *cmd)
+{
+ event_add_str(cmd->event, "cmd_tag", cmd->tag);
+ /* use "unknown" until we checked that the command name is known/valid */
+ event_add_str(cmd->event, "cmd_name", "unknown");
+ /* the actual command name received from client - as-is */
+ event_add_str(cmd->event, "cmd_input_name", cmd->name);
+}
+
+static struct client_command_context *
+client_command_new(struct client *client)
+{
+ struct client_command_context *cmd;
+
+ cmd = client_command_alloc(client);
+ if (client->free_parser != NULL) {
+ cmd->parser = client->free_parser;
+ client->free_parser = NULL;
+ } else {
+ cmd->parser =
+ imap_parser_create(client->input, client->output,
+ client->set->imap_max_line_length);
+ if (client->set->imap_literal_minus)
+ imap_parser_enable_literal_minus(cmd->parser);
+ }
+ return cmd;
+}
+
+void client_add_missing_io(struct client *client)
+{
+ if (client->io == NULL && !client->disconnected)
+ client->io = io_add_istream(client->input, client_input, client);
+}
+
+void client_command_free(struct client_command_context **_cmd)
+{
+ struct client_command_context *cmd = *_cmd;
+ struct client *client = cmd->client;
+ enum client_command_state state = cmd->state;
+
+ *_cmd = NULL;
+
+ i_assert(!cmd->executing);
+ i_assert(client->output_cmd_lock == NULL);
+
+ /* reset input idle time because command output might have taken a
+ long time and we don't want to disconnect client immediately then */
+ client->last_input = ioloop_time;
+ timeout_reset(client->to_idle);
+
+ if (cmd->cancel) {
+ cmd->cancel = FALSE;
+ client_send_tagline(cmd, "NO Command cancelled.");
+ }
+
+ i_free(client->last_cmd_name);
+ client->last_cmd_name = i_strdup(cmd->name);
+ client->last_cmd_stats = cmd->stats;
+
+ if (!cmd->param_error)
+ client->bad_counter = 0;
+
+ if (client->input_lock == cmd)
+ client->input_lock = NULL;
+ if (client->mailbox_change_lock == cmd)
+ client->mailbox_change_lock = NULL;
+
+ event_set_name(cmd->event, "imap_command_finished");
+ if (cmd->tagline_reply != NULL) {
+ event_add_str(cmd->event, "tagged_reply_state",
+ t_strcut(cmd->tagline_reply, ' '));
+ event_add_str(cmd->event, "tagged_reply", cmd->tagline_reply);
+ }
+ event_add_timeval(cmd->event, "last_run_time",
+ &cmd->stats.last_run_timeval);
+ event_add_int(cmd->event, "running_usecs", cmd->stats.running_usecs);
+ event_add_int(cmd->event, "lock_wait_usecs", cmd->stats.lock_wait_usecs);
+ event_add_int(cmd->event, "bytes_in", cmd->stats.bytes_in);
+ event_add_int(cmd->event, "bytes_out", cmd->stats.bytes_out);
+
+ e_debug(cmd->event, "Command finished: %s %s", cmd->name,
+ cmd->human_args != NULL ? cmd->human_args : "");
+ event_unref(&cmd->event);
+ event_unref(&cmd->global_event);
+
+ if (cmd->parser != NULL) {
+ if (client->free_parser == NULL) {
+ imap_parser_reset(cmd->parser);
+ client->free_parser = cmd->parser;
+ } else {
+ imap_parser_unref(&cmd->parser);
+ }
+ }
+
+ client->command_queue_size--;
+ DLLIST_REMOVE(&client->command_queue, cmd);
+ cmd = NULL;
+
+ if (client->command_queue == NULL) {
+ /* no commands left in the queue, we can clear the pool */
+ p_clear(client->command_pool);
+ timeout_remove(&client->to_idle_output);
+ }
+ imap_client_notify_command_freed(client);
+ imap_refresh_proctitle();
+
+ /* if command finished from external event, check input for more
+ unhandled commands since we may not be executing from client_input
+ or client_output. */
+ if (state == CLIENT_COMMAND_STATE_WAIT_EXTERNAL &&
+ !client->disconnected) {
+ client_add_missing_io(client);
+ io_set_pending(client->io);
+ }
+}
+
+static void client_check_command_hangs(struct client *client)
+{
+ struct client_command_context *cmd;
+ unsigned int unfinished_count = 0;
+ bool have_wait_unfinished = FALSE;
+
+ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+ switch (cmd->state) {
+ case CLIENT_COMMAND_STATE_WAIT_INPUT:
+ /* We need to be reading input for this command.
+ However, if there is already an output lock for
+ another command we'll wait for it to finish first.
+ This is needed because if there are any literals
+ we'd need to send "+ OK" responses. */
+ i_assert(client->io != NULL ||
+ (client->output_cmd_lock != NULL &&
+ client->output_cmd_lock != client->input_lock));
+ unfinished_count++;
+ break;
+ case CLIENT_COMMAND_STATE_WAIT_OUTPUT:
+ i_assert((io_loop_find_fd_conditions(current_ioloop, client->fd_out) & IO_WRITE) != 0);
+ unfinished_count++;
+ break;
+ case CLIENT_COMMAND_STATE_WAIT_EXTERNAL:
+ unfinished_count++;
+ break;
+ case CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY:
+ have_wait_unfinished = TRUE;
+ break;
+ case CLIENT_COMMAND_STATE_WAIT_SYNC:
+ if ((io_loop_find_fd_conditions(current_ioloop, client->fd_out) & IO_WRITE) == 0)
+ have_wait_unfinished = TRUE;
+ else {
+ /* we have an output callback, which will be
+ called soon and it'll run cmd_sync_delayed().
+ FIXME: is this actually wanted? */
+ }
+ break;
+ case CLIENT_COMMAND_STATE_DONE:
+ i_unreached();
+ }
+ }
+ i_assert(!have_wait_unfinished || unfinished_count > 0);
+}
+
+static bool client_remove_pending_unambiguity(struct client *client)
+{
+ if (client->input_lock != NULL) {
+ /* there's a command that has locked the input */
+ struct client_command_context *cmd = client->input_lock;
+
+ if (cmd->state != CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY)
+ return TRUE;
+
+ /* the command is waiting for existing ambiguity causing
+ commands to finish. */
+ if (client_command_is_ambiguous(cmd)) {
+ /* we could be waiting for existing sync to finish */
+ if (!cmd_sync_delayed(client))
+ return FALSE;
+ if (client_command_is_ambiguous(cmd))
+ return FALSE;
+ }
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
+ }
+ return TRUE;
+}
+
+void client_continue_pending_input(struct client *client)
+{
+ i_assert(!client->handling_input);
+
+ if (client->disconnected) {
+ client_destroy(client, NULL);
+ return;
+ }
+
+ /* this function is called at the end of I/O callbacks (and only there).
+ fix up the command states and verify that they're correct. */
+ while (client_remove_pending_unambiguity(client)) {
+ client_add_missing_io(client);
+
+ /* if there's unread data in buffer, handle it. */
+ if (i_stream_get_data_size(client->input) == 0 ||
+ client->disconnected)
+ break;
+
+ struct ostream *output = client->output;
+ o_stream_ref(output);
+ o_stream_cork(output);
+ bool ret = client_handle_input(client);
+ o_stream_uncork(output);
+ o_stream_unref(&output);
+ if (!ret)
+ break;
+ }
+ if (client->input->closed || client->output->closed)
+ client_destroy(client, NULL);
+ else
+ client_check_command_hangs(client);
+}
+
+/* Skip incoming data until newline is found,
+ returns TRUE if newline was found. */
+static bool client_skip_line(struct client *client)
+{
+ const unsigned char *data;
+ size_t i, data_size;
+
+ data = i_stream_get_data(client->input, &data_size);
+
+ for (i = 0; i < data_size; i++) {
+ if (data[i] == '\n') {
+ client->input_skip_line = FALSE;
+ i++;
+ break;
+ }
+ }
+
+ i_stream_skip(client->input, i);
+ return !client->input_skip_line;
+}
+
+static void client_idle_output_timeout(struct client *client)
+{
+ client_destroy(client, t_strdup_printf(
+ "Client has not read server output for for %"PRIdTIME_T" secs",
+ ioloop_time - client->last_output));
+}
+
+bool client_handle_unfinished_cmd(struct client_command_context *cmd)
+{
+ if (cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT) {
+ /* need more input */
+ return FALSE;
+ }
+ if (cmd->state != CLIENT_COMMAND_STATE_WAIT_OUTPUT) {
+ /* waiting for something */
+ if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC) {
+ /* this is mainly for APPEND. */
+ client_add_missing_io(cmd->client);
+ }
+ return TRUE;
+ }
+
+ /* output is blocking, we can execute more commands */
+ o_stream_set_flush_pending(cmd->client->output, TRUE);
+ if (cmd->client->to_idle_output == NULL) {
+ /* disconnect sooner if client isn't reading our output */
+ cmd->client->to_idle_output =
+ timeout_add(CLIENT_OUTPUT_TIMEOUT_MSECS,
+ client_idle_output_timeout, cmd->client);
+ }
+ return TRUE;
+}
+
+static void
+client_command_failed_early(struct client_command_context **_cmd,
+ const char *error)
+{
+ struct client_command_context *cmd = *_cmd;
+
+ /* ignore the rest of this line */
+ cmd->client->input_skip_line = TRUE;
+
+ io_loop_time_refresh();
+ command_stats_start(cmd);
+ client_send_command_error(cmd, error);
+ cmd->param_error = TRUE;
+ client_command_free(_cmd);
+}
+
+static bool client_command_input(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct command *command;
+ const char *tag, *name;
+ int ret;
+
+ if (cmd->func != NULL) {
+ /* command is being executed - continue it */
+ if (command_exec(cmd)) {
+ /* command execution was finished */
+ client_command_free(&cmd);
+ client_add_missing_io(client);
+ return TRUE;
+ }
+
+ return client_handle_unfinished_cmd(cmd);
+ }
+
+ if (cmd->tag == NULL) {
+ ret = imap_parser_read_tag(cmd->parser, &tag);
+ if (ret == 0)
+ return FALSE; /* need more data */
+ if (ret < 0) {
+ client_command_failed_early(&cmd, "Invalid tag.");
+ return TRUE;
+ }
+ cmd->tag = p_strdup(cmd->pool, tag);
+ }
+
+ if (cmd->name == NULL) {
+ ret = imap_parser_read_command_name(cmd->parser, &name);
+ if (ret == 0)
+ return FALSE; /* need more data */
+ if (ret < 0) {
+ client_command_failed_early(&cmd, "Invalid command name.");
+ return TRUE;
+ }
+
+ /* UID commands are a special case. better to handle them
+ here. */
+ if (!cmd->uid && strcasecmp(name, "UID") == 0) {
+ cmd->uid = TRUE;
+ return client_command_input(cmd);
+ }
+ cmd->name = !cmd->uid ? p_strdup(cmd->pool, name) :
+ p_strconcat(cmd->pool, "UID ", name, NULL);
+ client_command_init_finished(cmd);
+ imap_refresh_proctitle();
+ }
+
+ client->input_skip_line = TRUE;
+
+ if (cmd->name[0] == '\0') {
+ /* command not given - cmd->func is already NULL. */
+ } else if ((command = command_find(cmd->name)) != NULL) {
+ cmd->func = command->func;
+ cmd->cmd_flags = command->flags;
+ /* valid command - overwrite the "unknown" string set earlier */
+ event_add_str(cmd->global_event, "cmd_name", command->name);
+ event_strlist_append(cmd->global_event, "reason_code",
+ event_reason_code_prefix("imap", "cmd_", command->name));
+ event_add_str(cmd->event, "cmd_name", command->name);
+ if (client_command_is_ambiguous(cmd)) {
+ /* do nothing until existing commands are finished */
+ i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT);
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
+ io_remove(&client->io);
+ return FALSE;
+ }
+ }
+
+ if (cmd->func == NULL) {
+ /* unknown command */
+ client_command_failed_early(&cmd, "Unknown command.");
+ return TRUE;
+ } else {
+ i_assert(!client->disconnected);
+
+ return client_command_input(cmd);
+ }
+}
+
+static bool client_handle_next_command(struct client *client, bool *remove_io_r)
+{
+ *remove_io_r = FALSE;
+
+ if (client->input_lock != NULL) {
+ if (client->input_lock->state ==
+ CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY ||
+ /* we can't send literal "+ OK" replies if output is
+ locked by another command. */
+ (client->output_cmd_lock != NULL &&
+ client->output_cmd_lock != client->input_lock)) {
+ *remove_io_r = TRUE;
+ return FALSE;
+ }
+ return client_command_input(client->input_lock);
+ }
+
+ if (client->input_skip_line) {
+ /* first eat the previous command line */
+ if (!client_skip_line(client))
+ return FALSE;
+ client->input_skip_line = FALSE;
+ }
+
+ /* don't bother creating a new client command before there's at least
+ some input */
+ if (i_stream_get_data_size(client->input) == 0)
+ return FALSE;
+
+ /* beginning a new command */
+ if (client->command_queue_size >= CLIENT_COMMAND_QUEUE_MAX_SIZE ||
+ client->output_cmd_lock != NULL) {
+ /* wait for some of the commands to finish */
+ *remove_io_r = TRUE;
+ return FALSE;
+ }
+
+ client->input_lock = client_command_new(client);
+ return client_command_input(client->input_lock);
+}
+
+bool client_handle_input(struct client *client)
+{
+ bool ret, remove_io, handled_commands = FALSE;
+
+ i_assert(o_stream_is_corked(client->output) ||
+ client->output->stream_errno != 0);
+ i_assert(!client->disconnected);
+
+ client->handling_input = TRUE;
+ do {
+ T_BEGIN {
+ ret = client_handle_next_command(client, &remove_io);
+ } T_END;
+ if (ret)
+ handled_commands = TRUE;
+ } while (ret && !client->disconnected && client->io != NULL);
+ client->handling_input = FALSE;
+
+ if (remove_io)
+ io_remove(&client->io);
+ else
+ client_add_missing_io(client);
+ if (!handled_commands)
+ return FALSE;
+
+ if (client->input_lock == NULL) {
+ /* finished handling all commands. sync them all at once now. */
+ cmd_sync_delayed(client);
+ } else if (client->input_lock->state == CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY) {
+ /* the command may be waiting for previous command to sync. */
+ cmd_sync_delayed(client);
+ }
+ return TRUE;
+}
+
+void client_input(struct client *client)
+{
+ struct client_command_context *cmd;
+ struct ostream *output = client->output;
+ ssize_t bytes;
+
+ i_assert(client->io != NULL);
+
+ client->last_input = ioloop_time;
+ timeout_reset(client->to_idle);
+
+ bytes = i_stream_read(client->input);
+ if (bytes == -1) {
+ /* disconnected */
+ client_destroy(client, NULL);
+ return;
+ }
+
+ o_stream_ref(output);
+ o_stream_cork(output);
+ if (!client_handle_input(client) && bytes == -2) {
+ /* parameter word is longer than max. input buffer size.
+ this is most likely an error, so skip the new data
+ until newline is found. */
+ client->input_skip_line = TRUE;
+
+ cmd = client->input_lock != NULL ? client->input_lock :
+ client_command_new(client);
+ cmd->param_error = TRUE;
+ client_send_command_error(cmd, "Too long argument.");
+ client_command_free(&cmd);
+ }
+ o_stream_uncork(output);
+ o_stream_unref(&output);
+ imap_refresh_proctitle();
+
+ client_continue_pending_input(client);
+}
+
+static void client_output_cmd(struct client_command_context *cmd)
+{
+ bool finished;
+
+ /* continue processing command */
+ finished = command_exec(cmd);
+
+ if (!finished)
+ (void)client_handle_unfinished_cmd(cmd);
+ else {
+ /* command execution was finished */
+ client_command_free(&cmd);
+ }
+}
+
+static void client_output_commands(struct client *client)
+{
+ struct client_command_context *cmd;
+
+ /* mark all commands non-executed */
+ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next)
+ cmd->temp_executed = FALSE;
+
+ if (client->output_cmd_lock != NULL) {
+ client->output_cmd_lock->temp_executed = TRUE;
+ client_output_cmd(client->output_cmd_lock);
+ }
+ while (client->output_cmd_lock == NULL) {
+ /* go through the entire commands list every time in case
+ multiple commands were freed. temp_executed keeps track of
+ which messages we've called so far */
+ cmd = client->command_queue;
+ for (; cmd != NULL; cmd = cmd->next) {
+ if (!cmd->temp_executed &&
+ cmd->state == CLIENT_COMMAND_STATE_WAIT_OUTPUT) {
+ cmd->temp_executed = TRUE;
+ client_output_cmd(cmd);
+ break;
+ }
+ }
+ if (cmd == NULL) {
+ /* all commands executed */
+ break;
+ }
+ }
+}
+
+int client_output(struct client *client)
+{
+ int ret;
+
+ i_assert(!client->destroyed);
+
+ client->last_output = ioloop_time;
+ timeout_reset(client->to_idle);
+ if (client->to_idle_output != NULL)
+ timeout_reset(client->to_idle_output);
+
+ if ((ret = o_stream_flush(client->output)) < 0) {
+ client_destroy(client, NULL);
+ return 1;
+ }
+
+ client_output_commands(client);
+ (void)cmd_sync_delayed(client);
+
+ imap_refresh_proctitle_delayed();
+ if (client->output->closed)
+ client_destroy(client, NULL);
+ else {
+ /* corking is added automatically by ostream-file. we need to
+ uncork here before client_check_command_hangs() is called,
+ because otherwise it can assert-crash due to ioloop not
+ having IO_WRITE callback set for the ostream. */
+ o_stream_uncork(client->output);
+ client_continue_pending_input(client);
+ }
+ return ret;
+}
+
+bool client_handle_search_save_ambiguity(struct client_command_context *cmd)
+{
+ struct client_command_context *old_cmd = cmd->next;
+
+ /* search only commands that were added before this command
+ (commands are prepended to the queue, so they're after ourself) */
+ for (; old_cmd != NULL; old_cmd = old_cmd->next) {
+ if (old_cmd->search_save_result)
+ break;
+ }
+ if (old_cmd == NULL)
+ return FALSE;
+
+ /* ambiguity, wait until it's over */
+ i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT);
+ cmd->client->input_lock = cmd;
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
+ cmd->search_save_result_used = TRUE;
+ io_remove(&cmd->client->io);
+ return TRUE;
+}
+
+void client_enable(struct client *client, unsigned int feature_idx)
+{
+ if (client_has_enabled(client, feature_idx))
+ return;
+
+ const struct imap_feature *feat = imap_feature_idx(feature_idx);
+ feat->callback(client);
+ /* set after the callback, so the callback can see what features were
+ previously set */
+ bool value = TRUE;
+ array_idx_set(&client->enabled_features, feature_idx, &value);
+}
+
+bool client_has_enabled(struct client *client, unsigned int feature_idx)
+{
+ if (feature_idx >= array_count(&client->enabled_features))
+ return FALSE;
+ const bool *featurep =
+ array_idx(&client->enabled_features, feature_idx);
+ return *featurep;
+}
+
+static void imap_client_enable_condstore(struct client *client)
+{
+ struct mailbox_status status;
+ int ret;
+
+ if (client->mailbox == NULL)
+ return;
+
+ if ((client_enabled_mailbox_features(client) & MAILBOX_FEATURE_CONDSTORE) != 0)
+ return;
+
+ ret = mailbox_enable(client->mailbox, MAILBOX_FEATURE_CONDSTORE);
+ if (ret == 0) {
+ /* CONDSTORE being enabled while mailbox is selected.
+ Notify client of the latest HIGHESTMODSEQ. */
+ ret = mailbox_get_status(client->mailbox,
+ STATUS_HIGHESTMODSEQ, &status);
+ if (ret == 0) {
+ client_send_line(client, t_strdup_printf(
+ "* OK [HIGHESTMODSEQ %"PRIu64"] Highest",
+ status.highest_modseq));
+ }
+ }
+ if (ret < 0) {
+ client_send_untagged_storage_error(client,
+ mailbox_get_storage(client->mailbox));
+ }
+}
+
+static void imap_client_enable_qresync(struct client *client)
+{
+ /* enable also CONDSTORE */
+ client_enable(client, imap_feature_condstore);
+}
+
+enum mailbox_feature client_enabled_mailbox_features(struct client *client)
+{
+ enum mailbox_feature mailbox_features = 0;
+ const struct imap_feature *feature;
+ const bool *client_enabled;
+ unsigned int count;
+
+ client_enabled = array_get(&client->enabled_features, &count);
+ for (unsigned int idx = 0; idx < count; idx++) {
+ if (client_enabled[idx]) {
+ feature = imap_feature_idx(idx);
+ mailbox_features |= feature->mailbox_features;
+ }
+ }
+ return mailbox_features;
+}
+
+const char *const *client_enabled_features(struct client *client)
+{
+ ARRAY_TYPE(const_string) feature_strings;
+ const struct imap_feature *feature;
+ const bool *client_enabled;
+ unsigned int count;
+
+ t_array_init(&feature_strings, 8);
+ client_enabled = array_get(&client->enabled_features, &count);
+ for (unsigned int idx = 0; idx < count; idx++) {
+ if (client_enabled[idx]) {
+ feature = imap_feature_idx(idx);
+ array_push_back(&feature_strings, &feature->feature);
+ }
+ }
+ array_append_zero(&feature_strings);
+ return array_front(&feature_strings);
+}
+
+struct imap_search_update *
+client_search_update_lookup(struct client *client, const char *tag,
+ unsigned int *idx_r)
+{
+ struct imap_search_update *updates;
+ unsigned int i, count;
+
+ if (!array_is_created(&client->search_updates))
+ return NULL;
+
+ updates = array_get_modifiable(&client->search_updates, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(updates[i].tag, tag) == 0) {
+ *idx_r = i;
+ return &updates[i];
+ }
+ }
+ return NULL;
+}
+
+void client_search_updates_free(struct client *client)
+{
+ struct imap_search_update *update;
+
+ if (!array_is_created(&client->search_updates))
+ return;
+
+ array_foreach_modifiable(&client->search_updates, update)
+ imap_search_update_free(update);
+ array_clear(&client->search_updates);
+}
+
+void clients_init(void)
+{
+ imap_feature_condstore =
+ imap_feature_register("CONDSTORE", MAILBOX_FEATURE_CONDSTORE,
+ imap_client_enable_condstore);
+ imap_feature_qresync =
+ imap_feature_register("QRESYNC", MAILBOX_FEATURE_CONDSTORE,
+ imap_client_enable_qresync);
+}
+
+void clients_destroy_all(void)
+{
+ while (imap_clients != NULL) {
+ mail_storage_service_io_activate_user(imap_clients->service_user);
+ client_send_line(imap_clients, "* BYE Server shutting down.");
+ client_destroy(imap_clients, "Server shutting down.");
+ }
+}
+
+struct imap_client_vfuncs imap_client_vfuncs = {
+ .init = client_default_init,
+ .destroy = client_default_destroy,
+
+ .send_tagline = client_default_send_tagline,
+ .sync_notify_more = client_default_sync_notify_more,
+
+ .state_export = imap_state_export_base,
+ .state_import = imap_state_import_base,
+};
diff --git a/src/imap/imap-client.h b/src/imap/imap-client.h
new file mode 100644
index 0000000..48b0bc5
--- /dev/null
+++ b/src/imap/imap-client.h
@@ -0,0 +1,362 @@
+#ifndef IMAP_CLIENT_H
+#define IMAP_CLIENT_H
+
+#include "imap-commands.h"
+#include "message-size.h"
+
+#define CLIENT_COMMAND_QUEUE_MAX_SIZE 4
+/* Maximum number of CONTEXT=SEARCH UPDATEs. Clients probably won't need more
+ than a few, so this is mainly to avoid more or less accidental pointless
+ resource usage. */
+#define CLIENT_MAX_SEARCH_UPDATES 10
+
+struct client;
+struct mail_storage;
+struct mail_storage_service_ctx;
+struct lda_settings;
+struct imap_parser;
+struct imap_arg;
+struct imap_urlauth_context;
+
+struct mailbox_keywords {
+ /* All keyword names. The array itself exists in mail_index.
+ Keywords are currently only appended, they're never removed. */
+ const ARRAY_TYPE(keywords) *names;
+ /* Number of keywords announced to client via FLAGS/PERMANENTFLAGS.
+ This relies on keywords not being removed while mailbox is
+ selected. */
+ unsigned int announce_count;
+};
+
+struct imap_search_update {
+ char *tag;
+ struct mail_search_result *result;
+ bool return_uids;
+
+ pool_t fetch_pool;
+ struct imap_fetch_context *fetch_ctx;
+};
+
+enum client_command_state {
+ /* Waiting for more input */
+ CLIENT_COMMAND_STATE_WAIT_INPUT,
+ /* Waiting to be able to send more output */
+ CLIENT_COMMAND_STATE_WAIT_OUTPUT,
+ /* Waiting for external interaction */
+ CLIENT_COMMAND_STATE_WAIT_EXTERNAL,
+ /* Wait for other commands to finish execution */
+ CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY,
+ /* Waiting for other commands to finish so we can sync */
+ CLIENT_COMMAND_STATE_WAIT_SYNC,
+ /* Command is finished */
+ CLIENT_COMMAND_STATE_DONE
+};
+
+struct client_command_stats {
+ /* time when command handling was started - typically this is after
+ reading all the parameters. */
+ struct timeval start_time;
+ /* time when command handling was last finished. this is before
+ mailbox syncing is done. */
+ struct timeval last_run_timeval;
+ /* io_loop_get_wait_usecs()'s value when the command was started */
+ uint64_t start_ioloop_wait_usecs;
+ /* how many usecs this command itself has spent running */
+ uint64_t running_usecs;
+ /* how many usecs this command itself has spent waiting for locks */
+ uint64_t lock_wait_usecs;
+ /* how many bytes of client input/output command has used */
+ uint64_t bytes_in, bytes_out;
+};
+
+struct client_command_stats_start {
+ struct timeval timeval;
+ uint64_t lock_wait_usecs;
+ uint64_t bytes_in, bytes_out;
+};
+
+struct client_command_context {
+ struct client_command_context *prev, *next;
+ struct client *client;
+ struct event *event;
+ /* global_event is pushed to the global event stack while the command
+ is running. It has only the minimal fields that are actually wanted
+ to be in all the events while it's being run. */
+ struct event *global_event;
+
+ pool_t pool;
+ /* IMAP command tag */
+ const char *tag;
+ /* Name of this command */
+ const char *name;
+ /* Parameters for this command. These are generated from parsed IMAP
+ arguments, so they may not be exactly the same as how client sent
+ them. */
+ const char *args;
+ /* Parameters for this command generated with
+ imap_write_args_for_human(), so it's suitable for logging. */
+ const char *human_args;
+ enum command_flags cmd_flags;
+ const char *tagline_reply;
+
+ command_func_t *func;
+ void *context;
+
+ /* Module-specific contexts. */
+ ARRAY(union imap_module_context *) module_contexts;
+
+ struct imap_parser *parser;
+ enum client_command_state state;
+ struct client_command_stats stats;
+ struct client_command_stats_start stats_start;
+
+ struct imap_client_sync_context *sync;
+
+ bool uid:1; /* used UID command */
+ bool cancel:1; /* command is wanted to be cancelled */
+ bool param_error:1;
+ bool search_save_result:1; /* search result is being updated */
+ bool search_save_result_used:1; /* command uses search save */
+ bool temp_executed:1; /* temporary execution state tracking */
+ bool tagline_sent:1;
+ bool executing:1;
+};
+
+struct imap_client_vfuncs {
+ /* Perform client initialization. This is called when client creation is
+ finished completely. Particulary, at this point the namespaces are
+ fully initialized, which is not the case for the client create hook.
+ */
+ void (*init)(struct client *client);
+ /* Destroy the client.*/
+ void (*destroy)(struct client *client, const char *reason);
+
+ /* Send a tagged response line. */
+ void (*send_tagline)(struct client_command_context *cmd,
+ const char *data);
+ /* Run "mailbox syncing". This can send any unsolicited untagged
+ replies. Returns 1 = done, 0 = wait for more space in output buffer,
+ -1 = failed. */
+ int (*sync_notify_more)(struct imap_sync_context *ctx);
+
+ /* Export client state into buffer. Returns 1 if ok, 0 if some state
+ couldn't be preserved, -1 if temporary internal error occurred. */
+ int (*state_export)(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r);
+ /* Import a single block of client state from the given data. Returns
+ number of bytes successfully imported from the block, or 0 if state
+ is corrupted or contains unknown data (e.g. some plugin is no longer
+ loaded), -1 if temporary internal error occurred. */
+ ssize_t (*state_import)(struct client *client, bool internal,
+ const unsigned char *data, size_t size,
+ const char **error_r);
+};
+
+struct client {
+ struct client *prev, *next;
+
+ struct imap_client_vfuncs v;
+ struct event *event;
+ const char *const *userdb_fields; /* for internal session saving/restoring */
+
+ int fd_in, fd_out;
+ struct io *io;
+ struct istream *input;
+ struct ostream *output;
+ struct timeout *to_idle, *to_idle_output, *to_delayed_input;
+
+ pool_t pool;
+ struct mail_storage_service_user *service_user;
+ const struct imap_settings *set;
+ const struct smtp_submit_settings *smtp_set;
+ string_t *capability_string;
+ const char *disconnect_reason;
+
+ struct mail_user *user;
+ struct mailbox *mailbox;
+ struct mailbox_keywords keywords;
+ unsigned int sync_counter;
+ uint32_t messages_count, recent_count, uidvalidity;
+ ARRAY(bool) enabled_features;
+
+ time_t last_input, last_output;
+ unsigned int bad_counter;
+
+ /* one parser is kept here to be used for new commands */
+ struct imap_parser *free_parser;
+ /* command_pool is cleared when the command queue gets empty */
+ pool_t command_pool;
+ /* New commands are always prepended to the queue */
+ struct client_command_context *command_queue;
+ unsigned int command_queue_size;
+
+ char *last_cmd_name;
+ struct client_command_stats last_cmd_stats;
+
+ uint64_t sync_last_full_modseq;
+ uint64_t highest_fetch_modseq;
+ ARRAY_TYPE(seq_range) fetch_failed_uids;
+
+ /* For imap_logout_format statistics: */
+ unsigned int fetch_hdr_count, fetch_body_count;
+ uint64_t fetch_hdr_bytes, fetch_body_bytes;
+ unsigned int deleted_count, expunged_count, trashed_count;
+ unsigned int autoexpunged_count, append_count;
+
+ /* SEARCHRES extension: Last saved SEARCH result */
+ ARRAY_TYPE(seq_range) search_saved_uidset;
+ /* SEARCH=CONTEXT extension: Searches that get updated */
+ ARRAY(struct imap_search_update) search_updates;
+ /* NOTIFY extension */
+ struct imap_notify_context *notify_ctx;
+ uint32_t notify_uidnext;
+
+ /* client input/output is locked by this command */
+ struct client_command_context *input_lock;
+ struct client_command_context *output_cmd_lock;
+ /* command changing the mailbox */
+ struct client_command_context *mailbox_change_lock;
+
+ /* IMAP URLAUTH context (RFC4467) */
+ struct imap_urlauth_context *urlauth_ctx;
+
+ /* Module-specific contexts. */
+ ARRAY(union imap_module_context *) module_contexts;
+
+ /* syncing marks this TRUE when it sees \Deleted flags. this is by
+ EXPUNGE for Outlook-workaround. */
+ bool sync_seen_deletes:1;
+ bool logged_out:1;
+ bool disconnected:1;
+ bool hibernated:1;
+ bool unhibernated:1; /* client was created by unhibernation */
+ bool destroyed:1;
+ bool handling_input:1;
+ bool syncing:1;
+ bool id_logged:1;
+ bool mailbox_examined:1;
+ bool anvil_sent:1;
+ bool tls_compression:1;
+ bool input_skip_line:1; /* skip all the data until we've
+ found a new line */
+ bool modseqs_sent_since_sync:1;
+ bool notify_immediate_expunges:1;
+ bool notify_count_changes:1;
+ bool notify_flag_changes:1;
+ bool nonpermanent_modseqs:1;
+ bool state_import_bad_idle_done:1;
+ bool state_import_idle_continue:1;
+};
+
+struct imap_module_register {
+ unsigned int id;
+};
+
+union imap_module_context {
+ struct imap_client_vfuncs super;
+ struct imap_module_register *reg;
+};
+extern struct imap_module_register imap_module_register;
+
+extern struct client *imap_clients;
+extern unsigned int imap_client_count;
+
+extern unsigned int imap_feature_condstore;
+extern unsigned int imap_feature_qresync;
+
+/* Create new client with specified input/output handles. socket specifies
+ if the handle is a socket. */
+struct client *client_create(int fd_in, int fd_out, bool unhibernated,
+ struct event *event, struct mail_user *user,
+ struct mail_storage_service_user *service_user,
+ const struct imap_settings *set,
+ const struct smtp_submit_settings *smtp_set);
+void client_create_finish_io(struct client *client);
+/* Finish creating the client. Returns 0 if ok, -1 if there's an error. */
+int client_create_finish(struct client *client, const char **error_r);
+void client_add_istream_prefix(struct client *client,
+ const unsigned char *data, size_t size);
+void client_destroy(struct client *client, const char *reason) ATTR_NULL(2);
+
+/* Disconnect client connection */
+void client_disconnect(struct client *client, const char *reason);
+void client_disconnect_with_error(struct client *client,
+ const char *client_error);
+
+/* Add the given capability to the CAPABILITY reply. If imap_capability setting
+ has an explicit capability, nothing is changed. */
+void client_add_capability(struct client *client, const char *capability);
+
+/* Send a line of data to client. */
+void client_send_line(struct client *client, const char *data);
+/* Send a line of data to client. Returns 1 if ok, 0 if buffer is getting full,
+ -1 if error. This should be used when you're (potentially) sending a lot of
+ lines to client. */
+int client_send_line_next(struct client *client, const char *data);
+/* Send line of data to client, prefixed with client->tag. You need to prefix
+ the data with "OK ", "NO " or "BAD ". */
+void client_send_tagline(struct client_command_context *cmd, const char *data);
+
+/* Send a BAD command reply to client via client_send_tagline(). If there have
+ been too many command errors, the client is disconnected. client_error may
+ be NULL, in which case the error is looked up from imap_parser. */
+void client_send_command_error(struct client_command_context *cmd,
+ const char *client_error);
+
+/* Send a NO command reply with the default internal error message to client
+ via client_send_tagline(). */
+void client_send_internal_error(struct client_command_context *cmd);
+
+/* Read a number of arguments. Returns TRUE if everything was read or
+ FALSE if either needs more data or error occurred. */
+bool client_read_args(struct client_command_context *cmd, unsigned int count,
+ unsigned int flags, const struct imap_arg **args_r);
+/* Reads a number of string arguments. ... is a list of pointers where to
+ store the arguments. */
+bool client_read_string_args(struct client_command_context *cmd,
+ unsigned int count, ...);
+void client_args_finished(struct client_command_context *cmd,
+ const struct imap_arg *args);
+
+/* SEARCHRES extension: Call if $ is being used/updated, returns TRUE if we
+ have to wait for an existing SEARCH SAVE to finish. */
+bool client_handle_search_save_ambiguity(struct client_command_context *cmd);
+
+void client_enable(struct client *client, unsigned int feature_idx);
+/* Returns TRUE if the given feature is enabled */
+bool client_has_enabled(struct client *client, unsigned int feature_idx);
+/* Returns mailbox features that are currently enabled. */
+enum mailbox_feature client_enabled_mailbox_features(struct client *client);
+/* Returns all enabled features as strings. */
+const char *const *client_enabled_features(struct client *client);
+
+/* Send client processing to imap-idle process. If successful, returns TRUE
+ and destroys the client. If hibernation failed, the exact reason is
+ returned (mainly for unit tests). */
+bool imap_client_hibernate(struct client **client, const char **reason_r);
+
+struct imap_search_update *
+client_search_update_lookup(struct client *client, const char *tag,
+ unsigned int *idx_r);
+void client_search_updates_free(struct client *client);
+
+struct client_command_context *client_command_alloc(struct client *client);
+void client_command_init_finished(struct client_command_context *cmd);
+void client_command_cancel(struct client_command_context **cmd);
+void client_command_free(struct client_command_context **cmd);
+
+bool client_handle_unfinished_cmd(struct client_command_context *cmd);
+/* Handle any pending command input. This must be run at the end of all
+ I/O callbacks after they've (potentially) finished some commands. */
+void client_continue_pending_input(struct client *client);
+void client_add_missing_io(struct client *client);
+const char *client_stats(struct client *client);
+
+void client_input(struct client *client);
+bool client_handle_input(struct client *client);
+int client_output(struct client *client);
+
+void clients_init(void);
+void clients_destroy_all(void);
+
+#endif
diff --git a/src/imap/imap-commands-util.c b/src/imap/imap-commands-util.c
new file mode 100644
index 0000000..2d454a7
--- /dev/null
+++ b/src/imap/imap-commands-util.c
@@ -0,0 +1,402 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "array.h"
+#include "buffer.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "imap-resp-code.h"
+#include "imap-parser.h"
+#include "imap-sync.h"
+#include "imap-utf7.h"
+#include "imap-util.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+#include "imap-commands-util.h"
+
+struct mail_namespace *
+client_find_namespace_full(struct client *client,
+ const char **mailbox, const char **client_error_r)
+{
+ struct mail_namespace *namespaces = client->user->namespaces;
+ struct mail_namespace *ns;
+ string_t *utf8_name;
+
+ utf8_name = t_str_new(64);
+ if (imap_utf7_to_utf8(*mailbox, utf8_name) < 0) {
+ *client_error_r = "NO Mailbox name is not valid mUTF-7";
+ return NULL;
+ }
+
+ ns = mail_namespace_find(namespaces, str_c(utf8_name));
+ if ((ns->flags & NAMESPACE_FLAG_AUTOCREATED) != 0 &&
+ ns->prefix_len == 0) {
+ /* this matched only the autocreated prefix="" namespace.
+ give a nice human-readable error message */
+ *client_error_r = t_strdup_printf(
+ "NO Client tried to access nonexistent namespace. "
+ "(Mailbox name should probably be prefixed with: %s)",
+ mail_namespace_find_inbox(namespaces)->prefix);
+ return NULL;
+ }
+
+ if ((client->set->parsed_workarounds &
+ WORKAROUND_TB_EXTRA_MAILBOX_SEP) != 0 &&
+ str_len(utf8_name) > 0 &&
+ str_c(utf8_name)[str_len(utf8_name)-1] == mail_namespace_get_sep(ns)) {
+ /* drop the extra trailing hierarchy separator */
+ str_truncate(utf8_name, str_len(utf8_name)-1);
+ }
+
+ *mailbox = str_c(utf8_name);
+ return ns;
+}
+
+struct mail_namespace *
+client_find_namespace(struct client_command_context *cmd, const char **mailbox)
+{
+ struct mail_namespace *ns;
+ const char *client_error;
+
+ ns = client_find_namespace_full(cmd->client, mailbox, &client_error);
+ if (ns == NULL)
+ client_send_tagline(cmd, client_error);
+ return ns;
+}
+
+bool client_verify_open_mailbox(struct client_command_context *cmd)
+{
+ if (cmd->client->mailbox != NULL) {
+ event_add_str(cmd->global_event, "mailbox",
+ mailbox_get_vname(cmd->client->mailbox));
+ return TRUE;
+ } else {
+ client_send_tagline(cmd, "BAD No mailbox selected.");
+ return FALSE;
+ }
+}
+
+void imap_client_close_mailbox(struct client *client)
+{
+ struct mailbox *box;
+
+ i_assert(client->mailbox != NULL);
+
+ if (array_is_created(&client->fetch_failed_uids))
+ array_clear(&client->fetch_failed_uids);
+ client_search_updates_free(client);
+ array_free(&client->search_saved_uidset);
+
+ box = client->mailbox;
+ client->mailbox = NULL;
+
+ mailbox_free(&box);
+ client_update_mailbox_flags(client, NULL);
+}
+
+int client_open_save_dest_box(struct client_command_context *cmd,
+ const char *name, struct mailbox **destbox_r)
+{
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ const char *error_string;
+ enum mail_error error;
+
+ ns = client_find_namespace(cmd, &name);
+ if (ns == NULL)
+ return -1;
+
+ if (cmd->client->mailbox != NULL &&
+ mailbox_equals(cmd->client->mailbox, ns, name)) {
+ *destbox_r = cmd->client->mailbox;
+ return 0;
+ }
+ box = mailbox_alloc(ns->list, name, MAILBOX_FLAG_SAVEONLY);
+ if (mailbox_open(box) < 0) {
+ error_string = mailbox_get_last_error(box, &error);
+ if (error == MAIL_ERROR_NOTFOUND) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO [TRYCREATE] %s", error_string));
+ } else {
+ client_send_box_error(cmd, box);
+ }
+ mailbox_free(&box);
+ return -1;
+ }
+ if (mailbox_enable(box, client_enabled_mailbox_features(cmd->client)) < 0) {
+ client_send_box_error(cmd, box);
+ mailbox_free(&box);
+ return -1;
+ }
+ *destbox_r = box;
+ return 0;
+}
+
+const char *imap_client_command_get_reason(struct client_command_context *cmd)
+{
+ return cmd->args[0] == '\0' ? cmd->name :
+ t_strdup_printf("%s %s", cmd->name, cmd->human_args);
+}
+
+const char *
+imap_get_error_string(struct client_command_context *cmd,
+ const char *error_string, enum mail_error error)
+{
+ const char *resp_code = NULL;
+
+ switch (error) {
+ case MAIL_ERROR_NONE:
+ break;
+ case MAIL_ERROR_TEMP:
+ case MAIL_ERROR_LOOKUP_ABORTED: /* BUG: shouldn't be visible here */
+ resp_code = IMAP_RESP_CODE_SERVERBUG;
+ break;
+ case MAIL_ERROR_UNAVAILABLE:
+ resp_code = IMAP_RESP_CODE_UNAVAILABLE;
+ break;
+ case MAIL_ERROR_NOTPOSSIBLE:
+ case MAIL_ERROR_PARAMS:
+ resp_code = IMAP_RESP_CODE_CANNOT;
+ break;
+ case MAIL_ERROR_PERM:
+ resp_code = IMAP_RESP_CODE_NOPERM;
+ break;
+ case MAIL_ERROR_NOQUOTA:
+ resp_code = IMAP_RESP_CODE_OVERQUOTA;
+ break;
+ case MAIL_ERROR_NOTFOUND:
+ if ((cmd->cmd_flags & COMMAND_FLAG_USE_NONEXISTENT) != 0)
+ resp_code = IMAP_RESP_CODE_NONEXISTENT;
+ break;
+ case MAIL_ERROR_EXISTS:
+ resp_code = IMAP_RESP_CODE_ALREADYEXISTS;
+ break;
+ case MAIL_ERROR_EXPUNGED:
+ resp_code = IMAP_RESP_CODE_EXPUNGEISSUED;
+ break;
+ case MAIL_ERROR_INUSE:
+ resp_code = IMAP_RESP_CODE_INUSE;
+ break;
+ case MAIL_ERROR_CONVERSION:
+ case MAIL_ERROR_INVALIDDATA:
+ break;
+ case MAIL_ERROR_LIMIT:
+ resp_code = IMAP_RESP_CODE_LIMIT;
+ break;
+ }
+ if (resp_code == NULL || *error_string == '[')
+ return t_strconcat("NO ", error_string, NULL);
+ else
+ return t_strdup_printf("NO [%s] %s", resp_code, error_string);
+}
+
+void client_send_error(struct client_command_context *cmd,
+ const char *error_string, enum mail_error error)
+{
+ client_send_tagline(cmd, imap_get_error_string(cmd, error_string,
+ error));
+ client_disconnect_if_inconsistent(cmd->client);
+}
+
+void client_send_list_error(struct client_command_context *cmd,
+ struct mailbox_list *list)
+{
+ const char *error_string;
+ enum mail_error error;
+
+ error_string = mailbox_list_get_last_error(list, &error);
+ client_send_tagline(cmd, imap_get_error_string(cmd, error_string,
+ error));
+}
+
+void client_disconnect_if_inconsistent(struct client *client)
+{
+ if (client->mailbox != NULL &&
+ mailbox_is_inconsistent(client->mailbox)) {
+ /* we can't do forced CLOSE, so have to disconnect */
+ client_disconnect_with_error(client,
+ "IMAP session state is inconsistent, please relogin.");
+ }
+}
+
+void client_send_box_error(struct client_command_context *cmd,
+ struct mailbox *box)
+{
+ client_send_storage_error(cmd, mailbox_get_storage(box));
+}
+
+void client_send_storage_error(struct client_command_context *cmd,
+ struct mail_storage *storage)
+{
+ const char *error_string;
+ enum mail_error error;
+
+ error_string = mail_storage_get_last_error(storage, &error);
+ client_send_error(cmd, error_string, error);
+}
+
+void client_send_untagged_storage_error(struct client *client,
+ struct mail_storage *storage)
+{
+ const char *error_string;
+ enum mail_error error;
+
+ error_string = mail_storage_get_last_error(storage, &error);
+ client_send_line(client, t_strconcat("* NO ", error_string, NULL));
+
+ client_disconnect_if_inconsistent(client);
+}
+
+bool client_parse_mail_flags(struct client_command_context *cmd,
+ const struct imap_arg *args,
+ enum mail_flags *flags_r,
+ const char *const **keywords_r)
+{
+ const char *atom;
+ enum mail_flags flag;
+ ARRAY(const char *) keywords;
+
+ *flags_r = 0;
+ *keywords_r = NULL;
+ p_array_init(&keywords, cmd->pool, 16);
+
+ while (!IMAP_ARG_IS_EOL(args)) {
+ if (!imap_arg_get_atom(args, &atom)) {
+ client_send_command_error(cmd,
+ "Flags list contains non-atoms.");
+ return FALSE;
+ }
+
+ if (*atom == '\\') {
+ /* system flag */
+ atom = t_str_ucase(atom);
+ flag = imap_parse_system_flag(atom);
+ if (flag != 0 && flag != MAIL_RECENT)
+ *flags_r |= flag;
+ else {
+ client_send_command_error(cmd, t_strconcat(
+ "Invalid system flag ", atom, NULL));
+ return FALSE;
+ }
+ } else {
+ /* keyword validity checks are done by lib-storage */
+ array_push_back(&keywords, &atom);
+ }
+
+ args++;
+ }
+
+ if (array_count(&keywords) == 0)
+ *keywords_r = NULL;
+ else {
+ array_append_zero(&keywords); /* NULL-terminate */
+ *keywords_r = array_front(&keywords);
+ }
+ return TRUE;
+}
+
+void client_send_mailbox_flags(struct client *client, bool selecting)
+{
+ struct mailbox_status status;
+ unsigned int count = array_count(client->keywords.names);
+ const char *const *keywords;
+ string_t *str;
+
+ if (!selecting && count == client->keywords.announce_count) {
+ /* no changes to keywords and we're not selecting a mailbox */
+ return;
+ }
+
+ client->keywords.announce_count = count;
+ mailbox_get_open_status(client->mailbox, STATUS_PERMANENT_FLAGS,
+ &status);
+
+ keywords = count == 0 ? NULL :
+ array_front(client->keywords.names);
+ str = t_str_new(128);
+ str_append(str, "* FLAGS (");
+ imap_write_flags(str, status.flags, keywords);
+ str_append_c(str, ')');
+ client_send_line(client, str_c(str));
+
+ if (!status.permanent_keywords)
+ keywords = NULL;
+
+ str_truncate(str, 0);
+ str_append(str, "* OK [PERMANENTFLAGS (");
+ imap_write_flags(str, status.permanent_flags, keywords);
+ if (status.allow_new_keywords) {
+ if (status.permanent_flags != 0 || keywords != NULL)
+ str_append_c(str, ' ');
+ str_append(str, "\\*");
+ }
+ str_append(str, ")] ");
+
+ if (mailbox_is_readonly(client->mailbox))
+ str_append(str, "Read-only mailbox.");
+ else
+ str_append(str, "Flags permitted.");
+ client_send_line(client, str_c(str));
+}
+
+void client_update_mailbox_flags(struct client *client,
+ const ARRAY_TYPE(keywords) *keywords)
+{
+ client->keywords.names = keywords;
+ client->keywords.announce_count = 0;
+}
+
+const char *const *
+client_get_keyword_names(struct client *client, ARRAY_TYPE(keywords) *dest,
+ const ARRAY_TYPE(keyword_indexes) *src)
+{
+ unsigned int kw_index;
+ const char *const *all_names;
+ unsigned int all_count;
+
+ client_send_mailbox_flags(client, FALSE);
+
+ /* convert indexes to names */
+ all_names = array_get(client->keywords.names, &all_count);
+ array_clear(dest);
+ array_foreach_elem(src, kw_index) {
+ i_assert(kw_index < all_count);
+ array_push_back(dest, &all_names[kw_index]);
+ }
+
+ array_append_zero(dest);
+ return array_front(dest);
+}
+
+void msgset_generator_init(struct msgset_generator_context *ctx, string_t *str)
+{
+ i_zero(ctx);
+ ctx->str = str;
+ ctx->last_uid = (uint32_t)-1;
+}
+
+void msgset_generator_next(struct msgset_generator_context *ctx, uint32_t uid)
+{
+ i_assert(uid > 0);
+
+ if (uid-1 != ctx->last_uid) {
+ if (ctx->first_uid == 0)
+ ;
+ else if (ctx->first_uid == ctx->last_uid)
+ str_printfa(ctx->str, "%u,", ctx->first_uid);
+ else {
+ str_printfa(ctx->str, "%u:%u,",
+ ctx->first_uid, ctx->last_uid);
+ }
+ ctx->first_uid = uid;
+ }
+ ctx->last_uid = uid;
+}
+
+void msgset_generator_finish(struct msgset_generator_context *ctx)
+{
+ if (ctx->first_uid == ctx->last_uid)
+ str_printfa(ctx->str, "%u", ctx->first_uid);
+ else
+ str_printfa(ctx->str, "%u:%u", ctx->first_uid, ctx->last_uid);
+}
diff --git a/src/imap/imap-commands-util.h b/src/imap/imap-commands-util.h
new file mode 100644
index 0000000..997ed02
--- /dev/null
+++ b/src/imap/imap-commands-util.h
@@ -0,0 +1,81 @@
+#ifndef IMAP_COMMANDS_UTIL_H
+#define IMAP_COMMANDS_UTIL_H
+
+struct msgset_generator_context {
+ string_t *str;
+ uint32_t first_uid, last_uid;
+};
+
+struct mail_full_flags;
+struct mailbox_keywords;
+
+/* Finds namespace for given mailbox from namespaces. If namespace isn't found
+ or mailbox name is invalid, sends a tagged NO reply to client. */
+struct mail_namespace *
+client_find_namespace(struct client_command_context *cmd, const char **mailbox);
+struct mail_namespace *
+client_find_namespace_full(struct client *client,
+ const char **mailbox, const char **client_error_r);
+
+/* Returns TRUE if mailbox is selected. If not, sends "No mailbox selected"
+ error message to client. */
+bool client_verify_open_mailbox(struct client_command_context *cmd);
+/* Close the selected mailbox. */
+void imap_client_close_mailbox(struct client *client);
+
+/* Open APPEND/COPY destination mailbox. */
+int client_open_save_dest_box(struct client_command_context *cmd,
+ const char *name, struct mailbox **destbox_r);
+
+/* Returns string based in IMAP command name and parameters. */
+const char *imap_client_command_get_reason(struct client_command_context *cmd);
+/* Set transaction's reason to the IMAP command name and parameters. */
+void imap_transaction_set_cmd_reason(struct mailbox_transaction_context *trans,
+ struct client_command_context *cmd);
+const char *
+imap_get_error_string(struct client_command_context *cmd,
+ const char *error_string, enum mail_error error);
+
+void client_disconnect_if_inconsistent(struct client *client);
+
+/* Send an explicit error message to client. */
+void client_send_error(struct client_command_context *cmd,
+ const char *error_string, enum mail_error error);
+/* Send last mailbox list error message to client. */
+void client_send_list_error(struct client_command_context *cmd,
+ struct mailbox_list *list);
+/* Send last mail storage error message to client. */
+void client_send_storage_error(struct client_command_context *cmd,
+ struct mail_storage *storage);
+/* Send last mailbox's storage error message to client. */
+void client_send_box_error(struct client_command_context *cmd,
+ struct mailbox *box);
+
+/* Send untagged error message to client. */
+void client_send_untagged_storage_error(struct client *client,
+ struct mail_storage *storage);
+
+/* Parse flags. Returns TRUE if successful, if not sends an error message to
+ client. */
+bool client_parse_mail_flags(struct client_command_context *cmd,
+ const struct imap_arg *args,
+ enum mail_flags *flags_r,
+ const char *const **keywords_r);
+
+/* Send FLAGS + PERMANENTFLAGS to client if they have changed,
+ or if selecting=TRUE. */
+void client_send_mailbox_flags(struct client *client, bool selecting);
+/* Update client->keywords array. Use keywords=NULL when unselecting. */
+void client_update_mailbox_flags(struct client *client,
+ const ARRAY_TYPE(keywords) *keywords)
+ ATTR_NULL(2);
+/* Convert keyword indexes to keyword names in selected mailbox. */
+const char *const *
+client_get_keyword_names(struct client *client, ARRAY_TYPE(keywords) *dest,
+ const ARRAY_TYPE(keyword_indexes) *src);
+
+void msgset_generator_init(struct msgset_generator_context *ctx, string_t *str);
+void msgset_generator_next(struct msgset_generator_context *ctx, uint32_t uid);
+void msgset_generator_finish(struct msgset_generator_context *ctx);
+
+#endif
diff --git a/src/imap/imap-commands.c b/src/imap/imap-commands.c
new file mode 100644
index 0000000..b78d0a1
--- /dev/null
+++ b/src/imap/imap-commands.c
@@ -0,0 +1,247 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "array.h"
+#include "buffer.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "time-util.h"
+#include "imap-commands.h"
+
+
+struct command_hook {
+ command_hook_callback_t *pre;
+ command_hook_callback_t *post;
+};
+
+static const struct command imap4rev1_commands[] = {
+ { "CAPABILITY", cmd_capability, 0 },
+ { "LOGOUT", cmd_logout, COMMAND_FLAG_BREAKS_MAILBOX },
+ { "NOOP", cmd_noop, COMMAND_FLAG_BREAKS_SEQS },
+
+ { "APPEND", cmd_append, COMMAND_FLAG_BREAKS_SEQS |
+ /* finish syncing and sending
+ all tagged commands before
+ we wait for APPEND input */
+ COMMAND_FLAG_BREAKS_MAILBOX },
+ { "EXAMINE", cmd_examine, COMMAND_FLAG_BREAKS_MAILBOX },
+ { "CREATE", cmd_create, 0 },
+ { "DELETE", cmd_delete, COMMAND_FLAG_BREAKS_MAILBOX |
+ COMMAND_FLAG_USE_NONEXISTENT },
+ { "RENAME", cmd_rename, COMMAND_FLAG_USE_NONEXISTENT },
+ { "LIST", cmd_list, 0 },
+ { "LSUB", cmd_lsub, 0 },
+ { "SELECT", cmd_select, COMMAND_FLAG_BREAKS_MAILBOX },
+ { "STATUS", cmd_status, 0 },
+ { "SUBSCRIBE", cmd_subscribe, 0 },
+ { "UNSUBSCRIBE", cmd_unsubscribe, COMMAND_FLAG_USE_NONEXISTENT },
+
+ { "CHECK", cmd_check, COMMAND_FLAG_BREAKS_SEQS },
+ { "CLOSE", cmd_close, COMMAND_FLAG_BREAKS_MAILBOX },
+ { "COPY", cmd_copy, COMMAND_FLAG_USES_SEQS |
+ COMMAND_FLAG_BREAKS_SEQS },
+ { "EXPUNGE", cmd_expunge, COMMAND_FLAG_BREAKS_SEQS },
+ { "FETCH", cmd_fetch, COMMAND_FLAG_USES_SEQS },
+ { "SEARCH", cmd_search, COMMAND_FLAG_USES_SEQS },
+ { "STORE", cmd_store, COMMAND_FLAG_USES_SEQS },
+ { "UID COPY", cmd_copy, COMMAND_FLAG_BREAKS_SEQS },
+ { "UID FETCH", cmd_fetch, COMMAND_FLAG_BREAKS_SEQS },
+ { "UID SEARCH", cmd_search, COMMAND_FLAG_BREAKS_SEQS },
+ { "UID STORE", cmd_store, COMMAND_FLAG_BREAKS_SEQS }
+};
+#define IMAP4REV1_COMMANDS_COUNT N_ELEMENTS(imap4rev1_commands)
+
+static const struct command imap_ext_commands[] = {
+ /* IMAP extensions: */
+ { "CANCELUPDATE", cmd_cancelupdate,0 },
+ { "ENABLE", cmd_enable, 0 },
+ { "ID", cmd_id, 0 },
+ { "IDLE", cmd_idle, COMMAND_FLAG_BREAKS_SEQS |
+ COMMAND_FLAG_REQUIRES_SYNC |
+ /* finish syncing and sending
+ all tagged commands before
+ IDLE is started */
+ COMMAND_FLAG_BREAKS_MAILBOX },
+ { "GETMETADATA", cmd_getmetadata, 0 },
+ { "SETMETADATA", cmd_setmetadata, 0 },
+ { "NAMESPACE", cmd_namespace, 0 },
+ { "NOTIFY", cmd_notify, COMMAND_FLAG_BREAKS_SEQS },
+ { "SORT", cmd_sort, COMMAND_FLAG_USES_SEQS },
+ { "THREAD", cmd_thread, COMMAND_FLAG_USES_SEQS },
+ { "UID EXPUNGE", cmd_uid_expunge, COMMAND_FLAG_BREAKS_SEQS },
+ { "MOVE", cmd_move, COMMAND_FLAG_USES_SEQS |
+ COMMAND_FLAG_BREAKS_SEQS },
+ { "UID MOVE", cmd_move, COMMAND_FLAG_BREAKS_SEQS },
+ { "UID SORT", cmd_sort, COMMAND_FLAG_BREAKS_SEQS },
+ { "UID THREAD", cmd_thread, COMMAND_FLAG_BREAKS_SEQS },
+ { "UNSELECT", cmd_unselect, COMMAND_FLAG_BREAKS_MAILBOX },
+ { "X-CANCEL", cmd_x_cancel, 0 },
+ { "X-STATE", cmd_x_state, COMMAND_FLAG_REQUIRES_SYNC },
+ { "XLIST", cmd_list, 0 },
+ /* IMAP URLAUTH (RFC4467): */
+ { "GENURLAUTH", cmd_genurlauth, 0 },
+ { "RESETKEY", cmd_resetkey, 0 },
+ { "URLFETCH", cmd_urlfetch, 0 }
+};
+#define IMAP_EXT_COMMANDS_COUNT N_ELEMENTS(imap_ext_commands)
+
+ARRAY_TYPE(command) imap_commands;
+static bool commands_unsorted;
+static ARRAY(struct command_hook) command_hooks;
+
+void command_register(const char *name, command_func_t *func,
+ enum command_flags flags)
+{
+ struct command cmd;
+
+ i_zero(&cmd);
+ cmd.name = name;
+ cmd.func = func;
+ cmd.flags = flags;
+ array_push_back(&imap_commands, &cmd);
+
+ commands_unsorted = TRUE;
+}
+
+void command_unregister(const char *name)
+{
+ const struct command *cmd;
+ unsigned int i, count;
+
+ cmd = array_get(&imap_commands, &count);
+ for (i = 0; i < count; i++) {
+ if (strcasecmp(cmd[i].name, name) == 0) {
+ array_delete(&imap_commands, i, 1);
+ return;
+ }
+ }
+
+ i_error("Trying to unregister unknown command '%s'", name);
+}
+
+void command_register_array(const struct command *cmdarr, unsigned int count)
+{
+ commands_unsorted = TRUE;
+ array_append(&imap_commands, cmdarr, count);
+}
+
+void command_unregister_array(const struct command *cmdarr, unsigned int count)
+{
+ while (count > 0) {
+ command_unregister(cmdarr->name);
+ count--; cmdarr++;
+ }
+}
+
+void command_hook_register(command_hook_callback_t *pre,
+ command_hook_callback_t *post)
+{
+ struct command_hook hook;
+
+ hook.pre = pre;
+ hook.post = post;
+ array_push_back(&command_hooks, &hook);
+}
+
+void command_hook_unregister(command_hook_callback_t *pre,
+ command_hook_callback_t *post)
+{
+ const struct command_hook *hooks;
+ unsigned int i, count;
+
+ hooks = array_get(&command_hooks, &count);
+ for (i = 0; i < count; i++) {
+ if (hooks[i].pre == pre && hooks[i].post == post) {
+ array_delete(&command_hooks, i, 1);
+ return;
+ }
+ }
+ i_panic("command_hook_unregister(): hook not registered");
+}
+
+void command_stats_start(struct client_command_context *cmd)
+{
+ cmd->stats_start.timeval = ioloop_timeval;
+ cmd->stats_start.lock_wait_usecs = file_lock_wait_get_total_usecs();
+ cmd->stats_start.bytes_in = i_stream_get_absolute_offset(cmd->client->input);
+ cmd->stats_start.bytes_out = cmd->client->output->offset;
+}
+
+void command_stats_flush(struct client_command_context *cmd)
+{
+ io_loop_time_refresh();
+ cmd->stats.running_usecs +=
+ timeval_diff_usecs(&ioloop_timeval, &cmd->stats_start.timeval);
+ cmd->stats.lock_wait_usecs +=
+ file_lock_wait_get_total_usecs() -
+ cmd->stats_start.lock_wait_usecs;
+ cmd->stats.bytes_in += i_stream_get_absolute_offset(cmd->client->input) -
+ cmd->stats_start.bytes_in;
+ cmd->stats.bytes_out += cmd->client->output->offset -
+ cmd->stats_start.bytes_out;
+ /* allow flushing multiple times */
+ command_stats_start(cmd);
+}
+
+bool command_exec(struct client_command_context *cmd)
+{
+ const struct command_hook *hook;
+ bool finished;
+
+ i_assert(!cmd->executing);
+
+ io_loop_time_refresh();
+ command_stats_start(cmd);
+
+ event_push_global(cmd->global_event);
+ cmd->executing = TRUE;
+ array_foreach(&command_hooks, hook)
+ hook->pre(cmd);
+ finished = cmd->func(cmd);
+ array_foreach(&command_hooks, hook)
+ hook->post(cmd);
+ cmd->executing = FALSE;
+ event_pop_global(cmd->global_event);
+ if (cmd->state == CLIENT_COMMAND_STATE_DONE)
+ finished = TRUE;
+
+ command_stats_flush(cmd);
+ return finished;
+}
+
+static int command_cmp(const struct command *c1, const struct command *c2)
+{
+ return strcasecmp(c1->name, c2->name);
+}
+
+static int command_bsearch(const char *name, const struct command *cmd)
+{
+ return strcasecmp(name, cmd->name);
+}
+
+struct command *command_find(const char *name)
+{
+ if (commands_unsorted) {
+ array_sort(&imap_commands, command_cmp);
+ commands_unsorted = FALSE;
+ }
+
+ return array_bsearch(&imap_commands, name, command_bsearch);
+}
+
+void commands_init(void)
+{
+ i_array_init(&imap_commands, 64);
+ i_array_init(&command_hooks, 4);
+ commands_unsorted = FALSE;
+
+ command_register_array(imap4rev1_commands, IMAP4REV1_COMMANDS_COUNT);
+ command_register_array(imap_ext_commands, IMAP_EXT_COMMANDS_COUNT);
+}
+
+void commands_deinit(void)
+{
+ array_free(&imap_commands);
+ array_free(&command_hooks);
+}
diff --git a/src/imap/imap-commands.h b/src/imap/imap-commands.h
new file mode 100644
index 0000000..651a6df
--- /dev/null
+++ b/src/imap/imap-commands.h
@@ -0,0 +1,137 @@
+#ifndef IMAP_COMMANDS_H
+#define IMAP_COMMANDS_H
+
+struct client_command_context;
+
+#include "mail-storage.h"
+#include "mail-namespace.h"
+#include "imap-parser.h"
+#include "imap-sync.h"
+#include "imap-commands-util.h"
+
+typedef bool command_func_t(struct client_command_context *cmd);
+typedef void command_hook_callback_t(struct client_command_context *ctx);
+
+enum command_flags {
+ /* Command uses sequences as its input parameters */
+ COMMAND_FLAG_USES_SEQS = 0x01,
+ /* Command may reply with EXPUNGE, causing sequences to break */
+ COMMAND_FLAG_BREAKS_SEQS = 0x02,
+ /* Command changes the mailbox */
+ COMMAND_FLAG_BREAKS_MAILBOX = 0x04 | COMMAND_FLAG_BREAKS_SEQS,
+
+ /* Command uses selected mailbox */
+ COMMAND_FLAG_USES_MAILBOX = COMMAND_FLAG_BREAKS_MAILBOX |
+ COMMAND_FLAG_USES_SEQS,
+
+ /* Command requires mailbox syncing before it can do its job. */
+ COMMAND_FLAG_REQUIRES_SYNC = 0x08,
+ /* Command allows replying with [NONEXISTENT] imap resp code.
+ Dovecot internally returns it for all kinds of commands,
+ but unfortunately RFC 5530 specifies it only for "delete something"
+ operations. */
+ COMMAND_FLAG_USE_NONEXISTENT = 0x10
+};
+
+struct command {
+ const char *name;
+ command_func_t *func;
+
+ enum command_flags flags;
+};
+ARRAY_DEFINE_TYPE(command, struct command);
+
+extern ARRAY_TYPE(command) imap_commands;
+
+/* Register command. Given name parameter must be permanently stored until
+ command is unregistered. */
+void command_register(const char *name, command_func_t *func,
+ enum command_flags flags);
+void command_unregister(const char *name);
+
+/* Register array of commands. */
+void command_register_array(const struct command *cmdarr, unsigned int count);
+void command_unregister_array(const struct command *cmdarr, unsigned int count);
+
+/* Register hook callbacks that are called before and after all commands */
+void command_hook_register(command_hook_callback_t *pre,
+ command_hook_callback_t *post);
+void command_hook_unregister(command_hook_callback_t *pre,
+ command_hook_callback_t *post);
+/* Execute command and hooks */
+bool command_exec(struct client_command_context *cmd);
+/* Starts counting command statistics. */
+void command_stats_start(struct client_command_context *cmd);
+/* Finish counting command statistics. This is called automatically when
+ command_exec() returns, but it should be called explicitly if the stats are
+ needed during command_exec(). */
+void command_stats_flush(struct client_command_context *cmd);
+
+struct command *command_find(const char *name);
+
+void commands_init(void);
+void commands_deinit(void);
+
+/* IMAP4rev1 commands: */
+
+/* Non-Authenticated State */
+bool cmd_logout(struct client_command_context *cmd);
+
+bool cmd_capability(struct client_command_context *cmd);
+bool cmd_noop(struct client_command_context *cmd);
+
+/* Authenticated State */
+bool cmd_select(struct client_command_context *cmd);
+bool cmd_examine(struct client_command_context *cmd);
+
+bool cmd_create(struct client_command_context *cmd);
+bool cmd_delete(struct client_command_context *cmd);
+bool cmd_rename(struct client_command_context *cmd);
+
+bool cmd_subscribe(struct client_command_context *cmd);
+bool cmd_unsubscribe(struct client_command_context *cmd);
+
+bool cmd_list(struct client_command_context *cmd);
+bool cmd_lsub(struct client_command_context *cmd);
+
+bool cmd_status(struct client_command_context *cmd);
+bool cmd_append(struct client_command_context *cmd);
+
+/* Selected state */
+bool cmd_check(struct client_command_context *cmd);
+bool cmd_close(struct client_command_context *cmd);
+bool cmd_expunge(struct client_command_context *cmd);
+bool cmd_search(struct client_command_context *cmd);
+bool cmd_fetch(struct client_command_context *cmd);
+bool cmd_store(struct client_command_context *cmd);
+bool cmd_copy(struct client_command_context *cmd);
+bool cmd_uid(struct client_command_context *cmd);
+
+/* IMAP extensions: */
+bool cmd_cancelupdate(struct client_command_context *cmd);
+bool cmd_enable(struct client_command_context *cmd);
+bool cmd_id(struct client_command_context *cmd);
+bool cmd_idle(struct client_command_context *cmd);
+bool cmd_namespace(struct client_command_context *cmd);
+bool cmd_getmetadata(struct client_command_context *cmd);
+bool cmd_setmetadata(struct client_command_context *cmd);
+bool cmd_notify(struct client_command_context *cmd);
+bool cmd_sort(struct client_command_context *cmd);
+bool cmd_thread(struct client_command_context *cmd);
+bool cmd_uid_expunge(struct client_command_context *cmd);
+bool cmd_move(struct client_command_context *cmd);
+bool cmd_unselect(struct client_command_context *cmd);
+bool cmd_x_cancel(struct client_command_context *cmd);
+bool cmd_x_state(struct client_command_context *cmd);
+
+/* IMAP URLAUTH (RFC4467): */
+bool cmd_genurlauth(struct client_command_context *cmd);
+bool cmd_resetkey(struct client_command_context *cmd);
+bool cmd_urlfetch(struct client_command_context *cmd);
+
+/* private: */
+bool cmd_list_full(struct client_command_context *cmd, bool lsub);
+bool cmd_select_full(struct client_command_context *cmd, bool readonly);
+bool cmd_subscribe_full(struct client_command_context *cmd, bool subscribe);
+
+#endif
diff --git a/src/imap/imap-common.h b/src/imap/imap-common.h
new file mode 100644
index 0000000..ec23879
--- /dev/null
+++ b/src/imap/imap-common.h
@@ -0,0 +1,40 @@
+#ifndef IMAP_COMMON_H
+#define IMAP_COMMON_H
+
+/* Disconnect client after idling this many milliseconds */
+#define CLIENT_IDLE_TIMEOUT_MSECS (60*30*1000)
+
+/* If we can't send anything to client for this long, disconnect the client */
+#define CLIENT_OUTPUT_TIMEOUT_MSECS (5*60*1000)
+
+/* Stop buffering more data into output stream after this many bytes */
+#define CLIENT_OUTPUT_OPTIMAL_SIZE 2048
+
+/* Disconnect client when it sends too many bad commands in a row */
+#define CLIENT_MAX_BAD_COMMANDS 20
+
+#include "lib.h"
+#include "imap-client.h"
+#include "imap-settings.h"
+
+struct mail_storage_service_input;
+
+typedef void imap_client_created_func_t(struct client **client);
+
+extern imap_client_created_func_t *hook_client_created;
+extern bool imap_debug;
+extern struct event_category event_category_imap;
+
+/* Sets the hook_client_created and returns the previous hook,
+ which the new_hook should call if it's non-NULL. */
+imap_client_created_func_t * ATTR_NOWARN_UNUSED_RESULT
+imap_client_created_hook_set(imap_client_created_func_t *new_hook);
+
+void imap_refresh_proctitle(void);
+void imap_refresh_proctitle_delayed(void);
+
+int client_create_from_input(const struct mail_storage_service_input *input,
+ int fd_in, int fd_out, bool unhibernated,
+ struct client **client_r, const char **error_r);
+
+#endif
diff --git a/src/imap/imap-expunge.c b/src/imap/imap-expunge.c
new file mode 100644
index 0000000..ff1a213
--- /dev/null
+++ b/src/imap/imap-expunge.c
@@ -0,0 +1,111 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "mail-storage.h"
+#include "mail-search-build.h"
+#include "imap-search-args.h"
+#include "imap-expunge.h"
+
+#define IMAP_EXPUNGE_BATCH_SIZE 1000
+
+/* get a seqset of all the mails with \Deleted */
+static int imap_expunge_get_seqset(struct mailbox *box,
+ struct mail_search_arg *next_search_arg,
+ ARRAY_TYPE(seq_range) *seqset)
+{
+ struct mailbox_transaction_context *t;
+ struct mail_search_args *search_args;
+ struct mail_search_context *ctx;
+ struct mail *mail;
+ int ret;
+
+ search_args = mail_search_build_init();
+ search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
+ search_args->args->type = SEARCH_FLAGS;
+ search_args->args->value.flags = MAIL_DELETED;
+ search_args->args->next = next_search_arg;
+
+ /* Refresh the flags so we'll expunge all messages marked as \Deleted
+ by any session. */
+ t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_REFRESH,
+ "EXPUNGE");
+ ctx = mailbox_search_init(t, search_args, NULL, 0, NULL);
+
+ /* collect the seqs into a seqset */
+ while (mailbox_search_next(ctx, &mail))
+ seq_range_array_add(seqset, mail->seq);
+
+ ret = mailbox_search_deinit(&ctx);
+ /* commit in case a plugin made changes - failures should not abort the expunge */
+ (void) mailbox_transaction_commit(&t);
+ mail_search_args_unref(&search_args);
+
+ if (ret < 0)
+ array_free(seqset);
+
+ return ret;
+}
+
+int imap_expunge(struct mailbox *box, struct mail_search_arg *next_search_arg,
+ unsigned int *expunged_count)
+{
+ struct imap_search_seqset_iter *seqset_iter;
+ struct mail_search_args *search_args;
+ struct mailbox_status status;
+ bool expunges = FALSE;
+ int ret;
+
+ if (mailbox_is_readonly(box)) {
+ /* silently ignore */
+ return 0;
+ }
+
+ mailbox_get_open_status(box, STATUS_MESSAGES, &status);
+
+ search_args = mail_search_build_init();
+ search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
+ search_args->args->type = SEARCH_SEQSET;
+ p_array_init(&search_args->args->value.seqset, search_args->pool, 16);
+
+ if (imap_expunge_get_seqset(box, next_search_arg,
+ &search_args->args->value.seqset) < 0) {
+ mail_search_args_unref(&search_args);
+ return -1;
+ }
+
+ seqset_iter = imap_search_seqset_iter_init(search_args, status.messages,
+ IMAP_EXPUNGE_BATCH_SIZE);
+
+ do {
+ struct mailbox_transaction_context *t;
+ struct mail_search_context *ctx;
+ struct mail *mail;
+
+ t = mailbox_transaction_begin(box, 0, "EXPUNGE");
+ ctx = mailbox_search_init(t, search_args, NULL, 0, NULL);
+
+ while (mailbox_search_next(ctx, &mail)) {
+ *expunged_count += 1;
+ mail_expunge(mail);
+ expunges = TRUE;
+ }
+
+ ret = mailbox_search_deinit(&ctx);
+ if (ret < 0) {
+ mailbox_transaction_rollback(&t);
+ break;
+ } else {
+ ret = mailbox_transaction_commit(&t);
+ if (ret < 0)
+ break;
+ }
+ } while (imap_search_seqset_iter_next(seqset_iter));
+
+ imap_search_seqset_iter_deinit(&seqset_iter);
+ mail_search_args_unref(&search_args);
+
+ if (ret < 0)
+ return ret;
+
+ return expunges ? 1 : 0;
+}
diff --git a/src/imap/imap-expunge.h b/src/imap/imap-expunge.h
new file mode 100644
index 0000000..e5b3825
--- /dev/null
+++ b/src/imap/imap-expunge.h
@@ -0,0 +1,10 @@
+#ifndef IMAP_EXPUNGE_H
+#define IMAP_EXPUNGE_H
+
+struct mail_search_arg;
+
+int imap_expunge(struct mailbox *box, struct mail_search_arg *next_search_arg,
+ unsigned int *expunged_count)
+ ATTR_NULL(2);
+
+#endif
diff --git a/src/imap/imap-feature.c b/src/imap/imap-feature.c
new file mode 100644
index 0000000..d8d52b8
--- /dev/null
+++ b/src/imap/imap-feature.c
@@ -0,0 +1,46 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-feature.h"
+
+static ARRAY_TYPE(imap_feature) feature_register = ARRAY_INIT;
+
+bool imap_feature_lookup(const char *name, unsigned int *feature_idx_r)
+{
+ for (unsigned int idx = 0; idx < array_count(&feature_register); idx++) {
+ const struct imap_feature *feat =
+ array_idx(&feature_register, idx);
+ if (strcasecmp(name, feat->feature) == 0) {
+ *feature_idx_r = idx;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+const struct imap_feature *imap_feature_idx(unsigned int feature_idx)
+{
+ return array_idx(&feature_register, feature_idx);
+}
+
+unsigned int
+imap_feature_register(const char *feature, enum mailbox_feature mailbox_features,
+ imap_client_enable_callback_t *callback)
+{
+ struct imap_feature *feat = array_append_space(&feature_register);
+ feat->feature = feature;
+ feat->mailbox_features = mailbox_features;
+ feat->callback = callback;
+ return array_count(&feature_register)-1;
+}
+
+void imap_features_init(void)
+{
+ i_assert(!array_is_created(&feature_register));
+ i_array_init(&feature_register, 8);
+}
+
+void imap_features_deinit(void)
+{
+ array_free(&feature_register);
+}
diff --git a/src/imap/imap-feature.h b/src/imap/imap-feature.h
new file mode 100644
index 0000000..a96aa6e
--- /dev/null
+++ b/src/imap/imap-feature.h
@@ -0,0 +1,24 @@
+#ifndef IMAP_FEATURE_H
+#define IMAP_FEATURE_H
+
+typedef void imap_client_enable_callback_t(struct client *);
+
+struct imap_feature {
+ const char *feature;
+ enum mailbox_feature mailbox_features;
+ imap_client_enable_callback_t *callback;
+ bool enabled;
+};
+ARRAY_DEFINE_TYPE(imap_feature, struct imap_feature);
+
+bool imap_feature_lookup(const char *name, unsigned int *feature_idx_r);
+const struct imap_feature *imap_feature_idx(unsigned int feature_idx);
+
+unsigned int
+imap_feature_register(const char *feature, enum mailbox_feature mailbox_features,
+ imap_client_enable_callback_t *callback);
+
+void imap_features_init(void);
+void imap_features_deinit(void);
+
+#endif
diff --git a/src/imap/imap-fetch-body.c b/src/imap/imap-fetch-body.c
new file mode 100644
index 0000000..b467334
--- /dev/null
+++ b/src/imap/imap-fetch-body.c
@@ -0,0 +1,722 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "buffer.h"
+#include "str.h"
+#include "strescape.h"
+#include "istream.h"
+#include "ostream.h"
+#include "istream-header-filter.h"
+#include "message-parser.h"
+#include "mail-storage-private.h"
+#include "imap-quote.h"
+#include "imap-parser.h"
+#include "imap-msgpart.h"
+#include "imap-fetch.h"
+
+#include <ctype.h>
+#include <unistd.h>
+
+struct imap_fetch_body_data {
+ const char *section; /* NOTE: always uppercased */
+ struct imap_msgpart *msgpart;
+
+ bool partial:1;
+ bool binary:1;
+ bool binary_size:1;
+};
+
+struct imap_fetch_preview_data {
+ /* If TRUE, lazy modifier is specified. */
+ bool lazy:1;
+ /* Uses the pre-RFC 8970 standard (requires algorithm to be returned
+ * as part of FETCH response). */
+ bool old_standard:1;
+ /* If TRUE, uses "PREVIEW" command; if FALSE, uses older "SNIPPET"
+ * command. */
+ bool preview_cmd:1;
+};
+
+static void fetch_read_error(struct imap_fetch_context *ctx,
+ const char **disconnect_reason_r)
+{
+ struct imap_fetch_state *state = &ctx->state;
+
+ if (state->cur_input->stream_errno == ENOENT) {
+ if (state->cur_mail->expunged) {
+ *disconnect_reason_r = "Mail expunged while it was being FETCHed";
+ return;
+ }
+ }
+ mail_set_critical(state->cur_mail,
+ "read(%s) failed: %s (FETCH %s)",
+ i_stream_get_name(state->cur_input),
+ i_stream_get_error(state->cur_input),
+ state->cur_human_name);
+ *disconnect_reason_r = "FETCH read() failed";
+}
+
+static const char *get_body_name(const struct imap_fetch_body_data *body)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ if (body->binary_size)
+ str_append(str, "BINARY.SIZE");
+ else if (body->binary)
+ str_append(str, "BINARY");
+ else
+ str_append(str, "BODY");
+ str_printfa(str, "[%s]", body->section);
+ if (body->partial) {
+ str_printfa(str, "<%"PRIuUOFF_T">",
+ imap_msgpart_get_partial_offset(body->msgpart));
+ }
+ return str_c(str);
+}
+
+static string_t *get_prefix(struct imap_fetch_state *state,
+ const struct imap_fetch_body_data *body,
+ uoff_t size, bool has_nuls)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ if (state->cur_first)
+ state->cur_first = FALSE;
+ else
+ str_append_c(str, ' ');
+
+ str_append(str, get_body_name(body));
+
+ if (size == UOFF_T_MAX)
+ str_append(str, " NIL");
+ else if (has_nuls && body->binary)
+ str_printfa(str, " ~{%"PRIuUOFF_T"}\r\n", size);
+ else
+ str_printfa(str, " {%"PRIuUOFF_T"}\r\n", size);
+ return str;
+}
+
+static int fetch_stream_continue(struct imap_fetch_context *ctx)
+{
+ struct imap_fetch_state *state = &ctx->state;
+ const char *disconnect_reason;
+ uoff_t orig_input_offset = state->cur_input->v_offset;
+ enum ostream_send_istream_result res;
+
+ o_stream_set_max_buffer_size(ctx->client->output, 0);
+ res = o_stream_send_istream(ctx->client->output, state->cur_input);
+ o_stream_set_max_buffer_size(ctx->client->output, SIZE_MAX);
+
+ if (ctx->state.cur_stats_sizep != NULL) {
+ *ctx->state.cur_stats_sizep +=
+ state->cur_input->v_offset - orig_input_offset;
+ }
+
+ switch (res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ if (state->cur_input->v_offset != state->cur_size) {
+ /* Input stream gave less data than expected */
+ mail_set_cache_corrupted(state->cur_mail,
+ state->cur_size_field, t_strdup_printf(
+ "read(%s): FETCH %s got too little data: "
+ "%"PRIuUOFF_T" vs %"PRIuUOFF_T,
+ i_stream_get_name(state->cur_input),
+ state->cur_human_name,
+ state->cur_input->v_offset, state->cur_size));
+ client_disconnect(ctx->client, "FETCH failed");
+ return -1;
+ }
+ return 1;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ return 0;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ fetch_read_error(ctx, &disconnect_reason);
+ client_disconnect(ctx->client, disconnect_reason);
+ return -1;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ /* client disconnected */
+ return -1;
+ }
+ i_unreached();
+}
+
+static const char *
+get_body_human_name(pool_t pool, struct imap_fetch_body_data *body)
+{
+ string_t *str;
+ uoff_t partial_offset, partial_size;
+
+ str = t_str_new(64);
+ if (body->binary)
+ str_append(str, "BINARY[");
+ else
+ str_append(str, "BODY[");
+ str_append(str, body->section);
+ str_append_c(str, ']');
+
+ partial_offset = imap_msgpart_get_partial_offset(body->msgpart);
+ partial_size = imap_msgpart_get_partial_size(body->msgpart);
+ if (partial_offset != 0 || partial_size != UOFF_T_MAX) {
+ str_printfa(str, "<%"PRIuUOFF_T, partial_offset);
+ if (partial_size != UOFF_T_MAX)
+ str_printfa(str, ".%"PRIuUOFF_T, partial_size);
+ str_append_c(str, '>');
+ }
+ return p_strdup(pool, str_c(str));
+}
+
+static void fetch_state_update_stats(struct imap_fetch_context *ctx,
+ const struct imap_msgpart *msgpart)
+{
+ if (!imap_msgpart_contains_body(msgpart)) {
+ ctx->client->fetch_hdr_count++;
+ ctx->state.cur_stats_sizep = &ctx->client->fetch_hdr_bytes;
+ } else {
+ ctx->client->fetch_body_count++;
+ ctx->state.cur_stats_sizep = &ctx->client->fetch_body_bytes;
+ }
+}
+
+static int fetch_body_msgpart(struct imap_fetch_context *ctx, struct mail *mail,
+ struct imap_fetch_body_data *body)
+{
+ struct imap_msgpart_open_result result;
+ string_t *str;
+
+ if (mail == NULL) {
+ imap_msgpart_free(&body->msgpart);
+ return 1;
+ }
+
+ if (imap_msgpart_open(mail, body->msgpart, &result) < 0)
+ return -1;
+ i_assert(result.input->v_offset == 0);
+ ctx->state.cur_input = result.input;
+ ctx->state.cur_size = result.size;
+ ctx->state.cur_size_field = result.size_field;
+ ctx->state.cur_human_name = get_body_human_name(ctx->ctx_pool, body);
+
+ fetch_state_update_stats(ctx, body->msgpart);
+ str = get_prefix(&ctx->state, body, ctx->state.cur_size,
+ result.binary_decoded_input_has_nuls);
+ o_stream_nsend(ctx->client->output, str_data(str), str_len(str));
+
+ ctx->state.cont_handler = fetch_stream_continue;
+ return ctx->state.cont_handler(ctx);
+}
+
+static int fetch_binary_size(struct imap_fetch_context *ctx, struct mail *mail,
+ struct imap_fetch_body_data *body)
+{
+ string_t *str;
+ uoff_t size;
+
+ if (mail == NULL) {
+ imap_msgpart_free(&body->msgpart);
+ return 1;
+ }
+
+ if (imap_msgpart_size(mail, body->msgpart, &size) < 0)
+ return -1;
+
+ str = t_str_new(128);
+ if (ctx->state.cur_first)
+ ctx->state.cur_first = FALSE;
+ else
+ str_append_c(str, ' ');
+ str_printfa(str, "%s %"PRIuUOFF_T, get_body_name(body), size);
+
+ if (o_stream_send(ctx->client->output, str_data(str), str_len(str)) < 0)
+ return -1;
+ return 1;
+}
+
+/* Parse next digits in string into integer. Returns -1 if the integer
+ becomes too big and wraps. */
+static int read_uoff_t(const char **p, uoff_t *value)
+{
+ return str_parse_uoff(*p, value, p);
+}
+
+static int
+body_header_fields_parse(struct imap_fetch_init_context *ctx,
+ struct imap_fetch_body_data *body, const char *prefix,
+ const struct imap_arg *args, unsigned int args_count)
+{
+ string_t *str;
+ const char *value;
+ size_t i;
+
+ ctx->fetch_ctx->fetch_header_fields = TRUE;
+
+ str = str_new(ctx->pool, 128);
+ str_append(str, prefix);
+ str_append(str, " (");
+
+ for (i = 0; i < args_count; i++) {
+ if (!imap_arg_get_astring(&args[i], &value)) {
+ ctx->error = "Invalid BODY[..] parameter: "
+ "Header list contains non-strings";
+ return -1;
+ }
+ value = t_str_ucase(value);
+
+ if (i != 0)
+ str_append_c(str, ' ');
+
+ if (args[i].type == IMAP_ARG_ATOM)
+ str_append(str, value);
+ else
+ imap_append_quoted(str, value);
+ }
+ str_append_c(str, ')');
+ body->section = str_c(str);
+ return 0;
+}
+
+static int body_parse_partial(struct imap_fetch_body_data *body,
+ const char *p, const char **error_r)
+{
+ uoff_t offset, size = UOFF_T_MAX;
+
+ if (*p == '\0')
+ return 0;
+ /* <start.end> */
+ if (*p != '<') {
+ *error_r = "Unexpected data after ']'";
+ return -1;
+ }
+
+ p++;
+ body->partial = TRUE;
+
+ if (read_uoff_t(&p, &offset) < 0 || offset > OFF_T_MAX) {
+ /* wrapped */
+ *error_r = "Too big partial start";
+ return -1;
+ }
+
+ if (*p == '.') {
+ p++;
+ if (read_uoff_t(&p, &size) < 0 || size > OFF_T_MAX) {
+ /* wrapped */
+ *error_r = "Too big partial end";
+ return -1;
+ }
+ }
+
+ if (*p != '>') {
+ *error_r = "Missing '>' in partial";
+ return -1;
+ }
+ if (p[1] != '\0') {
+ *error_r = "Unexpected data after '>'";
+ return -1;
+ }
+ imap_msgpart_set_partial(body->msgpart, offset, size);
+ return 0;
+}
+
+bool imap_fetch_body_section_init(struct imap_fetch_init_context *ctx)
+{
+ struct imap_fetch_body_data *body;
+ const struct imap_arg *list_args;
+ unsigned int list_count;
+ const char *str, *p, *error;
+
+ i_assert(str_begins(ctx->name, "BODY"));
+ p = ctx->name + 4;
+
+ body = p_new(ctx->pool, struct imap_fetch_body_data, 1);
+
+ if (str_begins(p, ".PEEK"))
+ p += 5;
+ else
+ ctx->fetch_ctx->flags_update_seen = TRUE;
+ if (*p != '[') {
+ ctx->error = "Invalid BODY[..] parameter: Missing '['";
+ return FALSE;
+ }
+
+ if (imap_arg_get_list_full(&ctx->args[0], &list_args, &list_count)) {
+ /* BODY[HEADER.FIELDS.. (headers list)] */
+ if (!imap_arg_get_atom(&ctx->args[1], &str) ||
+ str[0] != ']') {
+ ctx->error = "Invalid BODY[..] parameter: Missing ']'";
+ return FALSE;
+ }
+ if (body_header_fields_parse(ctx, body, p+1,
+ list_args, list_count) < 0)
+ return FALSE;
+ p = str+1;
+ ctx->args += 2;
+ } else {
+ /* no headers list */
+ body->section = p+1;
+ p = strchr(body->section, ']');
+ if (p == NULL) {
+ ctx->error = "Invalid BODY[..] parameter: Missing ']'";
+ return FALSE;
+ }
+ body->section = p_strdup_until(ctx->pool, body->section, p);
+ p++;
+ }
+ if (imap_msgpart_parse(body->section, &body->msgpart) < 0) {
+ ctx->error = "Invalid BODY[..] section";
+ return FALSE;
+ }
+ ctx->fetch_ctx->fetch_data |=
+ imap_msgpart_get_fetch_data(body->msgpart);
+ imap_msgpart_get_wanted_headers(body->msgpart, &ctx->fetch_ctx->all_headers);
+
+ if (body_parse_partial(body, p, &error) < 0) {
+ ctx->error = p_strdup_printf(ctx->pool,
+ "Invalid BODY[..] parameter: %s", error);
+ return FALSE;
+ }
+
+ /* update the section name for the imap_fetch_add_handler() */
+ ctx->name = p_strdup(ctx->pool, get_body_name(body));
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT,
+ "NIL", fetch_body_msgpart, body);
+ return TRUE;
+}
+
+bool imap_fetch_binary_init(struct imap_fetch_init_context *ctx)
+{
+ struct imap_fetch_body_data *body;
+ const struct imap_arg *list_args;
+ unsigned int list_count;
+ const char *str, *p, *error;
+
+ i_assert(str_begins(ctx->name, "BINARY"));
+ p = ctx->name + 6;
+
+ body = p_new(ctx->pool, struct imap_fetch_body_data, 1);
+ body->binary = TRUE;
+
+ if (str_begins(p, ".SIZE")) {
+ /* fetch decoded size of the section */
+ p += 5;
+ body->binary_size = TRUE;
+ } else if (str_begins(p, ".PEEK")) {
+ p += 5;
+ } else {
+ ctx->fetch_ctx->flags_update_seen = TRUE;
+ }
+ if (*p != '[') {
+ ctx->error = "Invalid BINARY[..] parameter: Missing '['";
+ return FALSE;
+ }
+
+ if (imap_arg_get_list_full(&ctx->args[0], &list_args, &list_count)) {
+ /* BINARY[HEADER.FIELDS.. (headers list)] */
+ if (!imap_arg_get_atom(&ctx->args[1], &str) ||
+ str[0] != ']') {
+ ctx->error = "Invalid BINARY[..] parameter: Missing ']'";
+ return FALSE;
+ }
+ if (body_header_fields_parse(ctx, body, p+1,
+ list_args, list_count) < 0)
+ return FALSE;
+ p = str+1;
+ ctx->args += 2;
+ } else {
+ /* no headers list */
+ body->section = p+1;
+ p = strchr(body->section, ']');
+ if (p == NULL) {
+ ctx->error = "Invalid BINARY[..] parameter: Missing ']'";
+ return FALSE;
+ }
+ body->section = p_strdup_until(ctx->pool, body->section, p);
+ p++;
+ }
+ if (imap_msgpart_parse(body->section, &body->msgpart) < 0) {
+ ctx->error = "Invalid BINARY[..] section";
+ return FALSE;
+ }
+ imap_msgpart_set_decode_to_binary(body->msgpart);
+ ctx->fetch_ctx->fetch_data |=
+ imap_msgpart_get_fetch_data(body->msgpart);
+
+ if (!body->binary_size) {
+ if (body_parse_partial(body, p, &error) < 0) {
+ ctx->error = p_strdup_printf(ctx->pool,
+ "Invalid BINARY[..] parameter: %s", error);
+ return FALSE;
+ }
+ }
+
+ /* update the section name for the imap_fetch_add_handler() */
+ ctx->name = p_strdup(ctx->pool, get_body_name(body));
+ if (body->binary_size) {
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT,
+ "0", fetch_binary_size, body);
+ } else {
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT,
+ "NIL", fetch_body_msgpart, body);
+ }
+ return TRUE;
+}
+
+static int ATTR_NULL(3)
+fetch_rfc822_size(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ uoff_t size;
+
+ if (mail_get_virtual_size(mail, &size) < 0)
+ return -1;
+
+ str_printfa(ctx->state.cur_str, "RFC822.SIZE %"PRIuUOFF_T" ", size);
+ return 1;
+}
+
+static int
+fetch_and_free_msgpart(struct imap_fetch_context *ctx,
+ struct mail *mail, struct imap_msgpart **_msgpart)
+{
+ struct imap_msgpart_open_result result;
+ int ret;
+
+ ret = imap_msgpart_open(mail, *_msgpart, &result);
+ imap_msgpart_free(_msgpart);
+ if (ret < 0)
+ return -1;
+ i_assert(result.input->v_offset == 0);
+ ctx->state.cur_input = result.input;
+ ctx->state.cur_size = result.size;
+ ctx->state.cur_size_field = result.size_field;
+ ctx->state.cont_handler = fetch_stream_continue;
+ return 0;
+}
+
+static int ATTR_NULL(3)
+fetch_rfc822(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ struct imap_msgpart *msgpart;
+ const char *str;
+
+ msgpart = imap_msgpart_full();
+ fetch_state_update_stats(ctx, msgpart);
+ if (fetch_and_free_msgpart(ctx, mail, &msgpart) < 0)
+ return -1;
+
+ str = t_strdup_printf(" RFC822 {%"PRIuUOFF_T"}\r\n",
+ ctx->state.cur_size);
+ if (ctx->state.cur_first) {
+ str++; ctx->state.cur_first = FALSE;
+ }
+ o_stream_nsend_str(ctx->client->output, str);
+
+ ctx->state.cur_human_name = "RFC822";
+ return ctx->state.cont_handler(ctx);
+}
+
+static int ATTR_NULL(3)
+fetch_rfc822_header(struct imap_fetch_context *ctx,
+ struct mail *mail, void *context ATTR_UNUSED)
+{
+ struct imap_msgpart *msgpart;
+ const char *str;
+
+ msgpart = imap_msgpart_header();
+ fetch_state_update_stats(ctx, msgpart);
+ if (fetch_and_free_msgpart(ctx, mail, &msgpart) < 0)
+ return -1;
+
+ str = t_strdup_printf(" RFC822.HEADER {%"PRIuUOFF_T"}\r\n",
+ ctx->state.cur_size);
+ if (ctx->state.cur_first) {
+ str++; ctx->state.cur_first = FALSE;
+ }
+ o_stream_nsend_str(ctx->client->output, str);
+
+ ctx->state.cur_human_name = "RFC822.HEADER";
+ return ctx->state.cont_handler(ctx);
+}
+
+static int ATTR_NULL(3)
+fetch_rfc822_text(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ struct imap_msgpart *msgpart;
+ const char *str;
+
+ msgpart = imap_msgpart_body();
+ fetch_state_update_stats(ctx, msgpart);
+ if (fetch_and_free_msgpart(ctx, mail, &msgpart) < 0)
+ return -1;
+
+ str = t_strdup_printf(" RFC822.TEXT {%"PRIuUOFF_T"}\r\n",
+ ctx->state.cur_size);
+ if (ctx->state.cur_first) {
+ str++; ctx->state.cur_first = FALSE;
+ }
+ o_stream_nsend_str(ctx->client->output, str);
+
+ ctx->state.cur_human_name = "RFC822.TEXT";
+ return ctx->state.cont_handler(ctx);
+}
+
+bool imap_fetch_rfc822_init(struct imap_fetch_init_context *ctx)
+{
+ const char *name = ctx->name;
+
+ if (name[6] == '\0') {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
+ MAIL_FETCH_STREAM_BODY;
+ ctx->fetch_ctx->flags_update_seen = TRUE;
+ imap_fetch_add_handler(ctx, 0, "NIL",
+ fetch_rfc822, NULL);
+ return TRUE;
+ }
+
+ if (strcmp(name+6, ".SIZE") == 0) {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_VIRTUAL_SIZE;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "0", fetch_rfc822_size, NULL);
+ return TRUE;
+ }
+ if (strcmp(name+6, ".HEADER") == 0) {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
+ imap_fetch_add_handler(ctx, 0, "NIL",
+ fetch_rfc822_header, NULL);
+ return TRUE;
+ }
+ if (strcmp(name+6, ".TEXT") == 0) {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
+ ctx->fetch_ctx->flags_update_seen = TRUE;
+ imap_fetch_add_handler(ctx, 0, "NIL",
+ fetch_rfc822_text, NULL);
+ return TRUE;
+ }
+
+ ctx->error = t_strconcat("Unknown parameter ", name, NULL);
+ return FALSE;
+}
+
+static int ATTR_NULL(3)
+fetch_snippet(struct imap_fetch_context *ctx, struct mail *mail,
+ struct imap_fetch_preview_data *preview)
+{
+ enum mail_lookup_abort temp_lookup_abort = preview->lazy ? MAIL_LOOKUP_ABORT_NOT_IN_CACHE_START_CACHING : mail->lookup_abort;
+ enum mail_lookup_abort orig_lookup_abort = mail->lookup_abort;
+ const char *resp, *snippet;
+ int ret;
+
+ mail->lookup_abort = temp_lookup_abort;
+ ret = mail_get_special(mail, MAIL_FETCH_BODY_SNIPPET, &snippet);
+ mail->lookup_abort = orig_lookup_abort;
+
+ resp = preview->preview_cmd ? "PREVIEW" : "SNIPPET";
+
+ if (ret == 0) {
+ /* got it => nothing to do */
+ snippet++; /* skip over snippet version byte */
+ } else if (mailbox_get_last_mail_error(mail->box) != MAIL_ERROR_LOOKUP_ABORTED) {
+ /* actual error => bail */
+ return -1;
+ } else {
+ /*
+ * Two ways to get here:
+ * - not in cache && lazy => give up
+ * - not in cache && !lazy => someone higher up set
+ * MAIL_LOOKUP_ABORT_NOT_IN_CACHE and so even though we got
+ * a non-lazy request we failed the cache lookup.
+ *
+ * This is not an error, but since the scenario is
+ * sufficiently convoluted this else branch serves to
+ * document it.
+ *
+ * This path will return NIL as the preview response.
+ */
+ }
+
+ str_append(ctx->state.cur_str, resp);
+ if (preview->old_standard)
+ str_append(ctx->state.cur_str, " (FUZZY ");
+ else
+ str_append(ctx->state.cur_str, " ");
+ if (ret == 0)
+ imap_append_string(ctx->state.cur_str, snippet);
+ else
+ str_append(ctx->state.cur_str, "NIL");
+ if (preview->old_standard)
+ str_append(ctx->state.cur_str, ")");
+ str_append_c(ctx->state.cur_str, ' ');
+
+ return 1;
+}
+
+static bool fetch_preview_init(struct imap_fetch_init_context *ctx,
+ bool preview_cmd)
+{
+ const struct imap_arg *list_args;
+ unsigned int list_count;
+ struct imap_fetch_preview_data *preview;
+
+ preview = p_new(ctx->pool, struct imap_fetch_preview_data, 1);
+ preview->preview_cmd = preview_cmd;
+ /* We can tell we are using the "Old" (pre-RFC 8970) standard
+ * if:
+ * 1) SNIPPET command is used
+ * 2) FUZZY algorithm is explicitly requested
+ * The one usage we cannot catch is if PREVIEW command is used, and
+ * no algorithm is specified - the pre-RFC standard requires that the
+ * algorithm must be output as part of the response; the RFC standard
+ * does not output the algorithm. There is unfortunately nothing we
+ * can do about this, so we need to send the standards compliant
+ * way. */
+ preview->old_standard = !preview_cmd;
+
+ if (imap_arg_get_list_full(&ctx->args[0], &list_args, &list_count)) {
+ unsigned int i;
+
+ for (i = 0; i < list_count; i++) {
+ const char *str;
+
+ if (!imap_arg_get_atom(&list_args[i], &str)) {
+ ctx->error = "Invalid PREVIEW modifier";
+ return FALSE;
+ }
+
+ if (strcasecmp(str, "LAZY") == 0) {
+ preview->lazy = TRUE;
+ } else if (strcasecmp(str, "LAZY=FUZZY") == 0) {
+ preview->lazy = TRUE;
+ preview->old_standard = TRUE;
+ } else if (strcasecmp(str, "FUZZY") == 0) {
+ preview->old_standard = TRUE;
+ } else {
+ ctx->error = t_strdup_printf("'%s' is not a "
+ "supported PREVIEW modifier",
+ str);
+ return FALSE;
+ }
+ }
+
+ ctx->args += list_count;
+ }
+
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_BODY_SNIPPET;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "NIL", fetch_snippet, preview);
+ return TRUE;
+}
+
+bool imap_fetch_preview_init(struct imap_fetch_init_context *ctx)
+{
+ return fetch_preview_init(ctx, TRUE);
+}
+
+bool imap_fetch_snippet_init(struct imap_fetch_init_context *ctx)
+{
+ return fetch_preview_init(ctx, FALSE);
+}
diff --git a/src/imap/imap-fetch.c b/src/imap/imap-fetch.c
new file mode 100644
index 0000000..db811f7
--- /dev/null
+++ b/src/imap/imap-fetch.c
@@ -0,0 +1,1040 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "array.h"
+#include "buffer.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "message-size.h"
+#include "imap-date.h"
+#include "imap-utf7.h"
+#include "mail-search-build.h"
+#include "imap-commands.h"
+#include "imap-quote.h"
+#include "imap-fetch.h"
+#include "imap-util.h"
+
+#include <ctype.h>
+
+#define BODY_NIL_REPLY \
+ "\"text\" \"plain\" NIL NIL NIL \"7bit\" 0 0"
+#define ENVELOPE_NIL_REPLY \
+ "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)"
+
+static ARRAY(struct imap_fetch_handler) fetch_handlers;
+
+static int imap_fetch_handler_cmp(const struct imap_fetch_handler *h1,
+ const struct imap_fetch_handler *h2)
+{
+ return strcmp(h1->name, h2->name);
+}
+
+void imap_fetch_handlers_register(const struct imap_fetch_handler *handlers,
+ size_t count)
+{
+ array_append(&fetch_handlers, handlers, count);
+ array_sort(&fetch_handlers, imap_fetch_handler_cmp);
+}
+
+void imap_fetch_handler_unregister(const char *name)
+{
+ const struct imap_fetch_handler *handler, *first_handler;
+
+ first_handler = array_front(&fetch_handlers);
+ handler = imap_fetch_handler_lookup(name);
+ i_assert(handler != NULL);
+ array_delete(&fetch_handlers, handler - first_handler, 1);
+}
+
+static int
+imap_fetch_handler_bsearch(const char *name, const struct imap_fetch_handler *h)
+{
+ return strcmp(name, h->name);
+}
+
+const struct imap_fetch_handler *imap_fetch_handler_lookup(const char *name)
+{
+ return array_bsearch(&fetch_handlers, name, imap_fetch_handler_bsearch);
+}
+
+bool imap_fetch_init_handler(struct imap_fetch_init_context *init_ctx)
+{
+ const struct imap_fetch_handler *handler;
+ const char *lookup_name, *p;
+
+ for (p = init_ctx->name; i_isalnum(*p) || *p == '-'; p++) ;
+ lookup_name = t_strdup_until(init_ctx->name, p);
+
+ handler = array_bsearch(&fetch_handlers, lookup_name,
+ imap_fetch_handler_bsearch);
+ if (handler == NULL) {
+ init_ctx->error = t_strdup_printf("Unknown parameter: %s",
+ init_ctx->name);
+ return FALSE;
+ }
+ if (!handler->init(init_ctx)) {
+ i_assert(init_ctx->error != NULL);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void imap_fetch_init_nofail_handler(struct imap_fetch_context *ctx,
+ bool (*init)(struct imap_fetch_init_context *))
+{
+ struct imap_fetch_init_context init_ctx;
+
+ i_zero(&init_ctx);
+ init_ctx.fetch_ctx = ctx;
+ init_ctx.pool = ctx->ctx_pool;
+
+ if (!init(&init_ctx))
+ i_unreached();
+}
+
+int imap_fetch_att_list_parse(struct client *client, pool_t pool,
+ const struct imap_arg *list,
+ struct imap_fetch_context **fetch_ctx_r,
+ const char **client_error_r)
+{
+ struct imap_fetch_init_context init_ctx;
+ const char *str;
+
+ i_zero(&init_ctx);
+ init_ctx.fetch_ctx = imap_fetch_alloc(client, pool, "NOTIFY");
+ init_ctx.pool = pool;
+ init_ctx.args = list;
+
+ while (imap_arg_get_atom(init_ctx.args, &str)) {
+ init_ctx.name = t_str_ucase(str);
+ init_ctx.args++;
+ if (!imap_fetch_init_handler(&init_ctx)) {
+ *client_error_r = t_strconcat("Invalid fetch-att list: ",
+ init_ctx.error, NULL);
+ imap_fetch_free(&init_ctx.fetch_ctx);
+ return -1;
+ }
+ }
+ if (!IMAP_ARG_IS_EOL(init_ctx.args)) {
+ *client_error_r = "fetch-att list contains non-atoms.";
+ imap_fetch_free(&init_ctx.fetch_ctx);
+ return -1;
+ }
+ *fetch_ctx_r = init_ctx.fetch_ctx;
+ return 0;
+}
+
+struct imap_fetch_context *
+imap_fetch_alloc(struct client *client, pool_t pool, const char *reason)
+{
+ struct imap_fetch_context *ctx;
+
+ ctx = p_new(pool, struct imap_fetch_context, 1);
+ ctx->client = client;
+ ctx->ctx_pool = pool;
+ ctx->reason = p_strdup(pool, reason);
+ pool_ref(pool);
+
+ p_array_init(&ctx->all_headers, pool, 64);
+ p_array_init(&ctx->handlers, pool, 16);
+ p_array_init(&ctx->tmp_keywords, pool,
+ client->keywords.announce_count + 8);
+ return ctx;
+}
+
+#undef imap_fetch_add_handler
+void imap_fetch_add_handler(struct imap_fetch_init_context *ctx,
+ enum imap_fetch_handler_flags flags,
+ const char *nil_reply,
+ imap_fetch_handler_t *handler, void *context)
+{
+ /* partially because of broken clients, but also partially because
+ it potentially can make client implementations faster, we have a
+ buffered parameter which basically means that the handler promises
+ to write the output in fetch_ctx->state.cur_str. The cur_str is then
+ sent to client before calling any non-buffered handlers.
+
+ We try to keep the handler registration order the same as the
+ client requested them. This is especially useful to get UID
+ returned first, which some clients rely on..
+ */
+ const struct imap_fetch_context_handler *ctx_handler;
+ struct imap_fetch_context_handler h;
+
+ if (context == NULL) {
+ /* don't allow duplicate handlers */
+ array_foreach(&ctx->fetch_ctx->handlers, ctx_handler) {
+ if (ctx_handler->handler == handler &&
+ ctx_handler->context == NULL)
+ return;
+ }
+ }
+
+ i_zero(&h);
+ h.handler = handler;
+ h.context = context;
+ h.buffered = (flags & IMAP_FETCH_HANDLER_FLAG_BUFFERED) != 0;
+ h.want_deinit = (flags & IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT) != 0;
+ h.name = p_strdup(ctx->pool, ctx->name);
+ h.nil_reply = p_strdup(ctx->pool, nil_reply);
+
+ if (!h.buffered)
+ array_push_back(&ctx->fetch_ctx->handlers, &h);
+ else {
+ array_insert(&ctx->fetch_ctx->handlers,
+ ctx->fetch_ctx->buffered_handlers_count, &h, 1);
+ ctx->fetch_ctx->buffered_handlers_count++;
+ }
+}
+
+static void
+expunges_drop_known(struct mailbox *box,
+ const struct imap_fetch_qresync_args *qresync_args,
+ struct mailbox_transaction_context *trans,
+ ARRAY_TYPE(seq_range) *expunged_uids)
+{
+ struct mailbox_status status;
+ struct mail *mail;
+ const uint32_t *seqs, *uids;
+ unsigned int i, count;
+
+ seqs = array_get(qresync_args->qresync_sample_seqset, &count);
+ uids = array_front(qresync_args->qresync_sample_uidset);
+ i_assert(array_count(qresync_args->qresync_sample_uidset) == count);
+ i_assert(count > 0);
+
+ mailbox_get_open_status(box, STATUS_MESSAGES, &status);
+ mail = mail_alloc(trans, 0, NULL);
+
+ /* FIXME: we could do removals from the middle as well */
+ for (i = 0; i < count && seqs[i] <= status.messages; i++) {
+ mail_set_seq(mail, seqs[i]);
+ if (uids[i] != mail->uid)
+ break;
+ }
+ if (i > 0)
+ seq_range_array_remove_range(expunged_uids, 1, uids[i-1]);
+ mail_free(&mail);
+}
+
+static int
+get_expunges_fallback(struct mailbox *box,
+ const struct imap_fetch_qresync_args *qresync_args,
+ const ARRAY_TYPE(seq_range) *uid_filter_arr,
+ ARRAY_TYPE(seq_range) *expunged_uids)
+{
+ struct mailbox_transaction_context *trans;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ const struct seq_range *uid_filter;
+ struct mailbox_status status;
+ unsigned int i, count;
+ uint32_t next_uid;
+ int ret = 0;
+
+ uid_filter = array_get(uid_filter_arr, &count);
+ i_assert(count > 0);
+ i = 0;
+ next_uid = uid_filter[0].seq1;
+
+ /* search UIDs only in given range */
+ search_args = mail_search_build_init();
+ search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
+ search_args->args->type = SEARCH_UIDSET;
+ p_array_init(&search_args->args->value.seqset, search_args->pool, count);
+ array_append_array(&search_args->args->value.seqset, uid_filter_arr);
+
+ trans = mailbox_transaction_begin(box, 0, "FETCH send VANISHED");
+ search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ while (mailbox_search_next(search_ctx, &mail)) {
+ if (mail->uid == next_uid) {
+ if (next_uid < uid_filter[i].seq2)
+ next_uid++;
+ else if (++i < count)
+ next_uid = uid_filter[i].seq1;
+ else
+ break;
+ } else {
+ /* next_uid .. mail->uid-1 are expunged */
+ i_assert(mail->uid > next_uid);
+ while (mail->uid > uid_filter[i].seq2) {
+ seq_range_array_add_range(expunged_uids,
+ next_uid,
+ uid_filter[i].seq2);
+ i++;
+ i_assert(i < count);
+ next_uid = uid_filter[i].seq1;
+ }
+ if (next_uid != mail->uid) {
+ seq_range_array_add_range(expunged_uids,
+ next_uid,
+ mail->uid - 1);
+ }
+ if (uid_filter[i].seq2 != mail->uid)
+ next_uid = mail->uid + 1;
+ else if (++i < count)
+ next_uid = uid_filter[i].seq1;
+ else
+ break;
+ }
+ }
+ if (i < count) {
+ i_assert(next_uid <= uid_filter[i].seq2);
+ seq_range_array_add_range(expunged_uids, next_uid,
+ uid_filter[i].seq2);
+ i++;
+ }
+ for (; i < count; i++) {
+ seq_range_array_add_range(expunged_uids, uid_filter[i].seq1,
+ uid_filter[i].seq2);
+ }
+
+ mailbox_get_open_status(box, STATUS_UIDNEXT, &status);
+ seq_range_array_remove_range(expunged_uids, status.uidnext,
+ (uint32_t)-1);
+
+ if (mailbox_search_deinit(&search_ctx) < 0)
+ ret = -1;
+
+ if (ret == 0 && qresync_args->qresync_sample_seqset != NULL &&
+ array_is_created(qresync_args->qresync_sample_seqset))
+ expunges_drop_known(box, qresync_args, trans, expunged_uids);
+
+ (void)mailbox_transaction_commit(&trans);
+ return ret;
+}
+
+int imap_fetch_send_vanished(struct client *client, struct mailbox *box,
+ const struct mail_search_args *search_args,
+ const struct imap_fetch_qresync_args *qresync_args)
+{
+ const struct mail_search_arg *uidarg = search_args->args;
+ const struct mail_search_arg *modseqarg = uidarg->next;
+ const ARRAY_TYPE(seq_range) *uid_filter;
+ uint64_t modseq;
+ ARRAY_TYPE(seq_range) expunged_uids_range;
+ string_t *str;
+ int ret = 0;
+
+ i_assert(uidarg->type == SEARCH_UIDSET);
+ i_assert(modseqarg->type == SEARCH_MODSEQ);
+
+ uid_filter = &uidarg->value.seqset;
+ modseq = modseqarg->value.modseq->modseq - 1;
+
+ i_array_init(&expunged_uids_range, array_count(uid_filter));
+ if (!mailbox_get_expunged_uids(box, modseq, uid_filter, &expunged_uids_range)) {
+ /* return all expunged UIDs */
+ if (get_expunges_fallback(box, qresync_args, uid_filter,
+ &expunged_uids_range) < 0) {
+ array_clear(&expunged_uids_range);
+ ret = -1;
+ }
+ }
+ if (array_count(&expunged_uids_range) > 0) {
+ str = str_new(default_pool, 128);
+ str_append(str, "* VANISHED (EARLIER) ");
+ imap_write_seq_range(str, &expunged_uids_range);
+ str_append(str, "\r\n");
+ o_stream_nsend(client->output, str_data(str), str_len(str));
+ str_free(&str);
+ }
+ array_free(&expunged_uids_range);
+ return ret;
+}
+
+static void imap_fetch_init(struct imap_fetch_context *ctx)
+{
+ if (ctx->initialized)
+ return;
+ ctx->initialized = TRUE;
+
+ if (ctx->flags_update_seen && !ctx->flags_have_handler) {
+ ctx->flags_show_only_seen_changes = TRUE;
+ imap_fetch_init_nofail_handler(ctx, imap_fetch_flags_init);
+ }
+ if ((ctx->fetch_data &
+ (MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY)) != 0)
+ ctx->fetch_data |= MAIL_FETCH_NUL_STATE;
+}
+
+void imap_fetch_begin(struct imap_fetch_context *ctx, struct mailbox *box,
+ struct mail_search_args *search_args)
+{
+ enum mailbox_transaction_flags trans_flags =
+ MAILBOX_TRANSACTION_FLAG_REFRESH;
+ struct mailbox_header_lookup_ctx *wanted_headers = NULL;
+ const char *const *headers;
+
+ i_assert(!ctx->state.fetching);
+
+ imap_fetch_init(ctx);
+ i_zero(&ctx->state);
+
+ if (array_count(&ctx->all_headers) > 0 &&
+ ((ctx->fetch_data & (MAIL_FETCH_STREAM_HEADER |
+ MAIL_FETCH_STREAM_BODY)) == 0)) {
+ array_append_zero(&ctx->all_headers);
+
+ headers = array_front(&ctx->all_headers);
+ wanted_headers = mailbox_header_lookup_init(box, headers);
+ array_pop_back(&ctx->all_headers);
+ }
+
+ if (ctx->flags_update_seen) {
+ /* Hide the implicit \Seen flag addition. Otherwise a separate
+ untagged FETCH FLAGS (\Seen) would be sent on top of the
+ one FLAGS (\Seen) already added in the main FETCH reply.
+
+ We don't set this always, because some plugins might want
+ to do their own flag changes which we don't want hidden.
+ (Of course this isn't perfect since if implicit \Seen flags
+ are added, other flag changes are also hidden.) */
+ trans_flags |= MAILBOX_TRANSACTION_FLAG_HIDE;
+ }
+ ctx->state.trans = mailbox_transaction_begin(box, trans_flags,
+ ctx->reason);
+
+ mail_search_args_init(search_args, box, TRUE,
+ &ctx->client->search_saved_uidset);
+ ctx->state.search_ctx =
+ mailbox_search_init(ctx->state.trans, search_args, NULL,
+ ctx->fetch_data, wanted_headers);
+ ctx->state.cur_str = str_new(default_pool, 8192);
+ ctx->state.fetching = TRUE;
+
+ mailbox_header_lookup_unref(&wanted_headers);
+}
+
+static int imap_fetch_flush_buffer(struct imap_fetch_context *ctx)
+{
+ const unsigned char *data;
+ size_t len;
+
+ data = str_data(ctx->state.cur_str);
+ len = str_len(ctx->state.cur_str);
+
+ if (len == 0)
+ return 0;
+
+ /* there's an extra space at the end if we added any fetch items
+ to buffer */
+ if (data[len-1] == ' ') {
+ len--;
+ ctx->state.cur_first = FALSE;
+ }
+ ctx->state.line_partial = TRUE;
+
+ if (o_stream_send(ctx->client->output, data, len) < 0)
+ return -1;
+
+ str_truncate(ctx->state.cur_str, 0);
+ return 0;
+}
+
+static int imap_fetch_send_nil_reply(struct imap_fetch_context *ctx)
+{
+ const struct imap_fetch_context_handler *handler;
+
+ if (!ctx->state.cur_first)
+ str_append_c(ctx->state.cur_str, ' ');
+
+ handler = array_idx(&ctx->handlers, ctx->state.cur_handler);
+ str_printfa(ctx->state.cur_str, "%s %s ",
+ handler->name, handler->nil_reply);
+
+ if (!handler->buffered) {
+ if (imap_fetch_flush_buffer(ctx) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void imap_fetch_fix_empty_reply(struct imap_fetch_context *ctx)
+{
+ if (ctx->state.line_partial && ctx->state.cur_first) {
+ /* we've flushed an empty "FETCH (" reply so
+ far. we can't take it back, but RFC 3501
+ doesn't allow returning empty "FETCH ()"
+ either, so just add the current message's
+ UID there. */
+ str_printfa(ctx->state.cur_str, "UID %u ",
+ ctx->state.cur_mail->uid);
+ }
+}
+
+static bool imap_fetch_cur_failed(struct imap_fetch_context *ctx)
+{
+ ctx->failures = TRUE;
+ if (ctx->client->set->parsed_fetch_failure ==
+ IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY)
+ return FALSE;
+
+ if (!array_is_created(&ctx->fetch_failed_uids))
+ p_array_init(&ctx->fetch_failed_uids, ctx->ctx_pool, 8);
+ seq_range_array_add(&ctx->fetch_failed_uids, ctx->state.cur_mail->uid);
+
+ if (ctx->error == MAIL_ERROR_NONE) {
+ /* preserve the first error, since it may change in storage. */
+ ctx->errstr = p_strdup(ctx->ctx_pool,
+ mailbox_get_last_error(ctx->state.cur_mail->box, &ctx->error));
+ }
+ return TRUE;
+}
+
+static int imap_fetch_more_int(struct imap_fetch_context *ctx, bool cancel)
+{
+ struct imap_fetch_state *state = &ctx->state;
+ struct client *client = ctx->client;
+ const struct imap_fetch_context_handler *handlers;
+ unsigned int count;
+ int ret;
+
+ if (state->cont_handler != NULL) {
+ ret = state->cont_handler(ctx);
+ if (ret == 0)
+ return 0;
+
+ if (ret < 0) {
+ if (client->output->closed)
+ return -1;
+
+ if (state->cur_mail->expunged) {
+ /* not an error, just lost it. */
+ state->skipped_expunged_msgs = TRUE;
+ if (imap_fetch_send_nil_reply(ctx) < 0)
+ return -1;
+ } else {
+ if (!imap_fetch_cur_failed(ctx))
+ return -1;
+ }
+ }
+
+ state->cont_handler = NULL;
+ state->cur_handler++;
+ if (state->cur_input != NULL)
+ i_stream_unref(&state->cur_input);
+ }
+
+ handlers = array_get(&ctx->handlers, &count);
+ for (;;) {
+ if (o_stream_get_buffer_used_size(client->output) >=
+ CLIENT_OUTPUT_OPTIMAL_SIZE) {
+ ret = o_stream_flush(client->output);
+ if (ret <= 0)
+ return ret;
+ }
+
+ if (state->cur_mail == NULL) {
+ if (cancel)
+ return 1;
+
+ if (!mailbox_search_next(state->search_ctx,
+ &state->cur_mail))
+ break;
+
+ str_printfa(state->cur_str, "* %u FETCH (",
+ state->cur_mail->seq);
+ ctx->fetched_mails_count++;
+ state->cur_first = TRUE;
+ state->cur_str_prefix_size = str_len(state->cur_str);
+ i_assert(!state->line_partial);
+ }
+
+ for (; state->cur_handler < count; state->cur_handler++) {
+ if (str_len(state->cur_str) > 0 &&
+ !handlers[state->cur_handler].buffered) {
+ /* first non-buffered handler.
+ flush the buffer. */
+ if (imap_fetch_flush_buffer(ctx) < 0)
+ return -1;
+ }
+
+ i_assert(state->cur_input == NULL);
+ T_BEGIN {
+ const struct imap_fetch_context_handler *h =
+ &handlers[state->cur_handler];
+
+ ret = h->handler(ctx, state->cur_mail,
+ h->context);
+ } T_END;
+
+ if (ret == 0)
+ return 0;
+
+ if (ret < 0) {
+ if (state->cur_mail->expunged) {
+ /* not an error, just lost it. */
+ state->skipped_expunged_msgs = TRUE;
+ if (imap_fetch_send_nil_reply(ctx) < 0)
+ return -1;
+ } else {
+ i_assert(ret < 0 ||
+ state->cont_handler != NULL);
+ if (!imap_fetch_cur_failed(ctx))
+ return -1;
+ }
+ }
+
+ state->cont_handler = NULL;
+ if (state->cur_input != NULL)
+ i_stream_unref(&state->cur_input);
+ }
+
+ imap_fetch_fix_empty_reply(ctx);
+ if (str_len(state->cur_str) > 0 &&
+ (state->line_partial ||
+ str_len(state->cur_str) != state->cur_str_prefix_size)) {
+ /* no non-buffered handlers */
+ if (imap_fetch_flush_buffer(ctx) < 0)
+ return -1;
+ }
+
+ if (state->line_partial)
+ o_stream_nsend(client->output, ")\r\n", 3);
+ client->last_output = ioloop_time;
+
+ state->cur_mail = NULL;
+ state->cur_handler = 0;
+ state->line_partial = FALSE;
+ }
+
+ return ctx->failures ? -1 : 1;
+}
+
+int imap_fetch_more(struct imap_fetch_context *ctx,
+ struct client_command_context *cmd)
+{
+ int ret;
+
+ i_assert(ctx->client->output_cmd_lock == NULL ||
+ ctx->client->output_cmd_lock == cmd);
+
+ ret = imap_fetch_more_int(ctx, cmd->cancel);
+ if (ret < 0)
+ ctx->state.failed = TRUE;
+ if (ctx->state.line_partial) {
+ /* nothing can be sent until FETCH is finished */
+ ctx->client->output_cmd_lock = cmd;
+ }
+ if (cmd->cancel && ctx->client->output_cmd_lock != NULL) {
+ /* canceling didn't really work. we must not output
+ anything anymore. */
+ if (!ctx->client->destroyed)
+ client_disconnect(ctx->client, "Failed to cancel FETCH");
+ ctx->client->output_cmd_lock = NULL;
+ }
+ return ret;
+}
+
+int imap_fetch_more_no_lock_update(struct imap_fetch_context *ctx)
+{
+ int ret;
+
+ ret = imap_fetch_more_int(ctx, FALSE);
+ if (ret < 0) {
+ ctx->state.failed = TRUE;
+ if (ctx->state.line_partial) {
+ /* we can't send any more replies to client, because
+ the FETCH reply wasn't fully sent. */
+ client_disconnect(ctx->client,
+ "NOTIFY failed in the middle of FETCH reply");
+ }
+ }
+ return ret;
+}
+
+int imap_fetch_end(struct imap_fetch_context *ctx)
+{
+ struct imap_fetch_state *state = &ctx->state;
+
+ if (ctx->state.fetching) {
+ ctx->state.fetching = FALSE;
+ if (state->line_partial) {
+ imap_fetch_fix_empty_reply(ctx);
+ if (imap_fetch_flush_buffer(ctx) < 0)
+ state->failed = TRUE;
+ if (o_stream_send(ctx->client->output, ")\r\n", 3) < 0)
+ state->failed = TRUE;
+ }
+ }
+ ctx->client->output_cmd_lock = NULL;
+
+ str_free(&state->cur_str);
+
+ i_stream_unref(&state->cur_input);
+
+ if (state->search_ctx != NULL) {
+ if (mailbox_search_deinit(&state->search_ctx) < 0)
+ state->failed = TRUE;
+ }
+
+ if (state->trans != NULL) {
+ /* even if something failed, we want to commit changes to
+ cache, as well as possible \Seen flag changes for FETCH
+ replies we returned so far. */
+ if (mailbox_transaction_commit(&state->trans) < 0)
+ state->failed = TRUE;
+ }
+ return state->failed ? -1 : 0;
+}
+
+void imap_fetch_free(struct imap_fetch_context **_ctx)
+{
+ struct imap_fetch_context *ctx = *_ctx;
+ const struct imap_fetch_context_handler *handler;
+
+ *_ctx = NULL;
+
+ (void)imap_fetch_end(ctx);
+
+ array_foreach(&ctx->handlers, handler) {
+ if (handler->want_deinit)
+ handler->handler(ctx, NULL, handler->context);
+ }
+ pool_unref(&ctx->ctx_pool);
+}
+
+static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ const char *body;
+
+ if (mail_get_special(mail, MAIL_FETCH_IMAP_BODY, &body) < 0)
+ return -1;
+
+ if (ctx->state.cur_first)
+ ctx->state.cur_first = FALSE;
+ else {
+ if (o_stream_send(ctx->client->output, " ", 1) < 0)
+ return -1;
+ }
+
+ if (o_stream_send(ctx->client->output, "BODY (", 6) < 0 ||
+ o_stream_send_str(ctx->client->output, body) < 0 ||
+ o_stream_send(ctx->client->output, ")", 1) < 0)
+ return -1;
+ return 1;
+}
+
+static bool fetch_body_init(struct imap_fetch_init_context *ctx)
+{
+ if (ctx->name[4] == '\0') {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_BODY;
+ imap_fetch_add_handler(ctx, 0, "("BODY_NIL_REPLY")",
+ fetch_body, NULL);
+ return TRUE;
+ }
+ return imap_fetch_body_section_init(ctx);
+}
+
+static int fetch_bodystructure(struct imap_fetch_context *ctx,
+ struct mail *mail, void *context ATTR_UNUSED)
+{
+ const char *bodystructure;
+
+ if (mail_get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE,
+ &bodystructure) < 0)
+ return -1;
+
+ if (ctx->state.cur_first)
+ ctx->state.cur_first = FALSE;
+ else {
+ if (o_stream_send(ctx->client->output, " ", 1) < 0)
+ return -1;
+ }
+
+ if (o_stream_send(ctx->client->output, "BODYSTRUCTURE (", 15) < 0 ||
+ o_stream_send_str(ctx->client->output, bodystructure) < 0 ||
+ o_stream_send(ctx->client->output, ")", 1) < 0)
+ return -1;
+
+ return 1;
+}
+
+static bool fetch_bodystructure_init(struct imap_fetch_init_context *ctx)
+{
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
+ imap_fetch_add_handler(ctx, 0, "("BODY_NIL_REPLY" NIL NIL NIL NIL)",
+ fetch_bodystructure, NULL);
+ return TRUE;
+}
+
+static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ const char *envelope;
+
+ if (mail_get_special(mail, MAIL_FETCH_IMAP_ENVELOPE, &envelope) < 0)
+ return -1;
+
+ if (ctx->state.cur_first)
+ ctx->state.cur_first = FALSE;
+ else {
+ if (o_stream_send(ctx->client->output, " ", 1) < 0)
+ return -1;
+ }
+
+ if (o_stream_send(ctx->client->output, "ENVELOPE (", 10) < 0 ||
+ o_stream_send_str(ctx->client->output, envelope) < 0 ||
+ o_stream_send(ctx->client->output, ")", 1) < 0)
+ return -1;
+ return 1;
+}
+
+static bool fetch_envelope_init(struct imap_fetch_init_context *ctx)
+{
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_ENVELOPE;
+ imap_fetch_add_handler(ctx, 0, ENVELOPE_NIL_REPLY,
+ fetch_envelope, NULL);
+ return TRUE;
+}
+
+static int fetch_flags(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ enum mail_flags flags;
+ const char *const *keywords;
+
+ flags = mail_get_flags(mail);
+ if (ctx->flags_update_seen && (flags & MAIL_SEEN) == 0 &&
+ !mailbox_is_readonly(mail->box)) {
+ /* Add \Seen flag */
+ ctx->state.seen_flags_changed = TRUE;
+ flags |= MAIL_SEEN;
+ mail_update_flags(mail, MODIFY_ADD, MAIL_SEEN);
+ } else if (ctx->flags_show_only_seen_changes) {
+ return 1;
+ }
+
+ keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords,
+ mail_get_keyword_indexes(mail));
+
+ str_append(ctx->state.cur_str, "FLAGS (");
+ imap_write_flags(ctx->state.cur_str, flags, keywords);
+ str_append(ctx->state.cur_str, ") ");
+ return 1;
+}
+
+bool imap_fetch_flags_init(struct imap_fetch_init_context *ctx)
+{
+ ctx->fetch_ctx->flags_have_handler = TRUE;
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_FLAGS;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "()", fetch_flags, NULL);
+ return TRUE;
+}
+
+static int fetch_internaldate(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ time_t date;
+
+ if (mail_get_received_date(mail, &date) < 0)
+ return -1;
+
+ str_printfa(ctx->state.cur_str, "INTERNALDATE \"%s\" ",
+ imap_to_datetime(date));
+ return 1;
+}
+
+static bool fetch_internaldate_init(struct imap_fetch_init_context *ctx)
+{
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_RECEIVED_DATE;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "\"01-Jan-1970 00:00:00 +0000\"",
+ fetch_internaldate, NULL);
+ return TRUE;
+}
+
+static int fetch_modseq(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ uint64_t modseq;
+
+ modseq = mail_get_modseq(mail);
+ if (ctx->client->highest_fetch_modseq < modseq)
+ ctx->client->highest_fetch_modseq = modseq;
+ str_printfa(ctx->state.cur_str, "MODSEQ (%"PRIu64") ", modseq);
+ return 1;
+}
+
+bool imap_fetch_modseq_init(struct imap_fetch_init_context *ctx)
+{
+ if (ctx->fetch_ctx->client->nonpermanent_modseqs) {
+ ctx->error = "FETCH MODSEQ can't be used with non-permanent modseqs";
+ return FALSE;
+ }
+ client_enable(ctx->fetch_ctx->client, imap_feature_condstore);
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ NULL, fetch_modseq, NULL);
+ return TRUE;
+}
+
+static int fetch_uid(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ str_printfa(ctx->state.cur_str, "UID %u ", mail->uid);
+ return 1;
+}
+
+bool imap_fetch_uid_init(struct imap_fetch_init_context *ctx)
+{
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ NULL, fetch_uid, NULL);
+ return TRUE;
+}
+
+static int imap_fetch_savedate(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ time_t date;
+ int ret;
+
+ ret = mail_get_save_date(mail, &date);
+ if (ret < 0)
+ return -1;
+
+ if (ret == 0)
+ str_append(ctx->state.cur_str, "SAVEDATE NIL ");
+ else {
+ str_printfa(ctx->state.cur_str, "SAVEDATE \"%s\" ",
+ imap_to_datetime(date));
+ }
+ return 1;
+}
+
+static bool imap_fetch_savedate_init(struct imap_fetch_init_context *ctx)
+{
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_SAVE_DATE;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "NIL", imap_fetch_savedate, NULL);
+ return TRUE;
+}
+
+static int fetch_guid(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ const char *value;
+
+ if (mail_get_special(mail, MAIL_FETCH_GUID, &value) < 0)
+ return -1;
+
+ str_append(ctx->state.cur_str, "X-GUID ");
+ imap_append_astring(ctx->state.cur_str, value);
+ str_append_c(ctx->state.cur_str, ' ');
+ return 1;
+}
+
+static bool fetch_guid_init(struct imap_fetch_init_context *ctx)
+{
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_GUID;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "", fetch_guid, NULL);
+ return TRUE;
+}
+
+static int fetch_x_mailbox(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ const char *name;
+ string_t *mutf7_name;
+
+ if (mail_get_special(mail, MAIL_FETCH_MAILBOX_NAME, &name) < 0) {
+ /* This can happen with virtual mailbox if the backend mail
+ is expunged. */
+ return -1;
+ }
+
+ mutf7_name = t_str_new(strlen(name)*2);
+ if (imap_utf8_to_utf7(name, mutf7_name) < 0)
+ i_panic("FETCH: Mailbox name not UTF-8: %s", name);
+
+ str_append(ctx->state.cur_str, "X-MAILBOX ");
+ imap_append_astring(ctx->state.cur_str, str_c(mutf7_name));
+ str_append_c(ctx->state.cur_str, ' ');
+ return 1;
+}
+
+static bool fetch_x_mailbox_init(struct imap_fetch_init_context *ctx)
+{
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ NULL, fetch_x_mailbox, NULL);
+ return TRUE;
+}
+
+static int fetch_x_real_uid(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ struct mail *real_mail;
+
+ if (mail_get_backend_mail(mail, &real_mail) < 0)
+ return -1;
+ str_printfa(ctx->state.cur_str, "X-REAL-UID %u ", real_mail->uid);
+ return 1;
+}
+
+static bool fetch_x_real_uid_init(struct imap_fetch_init_context *ctx)
+{
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ NULL, fetch_x_real_uid, NULL);
+ return TRUE;
+}
+
+static int fetch_x_savedate(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ time_t date;
+
+ if (mail_get_save_date(mail, &date) < 0)
+ return -1;
+
+ str_printfa(ctx->state.cur_str, "X-SAVEDATE \"%s\" ",
+ imap_to_datetime(date));
+ return 1;
+}
+
+static bool fetch_x_savedate_init(struct imap_fetch_init_context *ctx)
+{
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_SAVE_DATE;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "\"01-Jan-1970 00:00:00 +0000\"",
+ fetch_x_savedate, NULL);
+ return TRUE;
+}
+
+static const struct imap_fetch_handler
+imap_fetch_default_handlers[] = {
+ { "BINARY", imap_fetch_binary_init },
+ { "BODY", fetch_body_init },
+ { "BODYSTRUCTURE", fetch_bodystructure_init },
+ { "ENVELOPE", fetch_envelope_init },
+ { "FLAGS", imap_fetch_flags_init },
+ { "INTERNALDATE", fetch_internaldate_init },
+ { "MODSEQ", imap_fetch_modseq_init },
+ { "PREVIEW", imap_fetch_preview_init },
+ { "RFC822", imap_fetch_rfc822_init },
+ { "SNIPPET", imap_fetch_snippet_init },
+ { "UID", imap_fetch_uid_init },
+ { "SAVEDATE", imap_fetch_savedate_init },
+ { "X-GUID", fetch_guid_init },
+ { "X-MAILBOX", fetch_x_mailbox_init },
+ { "X-REAL-UID", fetch_x_real_uid_init },
+ { "X-SAVEDATE", fetch_x_savedate_init }
+};
+
+void imap_fetch_handlers_init(void)
+{
+ i_array_init(&fetch_handlers, 32);
+ imap_fetch_handlers_register(imap_fetch_default_handlers,
+ N_ELEMENTS(imap_fetch_default_handlers));
+}
+
+void imap_fetch_handlers_deinit(void)
+{
+ array_free(&fetch_handlers);
+}
diff --git a/src/imap/imap-fetch.h b/src/imap/imap-fetch.h
new file mode 100644
index 0000000..ba8cfc8
--- /dev/null
+++ b/src/imap/imap-fetch.h
@@ -0,0 +1,166 @@
+#ifndef IMAP_FETCH_H
+#define IMAP_FETCH_H
+
+struct imap_fetch_context;
+
+enum imap_fetch_handler_flags {
+ IMAP_FETCH_HANDLER_FLAG_BUFFERED = 0x01,
+ IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT = 0x02
+};
+
+/* Returns 1 = ok, 0 = client output buffer full, call again, -1 = error.
+ mail = NULL for deinit. */
+typedef int imap_fetch_handler_t(struct imap_fetch_context *ctx,
+ struct mail *mail, void *context);
+
+struct imap_fetch_init_context {
+ struct imap_fetch_context *fetch_ctx;
+ pool_t pool;
+
+ const char *name;
+ const struct imap_arg *args;
+
+ const char *error;
+};
+
+struct imap_fetch_handler {
+ const char *name;
+
+ /* Returns FALSE and sets ctx->error if arg is invalid */
+ bool (*init)(struct imap_fetch_init_context *ctx);
+};
+
+struct imap_fetch_context_handler {
+ imap_fetch_handler_t *handler;
+ void *context;
+
+ const char *name;
+ const char *nil_reply;
+
+ bool buffered:1;
+ bool want_deinit:1;
+};
+
+struct imap_fetch_qresync_args {
+ const ARRAY_TYPE(uint32_t) *qresync_sample_seqset;
+ const ARRAY_TYPE(uint32_t) *qresync_sample_uidset;
+};
+
+struct imap_fetch_state {
+ struct mailbox_transaction_context *trans;
+ struct mail_search_context *search_ctx;
+
+ struct mail *cur_mail;
+ unsigned int cur_handler;
+ const char *cur_human_name;
+ uoff_t cur_size;
+ enum mail_fetch_field cur_size_field;
+ string_t *cur_str;
+ size_t cur_str_prefix_size;
+ struct istream *cur_input;
+ bool skip_cr;
+ int (*cont_handler)(struct imap_fetch_context *ctx);
+ uint64_t *cur_stats_sizep;
+
+ bool fetching:1;
+ bool seen_flags_changed:1;
+ /* TRUE if the first FETCH parameter result hasn't yet been sent to
+ the IMAP client. Note that this doesn't affect buffered content in
+ cur_str until it gets flushed out. */
+ bool cur_first:1;
+ /* TRUE if the cur_str prefix has been flushed. More data may still
+ be added to it. */
+ bool line_partial:1;
+ bool skipped_expunged_msgs:1;
+ bool failed:1;
+};
+
+struct imap_fetch_context {
+ struct client *client;
+ pool_t ctx_pool;
+ const char *reason;
+
+ enum mail_fetch_field fetch_data;
+ ARRAY_TYPE(const_string) all_headers;
+
+ ARRAY(struct imap_fetch_context_handler) handlers;
+ unsigned int buffered_handlers_count;
+
+ ARRAY_TYPE(keywords) tmp_keywords;
+
+ struct imap_fetch_state state;
+ ARRAY_TYPE(seq_range) fetch_failed_uids;
+ unsigned int fetched_mails_count;
+
+ enum mail_error error;
+ const char *errstr;
+
+ bool initialized:1;
+ bool failures:1;
+ bool flags_have_handler:1;
+ bool flags_update_seen:1;
+ bool flags_show_only_seen_changes:1;
+ /* HEADER.FIELDS or HEADER.FIELDS.NOT is fetched */
+ bool fetch_header_fields:1;
+};
+
+void imap_fetch_handlers_register(const struct imap_fetch_handler *handlers,
+ size_t count);
+void imap_fetch_handler_unregister(const char *name);
+
+void imap_fetch_add_handler(struct imap_fetch_init_context *ctx,
+ enum imap_fetch_handler_flags flags,
+ const char *nil_reply,
+ imap_fetch_handler_t *handler, void *context)
+ ATTR_NULL(3, 5);
+#define imap_fetch_add_handler(ctx, flags, nil_reply, handler, context) \
+ imap_fetch_add_handler(ctx, flags, nil_reply - \
+ CALLBACK_TYPECHECK(handler, int (*)( \
+ struct imap_fetch_context *, struct mail *, \
+ typeof(context))), \
+ (imap_fetch_handler_t *)handler, context)
+
+int imap_fetch_att_list_parse(struct client *client, pool_t pool,
+ const struct imap_arg *list,
+ struct imap_fetch_context **fetch_ctx_r,
+ const char **client_error_r);
+
+struct imap_fetch_context *
+imap_fetch_alloc(struct client *client, pool_t pool, const char *reason);
+void imap_fetch_free(struct imap_fetch_context **ctx);
+bool imap_fetch_init_handler(struct imap_fetch_init_context *init_ctx);
+void imap_fetch_init_nofail_handler(struct imap_fetch_context *ctx,
+ bool (*init)(struct imap_fetch_init_context *));
+const struct imap_fetch_handler *imap_fetch_handler_lookup(const char *name);
+
+void imap_fetch_begin(struct imap_fetch_context *ctx, struct mailbox *box,
+ struct mail_search_args *search_args);
+int imap_fetch_send_vanished(struct client *client, struct mailbox *box,
+ const struct mail_search_args *search_args,
+ const struct imap_fetch_qresync_args *qresync_args);
+/* Returns 1 if finished, 0 if more data is needed, -1 if error.
+ When 0 is returned, line_partial=TRUE if literal is open and must be
+ finished before anything else to client. */
+int imap_fetch_more(struct imap_fetch_context *ctx,
+ struct client_command_context *cmd);
+/* Like imap_fetch_more(), but don't check/update output_lock.
+ The caller must handle this itself. */
+int imap_fetch_more_no_lock_update(struct imap_fetch_context *ctx);
+int imap_fetch_end(struct imap_fetch_context *ctx);
+int imap_fetch_more(struct imap_fetch_context *ctx,
+ struct client_command_context *cmd);
+
+bool imap_fetch_flags_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_modseq_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_uid_init(struct imap_fetch_init_context *ctx);
+
+bool imap_fetch_body_section_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_rfc822_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_binary_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_preview_init(struct imap_fetch_init_context *ctx);
+bool imap_fetch_snippet_init(struct imap_fetch_init_context *ctx);
+
+void imap_fetch_handlers_init(void);
+void imap_fetch_handlers_deinit(void);
+
+#endif
diff --git a/src/imap/imap-list.c b/src/imap/imap-list.c
new file mode 100644
index 0000000..38a81ba
--- /dev/null
+++ b/src/imap/imap-list.c
@@ -0,0 +1,35 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "imap-list.h"
+
+bool imap_mailbox_flags2str(string_t *str, enum mailbox_info_flags flags)
+{
+ size_t orig_len = str_len(str);
+
+ if ((flags & MAILBOX_SUBSCRIBED) != 0)
+ str_append(str, "\\Subscribed ");
+
+ if ((flags & MAILBOX_NOSELECT) != 0)
+ str_append(str, "\\Noselect ");
+ if ((flags & MAILBOX_NONEXISTENT) != 0)
+ str_append(str, "\\NonExistent ");
+
+ if ((flags & MAILBOX_CHILDREN) != 0)
+ str_append(str, "\\HasChildren ");
+ else if ((flags & MAILBOX_NOINFERIORS) != 0)
+ str_append(str, "\\NoInferiors ");
+ else if ((flags & MAILBOX_NOCHILDREN) != 0)
+ str_append(str, "\\HasNoChildren ");
+
+ if ((flags & MAILBOX_MARKED) != 0)
+ str_append(str, "\\Marked ");
+ if ((flags & MAILBOX_UNMARKED) != 0)
+ str_append(str, "\\UnMarked ");
+
+ if (str_len(str) == orig_len)
+ return FALSE;
+ str_truncate(str, str_len(str)-1);
+ return TRUE;
+}
diff --git a/src/imap/imap-list.h b/src/imap/imap-list.h
new file mode 100644
index 0000000..6a0e076
--- /dev/null
+++ b/src/imap/imap-list.h
@@ -0,0 +1,7 @@
+#ifndef IMAP_LIST_H
+#define IMAP_LIST_H
+
+/* Returns TRUE if anything was added to the string. */
+bool imap_mailbox_flags2str(string_t *str, enum mailbox_info_flags flags);
+
+#endif
diff --git a/src/imap/imap-master-client.c b/src/imap/imap-master-client.c
new file mode 100644
index 0000000..84814f0
--- /dev/null
+++ b/src/imap/imap-master-client.c
@@ -0,0 +1,448 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "connection.h"
+#include "istream.h"
+#include "istream-unix.h"
+#include "ostream.h"
+#include "base64.h"
+#include "str.h"
+#include "strescape.h"
+#include "str-sanitize.h"
+#include "time-util.h"
+#include "process-title.h"
+#include "master-service.h"
+#include "mail-storage-service.h"
+#include "imap-client.h"
+#include "imap-state.h"
+#include "imap-master-client.h"
+
+struct imap_master_client {
+ struct connection conn;
+ bool imap_client_created;
+};
+
+struct imap_master_input {
+ /* input we've already read from the IMAP client. */
+ buffer_t *client_input;
+ /* output that imap-hibernate was supposed to send to IMAP client,
+ but couldn't send it yet. */
+ buffer_t *client_output;
+ /* IMAP connection state */
+ buffer_t *state;
+ /* command tag */
+ const char *tag;
+ /* Timestamp when hibernation started */
+ struct timeval hibernation_start_time;
+
+ dev_t peer_dev;
+ ino_t peer_ino;
+
+ bool state_import_bad_idle_done;
+ bool state_import_idle_continue;
+};
+
+static struct connection_list *master_clients = NULL;
+
+static void imap_master_client_destroy(struct connection *conn)
+{
+ struct imap_master_client *client = (struct imap_master_client *)conn;
+
+ if (!client->imap_client_created)
+ master_service_client_connection_destroyed(master_service);
+ connection_deinit(conn);
+ i_free(conn);
+}
+
+static int
+imap_master_client_parse_input(const char *const *args, pool_t pool,
+ struct mail_storage_service_input *input_r,
+ struct imap_master_input *master_input_r,
+ const char **error_r)
+{
+ const char *key, *value;
+ unsigned int peer_dev_major = 0, peer_dev_minor = 0;
+
+ i_zero(input_r);
+ i_zero(master_input_r);
+ master_input_r->client_input = buffer_create_dynamic(pool, 64);
+ master_input_r->client_output = buffer_create_dynamic(pool, 16);
+ master_input_r->state = buffer_create_dynamic(pool, 512);
+
+ input_r->module = input_r->service = "imap";
+ /* we never want to do userdb lookup again when restoring the client.
+ we have the userdb_fields cached already. */
+ input_r->flags_override_remove = MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+
+ if (args[0] == NULL) {
+ *error_r = "Missing username in input";
+ return -1;
+ }
+ input_r->username = args[0];
+
+ for (args++; *args != NULL; args++) {
+ value = strchr(*args, '=');
+ if (value != NULL)
+ key = t_strdup_until(*args, value++);
+ else {
+ key = *args;
+ value = "";
+ }
+ if (strcmp(key, "lip") == 0) {
+ if (net_addr2ip(value, &input_r->local_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid lip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "lport") == 0) {
+ if (net_str2port(value, &input_r->local_port) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid lport value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "rip") == 0) {
+ if (net_addr2ip(value, &input_r->remote_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid rip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "rport") == 0) {
+ if (net_str2port(value, &input_r->remote_port) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid rport value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_dev_major") == 0) {
+ if (str_to_uint(value, &peer_dev_major) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_dev_major value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_dev_minor") == 0) {
+ if (str_to_uint(value, &peer_dev_minor) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_dev_minor value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_ino") == 0) {
+ if (str_to_ino(value, &master_input_r->peer_ino) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_ino value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "session") == 0) {
+ input_r->session_id = value;
+ } else if (strcmp(key, "session_created") == 0) {
+ if (str_to_time(value, &input_r->session_create_time) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid session_created value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "hibernation_started") == 0) {
+ if (str_to_timeval(value, &master_input_r->hibernation_start_time) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid hibernation_started value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "userdb_fields") == 0) {
+ input_r->userdb_fields =
+ t_strsplit_tabescaped(value);
+ } else if (strcmp(key, "client_input") == 0) {
+ if (base64_decode(value, strlen(value), NULL,
+ master_input_r->client_input) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid client_input base64 value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "client_output") == 0) {
+ if (base64_decode(value, strlen(value), NULL,
+ master_input_r->client_output) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid client_output base64 value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "state") == 0) {
+ if (base64_decode(value, strlen(value), NULL,
+ master_input_r->state) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid state base64 value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "tag") == 0) {
+ master_input_r->tag = t_strdup(value);
+ } else if (strcmp(key, "bad-done") == 0) {
+ master_input_r->state_import_bad_idle_done = TRUE;
+ } else if (strcmp(key, "idle-continue") == 0) {
+ master_input_r->state_import_idle_continue = TRUE;
+ }
+ }
+ if (peer_dev_major != 0 || peer_dev_minor != 0) {
+ master_input_r->peer_dev =
+ makedev(peer_dev_major, peer_dev_minor);
+ }
+ return 0;
+}
+
+static int imap_master_client_verify(const struct imap_master_input *master_input,
+ int fd_client, const char **error_r)
+{
+ struct stat peer_st;
+
+ if (master_input->peer_ino == 0)
+ return 0;
+
+ /* make sure we have the right fd */
+ if (fstat(fd_client, &peer_st) < 0) {
+ *error_r = t_strdup_printf("fstat(peer) failed: %m");
+ return -1;
+ }
+ if (peer_st.st_ino != master_input->peer_ino ||
+ !CMP_DEV_T(peer_st.st_dev, master_input->peer_dev)) {
+ *error_r = t_strdup_printf(
+ "BUG: Expected peer device=%lu,%lu inode=%s doesn't match "
+ "client fd's actual device=%lu,%lu inode=%s",
+ (unsigned long)major(peer_st.st_dev),
+ (unsigned long)minor(peer_st.st_dev), dec2str(peer_st.st_ino),
+ (unsigned long)major(master_input->peer_dev),
+ (unsigned long)minor(master_input->peer_dev),
+ dec2str(master_input->peer_ino));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+imap_master_client_input_args(struct connection *conn, const char *const *args,
+ int fd_client, pool_t pool)
+{
+ struct imap_master_client *client = (struct imap_master_client *)conn;
+ struct client *imap_client;
+ struct mail_storage_service_input input;
+ struct imap_master_input master_input;
+ const char *error = NULL, *reason;
+ int ret;
+
+ if (imap_master_client_parse_input(args, pool, &input, &master_input,
+ &error) < 0) {
+ e_error(conn->event, "imap-master: Failed to parse client input: %s", error);
+ o_stream_nsend_str(conn->output, t_strdup_printf(
+ "-Failed to parse client input: %s\n", error));
+ i_close_fd(&fd_client);
+ return -1;
+ }
+ if (imap_master_client_verify(&master_input, fd_client, &error) < 0) {
+ e_error(conn->event, "imap-master: Failed to verify client input: %s", error);
+ o_stream_nsend_str(conn->output, t_strdup_printf(
+ "-Failed to verify client input: %s\n", error));
+ i_close_fd(&fd_client);
+ return -1;
+ }
+ process_title_set("[unhibernating]");
+
+ /* NOTE: before client_create_from_input() on failures we need to close
+ fd_client, but afterward it gets closed by client_destroy() */
+ ret = client_create_from_input(&input, fd_client, fd_client,
+ TRUE, &imap_client, &error);
+ if (ret < 0) {
+ e_error(conn->event,
+ "imap-master(%s): Failed to create client: %s",
+ input.username, error);
+ o_stream_nsend_str(conn->output, t_strdup_printf(
+ "-Failed to create client: %s\n", error));
+ i_close_fd(&fd_client);
+ return -1;
+ }
+ client->imap_client_created = TRUE;
+
+ long long hibernation_usecs =
+ timeval_diff_usecs(&ioloop_timeval,
+ &master_input.hibernation_start_time);
+ struct event *event = event_create(imap_client->event);
+ event_set_name(event, "imap_client_unhibernated");
+ event_add_int(event, "hibernation_usecs", hibernation_usecs);
+ imap_client->state_import_bad_idle_done =
+ master_input.state_import_bad_idle_done;
+ imap_client->state_import_idle_continue =
+ master_input.state_import_idle_continue;
+ if (imap_client->state_import_bad_idle_done) {
+ reason = "IDLE was stopped with BAD command";
+ event_add_str(event, "reason", "idle_bad_reply");
+ } else if (imap_client->state_import_idle_continue) {
+ reason = "mailbox changes need to be sent";
+ event_add_str(event, "reason", "mailbox_changes");
+ } else {
+ reason = "IDLE was stopped with DONE";
+ event_add_str(event, "reason", "idle_done");
+ }
+
+ /* Send a success notification before we start anything that lasts
+ potentially a long time. imap-hibernate process is waiting for us
+ to answer. Even if we fail later, we log the error anyway. From now
+ on it's our responsibility to also log the imap_client_unhibernated
+ event. */
+ o_stream_nsend_str(conn->output, "+\n");
+ (void)o_stream_flush(conn->output);
+
+ if (master_input.client_input->used > 0) {
+ client_add_istream_prefix(imap_client,
+ master_input.client_input->data,
+ master_input.client_input->used);
+ }
+
+ client_create_finish_io(imap_client);
+ if (client_create_finish(imap_client, &error) < 0) {
+ event_add_str(event, "error", error);
+ e_error(event, "imap-master: %s", error);
+ event_unref(&event);
+ client_destroy(imap_client, error);
+ return -1;
+ }
+ /* log prefix is set at this point, so we don't need to add the
+ username anymore to the log messages */
+
+ o_stream_nsend(imap_client->output,
+ master_input.client_output->data,
+ master_input.client_output->used);
+
+ struct event_reason *event_reason =
+ event_reason_begin("imap:unhibernate");
+ ret = imap_state_import_internal(imap_client, master_input.state->data,
+ master_input.state->used, &error);
+ event_reason_end(&event_reason);
+
+ if (ret <= 0) {
+ error = t_strdup_printf("Failed to import client state: %s", error);
+ event_add_str(event, "error", error);
+ e_error(event, "imap-master: %s", error);
+ event_unref(&event);
+ client_destroy(imap_client, "Client state initialization failed");
+ return -1;
+ }
+ if (imap_client->mailbox != NULL) {
+ /* Would be nice to set this earlier, but the previous errors
+ happen rarely enough that it shouldn't really matter. */
+ event_add_str(event, "mailbox",
+ mailbox_get_vname(imap_client->mailbox));
+ }
+
+ if (master_input.tag != NULL)
+ imap_state_import_idle_cmd_tag(imap_client, master_input.tag);
+
+ e_debug(event, "imap-master: Unhibernated because %s "
+ "(hibernated for %llu.%06llu secs)", reason,
+ hibernation_usecs/1000000, hibernation_usecs%1000000);
+ event_unref(&event);
+
+ /* make sure all pending input gets handled */
+ if (master_input.client_input->used > 0) {
+ e_debug(imap_client->event,
+ "imap-master: Pending client input: '%s'",
+ str_sanitize(str_c(master_input.client_input), 128));
+ io_set_pending(imap_client->io);
+ }
+
+ imap_refresh_proctitle();
+ /* we'll always disconnect the client afterwards */
+ return -1;
+}
+
+static int
+imap_master_client_input_line(struct connection *conn, const char *line)
+{
+ char *const *args;
+ pool_t pool;
+ int fd_client, ret;
+
+ if (!conn->version_received) {
+ if (connection_handshake_args_default(conn, t_strsplit_tabescaped(line)) < 0)
+ return -1;
+ conn->version_received = TRUE;
+ return 1;
+ }
+
+ fd_client = i_stream_unix_get_read_fd(conn->input);
+ if (fd_client == -1) {
+ e_error(conn->event, "imap-master: IMAP client fd not received");
+ return -1;
+ }
+
+ if (imap_debug)
+ e_debug(conn->event, "imap-master: Client input: %s", line);
+
+ pool = pool_alloconly_create("imap master client cmd", 1024);
+ args = p_strsplit_tabescaped(pool, line);
+ ret = imap_master_client_input_args(conn, (const void *)args,
+ fd_client, pool);
+ pool_unref(&pool);
+ return ret;
+}
+
+static void imap_master_client_idle_timeout(struct connection *conn)
+{
+ e_error(conn->event, "imap-master: Client didn't send any input for %"
+ PRIdTIME_T" seconds - disconnecting",
+ ioloop_time - conn->last_input_tv.tv_sec);
+
+ conn->disconnect_reason = CONNECTION_DISCONNECT_IDLE_TIMEOUT;
+ conn->v.destroy(conn);
+}
+
+void imap_master_client_create(int fd)
+{
+ struct imap_master_client *client;
+
+ client = i_new(struct imap_master_client, 1);
+ client->conn.unix_socket = TRUE;
+ connection_init_server(master_clients, &client->conn,
+ "imap-master", fd, fd);
+
+ /* read the first file descriptor that we can */
+ i_stream_unix_set_read_fd(client->conn.input);
+
+ imap_refresh_proctitle();
+}
+
+static struct connection_settings client_set = {
+ .service_name_in = "imap-master",
+ .service_name_out = "imap-master",
+ .major_version = 1,
+ .minor_version = 0,
+
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE,
+
+ /* less than imap-hibernate's IMAP_MASTER_CONNECTION_TIMEOUT_MSECS */
+ .input_idle_timeout_secs = 25,
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = imap_master_client_destroy,
+ .input_line = imap_master_client_input_line,
+ .idle_timeout = imap_master_client_idle_timeout,
+};
+
+bool imap_master_clients_refresh_proctitle(void)
+{
+ switch (master_clients->connections_count) {
+ case 0:
+ return FALSE;
+ case 1:
+ process_title_set("[waiting on unhibernate client]");
+ return TRUE;
+ default:
+ process_title_set(t_strdup_printf("[unhibernating %u clients]",
+ master_clients->connections_count));
+ return TRUE;
+ }
+}
+
+void imap_master_clients_init(void)
+{
+ master_clients = connection_list_init(&client_set, &client_vfuncs);
+}
+
+void imap_master_clients_deinit(void)
+{
+ connection_list_deinit(&master_clients);
+}
diff --git a/src/imap/imap-master-client.h b/src/imap/imap-master-client.h
new file mode 100644
index 0000000..ef66ab9
--- /dev/null
+++ b/src/imap/imap-master-client.h
@@ -0,0 +1,10 @@
+#ifndef IMAP_MASTER_CLIENT_H
+#define IMAP_MASTER_CLIENT_H
+
+void imap_master_client_create(int fd);
+bool imap_master_clients_refresh_proctitle(void);
+
+void imap_master_clients_init(void);
+void imap_master_clients_deinit(void);
+
+#endif
diff --git a/src/imap/imap-notify.c b/src/imap/imap-notify.c
new file mode 100644
index 0000000..fa2c9ef
--- /dev/null
+++ b/src/imap/imap-notify.c
@@ -0,0 +1,523 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "ostream.h"
+#include "imap-quote.h"
+#include "mailbox-list-iter.h"
+#include "mailbox-list-notify.h"
+#include "mail-search.h"
+#include "mail-search-build.h"
+#include "imap-commands.h"
+#include "imap-fetch.h"
+#include "imap-list.h"
+#include "imap-status.h"
+#include "imap-notify.h"
+
+#define IMAP_NOTIFY_WATCH_ADD_DELAY_MSECS 1000
+
+static int imap_notify_list(struct imap_notify_namespace *notify_ns,
+ const struct mailbox_list_notify_rec *rec,
+ enum mailbox_info_flags flags)
+{
+ string_t *str = t_str_new(128);
+ char ns_sep = mail_namespace_get_sep(notify_ns->ns);
+
+ str_append(str, "* LIST (");
+ imap_mailbox_flags2str(str, flags);
+ str_append(str, ") \"");
+ if (ns_sep == '\\')
+ str_append_c(str, '\\');
+ str_append_c(str, ns_sep);
+ str_append(str, "\" ");
+
+ imap_append_astring(str, rec->vname);
+ if (rec->old_vname != NULL) {
+ str_append(str, " (\"OLDNAME\" (");
+ imap_append_astring(str, rec->old_vname);
+ str_append(str, "))");
+ }
+ return client_send_line_next(notify_ns->ctx->client, str_c(str));
+}
+
+static int imap_notify_status(struct imap_notify_namespace *notify_ns,
+ const struct mailbox_list_notify_rec *rec)
+{
+ struct client *client = notify_ns->ctx->client;
+ struct mailbox *box;
+ struct imap_status_items items;
+ struct imap_status_result result;
+ enum mail_error error;
+ int ret = 1;
+
+ i_zero(&items);
+ if (client_has_enabled(client, imap_feature_condstore))
+ items.flags |= IMAP_STATUS_ITEM_HIGHESTMODSEQ;
+
+ box = mailbox_alloc(notify_ns->ns->list, rec->vname, 0);
+ if ((rec->events & MAILBOX_LIST_NOTIFY_UIDVALIDITY) != 0) {
+ items.flags |= IMAP_STATUS_ITEM_UIDVALIDITY |
+ IMAP_STATUS_ITEM_UIDNEXT | IMAP_STATUS_ITEM_MESSAGES |
+ IMAP_STATUS_ITEM_UNSEEN;
+ }
+ if ((rec->events & (MAILBOX_LIST_NOTIFY_APPENDS |
+ MAILBOX_LIST_NOTIFY_EXPUNGES)) != 0)
+ items.flags |= IMAP_STATUS_ITEM_UIDNEXT |
+ IMAP_STATUS_ITEM_MESSAGES | IMAP_STATUS_ITEM_UNSEEN;
+ if ((rec->events & MAILBOX_LIST_NOTIFY_SEEN_CHANGES) != 0)
+ items.flags |= IMAP_STATUS_ITEM_UNSEEN;
+ if ((rec->events & MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES) != 0) {
+ /* if HIGHESTMODSEQ isn't being sent, don't send anything */
+ }
+ if (imap_status_items_is_empty(&items)) {
+ /* don't send anything */
+ } else if (imap_status_get_result(client, box, &items, &result) < 0) {
+ /* hide permission errors from client. we don't want to leak
+ information about existence of mailboxes where user doesn't
+ have access to */
+ (void)mailbox_get_last_error(box, &error);
+ if (error != MAIL_ERROR_PERM)
+ ret = -1;
+ } else {
+ ret = imap_status_send(client, rec->vname, &items, &result);
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static int
+imap_notify_next(struct imap_notify_namespace *notify_ns,
+ const struct mailbox_list_notify_rec *rec)
+{
+ enum mailbox_info_flags mailbox_flags;
+ int ret;
+
+ if ((rec->events & MAILBOX_LIST_NOTIFY_CREATE) != 0) {
+ if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name,
+ &mailbox_flags) < 0)
+ mailbox_flags = 0;
+ if ((ret = imap_notify_list(notify_ns, rec, mailbox_flags)) <= 0)
+ return ret;
+ }
+ if ((rec->events & MAILBOX_LIST_NOTIFY_DELETE) != 0) {
+ if ((ret = imap_notify_list(notify_ns, rec, MAILBOX_NONEXISTENT)) < 0)
+ return ret;
+ }
+ if ((rec->events & MAILBOX_LIST_NOTIFY_RENAME) != 0) {
+ if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name,
+ &mailbox_flags) < 0)
+ mailbox_flags = 0;
+ if ((ret = imap_notify_list(notify_ns, rec, mailbox_flags)) < 0)
+ return ret;
+ }
+ if ((rec->events & MAILBOX_LIST_NOTIFY_SUBSCRIBE) != 0) {
+ if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name,
+ &mailbox_flags) < 0)
+ mailbox_flags = 0;
+ if ((ret = imap_notify_list(notify_ns, rec,
+ mailbox_flags | MAILBOX_SUBSCRIBED)) < 0)
+ return ret;
+ }
+ if ((rec->events & MAILBOX_LIST_NOTIFY_UNSUBSCRIBE) != 0) {
+ if (mailbox_list_mailbox(notify_ns->ns->list, rec->storage_name,
+ &mailbox_flags) < 0)
+ mailbox_flags = 0;
+ if ((ret = imap_notify_list(notify_ns, rec, mailbox_flags)) < 0)
+ return ret;
+ }
+ if ((rec->events & (MAILBOX_LIST_NOTIFY_UIDVALIDITY |
+ MAILBOX_LIST_NOTIFY_APPENDS |
+ MAILBOX_LIST_NOTIFY_EXPUNGES |
+ MAILBOX_LIST_NOTIFY_SEEN_CHANGES |
+ MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES)) != 0) {
+ if ((ret = imap_notify_status(notify_ns, rec)) < 0)
+ return ret;
+ }
+ return 1;
+}
+
+static bool
+imap_notify_match_event(struct imap_notify_namespace *notify_ns,
+ const struct imap_notify_mailboxes *notify_boxes,
+ const struct mailbox_list_notify_rec *rec)
+{
+ enum imap_notify_event wanted_events = notify_boxes->events;
+ struct mailbox *box;
+
+ /* check for mailbox list events first */
+ if ((wanted_events & IMAP_NOTIFY_EVENT_MAILBOX_NAME) != 0) {
+ if ((rec->events & (MAILBOX_LIST_NOTIFY_CREATE |
+ MAILBOX_LIST_NOTIFY_DELETE |
+ MAILBOX_LIST_NOTIFY_RENAME)) != 0)
+ return TRUE;
+ }
+ if ((wanted_events & IMAP_NOTIFY_EVENT_SUBSCRIPTION_CHANGE) != 0) {
+ if ((rec->events & (MAILBOX_LIST_NOTIFY_SUBSCRIBE |
+ MAILBOX_LIST_NOTIFY_UNSUBSCRIBE)) != 0)
+ return TRUE;
+ }
+
+ /* if this is an event for the selected mailbox, ignore it */
+ box = notify_ns->ctx->client->mailbox;
+ if (box != NULL && mailbox_equals(box, notify_ns->ns, rec->vname))
+ return FALSE;
+
+ if ((wanted_events & (IMAP_NOTIFY_EVENT_MESSAGE_NEW |
+ IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE |
+ IMAP_NOTIFY_EVENT_FLAG_CHANGE)) != 0) {
+ if ((rec->events & MAILBOX_LIST_NOTIFY_UIDVALIDITY) != 0)
+ return TRUE;
+ }
+ if ((wanted_events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) != 0) {
+ if ((rec->events & MAILBOX_LIST_NOTIFY_APPENDS) != 0)
+ return TRUE;
+ }
+ if ((wanted_events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) != 0) {
+ if ((rec->events & MAILBOX_LIST_NOTIFY_EXPUNGES) != 0)
+ return TRUE;
+ }
+ if ((wanted_events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) != 0) {
+ if ((rec->events & (MAILBOX_LIST_NOTIFY_SEEN_CHANGES |
+ MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES)) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+bool imap_notify_match_mailbox(struct imap_notify_namespace *notify_ns,
+ const struct imap_notify_mailboxes *notify_boxes,
+ const char *vname)
+{
+ struct mailbox *box;
+ const char *name;
+ size_t name_len;
+ char ns_sep;
+ bool ret;
+
+ switch (notify_boxes->type) {
+ case IMAP_NOTIFY_TYPE_SUBSCRIBED:
+ box = mailbox_alloc(notify_ns->ns->list, vname, 0);
+ ret = mailbox_is_subscribed(box);
+ mailbox_free(&box);
+ return ret;
+ case IMAP_NOTIFY_TYPE_SUBTREE:
+ ns_sep = mail_namespace_get_sep(notify_ns->ns);
+ array_foreach_elem(&notify_boxes->names, name) {
+ name_len = strlen(name);
+ if (name_len == 0) {
+ /* everything under root. NOTIFY spec itself
+ doesn't define this, but we use it for
+ implementing "personal" */
+ return TRUE;
+ }
+ if (str_begins(vname, name) &&
+ (vname[name_len] == '\0' ||
+ vname[name_len] == ns_sep))
+ return TRUE;
+ }
+ break;
+ case IMAP_NOTIFY_TYPE_MAILBOX:
+ array_foreach_elem(&notify_boxes->names, name) {
+ if (strcmp(name, vname) == 0)
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static bool
+imap_notify_match(struct imap_notify_namespace *notify_ns,
+ const struct mailbox_list_notify_rec *rec)
+{
+ const struct imap_notify_mailboxes *notify_boxes;
+
+ array_foreach(&notify_ns->mailboxes, notify_boxes) {
+ if (imap_notify_match_event(notify_ns, notify_boxes, rec) &&
+ imap_notify_match_mailbox(notify_ns, notify_boxes, rec->vname))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int imap_client_notify_ns(struct imap_notify_namespace *notify_ns)
+{
+ const struct mailbox_list_notify_rec *rec;
+ int ret, ret2 = 1;
+
+ if (notify_ns->notify == NULL)
+ return 0; /* notifications not supported in this namespace */
+
+ while ((ret = mailbox_list_notify_next(notify_ns->notify, &rec)) > 0) {
+ if (imap_notify_match(notify_ns, rec)) T_BEGIN {
+ ret2 = imap_notify_next(notify_ns, rec);
+ } T_END;
+ if (ret2 <= 0)
+ break;
+ }
+ if (ret < 0) {
+ /* failed to get some notifications */
+ return -1;
+ }
+ return ret2;
+}
+
+static int
+imap_client_notify_selected(struct client *client)
+{
+ struct imap_fetch_context *fetch_ctx = client->notify_ctx->fetch_ctx;
+ int ret;
+
+ if (!fetch_ctx->state.fetching)
+ return 1;
+
+ if ((ret = imap_fetch_more_no_lock_update(fetch_ctx)) == 0)
+ return 0;
+ /* finished the FETCH */
+ if (imap_fetch_end(fetch_ctx) < 0)
+ return -1;
+ return ret;
+}
+
+static int imap_client_notify_more(struct client *client)
+{
+ struct imap_notify_namespace *notify_ns;
+ int ret = 1;
+
+ struct event_reason *reason = event_reason_begin("imap:notify_update");
+
+ /* send notifications for selected mailbox first. note that it may
+ leave the client's output stream in the middle of a FETCH reply. */
+ if (client->notify_ctx->fetch_ctx != NULL) {
+ if ((ret = imap_client_notify_selected(client)) < 0) {
+ client->notify_ctx->fetch_ctx->state.failed = FALSE;
+ ret = -1;
+ }
+ }
+
+ /* send notifications for non-selected mailboxes */
+ array_foreach_modifiable(&client->notify_ctx->namespaces, notify_ns) {
+ if (ret == 0)
+ break;
+ if (imap_client_notify_ns(notify_ns) < 0)
+ ret = -1;
+ }
+
+ if (ret < 0) {
+ client_send_line(client,
+ "* NO NOTIFY error, some events may have got lost");
+ }
+ event_reason_end(&reason);
+ return ret;
+}
+
+int imap_client_notify_newmails(struct client *client)
+{
+ struct imap_fetch_context *fetch_ctx = client->notify_ctx->fetch_ctx;
+ struct mailbox_status status;
+ struct mail_search_args *search_args;
+ struct mail_search_arg *arg;
+
+ i_assert(client->mailbox != NULL);
+
+ if (fetch_ctx == NULL) {
+ /* FETCH notifications not enabled in this session */
+ return 1;
+ }
+ if (client->notify_ctx->notifying)
+ return imap_client_notify_more(client);
+ client->notify_ctx->notifying = TRUE;
+
+ i_assert(!fetch_ctx->state.fetching);
+
+ mailbox_get_open_status(client->mailbox, STATUS_UIDNEXT, &status);
+
+ search_args = mail_search_build_init();
+ arg = mail_search_build_add(search_args, SEARCH_UIDSET);
+ p_array_init(&arg->value.seqset, search_args->pool, 1);
+ seq_range_array_add_range(&arg->value.seqset,
+ client->notify_uidnext, status.uidnext-1);
+ client->notify_uidnext = status.uidnext;
+
+ imap_fetch_begin(fetch_ctx, client->mailbox, search_args);
+ mail_search_args_unref(&search_args);
+
+ return imap_client_notify_more(client);
+}
+
+void imap_client_notify_finished(struct client *client)
+{
+ if (client->notify_ctx != NULL)
+ client->notify_ctx->notifying = FALSE;
+}
+
+static void notify_callback(struct imap_notify_namespace *notify_ns)
+{
+ struct event_reason *reason = event_reason_begin("imap:notify_update");
+ o_stream_cork(notify_ns->ctx->client->output);
+ imap_client_notify_ns(notify_ns);
+ o_stream_uncork(notify_ns->ctx->client->output);
+ event_reason_end(&reason);
+}
+
+static enum mailbox_list_notify_event
+imap_events_to_notify(enum imap_notify_event events)
+{
+ enum mailbox_list_notify_event ret = 0;
+
+ if ((events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) != 0) {
+ ret |= MAILBOX_LIST_NOTIFY_APPENDS |
+ MAILBOX_LIST_NOTIFY_UIDVALIDITY;
+ }
+ if ((events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) != 0) {
+ ret |= MAILBOX_LIST_NOTIFY_EXPUNGES |
+ MAILBOX_LIST_NOTIFY_UIDVALIDITY;
+ }
+ if ((events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) != 0) {
+ ret |= MAILBOX_LIST_NOTIFY_SEEN_CHANGES |
+ MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES |
+ MAILBOX_LIST_NOTIFY_UIDVALIDITY;
+ }
+ if ((events & IMAP_NOTIFY_EVENT_MAILBOX_NAME) != 0) {
+ ret |= MAILBOX_LIST_NOTIFY_CREATE |
+ MAILBOX_LIST_NOTIFY_DELETE |
+ MAILBOX_LIST_NOTIFY_RENAME;
+ }
+ if ((events & IMAP_NOTIFY_EVENT_SUBSCRIPTION_CHANGE) != 0) {
+ ret |= MAILBOX_LIST_NOTIFY_SUBSCRIBE |
+ MAILBOX_LIST_NOTIFY_UNSUBSCRIBE;
+ }
+ return ret;
+}
+
+static void imap_notify_callback(struct mailbox *box, struct client *client)
+{
+ struct client_command_context *cmd;
+ enum mailbox_sync_flags sync_flags = 0;
+
+ i_assert(client->command_queue_size == 0);
+ i_assert(box == client->mailbox);
+
+ /* create a fake command to handle this */
+ cmd = client_command_alloc(client);
+ cmd->tag = "*";
+ cmd->name = "NOTIFY-CALLBACK";
+ client_command_init_finished(cmd);
+
+ if (!client->notify_ctx->selected_immediate_expunges)
+ sync_flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
+ if (cmd_sync(cmd, sync_flags, 0, NULL))
+ i_unreached();
+ (void)cmd_sync_delayed(client);
+}
+
+static void imap_notify_watch_selected_mailbox(struct client *client)
+{
+ i_assert(client->command_queue_size == 0);
+
+ if (client->mailbox == NULL) {
+ /* mailbox not selected */
+ return;
+ }
+ if (client->notify_ctx == NULL || !client->notify_ctx->selected_set) {
+ /* client doesn't want selected mailbox notifications */
+ return;
+
+ }
+ mailbox_notify_changes(client->mailbox, imap_notify_callback, client);
+ client->notify_ctx->watching_mailbox = TRUE;
+}
+
+static void imap_notify_watch_timeout(struct client *client)
+{
+ timeout_remove(&client->notify_ctx->to_watch);
+ imap_notify_watch_selected_mailbox(client);
+}
+
+void imap_client_notify_command_freed(struct client *client)
+{
+ struct imap_notify_context *ctx = client->notify_ctx;
+
+ if (ctx == NULL)
+ return;
+
+ if (client->command_queue_size > 0) {
+ /* don't add it until all commands are finished */
+ i_assert(ctx->to_watch == NULL);
+ return;
+ }
+
+ /* add mailbox watch back after a small delay. if another command
+ is started this timeout is aborted. */
+ ctx->to_watch = timeout_add(IMAP_NOTIFY_WATCH_ADD_DELAY_MSECS,
+ imap_notify_watch_timeout, client);
+}
+
+void imap_client_notify_command_allocated(struct client *client)
+{
+ struct imap_notify_context *ctx = client->notify_ctx;
+
+ if (ctx == NULL)
+ return;
+
+ /* remove mailbox watcher before starting any commands */
+ if (ctx->watching_mailbox) {
+ mailbox_notify_changes_stop(client->mailbox);
+ ctx->watching_mailbox = FALSE;
+ }
+ timeout_remove(&ctx->to_watch);
+}
+
+int imap_notify_begin(struct imap_notify_context *ctx)
+{
+ struct imap_notify_namespace *notify_ns;
+ const struct imap_notify_mailboxes *notify_boxes;
+ enum mailbox_list_notify_event notify_events;
+ int ret = -1;
+
+ array_foreach_modifiable(&ctx->namespaces, notify_ns) {
+ notify_events = 0;
+ array_foreach(&notify_ns->mailboxes, notify_boxes) {
+ notify_events |=
+ imap_events_to_notify(notify_boxes->events);
+ }
+ if (mailbox_list_notify_init(notify_ns->ns->list, notify_events,
+ &notify_ns->notify) < 0) {
+ /* notifications not supported */
+ } else {
+ ret = 0;
+ mailbox_list_notify_wait(notify_ns->notify,
+ notify_callback, notify_ns);
+ }
+ }
+ /* enable NOTIFY as long as even one namespace supports it,
+ ignore the rest */
+ return ret;
+}
+
+void imap_notify_deinit(struct imap_notify_context **_ctx)
+{
+ struct imap_notify_context *ctx = *_ctx;
+ struct imap_notify_namespace *notify_ns;
+
+ *_ctx = NULL;
+
+ array_foreach_modifiable(&ctx->namespaces, notify_ns) {
+ if (notify_ns->notify != NULL)
+ mailbox_list_notify_deinit(&notify_ns->notify);
+ }
+ timeout_remove(&ctx->to_watch);
+ if (ctx->fetch_ctx != NULL)
+ imap_fetch_free(&ctx->fetch_ctx);
+ pool_unref(&ctx->pool);
+}
+
+void imap_notify_flush(struct imap_notify_context *ctx)
+{
+ struct imap_notify_namespace *notify_ns;
+
+ array_foreach_modifiable(&ctx->namespaces, notify_ns) {
+ if (notify_ns->notify != NULL)
+ mailbox_list_notify_flush(notify_ns->notify);
+ }
+}
diff --git a/src/imap/imap-notify.h b/src/imap/imap-notify.h
new file mode 100644
index 0000000..936d622
--- /dev/null
+++ b/src/imap/imap-notify.h
@@ -0,0 +1,75 @@
+#ifndef IMAP_NOTIFY_H
+#define IMAP_NOTIFY_H
+
+enum imap_notify_type {
+ IMAP_NOTIFY_TYPE_SUBSCRIBED,
+ IMAP_NOTIFY_TYPE_SUBTREE,
+ IMAP_NOTIFY_TYPE_MAILBOX
+};
+
+enum imap_notify_event {
+ IMAP_NOTIFY_EVENT_MESSAGE_NEW = 0x01,
+ IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE = 0x02,
+ IMAP_NOTIFY_EVENT_FLAG_CHANGE = 0x04,
+ IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE = 0x08,
+ IMAP_NOTIFY_EVENT_MAILBOX_NAME = 0x10,
+ IMAP_NOTIFY_EVENT_SUBSCRIPTION_CHANGE = 0x20,
+ IMAP_NOTIFY_EVENT_MAILBOX_METADATA_CHANGE = 0x40,
+ IMAP_NOTIFY_EVENT_SERVER_METADATA_CHANGE = 0x80
+};
+#define UNSUPPORTED_EVENTS \
+ (IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE | \
+ IMAP_NOTIFY_EVENT_MAILBOX_METADATA_CHANGE | \
+ IMAP_NOTIFY_EVENT_SERVER_METADATA_CHANGE)
+
+struct imap_notify_mailboxes {
+ enum imap_notify_event events;
+ enum imap_notify_type type;
+ ARRAY_TYPE(const_string) names;
+};
+
+struct imap_notify_namespace {
+ struct imap_notify_context *ctx;
+ struct mail_namespace *ns;
+
+ struct mailbox_list_notify *notify;
+ ARRAY(struct imap_notify_mailboxes) mailboxes;
+};
+
+struct imap_notify_context {
+ pool_t pool;
+ struct client *client;
+ const char *error;
+
+ ARRAY(struct imap_notify_namespace) namespaces;
+ enum imap_notify_event selected_events;
+ enum imap_notify_event global_used_events;
+ unsigned int global_max_mailbox_names;
+
+ struct imap_fetch_context *fetch_ctx;
+ struct timeout *to_watch;
+
+ bool have_subscriptions:1;
+ bool selected_set:1;
+ bool selected_immediate_expunges:1;
+ bool send_immediate_status:1;
+ bool watching_mailbox:1;
+ bool notifying:1;
+};
+
+bool imap_notify_match_mailbox(struct imap_notify_namespace *notify_ns,
+ const struct imap_notify_mailboxes *notify_boxes,
+ const char *vname);
+
+int imap_client_notify_newmails(struct client *client);
+void imap_client_notify_finished(struct client *client);
+
+void imap_client_notify_command_allocated(struct client *client);
+void imap_client_notify_command_freed(struct client *client);
+
+int imap_notify_begin(struct imap_notify_context *ctx);
+void imap_notify_deinit(struct imap_notify_context **ctx);
+
+void imap_notify_flush(struct imap_notify_context *ctx);
+
+#endif
diff --git a/src/imap/imap-search-args.c b/src/imap/imap-search-args.c
new file mode 100644
index 0000000..67e8679
--- /dev/null
+++ b/src/imap/imap-search-args.c
@@ -0,0 +1,353 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "mail-storage.h"
+#include "mail-search-parser.h"
+#include "mail-search-build.h"
+#include "imap-search-args.h"
+#include "imap-parser.h"
+#include "imap-seqset.h"
+
+
+struct search_build_data {
+ pool_t pool;
+ struct mailbox *box;
+ const char *error;
+};
+
+static bool search_args_have_searchres(struct mail_search_arg *sargs)
+{
+ for (; sargs != NULL; sargs = sargs->next) {
+ switch (sargs->type) {
+ case SEARCH_UIDSET:
+ if (strcmp(sargs->value.str, "$") == 0)
+ return TRUE;
+ break;
+ case SEARCH_SUB:
+ case SEARCH_OR:
+ if (search_args_have_searchres(sargs->value.subargs))
+ return TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static void imap_search_saved_uidset_clear(struct client_command_context *cmd)
+{
+ if (array_is_created(&cmd->client->search_saved_uidset))
+ array_clear(&cmd->client->search_saved_uidset);
+ else
+ i_array_init(&cmd->client->search_saved_uidset, 128);
+}
+
+int imap_search_args_build(struct client_command_context *cmd,
+ const struct imap_arg *args, const char *charset,
+ struct mail_search_args **search_args_r)
+{
+ struct mail_search_parser *parser;
+ struct mail_search_args *sargs;
+ const char *client_error;
+ int ret;
+
+ if (IMAP_ARG_IS_EOL(args)) {
+ client_send_command_error(cmd, "Missing search parameters");
+ return -1;
+ }
+
+ parser = mail_search_parser_init_imap(args);
+ ret = mail_search_build(mail_search_register_get_imap(),
+ parser, &charset, &sargs, &client_error);
+ mail_search_parser_deinit(&parser);
+ if (ret < 0) {
+ if (charset == NULL) {
+ if (cmd->search_save_result)
+ imap_search_saved_uidset_clear(cmd);
+ client_send_tagline(cmd, t_strconcat(
+ "NO [BADCHARSET] ", client_error, NULL));
+ } else {
+ client_send_command_error(cmd, client_error);
+ }
+ return -1;
+ }
+
+ if (search_args_have_searchres(sargs->args)) {
+ if (client_handle_search_save_ambiguity(cmd))
+ return 0;
+ }
+
+ mail_search_args_init(sargs, cmd->client->mailbox, TRUE,
+ &cmd->client->search_saved_uidset);
+ if (cmd->search_save_result) {
+ /* clear the SAVE resultset only after potentially using $
+ in the search args themselves */
+ imap_search_saved_uidset_clear(cmd);
+ }
+ *search_args_r = sargs;
+ return 1;
+}
+
+static bool
+msgset_is_valid(ARRAY_TYPE(seq_range) *seqset, uint32_t messages_count)
+{
+ const struct seq_range *range;
+ unsigned int count;
+
+ /* when there are no messages, all messagesets are invalid.
+ if there's at least one message:
+ - * gives seq1 = seq2 = (uint32_t)-1
+ - n:* should work if n <= messages_count
+ - n:m or m should work if m <= messages_count
+ */
+ range = array_get(seqset, &count);
+ if (count == 0 || messages_count == 0)
+ return FALSE;
+
+ if (range[count-1].seq2 == (uint32_t)-1) {
+ if (range[count-1].seq1 > messages_count &&
+ range[count-1].seq1 != (uint32_t)-1)
+ return FALSE;
+ } else {
+ if (range[count-1].seq2 > messages_count)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int imap_search_get_msgset_arg(struct client_command_context *cmd,
+ const char *messageset,
+ struct mail_search_args **args_r,
+ const char **error_r)
+{
+ struct mail_search_args *args;
+
+ args = mail_search_build_init();
+ args->args = p_new(args->pool, struct mail_search_arg, 1);
+ args->args->type = SEARCH_SEQSET;
+ p_array_init(&args->args->value.seqset, args->pool, 16);
+ if (imap_seq_set_parse(messageset, &args->args->value.seqset) < 0 ||
+ !msgset_is_valid(&args->args->value.seqset,
+ cmd->client->messages_count)) {
+ *error_r = "Invalid messageset";
+ mail_search_args_unref(&args);
+ return -1;
+ }
+ *args_r = args;
+ return 0;
+}
+
+static int
+imap_search_get_uidset_arg(const char *uidset, struct mail_search_args **args_r,
+ const char **error_r)
+{
+ struct mail_search_args *args;
+
+ args = mail_search_build_init();
+ args->args = p_new(args->pool, struct mail_search_arg, 1);
+ args->args->type = SEARCH_UIDSET;
+ p_array_init(&args->args->value.seqset, args->pool, 16);
+ if (imap_seq_set_parse(uidset, &args->args->value.seqset) < 0) {
+ *error_r = "Invalid uidset";
+ mail_search_args_unref(&args);
+ return -1;
+ }
+
+ *args_r = args;
+ return 0;
+}
+
+int imap_search_get_seqset(struct client_command_context *cmd,
+ const char *set, bool uid,
+ struct mail_search_args **search_args_r)
+{
+ int ret;
+
+ ret = imap_search_get_anyset(cmd, set, uid, search_args_r);
+ if (ret > 0) {
+ mail_search_args_init(*search_args_r,
+ cmd->client->mailbox, TRUE,
+ &cmd->client->search_saved_uidset);
+ }
+ return ret;
+}
+
+void imap_search_anyset_to_uidset(struct client_command_context *cmd,
+ struct mail_search_args *args)
+{
+ struct mail_search_arg *arg = args->args;
+
+ i_assert(arg->next == NULL);
+ switch (arg->type) {
+ case SEARCH_ALL:
+ if (arg->match_not)
+ break;
+ t_array_init(&arg->value.seqset, 1);
+ seq_range_array_add_range(&arg->value.seqset, 1, (uint32_t)-1);
+ /* fall through */
+ case SEARCH_SEQSET: {
+ ARRAY_TYPE(seq_range) seqs = arg->value.seqset;
+
+ arg->type = SEARCH_UIDSET;
+ p_array_init(&arg->value.seqset, args->pool, 16);
+ mailbox_get_uid_range(cmd->client->mailbox, &seqs,
+ &arg->value.seqset);
+ break;
+ }
+ case SEARCH_UIDSET:
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static int imap_search_get_searchres(struct client_command_context *cmd,
+ struct mail_search_args **search_args_r)
+{
+ struct mail_search_args *search_args;
+
+ if (client_handle_search_save_ambiguity(cmd))
+ return 0;
+
+ search_args = mail_search_build_init();
+ search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
+ if (array_is_created(&cmd->client->search_saved_uidset)) {
+ search_args->args->type = SEARCH_UIDSET;
+ p_array_init(&search_args->args->value.seqset,
+ search_args->pool,
+ array_count(&cmd->client->search_saved_uidset));
+ array_append_array(&search_args->args->value.seqset,
+ &cmd->client->search_saved_uidset);
+ } else {
+ /* $ not set yet, match nothing */
+ search_args->args->type = SEARCH_ALL;
+ search_args->args->match_not = TRUE;
+ }
+ *search_args_r = search_args;
+ return 1;
+}
+
+int imap_search_get_anyset(struct client_command_context *cmd,
+ const char *set, bool uid,
+ struct mail_search_args **search_args_r)
+{
+ const char *client_error = NULL;
+ int ret;
+
+ if (strcmp(set, "$") == 0) {
+ /* SEARCHRES extension: replace $ with the last saved
+ search result */
+ return imap_search_get_searchres(cmd, search_args_r);
+ }
+ if (!uid) {
+ ret = imap_search_get_msgset_arg(cmd, set, search_args_r,
+ &client_error);
+ } else {
+ ret = imap_search_get_uidset_arg(set, search_args_r,
+ &client_error);
+ }
+ if (ret < 0) {
+ client_send_command_error(cmd, client_error);
+ return -1;
+ }
+ return 1;
+}
+
+void imap_search_add_changed_since(struct mail_search_args *search_args,
+ uint64_t modseq)
+{
+ struct mail_search_arg *search_arg;
+
+ search_arg = p_new(search_args->pool, struct mail_search_arg, 1);
+ search_arg->type = SEARCH_MODSEQ;
+ search_arg->value.modseq =
+ p_new(search_args->pool, struct mail_search_modseq, 1);
+ search_arg->value.modseq->modseq = modseq + 1;
+
+ search_arg->next = search_args->args->next;
+ search_args->args->next = search_arg;
+}
+
+struct imap_search_seqset_iter {
+ struct mail_search_args *search_args;
+ ARRAY_TYPE(seq_range) seqset_left;
+ unsigned int batch_size;
+};
+
+static void imap_search_seqset_next_batch(struct imap_search_seqset_iter *iter)
+{
+ array_clear(&iter->search_args->args->value.seqset);
+ seq_range_array_merge_n(&iter->search_args->args->value.seqset,
+ &iter->seqset_left, iter->batch_size);
+}
+
+struct imap_search_seqset_iter *
+imap_search_seqset_iter_init(struct mail_search_args *search_args,
+ uint32_t messages_count, unsigned int batch_size)
+{
+ struct imap_search_seqset_iter *iter;
+
+ i_assert(search_args->args->next == NULL);
+
+ iter = i_new(struct imap_search_seqset_iter, 1);
+ iter->search_args = search_args;
+ iter->batch_size = batch_size;
+ mail_search_args_ref(iter->search_args);
+
+ switch (search_args->args->type) {
+ case SEARCH_SEQSET:
+ case SEARCH_UIDSET:
+ break;
+ case SEARCH_ALL:
+ if (search_args->args->match_not) {
+ /* $ used before search result was saved */
+ return iter;
+ }
+ /* 1:* - convert to seqset */
+ search_args->args->type = SEARCH_SEQSET;
+ p_array_init(&search_args->args->value.seqset,
+ search_args->pool, 1);
+ seq_range_array_add_range(&search_args->args->value.seqset,
+ 1, messages_count);
+ break;
+ default:
+ i_panic("Unexpected search_args type %d",
+ search_args->args->type);
+ }
+
+ i_assert(search_args->args->type == SEARCH_SEQSET ||
+ search_args->args->type == SEARCH_UIDSET);
+
+ i_array_init(&iter->seqset_left,
+ array_count(&search_args->args->value.seqset));
+ array_append_array(&iter->seqset_left, &search_args->args->value.seqset);
+ imap_search_seqset_next_batch(iter);
+ return iter;
+}
+
+void imap_search_seqset_iter_deinit(struct imap_search_seqset_iter **_iter)
+{
+ struct imap_search_seqset_iter *iter = *_iter;
+
+ if (iter == NULL)
+ return;
+
+ mail_search_args_unref(&iter->search_args);
+ array_free(&iter->seqset_left);
+ i_free(iter);
+}
+
+bool imap_search_seqset_iter_next(struct imap_search_seqset_iter *iter)
+{
+ if (!array_is_created(&iter->seqset_left) ||
+ array_count(&iter->seqset_left) == 0)
+ return FALSE;
+
+ /* remove the last batch of searched mails from seqset_left */
+ if (seq_range_array_remove_seq_range(&iter->seqset_left,
+ &iter->search_args->args->value.seqset) == 0)
+ i_unreached();
+ imap_search_seqset_next_batch(iter);
+ return array_count(&iter->search_args->args->value.seqset) > 0;
+}
diff --git a/src/imap/imap-search-args.h b/src/imap/imap-search-args.h
new file mode 100644
index 0000000..4bf6c4c
--- /dev/null
+++ b/src/imap/imap-search-args.h
@@ -0,0 +1,47 @@
+#ifndef IMAP_SEARCH_ARGS_H
+#define IMAP_SEARCH_ARGS_H
+
+#include "mail-search.h"
+
+struct imap_arg;
+struct mailbox;
+struct client_command_context;
+
+/* Builds search arguments based on IMAP arguments. Returns -1 if search
+ arguments are invalid, 0 if we have to wait for unambiguity,
+ 1 if we can continue. */
+int imap_search_args_build(struct client_command_context *cmd,
+ const struct imap_arg *args, const char *charset,
+ struct mail_search_args **search_args_r);
+
+/* Returns -1 if set is invalid, 0 if we have to wait for unambiguity,
+ 1 if we were successful. search_args_r is set to contain either a seqset
+ or uidset. */
+int imap_search_get_anyset(struct client_command_context *cmd,
+ const char *set, bool uid,
+ struct mail_search_args **search_args_r);
+/* Like imap_search_get_anyset(), but always returns a seqset. */
+int imap_search_get_seqset(struct client_command_context *cmd,
+ const char *set, bool uid,
+ struct mail_search_args **search_args_r);
+/* Convert search args returned by imap_search_get_anyset() to SEARCH_UIDSET. */
+void imap_search_anyset_to_uidset(struct client_command_context *cmd,
+ struct mail_search_args *args);
+
+void imap_search_add_changed_since(struct mail_search_args *search_args,
+ uint64_t modseq);
+
+/* Iterate search_args in batches of messages. The search_args itself is
+ modified each time imap_search_seqset_iter_next() is called. Note that
+ search_args is expected to come from imap_search_get_anyset(), so it should
+ have a single parameter containing SEARCH_ALL, SEARCH_SEQSET or
+ SEARCH_UIDSET. */
+struct imap_search_seqset_iter *
+imap_search_seqset_iter_init(struct mail_search_args *search_args,
+ uint32_t messages_count, unsigned int batch_size);
+/* Iterate the next batch. Returns TRUE if the batch was updated, FALSE if
+ all the batches have been iterated. */
+bool imap_search_seqset_iter_next(struct imap_search_seqset_iter *iter);
+void imap_search_seqset_iter_deinit(struct imap_search_seqset_iter **iter);
+
+#endif
diff --git a/src/imap/imap-search.c b/src/imap/imap-search.c
new file mode 100644
index 0000000..8b5d660
--- /dev/null
+++ b/src/imap/imap-search.c
@@ -0,0 +1,612 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ostream.h"
+#include "str.h"
+#include "seq-range-array.h"
+#include "time-util.h"
+#include "imap-resp-code.h"
+#include "imap-quote.h"
+#include "imap-seqset.h"
+#include "imap-util.h"
+#include "mail-search-build.h"
+#include "imap-fetch.h"
+#include "imap-commands.h"
+#include "imap-search-args.h"
+#include "imap-search.h"
+
+
+static int imap_search_deinit(struct imap_search_context *ctx);
+
+static int
+imap_partial_range_parse(struct imap_search_context *ctx, const char *str)
+{
+ ctx->partial1 = 0;
+ ctx->partial2 = 0;
+ for (; *str >= '0' && *str <= '9'; str++)
+ ctx->partial1 = ctx->partial1 * 10 + *str-'0';
+ if (*str != ':' || ctx->partial1 == 0)
+ return -1;
+ for (str++; *str >= '0' && *str <= '9'; str++)
+ ctx->partial2 = ctx->partial2 * 10 + *str-'0';
+ if (*str != '\0' || ctx->partial2 == 0)
+ return -1;
+
+ if (ctx->partial1 > ctx->partial2) {
+ uint32_t temp = ctx->partial2;
+ ctx->partial2 = ctx->partial1;
+ ctx->partial1 = temp;
+ }
+
+ return 0;
+}
+
+static bool
+search_parse_fetch_att(struct imap_search_context *ctx,
+ const struct imap_arg *update_args)
+{
+ const char *client_error;
+
+ ctx->fetch_pool = pool_alloconly_create("search update fetch", 512);
+ if (imap_fetch_att_list_parse(ctx->cmd->client, ctx->fetch_pool,
+ update_args, &ctx->fetch_ctx,
+ &client_error) < 0) {
+ client_send_command_error(ctx->cmd, t_strconcat(
+ "SEARCH UPDATE fetch-att: ", client_error, NULL));
+ pool_unref(&ctx->fetch_pool);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+search_parse_return_options(struct imap_search_context *ctx,
+ const struct imap_arg *args)
+{
+ struct client_command_context *cmd = ctx->cmd;
+ const struct imap_arg *update_args;
+ const char *name, *str;
+ unsigned int idx;
+
+ while (!IMAP_ARG_IS_EOL(args)) {
+ if (!imap_arg_get_atom(args, &name)) {
+ client_send_command_error(cmd,
+ "SEARCH return options contain non-atoms.");
+ return FALSE;
+ }
+ name = t_str_ucase(name);
+ args++;
+ if (strcmp(name, "MIN") == 0)
+ ctx->return_options |= SEARCH_RETURN_MIN;
+ else if (strcmp(name, "MAX") == 0)
+ ctx->return_options |= SEARCH_RETURN_MAX;
+ else if (strcmp(name, "ALL") == 0)
+ ctx->return_options |= SEARCH_RETURN_ALL;
+ else if (strcmp(name, "COUNT") == 0)
+ ctx->return_options |= SEARCH_RETURN_COUNT;
+ else if (strcmp(name, "SAVE") == 0)
+ ctx->return_options |= SEARCH_RETURN_SAVE;
+ else if (strcmp(name, "CONTEXT") == 0) {
+ /* no-op */
+ } else if (strcmp(name, "UPDATE") == 0) {
+ if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0) {
+ client_send_command_error(cmd,
+ "SEARCH return options have duplicate UPDATE.");
+ return FALSE;
+ }
+ ctx->return_options |= SEARCH_RETURN_UPDATE;
+ if (imap_arg_get_list(args, &update_args)) {
+ if (!search_parse_fetch_att(ctx, update_args))
+ return FALSE;
+ args++;
+ }
+ } else if (strcmp(name, "RELEVANCY") == 0)
+ ctx->return_options |= SEARCH_RETURN_RELEVANCY;
+ else if (strcmp(name, "PARTIAL") == 0) {
+ if (ctx->partial1 != 0) {
+ client_send_command_error(cmd,
+ "PARTIAL can be used only once.");
+ return FALSE;
+ }
+ ctx->return_options |= SEARCH_RETURN_PARTIAL;
+ if (!imap_arg_get_atom(args, &str)) {
+ client_send_command_error(cmd,
+ "PARTIAL range missing.");
+ return FALSE;
+ }
+ if (imap_partial_range_parse(ctx, str) < 0) {
+ client_send_command_error(cmd,
+ "PARTIAL range broken.");
+ return FALSE;
+ }
+ args++;
+ } else {
+ client_send_command_error(cmd,
+ "Unknown SEARCH return option");
+ return FALSE;
+ }
+ }
+
+ if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0 &&
+ client_search_update_lookup(cmd->client, cmd->tag, &idx) != NULL) {
+ client_send_command_error(cmd, "Duplicate search update tag");
+ return FALSE;
+ }
+ if ((ctx->return_options & SEARCH_RETURN_PARTIAL) != 0 &&
+ (ctx->return_options & SEARCH_RETURN_ALL) != 0) {
+ client_send_command_error(cmd, "PARTIAL conflicts with ALL");
+ return FALSE;
+ }
+
+ if (ctx->return_options == 0)
+ ctx->return_options = SEARCH_RETURN_ALL;
+ ctx->return_options |= SEARCH_RETURN_ESEARCH;
+ return TRUE;
+}
+
+static void imap_search_args_check(struct imap_search_context *ctx,
+ const struct mail_search_arg *sargs)
+{
+ for (; sargs != NULL; sargs = sargs->next) {
+ switch (sargs->type) {
+ case SEARCH_SEQSET:
+ ctx->have_seqsets = TRUE;
+ break;
+ case SEARCH_MODSEQ:
+ ctx->have_modseqs = TRUE;
+ break;
+ case SEARCH_OR:
+ case SEARCH_SUB:
+ imap_search_args_check(ctx, sargs->value.subargs);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void imap_search_result_save(struct imap_search_context *ctx)
+{
+ struct client *client = ctx->cmd->client;
+ struct mail_search_result *result;
+ struct imap_search_update *update;
+
+ if (!array_is_created(&client->search_updates))
+ i_array_init(&client->search_updates, 32);
+ else if (array_count(&client->search_updates) >=
+ CLIENT_MAX_SEARCH_UPDATES) {
+ /* too many updates */
+ string_t *str = t_str_new(256);
+ str_append(str, "* NO [NOUPDATE ");
+ imap_append_quoted(str, ctx->cmd->tag);
+ str_append_c(str, ']');
+ client_send_line(client, str_c(str));
+ ctx->return_options &= ENUM_NEGATE(SEARCH_RETURN_UPDATE);
+ imap_search_context_free(ctx);
+ return;
+ }
+ result = mailbox_search_result_save(ctx->search_ctx,
+ MAILBOX_SEARCH_RESULT_FLAG_UPDATE |
+ MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC);
+
+ update = array_append_space(&client->search_updates);
+ update->tag = i_strdup(ctx->cmd->tag);
+ update->result = result;
+ update->return_uids = ctx->cmd->uid;
+ update->fetch_pool = ctx->fetch_pool;
+ update->fetch_ctx = ctx->fetch_ctx;
+ ctx->fetch_pool = NULL;
+ ctx->fetch_ctx = NULL;
+}
+
+static void imap_search_send_result_standard(struct imap_search_context *ctx)
+{
+ const struct seq_range *range;
+ string_t *str;
+ uint32_t seq;
+
+ str = t_str_new(1024);
+ str_append(str, ctx->sorting ? "* SORT" : "* SEARCH");
+ array_foreach(&ctx->result, range) {
+ for (seq = range->seq1; seq <= range->seq2; seq++)
+ str_printfa(str, " %u", seq);
+ if (str_len(str) >= 1024-32) {
+ o_stream_nsend(ctx->cmd->client->output,
+ str_data(str), str_len(str));
+ str_truncate(str, 0);
+ }
+ }
+
+ if (ctx->highest_seen_modseq != 0) {
+ str_printfa(str, " (MODSEQ %"PRIu64")",
+ ctx->highest_seen_modseq);
+ }
+ str_append(str, "\r\n");
+ o_stream_nsend(ctx->cmd->client->output, str_data(str), str_len(str));
+}
+
+static void
+imap_search_send_partial(struct imap_search_context *ctx, string_t *str)
+{
+ str_printfa(str, " PARTIAL (%u:%u ", ctx->partial1, ctx->partial2);
+ if (array_count(&ctx->result) == 0) {
+ /* no results (in range) */
+ str_append(str, "NIL");
+ } else {
+ imap_write_seq_range(str, &ctx->result);
+ }
+ str_append_c(str, ')');
+}
+
+static void
+imap_search_send_relevancy(struct imap_search_context *ctx, string_t *dest)
+{
+ const float *scores;
+ unsigned int i, count;
+ float diff, imap_score;
+
+ scores = array_get(&ctx->relevancy_scores, &count);
+ if (count == 0)
+ return;
+
+ /* we'll need to convert float scores to numbers 1..100
+ FIXME: would be a good idea to try to detect non-linear score
+ mappings and convert them better.. */
+ diff = ctx->max_relevancy - ctx->min_relevancy;
+ if (diff == 0)
+ diff = 1.0;
+ for (i = 0; i < count; i++) {
+ if (i > 0)
+ str_append_c(dest, ' ');
+ imap_score = (scores[i] - ctx->min_relevancy) / diff * 100.0;
+ if (imap_score < 1)
+ str_append(dest, "1");
+ else
+ str_printfa(dest, "%u", (unsigned int)imap_score);
+ }
+}
+
+static void imap_search_send_result(struct imap_search_context *ctx)
+{
+ struct client *client = ctx->cmd->client;
+ string_t *str;
+
+ if ((ctx->return_options & SEARCH_RETURN_ESEARCH) == 0) {
+ imap_search_send_result_standard(ctx);
+ return;
+ }
+
+ if (ctx->return_options ==
+ (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_SAVE)) {
+ /* we only wanted to save the result, don't return
+ ESEARCH result. */
+ return;
+ }
+
+ str = str_new(default_pool, 1024);
+ str_append(str, "* ESEARCH (TAG ");
+ imap_append_string(str, ctx->cmd->tag);
+ str_append_c(str, ')');
+
+ if (ctx->cmd->uid)
+ str_append(str, " UID");
+
+ if ((ctx->return_options & SEARCH_RETURN_MIN) != 0 && ctx->min_id != 0)
+ str_printfa(str, " MIN %u", ctx->min_id);
+ if ((ctx->return_options & SEARCH_RETURN_MAX) != 0 &&
+ ctx->max_seq != 0) {
+ uint32_t id = ctx->cmd->uid ? ctx->max_uid : ctx->max_seq;
+ str_printfa(str, " MAX %u", id);
+ }
+
+ if ((ctx->return_options & SEARCH_RETURN_ALL) != 0 &&
+ array_count(&ctx->result) > 0) {
+ str_append(str, " ALL ");
+ imap_write_seq_range(str, &ctx->result);
+ }
+ if ((ctx->return_options & SEARCH_RETURN_RELEVANCY) != 0) {
+ str_append(str, " RELEVANCY (");
+ imap_search_send_relevancy(ctx, str);
+ str_append_c(str, ')');
+ }
+
+ if ((ctx->return_options & SEARCH_RETURN_PARTIAL) != 0)
+ imap_search_send_partial(ctx, str);
+
+ if ((ctx->return_options & SEARCH_RETURN_COUNT) != 0)
+ str_printfa(str, " COUNT %u", ctx->result_count);
+ if (ctx->highest_seen_modseq != 0) {
+ str_printfa(str, " MODSEQ %"PRIu64,
+ ctx->highest_seen_modseq);
+ }
+ str_append(str, "\r\n");
+ o_stream_nsend(client->output, str_data(str), str_len(str));
+ str_free(&str);
+}
+
+static void
+search_update_mail(struct imap_search_context *ctx, struct mail *mail)
+{
+ uint64_t modseq;
+
+ if (ctx->max_update_seq == mail->seq) {
+ /* MIN already handled this mail */
+ return;
+ }
+ ctx->max_update_seq = mail->seq;
+
+ if ((ctx->return_options & SEARCH_RETURN_MODSEQ) != 0) {
+ modseq = mail_get_modseq(mail);
+ if (ctx->highest_seen_modseq < modseq)
+ ctx->highest_seen_modseq = modseq;
+ }
+ if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) {
+ seq_range_array_add(&ctx->cmd->client->search_saved_uidset,
+ mail->uid);
+ }
+ if ((ctx->return_options & SEARCH_RETURN_RELEVANCY) != 0) {
+ const char *str;
+ float score;
+
+ if (mail_get_special(mail, MAIL_FETCH_SEARCH_RELEVANCY, &str) < 0)
+ score = 0;
+ else
+ score = strtod(str, NULL);
+ array_push_back(&ctx->relevancy_scores, &score);
+ if (ctx->min_relevancy > score)
+ ctx->min_relevancy = score;
+ if (ctx->max_relevancy < score)
+ ctx->max_relevancy = score;
+ }
+}
+
+static void search_add_result_id(struct imap_search_context *ctx, uint32_t id)
+{
+ struct seq_range *range;
+ unsigned int count;
+
+ /* only append the data. this is especially important when we're
+ returning a sort result. */
+ range = array_get_modifiable(&ctx->result, &count);
+ if (count > 0 && id == range[count-1].seq2 + 1) {
+ range[count-1].seq2++;
+ } else {
+ range = array_append_space(&ctx->result);
+ range->seq1 = range->seq2 = id;
+ }
+}
+
+static bool cmd_search_more(struct client_command_context *cmd)
+{
+ struct imap_search_context *ctx = cmd->context;
+ enum search_return_options opts = ctx->return_options;
+ struct mail *mail;
+ enum mailbox_sync_flags sync_flags;
+ uint32_t id;
+ const char *ok_reply;
+ bool tryagain, lost_data;
+
+ if (cmd->cancel) {
+ (void)imap_search_deinit(ctx);
+ return TRUE;
+ }
+
+ while (mailbox_search_next_nonblock(ctx->search_ctx,
+ &mail, &tryagain)) {
+ id = cmd->uid ? mail->uid : mail->seq;
+ ctx->result_count++;
+
+ ctx->max_seq = mail->seq;
+ ctx->max_uid = mail->uid;
+ if (HAS_ANY_BITS(opts, SEARCH_RETURN_MIN) && ctx->min_id == 0) {
+ /* MIN not set yet */
+ ctx->min_id = id;
+ search_update_mail(ctx, mail);
+ }
+ if (HAS_ANY_BITS(opts, SEARCH_RETURN_ALL)) {
+ /* ALL and PARTIAL are mutually exclusive */
+ i_assert(HAS_NO_BITS(opts, SEARCH_RETURN_PARTIAL));
+ search_add_result_id(ctx, id);
+ } else if ((opts & SEARCH_RETURN_PARTIAL) != 0) {
+ /* only update if it's within range */
+ i_assert(HAS_NO_BITS(opts, SEARCH_RETURN_ALL));
+ if (ctx->partial1 <= ctx->result_count &&
+ ctx->partial2 >= ctx->result_count)
+ search_add_result_id(ctx, id);
+ else if (HAS_ALL_BITS(opts, SEARCH_RETURN_COUNT |
+ SEARCH_RETURN_SAVE)) {
+ /* (SAVE COUNT PARTIAL n:m) must include all
+ results in SAVE, but not include mails
+ outside the PARTIAL range in MODSEQ or
+ RELEVANCY */
+ seq_range_array_add(&cmd->client->search_saved_uidset,
+ mail->uid);
+ continue;
+ } else {
+ continue;
+ }
+ } else if (HAS_ANY_BITS(opts, SEARCH_RETURN_COUNT)) {
+ /* with COUNT don't add it to results, but handle
+ SAVE and MODSEQ */
+ } else if (HAS_ANY_BITS(opts, SEARCH_RETURN_MIN |
+ SEARCH_RETURN_MAX)) {
+ /* MIN and/or MAX only requested, but we don't know if
+ this is MAX until the search is finished. */
+ continue;
+ } else if (HAS_ANY_BITS(opts, SEARCH_RETURN_SAVE)) {
+ /* Only SAVE used */
+ }
+ search_update_mail(ctx, mail);
+ }
+ if (tryagain)
+ return FALSE;
+
+ if ((opts & SEARCH_RETURN_MAX) != 0 && ctx->max_seq != 0 &&
+ ctx->max_update_seq != ctx->max_seq &&
+ HAS_ANY_BITS(opts, SEARCH_RETURN_MODSEQ |
+ SEARCH_RETURN_SAVE | SEARCH_RETURN_RELEVANCY)) {
+ /* finish handling MAX */
+ mail = mail_alloc(ctx->trans, 0, NULL);
+ mail_set_seq(mail, ctx->max_seq);
+ search_update_mail(ctx, mail);
+ mail_free(&mail);
+ }
+
+ lost_data = mailbox_search_seen_lost_data(ctx->search_ctx);
+ if (imap_search_deinit(ctx) < 0) {
+ client_send_box_error(cmd, cmd->client->mailbox);
+ return TRUE;
+ }
+
+ sync_flags = MAILBOX_SYNC_FLAG_FAST;
+ if (!cmd->uid || ctx->have_seqsets)
+ sync_flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
+ ok_reply = t_strdup_printf("OK %s%s completed",
+ lost_data ? "["IMAP_RESP_CODE_EXPUNGEISSUED"] " : "",
+ !ctx->sorting ? "Search" : "Sort");
+ return cmd_sync(cmd, sync_flags, 0, ok_reply);
+}
+
+static void cmd_search_more_callback(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ bool finished;
+
+ o_stream_cork(client->output);
+ finished = command_exec(cmd);
+ o_stream_uncork(client->output);
+
+ if (!finished)
+ (void)client_handle_unfinished_cmd(cmd);
+ else
+ client_command_free(&cmd);
+ cmd_sync_delayed(client);
+
+ client_continue_pending_input(client);
+}
+
+int cmd_search_parse_return_if_found(struct imap_search_context *ctx,
+ const struct imap_arg **_args)
+{
+ const struct imap_arg *list_args, *args = *_args;
+ struct client_command_context *cmd = ctx->cmd;
+
+ if (!imap_arg_atom_equals(&args[0], "RETURN") ||
+ !imap_arg_get_list(&args[1], &list_args)) {
+ ctx->return_options = SEARCH_RETURN_ALL;
+ return 1;
+ }
+
+ if (!search_parse_return_options(ctx, list_args)) {
+ imap_search_context_free(ctx);
+ return -1;
+ }
+
+ if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) {
+ /* wait if there is another SEARCH SAVE command running. */
+ if (client_handle_search_save_ambiguity(cmd)) {
+ imap_search_context_free(ctx);
+ return 0;
+ }
+
+ cmd->search_save_result = TRUE;
+ }
+
+ *_args = args + 2;
+ return 1;
+}
+
+bool imap_search_start(struct imap_search_context *ctx,
+ struct mail_search_args *sargs,
+ const enum mail_sort_type *sort_program)
+{
+ struct client_command_context *cmd = ctx->cmd;
+
+ imap_search_args_check(ctx, sargs->args);
+
+ if (ctx->have_modseqs) {
+ ctx->return_options |= SEARCH_RETURN_MODSEQ;
+ client_enable(cmd->client, imap_feature_condstore);
+ }
+
+ ctx->box = cmd->client->mailbox;
+ ctx->trans = mailbox_transaction_begin(ctx->box, 0,
+ imap_client_command_get_reason(cmd));
+ ctx->sargs = sargs;
+ ctx->search_ctx =
+ mailbox_search_init(ctx->trans, sargs, sort_program, 0, NULL);
+ ctx->sorting = sort_program != NULL;
+ i_array_init(&ctx->result, 128);
+ if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0)
+ imap_search_result_save(ctx);
+ else {
+ i_assert(ctx->fetch_ctx == NULL);
+ }
+ if ((ctx->return_options & SEARCH_RETURN_RELEVANCY) != 0)
+ i_array_init(&ctx->relevancy_scores, 128);
+
+ cmd->func = cmd_search_more;
+ cmd->context = ctx;
+
+ if (cmd_search_more(cmd))
+ return TRUE;
+
+ /* we may have moved onto syncing by now */
+ if (cmd->func == cmd_search_more) {
+ ctx->to = timeout_add(0, cmd_search_more_callback, cmd);
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_EXTERNAL;
+ }
+ return FALSE;
+}
+
+static int imap_search_deinit(struct imap_search_context *ctx)
+{
+ int ret = 0;
+
+ if (mailbox_search_deinit(&ctx->search_ctx) < 0)
+ ret = -1;
+
+ /* Send the result also after failing. It might have something useful,
+ even though it didn't fully succeed. The client should be able to
+ realize that there was some failure because NO is returned. */
+ if (!ctx->cmd->cancel &&
+ (ret == 0 || array_count(&ctx->result) > 0))
+ imap_search_send_result(ctx);
+
+ if (ret < 0 || ctx->cmd->cancel) {
+ /* search failed */
+ if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0)
+ array_clear(&ctx->cmd->client->search_saved_uidset);
+ }
+
+ (void)mailbox_transaction_commit(&ctx->trans);
+
+ timeout_remove(&ctx->to);
+ if (array_is_created(&ctx->relevancy_scores))
+ array_free(&ctx->relevancy_scores);
+ array_free(&ctx->result);
+ mail_search_args_deinit(ctx->sargs);
+ mail_search_args_unref(&ctx->sargs);
+ imap_search_context_free(ctx);
+
+ ctx->cmd->context = NULL;
+ return ret;
+}
+
+void imap_search_context_free(struct imap_search_context *ctx)
+{
+ if (ctx->fetch_ctx != NULL) {
+ imap_fetch_free(&ctx->fetch_ctx);
+ pool_unref(&ctx->fetch_pool);
+ }
+}
+
+void imap_search_update_free(struct imap_search_update *update)
+{
+ if (update->fetch_ctx != NULL) {
+ imap_fetch_free(&update->fetch_ctx);
+ pool_unref(&update->fetch_pool);
+ }
+ mailbox_search_result_free(&update->result);
+ i_free(update->tag);
+}
diff --git a/src/imap/imap-search.h b/src/imap/imap-search.h
new file mode 100644
index 0000000..f665375
--- /dev/null
+++ b/src/imap/imap-search.h
@@ -0,0 +1,61 @@
+#ifndef IMAP_SEARCH_H
+#define IMAP_SEARCH_H
+
+#include <sys/time.h>
+
+enum search_return_options {
+ SEARCH_RETURN_ESEARCH = 0x0001,
+ SEARCH_RETURN_MIN = 0x0002,
+ SEARCH_RETURN_MAX = 0x0004,
+ SEARCH_RETURN_ALL = 0x0008,
+ SEARCH_RETURN_COUNT = 0x0010,
+ SEARCH_RETURN_MODSEQ = 0x0020,
+ SEARCH_RETURN_SAVE = 0x0040,
+ SEARCH_RETURN_UPDATE = 0x0080,
+ SEARCH_RETURN_PARTIAL = 0x0100,
+ SEARCH_RETURN_RELEVANCY = 0x0200
+/* Options that don't return any seq/uid results, and also don't affect
+ SEARCHRES $ when combined with MIN/MAX. */
+#define SEARCH_RETURN_NORESULTS \
+ (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_MODSEQ | SEARCH_RETURN_SAVE | \
+ SEARCH_RETURN_UPDATE | SEARCH_RETURN_RELEVANCY)
+};
+
+struct imap_search_context {
+ struct client_command_context *cmd;
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+ struct mail_search_context *search_ctx;
+
+ pool_t fetch_pool;
+ struct imap_fetch_context *fetch_ctx;
+
+ struct mail_search_args *sargs;
+ enum search_return_options return_options;
+ uint32_t partial1, partial2;
+
+ struct timeout *to;
+ ARRAY_TYPE(seq_range) result;
+ unsigned int result_count;
+ uint32_t min_id, max_update_seq, max_seq, max_uid;
+
+ ARRAY(float) relevancy_scores;
+ float min_relevancy, max_relevancy;
+
+ uint64_t highest_seen_modseq;
+
+ bool have_seqsets:1;
+ bool have_modseqs:1;
+ bool sorting:1;
+};
+
+int cmd_search_parse_return_if_found(struct imap_search_context *ctx,
+ const struct imap_arg **args);
+void imap_search_context_free(struct imap_search_context *ctx);
+
+bool imap_search_start(struct imap_search_context *ctx,
+ struct mail_search_args *sargs,
+ const enum mail_sort_type *sort_program) ATTR_NULL(3);
+void imap_search_update_free(struct imap_search_update *update);
+
+#endif
diff --git a/src/imap/imap-settings.c b/src/imap/imap-settings.c
new file mode 100644
index 0000000..2acf9dd
--- /dev/null
+++ b/src/imap/imap-settings.c
@@ -0,0 +1,197 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "hostpid.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "mail-storage-settings.h"
+#include "smtp-submit-settings.h"
+#include "imap-settings.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+static bool imap_settings_verify(void *_set, pool_t pool,
+ const char **error_r);
+
+/* <settings checks> */
+static struct file_listener_settings imap_unix_listeners_array[] = {
+ { "login/imap", 0666, "", "" },
+ { "imap-master", 0600, "", "" }
+};
+static struct file_listener_settings *imap_unix_listeners[] = {
+ &imap_unix_listeners_array[0],
+ &imap_unix_listeners_array[1]
+};
+static buffer_t imap_unix_listeners_buf = {
+ { { imap_unix_listeners, sizeof(imap_unix_listeners) } }
+};
+/* </settings checks> */
+
+struct service_settings imap_service_settings = {
+ .name = "imap",
+ .protocol = "imap",
+ .type = "",
+ .executable = "imap",
+ .user = "",
+ .group = "",
+ .privileged_group = "",
+ .extra_groups = "$default_internal_group",
+ .chroot = "",
+
+ .drop_priv_before_exec = FALSE,
+
+ .process_min_avail = 0,
+ .process_limit = 1024,
+ .client_limit = 1,
+ .service_count = 1,
+ .idle_kill = 0,
+ .vsz_limit = UOFF_T_MAX,
+
+ .unix_listeners = { { &imap_unix_listeners_buf,
+ sizeof(imap_unix_listeners[0]) } },
+ .fifo_listeners = ARRAY_INIT,
+ .inet_listeners = ARRAY_INIT
+};
+
+#undef DEF
+#undef DEFLIST
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct imap_settings)
+#define DEFLIST(field, name, defines) \
+ { .type = SET_DEFLIST, .key = name, \
+ .offset = offsetof(struct imap_settings, field), \
+ .list_info = defines }
+
+static const struct setting_define imap_setting_defines[] = {
+ DEF(BOOL, verbose_proctitle),
+ DEF(STR_VARS, rawlog_dir),
+
+ DEF(SIZE, imap_max_line_length),
+ DEF(TIME, imap_idle_notify_interval),
+ DEF(STR, imap_capability),
+ DEF(STR, imap_client_workarounds),
+ DEF(STR, imap_logout_format),
+ DEF(STR, imap_id_send),
+ DEF(STR, imap_id_log),
+ DEF(ENUM, imap_fetch_failure),
+ DEF(BOOL, imap_metadata),
+ DEF(BOOL, imap_literal_minus),
+ DEF(TIME, imap_hibernate_timeout),
+
+ DEF(STR, imap_urlauth_host),
+ DEF(IN_PORT, imap_urlauth_port),
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct imap_settings imap_default_settings = {
+ .verbose_proctitle = FALSE,
+ .rawlog_dir = "",
+
+ /* RFC-2683 recommends at least 8000 bytes. Some clients however don't
+ break large message sets to multiple commands, so we're pretty
+ liberal by default. */
+ .imap_max_line_length = 64*1024,
+ .imap_idle_notify_interval = 2*60,
+ .imap_capability = "",
+ .imap_client_workarounds = "",
+ .imap_logout_format = "in=%i out=%o deleted=%{deleted} "
+ "expunged=%{expunged} trashed=%{trashed} "
+ "hdr_count=%{fetch_hdr_count} hdr_bytes=%{fetch_hdr_bytes} "
+ "body_count=%{fetch_body_count} body_bytes=%{fetch_body_bytes}",
+ .imap_id_send = "name *",
+ .imap_id_log = "",
+ .imap_fetch_failure = "disconnect-immediately:disconnect-after:no-after",
+ .imap_metadata = FALSE,
+ .imap_literal_minus = FALSE,
+ .imap_hibernate_timeout = 0,
+
+ .imap_urlauth_host = "",
+ .imap_urlauth_port = 143
+};
+
+static const struct setting_parser_info *imap_setting_dependencies[] = {
+ &mail_user_setting_parser_info,
+ &smtp_submit_setting_parser_info,
+ NULL
+};
+
+const struct setting_parser_info imap_setting_parser_info = {
+ .module_name = "imap",
+ .defines = imap_setting_defines,
+ .defaults = &imap_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct imap_settings),
+
+ .parent_offset = SIZE_MAX,
+
+ .check_func = imap_settings_verify,
+ .dependencies = imap_setting_dependencies
+};
+
+/* <settings checks> */
+struct imap_client_workaround_list {
+ const char *name;
+ enum imap_client_workarounds num;
+};
+
+static const struct imap_client_workaround_list imap_client_workaround_list[] = {
+ { "delay-newmail", WORKAROUND_DELAY_NEWMAIL },
+ { "tb-extra-mailbox-sep", WORKAROUND_TB_EXTRA_MAILBOX_SEP },
+ { "tb-lsub-flags", WORKAROUND_TB_LSUB_FLAGS },
+ { NULL, 0 }
+};
+
+static int
+imap_settings_parse_workarounds(struct imap_settings *set,
+ const char **error_r)
+{
+ enum imap_client_workarounds client_workarounds = 0;
+ const struct imap_client_workaround_list *list;
+ const char *const *str;
+
+ str = t_strsplit_spaces(set->imap_client_workarounds, " ,");
+ for (; *str != NULL; str++) {
+ list = imap_client_workaround_list;
+ for (; list->name != NULL; list++) {
+ if (strcasecmp(*str, list->name) == 0) {
+ client_workarounds |= list->num;
+ break;
+ }
+ }
+ if (list->name == NULL) {
+ *error_r = t_strdup_printf("imap_client_workarounds: "
+ "Unknown workaround: %s", *str);
+ return -1;
+ }
+ }
+ set->parsed_workarounds = client_workarounds;
+ return 0;
+}
+
+
+static bool
+imap_settings_verify(void *_set, pool_t pool ATTR_UNUSED, const char **error_r)
+{
+ struct imap_settings *set = _set;
+
+ if (imap_settings_parse_workarounds(set, error_r) < 0)
+ return FALSE;
+
+ if (strcmp(set->imap_fetch_failure, "disconnect-immediately") == 0)
+ set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY;
+ else if (strcmp(set->imap_fetch_failure, "disconnect-after") == 0)
+ set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_AFTER;
+ else if (strcmp(set->imap_fetch_failure, "no-after") == 0)
+ set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_NO_AFTER;
+ else {
+ *error_r = t_strdup_printf("Unknown imap_fetch_failure: %s",
+ set->imap_fetch_failure);
+ return FALSE;
+ }
+ return TRUE;
+}
+/* </settings checks> */
diff --git a/src/imap/imap-settings.h b/src/imap/imap-settings.h
new file mode 100644
index 0000000..f4ff7bc
--- /dev/null
+++ b/src/imap/imap-settings.h
@@ -0,0 +1,49 @@
+#ifndef IMAP_SETTINGS_H
+#define IMAP_SETTINGS_H
+
+#include "net.h"
+
+struct mail_user_settings;
+
+/* <settings checks> */
+enum imap_client_workarounds {
+ WORKAROUND_DELAY_NEWMAIL = 0x01,
+ WORKAROUND_TB_EXTRA_MAILBOX_SEP = 0x08,
+ WORKAROUND_TB_LSUB_FLAGS = 0x10
+};
+
+enum imap_client_fetch_failure {
+ IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY,
+ IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_AFTER,
+ IMAP_CLIENT_FETCH_FAILURE_NO_AFTER,
+};
+/* </settings checks> */
+
+struct imap_settings {
+ bool verbose_proctitle;
+ const char *rawlog_dir;
+
+ /* imap: */
+ uoff_t imap_max_line_length;
+ unsigned int imap_idle_notify_interval;
+ const char *imap_capability;
+ const char *imap_client_workarounds;
+ const char *imap_logout_format;
+ const char *imap_id_send;
+ const char *imap_id_log;
+ const char *imap_fetch_failure;
+ bool imap_metadata;
+ bool imap_literal_minus;
+ unsigned int imap_hibernate_timeout;
+
+ /* imap urlauth: */
+ const char *imap_urlauth_host;
+ in_port_t imap_urlauth_port;
+
+ enum imap_client_workarounds parsed_workarounds;
+ enum imap_client_fetch_failure parsed_fetch_failure;
+};
+
+extern const struct setting_parser_info imap_setting_parser_info;
+
+#endif
diff --git a/src/imap/imap-state.c b/src/imap/imap-state.c
new file mode 100644
index 0000000..2b064ec
--- /dev/null
+++ b/src/imap/imap-state.c
@@ -0,0 +1,897 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "crc32.h"
+#include "numpack.h"
+#include "net.h"
+#include "ostream.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "imap-util.h"
+#include "mail-search-build.h"
+#include "mail-storage-private.h"
+#include "mailbox-recent-flags.h"
+#include "imap-client.h"
+#include "imap-feature.h"
+#include "imap-fetch.h"
+#include "imap-search-args.h"
+#include "imap-state.h"
+
+enum imap_state_type_public {
+ IMAP_STATE_TYPE_MAILBOX = 'B',
+ IMAP_STATE_TYPE_ENABLED_FEATURE = 'F',
+ IMAP_STATE_TYPE_SEARCHRES = '1',
+};
+
+enum imap_state_type_internal {
+ IMAP_STATE_TYPE_ID_LOGGED = 'I',
+ IMAP_STATE_TYPE_TLS_COMPRESSION = 'C',
+};
+
+struct mailbox_import_state {
+ const char *vname;
+ guid_128_t mailbox_guid;
+ bool examined;
+ uint32_t keywords_count, keywords_crc32, uids_crc32;
+ uint32_t uidvalidity, uidnext, messages;
+ uint64_t highest_modseq;
+ ARRAY_TYPE(seq_range) recent_uids;
+};
+
+static void
+export_seq_range(buffer_t *dest, const ARRAY_TYPE(seq_range) *range)
+{
+ const struct seq_range *uids;
+ unsigned int i, count;
+ uint32_t next_uid;
+
+ uids = array_get(range, &count);
+ numpack_encode(dest, count);
+ next_uid = 1;
+ for (i = 0; i < count; i++) {
+ i_assert(uids[i].seq1 >= next_uid);
+ if (uids[i].seq1 == uids[i].seq2) {
+ numpack_encode(dest, (uids[i].seq1 - next_uid) << 1);
+ } else {
+ numpack_encode(dest, 1 | ((uids[i].seq1 - next_uid) << 1));
+ numpack_encode(dest, uids[i].seq2 - uids[i].seq1 - 1);
+ }
+ next_uid = uids[i].seq2 + 1;
+ }
+}
+
+static int
+import_seq_range(const unsigned char **data, const unsigned char *end,
+ ARRAY_TYPE(seq_range) *range)
+{
+ uint32_t i, count, next_uid, num, uid1, uid2;
+
+ if (numpack_decode32(data, end, &count) < 0)
+ return -1;
+ next_uid = 1;
+
+ for (i = 0; i < count; i++) {
+ if (numpack_decode32(data, end, &num) < 0)
+ return -1;
+ uid1 = next_uid + (num >> 1);
+ if ((num & 1) == 0) {
+ uid2 = uid1;
+ seq_range_array_add(range, uid1);
+ } else {
+ if (numpack_decode32(data, end, &num) < 0)
+ return -1;
+ uid2 = uid1 + num + 1;
+ seq_range_array_add_range(range, uid1, uid2);
+ }
+ next_uid = uid2 + 1;
+ }
+ return 0;
+}
+
+int imap_state_export_internal(struct client *client, buffer_t *dest,
+ const char **error_r)
+{
+ /* the only IMAP command we allow running is IDLE or X-STATE */
+ if (client->command_queue_size > 1) {
+ *error_r = "Multiple commands in progress";
+ return 0;
+ }
+ if (client->command_queue == NULL ||
+ strcasecmp(client->command_queue->name, "IDLE") != 0) {
+ /* this would require saving the seq <-> uid mapping
+ and restore it on import. quite a lot of trouble if
+ messages have been expunged in the mean time. */
+ *error_r = "Non-IDLE connections not supported currently";
+ return 0;
+ }
+ return client->v.state_export(client, TRUE, dest, error_r);
+}
+
+int imap_state_export_external(struct client *client, buffer_t *dest,
+ const char **error_r)
+{
+ if (client->command_queue_size > 1) {
+ *error_r = "Multiple commands in progress";
+ return 0;
+ }
+
+ i_assert(client->command_queue_size == 1);
+ i_assert(strcmp(client->command_queue->name, "X-STATE") == 0);
+ return client->v.state_export(client, FALSE, dest, error_r);
+}
+
+static int
+imap_state_import(struct client *client, bool internal,
+ const unsigned char *data, size_t size, const char **error_r)
+{
+ ssize_t ret;
+
+ while (size > 0) {
+ ret = client->v.state_import(client, internal,
+ data, size, error_r);
+ if (ret <= 0) {
+ i_assert(*error_r != NULL);
+ return ret < 0 ? -1 : 0;
+ }
+ i_assert((size_t)ret <= size);
+ data += ret;
+ size -= ret;
+ }
+ return 1;
+}
+
+int imap_state_import_internal(struct client *client,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ return imap_state_import(client, TRUE, data, size, error_r);
+}
+
+int imap_state_import_external(struct client *client,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ return imap_state_import(client, FALSE, data, size, error_r);
+}
+
+static int
+imap_state_export_mailbox_mails(buffer_t *dest, struct mailbox *box,
+ const char **error_r)
+{
+ struct mailbox_transaction_context *trans;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ ARRAY_TYPE(seq_range) recent_uids;
+ uint32_t crc = 0;
+ int ret = 1;
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+
+ trans = mailbox_transaction_begin(box, 0,
+ "unhibernate imap_state_export_mailbox_mails");
+ search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ t_array_init(&recent_uids, 8);
+ while (mailbox_search_next(search_ctx, &mail)) {
+ crc = crc32_data_more(crc, &mail->uid, sizeof(mail->uid));
+ if ((mail_get_flags(mail) & MAIL_RECENT) != 0)
+ seq_range_array_add(&recent_uids, mail->uid);
+ }
+ if (mailbox_search_deinit(&search_ctx) < 0) {
+ *error_r = mailbox_get_last_internal_error(box, NULL);
+ ret = -1;
+ }
+ (void)mailbox_transaction_commit(&trans);
+
+ numpack_encode(dest, crc);
+ export_seq_range(dest, &recent_uids);
+ return ret;
+}
+
+static uint32_t
+mailbox_status_keywords_crc32(const struct mailbox_status *status)
+{
+ const char *str;
+ uint32_t crc = 0;
+
+ array_foreach_elem(status->keywords, str)
+ crc = crc32_str(str);
+ return crc;
+}
+
+static int
+imap_state_export_mailbox(buffer_t *dest, struct client *client,
+ struct mailbox *box, const char **error_r)
+{
+ struct mailbox_status status;
+ struct mailbox_metadata metadata;
+ const char *vname = mailbox_get_vname(box);
+ enum mail_error mail_error;
+
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT |
+ STATUS_MESSAGES | STATUS_HIGHESTMODSEQ |
+ STATUS_KEYWORDS,
+ &status);
+ if (status.nonpermanent_modseqs) {
+ *error_r = "Nonpermanent modseqs";
+ return 0;
+ }
+
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) {
+ *error_r = mailbox_get_last_internal_error(box, &mail_error);
+ /* if the selected mailbox can't have a GUID, fail silently */
+ return mail_error == MAIL_ERROR_NOTPOSSIBLE ? 0 : -1;
+ }
+
+ buffer_append_c(dest, IMAP_STATE_TYPE_MAILBOX);
+ buffer_append(dest, vname, strlen(vname)+1);
+ buffer_append(dest, metadata.guid, sizeof(metadata.guid));
+
+ buffer_append_c(dest, client->mailbox_examined ? 1 : 0);
+ numpack_encode(dest, status.uidvalidity);
+ numpack_encode(dest, status.uidnext);
+ numpack_encode(dest, status.messages);
+ if (client_has_enabled(client, imap_feature_qresync) &&
+ !client->nonpermanent_modseqs)
+ numpack_encode(dest, client->sync_last_full_modseq);
+ else
+ numpack_encode(dest, status.highest_modseq);
+
+ /* keywords count + CRC32 should be enough to figure out if it
+ needs to be resent */
+ numpack_encode(dest, array_count(status.keywords));
+ numpack_encode(dest, mailbox_status_keywords_crc32(&status));
+
+ /* we're now basically done, but just in case there's a bug add a
+ checksum of the currently existing UIDs and verify it when
+ importing. this also writes the list of recent UIDs. */
+ return imap_state_export_mailbox_mails(dest, box, error_r);
+}
+
+int imap_state_export_base(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r)
+{
+ int ret;
+
+ str_append(dest, "base\n");
+ if (array_is_created(&client->search_updates) &&
+ array_count(&client->search_updates) > 0) {
+ /* these could be tricky */
+ *error_r = "CONTEXT=SEARCH updates not supported currently";
+ return 0;
+ }
+ if (client->notify_ctx != NULL) {
+ /* FIXME: this really should be supported. also IDLE wouldn't
+ be needed if NOTIFY allows sending EXPUNGEs to selected
+ mailbox. */
+ *error_r = "NOTIFY not supported currently";
+ return 0;
+ }
+
+ if (client->mailbox != NULL) {
+ ret = imap_state_export_mailbox(dest, client,
+ client->mailbox, error_r);
+ if (ret <= 0)
+ return ret;
+ }
+
+ /* IMAP features */
+ const char *const *features = client_enabled_features(client);
+ if (features != NULL) {
+ for (unsigned int i = 0; features[i] != NULL; i++) {
+ buffer_append_c(dest, IMAP_STATE_TYPE_ENABLED_FEATURE);
+ buffer_append(dest, features[i], strlen(features[i])+1);
+ }
+ }
+ if (internal) {
+ if (client->id_logged)
+ buffer_append_c(dest, IMAP_STATE_TYPE_ID_LOGGED);
+ if (client->tls_compression)
+ buffer_append_c(dest, IMAP_STATE_TYPE_TLS_COMPRESSION);
+ }
+
+ /* IMAP SEARCHRES extension */
+ if (array_is_created(&client->search_saved_uidset) &&
+ array_count(&client->search_saved_uidset) > 0) {
+ buffer_append_c(dest, IMAP_STATE_TYPE_SEARCHRES);
+ export_seq_range(dest, &client->search_saved_uidset);
+ }
+ return 1;
+}
+
+static int
+import_string(const unsigned char **data, const unsigned char *end,
+ const char **str_r)
+{
+ const unsigned char *p;
+
+ p = memchr(*data, '\0', end - *data);
+ if (p == NULL)
+ return -1;
+ *str_r = (const void *)*data;
+ *data = p + 1;
+ return 0;
+}
+
+static int
+import_send_expunges(struct client *client,
+ const struct mailbox_import_state *state,
+ unsigned int *expunge_count_r,
+ const char **error_r)
+{
+ struct mailbox_transaction_context *trans;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ uint32_t crc = 0, seq, expunged_uid;
+ ARRAY_TYPE(seq_range) uids_filter, expunged_uids;
+ ARRAY_TYPE(uint32_t) expunged_seqs;
+ struct seq_range_iter iter;
+ const uint32_t *seqs;
+ unsigned int i, expunge_count, n = 0;
+ string_t *str;
+ int ret = 0;
+
+ *expunge_count_r = 0;
+
+ if (state->messages == 0) {
+ /* the mailbox was empty originally - there couldn't be any
+ pending expunges. */
+ return 0;
+ }
+ if (state->uidnext <= 1) {
+ *error_r = "Invalid UIDNEXT";
+ return -1;
+ }
+
+ /* get all the message UIDs expunged since the last known modseq */
+ t_array_init(&uids_filter, 1);
+ t_array_init(&expunged_uids, 128);
+ seq_range_array_add_range(&uids_filter, 1, state->uidnext-1);
+ if (!mailbox_get_expunged_uids(client->mailbox, state->highest_modseq,
+ &uids_filter, &expunged_uids)) {
+ *error_r = t_strdup_printf(
+ "Couldn't get recently expunged UIDs "
+ "(uidnext=%u highest_modseq=%"PRIu64")",
+ state->uidnext, state->highest_modseq);
+ return -1;
+ }
+ seq_range_array_iter_init(&iter, &expunged_uids);
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+
+ trans = mailbox_transaction_begin(client->mailbox, 0,
+ "unhibernate import_send_expunges");
+ search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ /* find sequence numbers for the expunged UIDs */
+ t_array_init(&expunged_seqs, array_count(&expunged_uids)+1); seq = 0;
+ while (mailbox_search_next(search_ctx, &mail)) {
+ while (seq_range_array_iter_nth(&iter, n, &expunged_uid) &&
+ expunged_uid < mail->uid && seq < state->messages) {
+ seq++; n++;
+ array_push_back(&expunged_seqs, &seq);
+ crc = crc32_data_more(crc, &expunged_uid,
+ sizeof(expunged_uid));
+ }
+ if (seq == state->messages)
+ break;
+ crc = crc32_data_more(crc, &mail->uid, sizeof(mail->uid));
+ if (++seq == state->messages)
+ break;
+ }
+ while (seq_range_array_iter_nth(&iter, n, &expunged_uid) &&
+ seq < state->messages) {
+ seq++; n++;
+ array_push_back(&expunged_seqs, &seq);
+ crc = crc32_data_more(crc, &expunged_uid,
+ sizeof(expunged_uid));
+ }
+
+ if (mailbox_search_deinit(&search_ctx) < 0) {
+ *error_r = mailbox_get_last_internal_error(client->mailbox, NULL);
+ ret = -1;
+ } else if (seq != state->messages) {
+ *error_r = t_strdup_printf("Message count mismatch after "
+ "handling expunges (%u != %u)",
+ seq, state->messages);
+ ret = -1;
+ }
+ (void)mailbox_transaction_commit(&trans);
+ if (ret < 0)
+ return -1;
+
+ seqs = array_get(&expunged_seqs, &expunge_count);
+ if (client->messages_count + expunge_count < state->messages) {
+ *error_r = t_strdup_printf("Message count too low after "
+ "handling expunges (%u < %u)",
+ client->messages_count + expunge_count,
+ state->messages);
+ return -1;
+ }
+ if (crc != state->uids_crc32) {
+ *error_r = t_strdup_printf("Message UIDs CRC32 mismatch (%u != %u)",
+ crc, state->uids_crc32);
+ return -1;
+ }
+
+ if (!client_has_enabled(client, imap_feature_qresync)) {
+ str = t_str_new(32);
+ for (i = expunge_count; i > 0; i--) {
+ str_truncate(str, 0);
+ str_printfa(str, "* %u EXPUNGE", seqs[i-1]);
+ client_send_line(client, str_c(str));
+ }
+ } else {
+ str = str_new(default_pool, 128);
+ str_append(str, "* VANISHED ");
+ imap_write_seq_range(str, &expunged_uids);
+ str_append(str, "\r\n");
+ o_stream_nsend(client->output, str_data(str), str_len(str));
+ str_free(&str);
+ }
+ *expunge_count_r = expunge_count;
+ return 0;
+}
+
+static int
+import_send_flag_changes(struct client *client,
+ const struct mailbox_import_state *state,
+ unsigned int *flag_change_count_r)
+{
+ struct imap_fetch_context *fetch_ctx;
+ struct mail_search_args *search_args;
+ ARRAY_TYPE(seq_range) old_uids;
+ pool_t pool;
+ int ret;
+
+ *flag_change_count_r = 0;
+ if (state->messages == 0)
+ return 0;
+
+ t_array_init(&old_uids, 1);
+ seq_range_array_add_range(&old_uids, 1, state->uidnext-1);
+
+ search_args = mail_search_build_init();
+ search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
+ search_args->args->type = SEARCH_UIDSET;
+ search_args->args->value.seqset = old_uids;
+ imap_search_add_changed_since(search_args, state->highest_modseq);
+
+ pool = pool_alloconly_create("imap state flag changes", 1024);
+ fetch_ctx = imap_fetch_alloc(client, pool, "unhibernate");
+ pool_unref(&pool);
+
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_flags_init);
+ if (client_has_enabled(client, imap_feature_qresync)) {
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_uid_init);
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_modseq_init);
+ }
+
+ imap_fetch_begin(fetch_ctx, client->mailbox, search_args);
+ mail_search_args_unref(&search_args);
+
+ /* FIXME: ideally do this asynchronously.. */
+ while (imap_fetch_more_no_lock_update(fetch_ctx) == 0) ;
+
+ ret = imap_fetch_end(fetch_ctx);
+ *flag_change_count_r = fetch_ctx->fetched_mails_count;
+ imap_fetch_free(&fetch_ctx);
+ return ret;
+}
+
+static ssize_t
+import_state_mailbox_struct(const unsigned char *data, size_t size,
+ struct mailbox_import_state *state_r,
+ const char **error_r)
+{
+ const unsigned char *p = data, *end = data + size;
+
+ i_zero(state_r);
+ t_array_init(&state_r->recent_uids, 8);
+
+ /* vname */
+ if (import_string(&p, end, &state_r->vname) < 0) {
+ *error_r = "Mailbox state truncated at name";
+ return 0;
+ }
+
+ /* GUID */
+ if (end-p < (int)sizeof(state_r->mailbox_guid)) {
+ *error_r = "Mailbox state truncated at GUID";
+ return 0;
+ }
+ memcpy(state_r->mailbox_guid, p, sizeof(state_r->mailbox_guid));
+ p += sizeof(state_r->mailbox_guid);
+
+ if (guid_128_is_empty(state_r->mailbox_guid)) {
+ *error_r = "Empty GUID";
+ return 0;
+ }
+
+ /* EXAMINEd vs SELECTed */
+ if (p == end) {
+ *error_r = "Mailbox state truncated at examined-flag";
+ return 0;
+ }
+ state_r->examined = p[0] != 0;
+ p++;
+
+ /* mailbox state */
+ if (numpack_decode32(&p, end, &state_r->uidvalidity) < 0 ||
+ numpack_decode32(&p, end, &state_r->uidnext) < 0 ||
+ numpack_decode32(&p, end, &state_r->messages) < 0 ||
+ numpack_decode(&p, end, &state_r->highest_modseq) < 0 ||
+ numpack_decode32(&p, end, &state_r->keywords_count) < 0 ||
+ numpack_decode32(&p, end, &state_r->keywords_crc32) < 0 ||
+ numpack_decode32(&p, end, &state_r->uids_crc32) < 0 ||
+ import_seq_range(&p, end, &state_r->recent_uids) < 0) {
+ *error_r = "Mailbox state truncated";
+ return 0;
+ }
+ if (state_r->uidvalidity == 0) {
+ *error_r = "Empty UIDVALIDITY";
+ return 0;
+ }
+ if (state_r->uidnext == 0) {
+ *error_r = "Empty UIDNEXT";
+ return 0;
+ }
+ return p - data;
+}
+
+static int
+import_state_mailbox_open(struct client *client,
+ const struct mailbox_import_state *state,
+ const char **error_r)
+{
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+ struct mailbox_status status;
+ const struct seq_range *range;
+ enum mailbox_flags flags = 0;
+ unsigned int expunge_count, new_mails_count = 0, flag_change_count = 0;
+ uint32_t uid;
+ int ret = 0;
+
+ ns = mail_namespace_find(client->user->namespaces, state->vname);
+ if (ns == NULL) {
+ *error_r = "Namespace not found for mailbox";
+ return -1;
+ }
+
+ if (state->examined)
+ flags |= MAILBOX_FLAG_READONLY;
+ else
+ flags |= MAILBOX_FLAG_DROP_RECENT;
+ box = mailbox_alloc(ns->list, state->vname, flags);
+ if (mailbox_open(box) < 0) {
+ *error_r = t_strdup_printf("Couldn't open mailbox: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ mailbox_free(&box);
+ return -1;
+ }
+
+ ret = mailbox_enable(box, client_enabled_mailbox_features(client));
+ if (ret < 0 || mailbox_sync(box, 0) < 0) {
+ *error_r = t_strdup_printf("Couldn't sync mailbox: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ mailbox_free(&box);
+ return -1;
+ }
+ /* verify that this still looks like the same mailbox */
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) {
+ *error_r = mailbox_get_last_internal_error(box, NULL);
+ mailbox_free(&box);
+ return -1;
+ }
+ if (!guid_128_equals(metadata.guid, state->mailbox_guid)) {
+ *error_r = t_strdup_printf("Mailbox GUID has changed %s->%s",
+ guid_128_to_string(state->mailbox_guid),
+ guid_128_to_string(metadata.guid));
+ mailbox_free(&box);
+ return -1;
+ }
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT |
+ STATUS_HIGHESTMODSEQ | STATUS_RECENT |
+ STATUS_KEYWORDS, &status);
+ if (status.uidvalidity != state->uidvalidity) {
+ *error_r = t_strdup_printf("Mailbox UIDVALIDITY has changed %u->%u",
+ state->uidvalidity, status.uidvalidity);
+ mailbox_free(&box);
+ return -1;
+ }
+ if (status.uidnext < state->uidnext) {
+ *error_r = t_strdup_printf("Mailbox UIDNEXT shrank %u -> %u",
+ state->uidnext, status.uidnext);
+ mailbox_free(&box);
+ return -1;
+ }
+ if (status.highest_modseq < state->highest_modseq) {
+ *error_r = t_strdup_printf("Mailbox HIGHESTMODSEQ shrank %"PRIu64" -> %"PRIu64,
+ state->highest_modseq,
+ status.highest_modseq);
+ mailbox_free(&box);
+ return -1;
+ }
+
+ client->mailbox = box;
+ client->mailbox_examined = state->examined;
+ client->messages_count = status.messages;
+ client->uidvalidity = status.uidvalidity;
+ client->notify_uidnext = status.uidnext;
+
+ if (import_send_expunges(client, state, &expunge_count, error_r) < 0)
+ return -1;
+ i_assert(expunge_count <= state->messages);
+ if (state->messages - expunge_count > client->messages_count) {
+ *error_r = t_strdup_printf("Mailbox message count shrank %u -> %u",
+ client->messages_count,
+ state->messages - expunge_count);
+ return -1;
+ }
+
+ client_update_mailbox_flags(client, status.keywords);
+ array_foreach(&state->recent_uids, range) {
+ for (uid = range->seq1; uid <= range->seq2; uid++) {
+ uint32_t seq;
+
+ if (mail_index_lookup_seq(box->view, uid, &seq))
+ mailbox_recent_flags_set_uid_forced(box, uid);
+ }
+ }
+ client->recent_count = mailbox_recent_flags_count(box);
+
+ if (state->messages - expunge_count < client->messages_count) {
+ /* new messages arrived */
+ new_mails_count = client->messages_count -
+ (state->messages - expunge_count);
+ client_send_line(client,
+ t_strdup_printf("* %u EXISTS", client->messages_count));
+ client_send_line(client,
+ t_strdup_printf("* %u RECENT", client->recent_count));
+ }
+
+ if (array_count(status.keywords) == state->keywords_count &&
+ mailbox_status_keywords_crc32(&status) == state->keywords_crc32) {
+ /* no changes to keywords */
+ client->keywords.announce_count = state->keywords_count;
+ } else {
+ client_send_mailbox_flags(client, TRUE);
+ }
+ if (import_send_flag_changes(client, state, &flag_change_count) < 0) {
+ *error_r = "Couldn't send flag changes";
+ return -1;
+ }
+ if (client_has_enabled(client, imap_feature_qresync) &&
+ !client->nonpermanent_modseqs &&
+ status.highest_modseq != state->highest_modseq) {
+ client_send_line(client, t_strdup_printf(
+ "* OK [HIGHESTMODSEQ %"PRIu64"] Highest",
+ status.highest_modseq));
+ client->sync_last_full_modseq = status.highest_modseq;
+ }
+ e_debug(client->event,
+ "Unhibernation sync: %u expunges, %u new messages, %u flag changes, %"PRIu64" modseq changes",
+ expunge_count, new_mails_count, flag_change_count,
+ status.highest_modseq - state->highest_modseq);
+ return 0;
+}
+
+static ssize_t
+import_state_mailbox(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ struct mailbox_import_state state;
+ ssize_t ret;
+
+ if (client->mailbox != NULL) {
+ *error_r = "Duplicate mailbox state";
+ return 0;
+ }
+
+ ret = import_state_mailbox_struct(data, size, &state, error_r);
+ if (ret <= 0) {
+ i_assert(*error_r != NULL);
+ return ret;
+ }
+ if (import_state_mailbox_open(client, &state, error_r) < 0) {
+ *error_r = t_strdup_printf("Mailbox %s: %s", state.vname, *error_r);
+ return -1;
+ }
+ return ret;
+}
+
+static ssize_t
+import_state_enabled_feature(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ const unsigned char *p = data, *end = data + size;
+ const char *name;
+ unsigned int feature_idx;
+
+ if (import_string(&p, end, &name) < 0) {
+ *error_r = "Mailbox state truncated at name";
+ return 0;
+ }
+ if (!imap_feature_lookup(name, &feature_idx)) {
+ *error_r = t_strdup_printf("Unknown feature '%s'", name);
+ return 0;
+ }
+ client_enable(client, feature_idx);
+ return p - data;
+}
+
+static ssize_t
+import_state_searchres(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ const unsigned char *p = data;
+
+ i_array_init(&client->search_saved_uidset, 128);
+ if (import_seq_range(&p, data+size, &client->search_saved_uidset) < 0) {
+ *error_r = "Invalid SEARCHRES seq-range";
+ return 0;
+ }
+ return p - data;
+}
+
+static ssize_t
+import_state_id_logged(struct client *client,
+ const unsigned char *data ATTR_UNUSED,
+ size_t size ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ client->id_logged = TRUE;
+ return 0;
+}
+
+static ssize_t
+import_state_tls_compression(struct client *client,
+ const unsigned char *data ATTR_UNUSED,
+ size_t size ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ client->tls_compression = TRUE;
+ return 0;
+}
+
+void imap_state_import_idle_cmd_tag(struct client *client, const char *tag)
+{
+ if (client->state_import_idle_continue) {
+ /* IDLE command continues */
+ struct client_command_context *cmd;
+ struct command *command;
+
+ cmd = client_command_alloc(client);
+ cmd->tag = p_strdup(cmd->pool, tag);
+ cmd->name = "IDLE";
+
+ command = command_find("IDLE");
+ i_assert(command != NULL);
+ cmd->func = command->func;
+ cmd->cmd_flags = command->flags;
+ client_command_init_finished(cmd);
+
+ if (command_exec(cmd)) {
+ /* IDLE terminated because of an external change, but
+ DONE was already buffered */
+ client_command_free(&cmd);
+ client_add_missing_io(client);
+ } else {
+ i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT ||
+ cmd->state == CLIENT_COMMAND_STATE_WAIT_OUTPUT);
+ }
+ } else {
+ /* we're finishing IDLE command */
+ client_send_line(client, t_strdup_printf(
+ "%s %s Idle completed.", tag,
+ client->state_import_bad_idle_done ? "BAD" : "OK"));
+ }
+}
+
+static struct {
+ enum imap_state_type_public type;
+ ssize_t (*import)(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r);
+} imap_states_public[] = {
+ { IMAP_STATE_TYPE_MAILBOX, import_state_mailbox },
+ { IMAP_STATE_TYPE_ENABLED_FEATURE, import_state_enabled_feature },
+ { IMAP_STATE_TYPE_SEARCHRES, import_state_searchres }
+};
+
+static struct {
+ enum imap_state_type_internal type;
+ ssize_t (*import)(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r);
+} imap_states_internal[] = {
+ { IMAP_STATE_TYPE_ID_LOGGED, import_state_id_logged },
+ { IMAP_STATE_TYPE_TLS_COMPRESSION, import_state_tls_compression }
+};
+
+static ssize_t
+imap_state_try_import_public(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ unsigned int i;
+ ssize_t ret;
+
+ i_assert(size > 0);
+
+ for (i = 0; i < N_ELEMENTS(imap_states_public); i++) {
+ if (imap_states_public[i].type == data[0]) {
+ ret = imap_states_public[i].
+ import(client, data+1, size-1, error_r);
+ return ret < 0 ? -1 : ret+1;
+ }
+ }
+ return -2;
+}
+
+static ssize_t
+imap_state_try_import_internal(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ unsigned int i;
+ ssize_t ret;
+
+ i_assert(size > 0);
+
+ for (i = 0; i < N_ELEMENTS(imap_states_internal); i++) {
+ if (imap_states_internal[i].type == data[0]) {
+ ret = imap_states_internal[i].
+ import(client, data+1, size-1, error_r);
+ return ret < 0 ? -1 : ret+1;
+ }
+ }
+ return -2;
+}
+
+ssize_t imap_state_import_base(struct client *client, bool internal,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ const unsigned char *p;
+ ssize_t ret;
+ size_t pos;
+
+ i_assert(client->mailbox == NULL);
+
+ *error_r = NULL;
+
+ if (size < 5 || memcmp(data, "base\n", 5) != 0) {
+ p = memchr(data, '\n', size);
+ if (p == NULL)
+ p = data + I_MIN(size, 20);
+ *error_r = t_strdup_printf("Unknown state block '%s'",
+ str_sanitize(t_strdup_until(data, p), 20));
+ return 0;
+ }
+
+ pos = 5;
+ while (pos < size) {
+ ret = imap_state_try_import_public(client, data+pos,
+ size-pos, error_r);
+ if (ret == -2 && internal) {
+ ret = imap_state_try_import_internal(client, data+pos,
+ size-pos, error_r);
+ }
+ if (ret < 0 || *error_r != NULL) {
+ if (ret == -2) {
+ *error_r = t_strdup_printf("Unknown type '%c'",
+ data[pos]);
+ }
+ i_assert(*error_r != NULL);
+ return ret < 0 ? -1 : 0;
+ }
+ i_assert(size - pos >= (size_t)ret);
+ pos += ret;
+ }
+ return pos;
+}
diff --git a/src/imap/imap-state.h b/src/imap/imap-state.h
new file mode 100644
index 0000000..ee00376
--- /dev/null
+++ b/src/imap/imap-state.h
@@ -0,0 +1,30 @@
+#ifndef IMAP_STATE_H
+#define IMAP_STATE_H
+
+/* Export the IMAP client state to the given buffer. Returns 1 if ok,
+ 0 if state couldn't be exported, -1 if temporary internal error error. */
+int imap_state_export_internal(struct client *client, buffer_t *dest,
+ const char **error_r);
+int imap_state_export_external(struct client *client, buffer_t *dest,
+ const char **error_r);
+
+/* Returns 1 if ok, 0 if state was corrupted, -1 if other error. Internal state
+ comes from another Dovecot component, which can override IP addresses,
+ session IDs, etc. */
+int imap_state_import_internal(struct client *client,
+ const unsigned char *data, size_t size,
+ const char **error_r);
+int imap_state_import_external(struct client *client,
+ const unsigned char *data, size_t size,
+ const char **error_r);
+
+/* INTERNAL API: Note that the "internal" flag specifies whether we're doing
+ the import/export from/to another Dovecot component or an untrusted
+ IMAP client. */
+int imap_state_export_base(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r);
+ssize_t imap_state_import_base(struct client *client, bool internal,
+ const unsigned char *data, size_t size,
+ const char **error_r);
+void imap_state_import_idle_cmd_tag(struct client *client, const char *tag);
+#endif
diff --git a/src/imap/imap-status.c b/src/imap/imap-status.c
new file mode 100644
index 0000000..341e537
--- /dev/null
+++ b/src/imap/imap-status.c
@@ -0,0 +1,172 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "hex-binary.h"
+#include "str.h"
+#include "imap-quote.h"
+#include "imap-status.h"
+
+int imap_status_parse_items(struct client_command_context *cmd,
+ const struct imap_arg *args,
+ struct imap_status_items *items_r)
+{
+ enum imap_status_item_flags flags = 0;
+ const char *item;
+
+ if (IMAP_ARG_IS_EOL(args)) {
+ client_send_command_error(cmd, "Empty status list.");
+ return -1;
+ }
+
+ i_zero(items_r);
+ for (; !IMAP_ARG_IS_EOL(args); args++) {
+ if (!imap_arg_get_atom(args, &item)) {
+ /* list may contain only atoms */
+ client_send_command_error(cmd,
+ "Status list contains non-atoms.");
+ return -1;
+ }
+
+ item = t_str_ucase(item);
+ if (strcmp(item, "MESSAGES") == 0)
+ flags |= IMAP_STATUS_ITEM_MESSAGES;
+ else if (strcmp(item, "RECENT") == 0)
+ flags |= IMAP_STATUS_ITEM_RECENT;
+ else if (strcmp(item, "UIDNEXT") == 0)
+ flags |= IMAP_STATUS_ITEM_UIDNEXT;
+ else if (strcmp(item, "UIDVALIDITY") == 0)
+ flags |= IMAP_STATUS_ITEM_UIDVALIDITY;
+ else if (strcmp(item, "UNSEEN") == 0)
+ flags |= IMAP_STATUS_ITEM_UNSEEN;
+ else if (strcmp(item, "HIGHESTMODSEQ") == 0)
+ flags |= IMAP_STATUS_ITEM_HIGHESTMODSEQ;
+ else if (strcmp(item, "SIZE") == 0)
+ flags |= IMAP_STATUS_ITEM_SIZE;
+ else if (strcmp(item, "X-SIZE") == 0)
+ flags |= IMAP_STATUS_ITEM_X_SIZE;
+ else if (strcmp(item, "X-GUID") == 0)
+ flags |= IMAP_STATUS_ITEM_X_GUID;
+ else {
+ client_send_command_error(cmd, t_strconcat(
+ "Invalid status item ", item, NULL));
+ return -1;
+ }
+ }
+
+ items_r->flags = flags;
+ return 0;
+}
+
+int imap_status_get_result(struct client *client, struct mailbox *box,
+ const struct imap_status_items *items,
+ struct imap_status_result *result_r)
+{
+ enum mailbox_status_items status = 0;
+ enum mailbox_metadata_items metadata = 0;
+ int ret;
+
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_MESSAGES))
+ status |= STATUS_MESSAGES;
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_RECENT))
+ status |= STATUS_RECENT;
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_UIDNEXT))
+ status |= STATUS_UIDNEXT;
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_UIDVALIDITY))
+ status |= STATUS_UIDVALIDITY;
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_UNSEEN))
+ status |= STATUS_UNSEEN;
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_HIGHESTMODSEQ)) {
+ client_enable(client, imap_feature_condstore);
+ status |= STATUS_HIGHESTMODSEQ;
+ }
+ if (HAS_ANY_BITS(items->flags, IMAP_STATUS_ITEM_SIZE |
+ IMAP_STATUS_ITEM_X_SIZE))
+ metadata |= MAILBOX_METADATA_VIRTUAL_SIZE;
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_X_GUID))
+ metadata |= MAILBOX_METADATA_GUID;
+
+ ret = mailbox_get_status(box, status, &result_r->status);
+ if (metadata != 0 && ret == 0)
+ ret = mailbox_get_metadata(box, metadata, &result_r->metadata);
+
+ return ret;
+}
+
+int imap_status_get(struct client_command_context *cmd,
+ struct mail_namespace *ns, const char *mailbox,
+ const struct imap_status_items *items,
+ struct imap_status_result *result_r)
+{
+ struct client *client = cmd->client;
+ struct mailbox *box;
+ const char *errstr;
+ int ret = 0;
+
+ if (client->mailbox != NULL &&
+ mailbox_equals(client->mailbox, ns, mailbox)) {
+ /* this mailbox is selected */
+ box = client->mailbox;
+ } else {
+ /* open the mailbox */
+ box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY);
+ (void)mailbox_enable(box, client_enabled_mailbox_features(client));
+ }
+
+ ret = imap_status_get_result(client, box, items, result_r);
+ if (ret < 0) {
+ errstr = mailbox_get_last_error(box, &result_r->error);
+ result_r->errstr = imap_get_error_string(cmd, errstr,
+ result_r->error);
+ }
+ if (box != client->mailbox)
+ mailbox_free(&box);
+ return ret;
+}
+
+int imap_status_send(struct client *client, const char *mailbox_mutf7,
+ const struct imap_status_items *items,
+ const struct imap_status_result *result)
+{
+ const struct mailbox_status *status = &result->status;
+ string_t *str;
+ size_t prefix_len;
+
+ str = t_str_new(128);
+ str_append(str, "* STATUS ");
+ imap_append_astring(str, mailbox_mutf7);
+ str_append(str, " (");
+
+ prefix_len = str_len(str);
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_MESSAGES))
+ str_printfa(str, "MESSAGES %u ", status->messages);
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_RECENT))
+ str_printfa(str, "RECENT %u ", status->recent);
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_UIDNEXT))
+ str_printfa(str, "UIDNEXT %u ", status->uidnext);
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_UIDVALIDITY))
+ str_printfa(str, "UIDVALIDITY %u ", status->uidvalidity);
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_UNSEEN))
+ str_printfa(str, "UNSEEN %u ", status->unseen);
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_HIGHESTMODSEQ)) {
+ str_printfa(str, "HIGHESTMODSEQ %"PRIu64" ",
+ status->highest_modseq);
+ }
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_SIZE)) {
+ str_printfa(str, "SIZE %"PRIu64" ",
+ result->metadata.virtual_size);
+ }
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_X_SIZE)) {
+ str_printfa(str, "X-SIZE %"PRIu64" ",
+ result->metadata.virtual_size);
+ }
+ if (HAS_ALL_BITS(items->flags, IMAP_STATUS_ITEM_X_GUID)) {
+ str_printfa(str, "X-GUID %s ",
+ guid_128_to_string(result->metadata.guid));
+ }
+
+ if (str_len(str) != prefix_len)
+ str_truncate(str, str_len(str)-1);
+ str_append_c(str, ')');
+
+ return client_send_line_next(client, str_c(str));
+}
diff --git a/src/imap/imap-status.h b/src/imap/imap-status.h
new file mode 100644
index 0000000..58bd581
--- /dev/null
+++ b/src/imap/imap-status.h
@@ -0,0 +1,51 @@
+#ifndef IMAP_STATUS_H
+#define IMAP_STATUS_H
+
+enum imap_status_item_flags {
+ IMAP_STATUS_ITEM_MESSAGES = BIT(0),
+ IMAP_STATUS_ITEM_RECENT = BIT(1),
+ IMAP_STATUS_ITEM_UIDNEXT = BIT(2),
+ IMAP_STATUS_ITEM_UIDVALIDITY = BIT(3),
+ IMAP_STATUS_ITEM_UNSEEN = BIT(4),
+ IMAP_STATUS_ITEM_HIGHESTMODSEQ = BIT(5),
+ IMAP_STATUS_ITEM_SIZE = BIT(6),
+
+ IMAP_STATUS_ITEM_X_SIZE = BIT(16), /* to be deprecated */
+ IMAP_STATUS_ITEM_X_GUID = BIT(17),
+};
+
+struct imap_status_items {
+ enum imap_status_item_flags flags;
+};
+
+struct imap_status_result {
+ struct mailbox_status status;
+ struct mailbox_metadata metadata;
+ enum mail_error error;
+ const char *errstr;
+};
+
+static inline bool
+imap_status_items_is_empty(const struct imap_status_items *items)
+{
+ return (items->flags == 0);
+}
+
+int imap_status_parse_items(struct client_command_context *cmd,
+ const struct imap_arg *args,
+ struct imap_status_items *items_r);
+
+int imap_status_get_result(struct client *client, struct mailbox *box,
+ const struct imap_status_items *items,
+ struct imap_status_result *result_r);
+int imap_status_get(struct client_command_context *cmd,
+ struct mail_namespace *ns, const char *mailbox,
+ const struct imap_status_items *items,
+ struct imap_status_result *result_r);
+
+int imap_status_send(struct client *client, const char *mailbox_mutf7,
+ const struct imap_status_items *items,
+ const struct imap_status_result *result)
+ ATTR_NOWARN_UNUSED_RESULT;
+
+#endif
diff --git a/src/imap/imap-sync-private.h b/src/imap/imap-sync-private.h
new file mode 100644
index 0000000..b0b1715
--- /dev/null
+++ b/src/imap/imap-sync-private.h
@@ -0,0 +1,47 @@
+#ifndef IMAP_SYNC_PRIVATE_H
+#define IMAP_SYNC_PRIVATE_H
+
+#include "imap-sync.h"
+
+struct imap_client_sync_context {
+ /* if multiple commands are in progress, we may need to wait for them
+ to finish before syncing mailbox. */
+ unsigned int counter;
+ enum mailbox_sync_flags flags;
+ enum imap_sync_flags imap_flags;
+ const char *tagline;
+};
+
+struct imap_sync_context {
+ struct client *client;
+ struct mailbox *box;
+ enum imap_sync_flags imap_flags;
+
+ struct mailbox_transaction_context *t;
+ struct mailbox_sync_context *sync_ctx;
+ struct mail *mail;
+
+ struct mailbox_status status;
+ struct mailbox_sync_status sync_status;
+
+ struct mailbox_sync_rec sync_rec;
+ ARRAY_TYPE(keywords) tmp_keywords;
+ ARRAY_TYPE(seq_range) expunges;
+ uint32_t seq;
+
+ ARRAY_TYPE(seq_range) search_adds, search_removes;
+ unsigned int search_update_idx;
+
+ unsigned int messages_count;
+
+ /* Module-specific contexts. */
+ ARRAY(union imap_module_context *) module_contexts;
+
+ bool failed:1;
+ bool finished:1;
+ bool no_newmail:1;
+ bool have_new_mails:1;
+ bool search_update_notifying:1;
+};
+
+#endif
diff --git a/src/imap/imap-sync.c b/src/imap/imap-sync.c
new file mode 100644
index 0000000..fc49ae7
--- /dev/null
+++ b/src/imap/imap-sync.c
@@ -0,0 +1,841 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "ostream.h"
+#include "mail-user.h"
+#include "mail-storage.h"
+#include "mail-search-build.h"
+#include "imap-quote.h"
+#include "imap-util.h"
+#include "imap-fetch.h"
+#include "imap-notify.h"
+#include "imap-commands.h"
+#include "imap-sync-private.h"
+
+static void uids_to_seqs(struct mailbox *box, ARRAY_TYPE(seq_range) *uids)
+{
+ T_BEGIN {
+ ARRAY_TYPE(seq_range) seqs;
+ const struct seq_range *range;
+ uint32_t seq1, seq2;
+
+ t_array_init(&seqs, array_count(uids));
+ array_foreach(uids, range) {
+ mailbox_get_seq_range(box, range->seq1, range->seq2,
+ &seq1, &seq2);
+ /* since we have to notify about expunged messages,
+ we expect that all the referenced UIDs exist */
+ i_assert(seq1 != 0);
+ i_assert(seq2 - seq1 == range->seq2 - range->seq1);
+
+ seq_range_array_add_range(&seqs, seq1, seq2);
+ }
+ /* replace uids with seqs */
+ array_clear(uids);
+ array_append_array(uids, &seqs);
+
+ } T_END;
+}
+
+static int search_update_fetch_more(const struct imap_search_update *update)
+{
+ int ret;
+
+ if ((ret = imap_fetch_more_no_lock_update(update->fetch_ctx)) == 0)
+ return 0;
+ /* finished the FETCH */
+ if (imap_fetch_end(update->fetch_ctx) < 0)
+ return -1;
+ return ret;
+}
+
+static int
+imap_sync_send_fetch_to_search_update(struct imap_sync_context *ctx,
+ const struct imap_search_update *update)
+{
+ struct mail_search_args *search_args;
+ struct mail_search_arg *arg;
+ ARRAY_TYPE(seq_range) seqs;
+
+ if (ctx->search_update_notifying)
+ return search_update_fetch_more(update);
+
+ i_assert(!update->fetch_ctx->state.fetching);
+
+ if (array_count(&ctx->search_adds) == 0 || !ctx->have_new_mails)
+ return 1;
+
+ search_args = mail_search_build_init();
+ arg = mail_search_build_add(search_args, SEARCH_UIDSET);
+ p_array_init(&arg->value.seqset, search_args->pool, 1);
+
+ /* find the newly appended messages: ctx->messages_count is the message
+ count before new messages found by sync, client->messages_count is
+ the number of messages after. */
+ t_array_init(&seqs, 1);
+ seq_range_array_add_range(&seqs, ctx->messages_count+1,
+ ctx->client->messages_count);
+ mailbox_get_uid_range(ctx->client->mailbox, &seqs, &arg->value.seqset);
+ /* remove messages not in the search_adds list */
+ seq_range_array_intersect(&arg->value.seqset, &ctx->search_adds);
+
+ imap_fetch_begin(update->fetch_ctx, ctx->client->mailbox, search_args);
+ mail_search_args_unref(&search_args);
+ return search_update_fetch_more(update);
+}
+
+static int
+imap_sync_send_search_update(struct imap_sync_context *ctx,
+ const struct imap_search_update *update,
+ bool removes_only)
+{
+ string_t *cmd;
+ int ret = 1;
+
+ if (!ctx->search_update_notifying) {
+ mailbox_search_result_sync(update->result, &ctx->search_removes,
+ &ctx->search_adds);
+ }
+ if (array_count(&ctx->search_adds) == 0 &&
+ array_count(&ctx->search_removes) == 0)
+ return 1;
+
+ i_assert(array_count(&ctx->search_adds) == 0 || !removes_only);
+ if (update->fetch_ctx != NULL) {
+ ret = imap_sync_send_fetch_to_search_update(ctx, update);
+ if (ret == 0) {
+ ctx->search_update_notifying = TRUE;
+ return 0;
+ }
+ }
+ ctx->search_update_notifying = FALSE;
+
+ cmd = t_str_new(256);
+ str_append(cmd, "* ESEARCH (TAG ");
+ imap_append_string(cmd, update->tag);
+ str_append_c(cmd, ')');
+ if (update->return_uids)
+ str_append(cmd, " UID");
+ else {
+ /* convert to sequences */
+ uids_to_seqs(ctx->client->mailbox, &ctx->search_removes);
+ uids_to_seqs(ctx->client->mailbox, &ctx->search_adds);
+ }
+
+ if (array_count(&ctx->search_removes) != 0) {
+ str_printfa(cmd, " REMOVEFROM (0 ");
+ imap_write_seq_range(cmd, &ctx->search_removes);
+ str_append_c(cmd, ')');
+ }
+ if (array_count(&ctx->search_adds) != 0) {
+ str_printfa(cmd, " ADDTO (0 ");
+ imap_write_seq_range(cmd, &ctx->search_adds);
+ str_append_c(cmd, ')');
+ }
+ str_append(cmd, "\r\n");
+ o_stream_nsend(ctx->client->output, str_data(cmd), str_len(cmd));
+ return ret;
+}
+
+static int
+imap_sync_send_search_updates(struct imap_sync_context *ctx, bool removes_only)
+{
+ const struct imap_search_update *updates;
+ unsigned int i, count;
+ int ret = 1;
+
+ if (!array_is_created(&ctx->client->search_updates))
+ return 1;
+
+ if (!array_is_created(&ctx->search_removes)) {
+ i_array_init(&ctx->search_removes, 64);
+ i_array_init(&ctx->search_adds, 128);
+ }
+
+ updates = array_get(&ctx->client->search_updates, &count);
+ for (i = ctx->search_update_idx; i < count; i++) {
+ T_BEGIN {
+ ret = imap_sync_send_search_update(ctx, &updates[i],
+ removes_only);
+ } T_END;
+ if (ret <= 0)
+ break;
+ }
+ ctx->search_update_idx = i;
+ return ret;
+}
+
+struct imap_sync_context *
+imap_sync_init(struct client *client, struct mailbox *box,
+ enum imap_sync_flags imap_flags, enum mailbox_sync_flags flags)
+{
+ struct imap_sync_context *ctx;
+
+ i_assert(client->mailbox == box);
+
+ if (client->notify_immediate_expunges) {
+ /* NOTIFY enabled without SELECTED-DELAYED */
+ flags &= ENUM_NEGATE(MAILBOX_SYNC_FLAG_NO_EXPUNGES);
+ }
+
+ ctx = i_new(struct imap_sync_context, 1);
+ ctx->client = client;
+ ctx->box = box;
+ ctx->imap_flags = imap_flags;
+ i_array_init(&ctx->module_contexts, 5);
+
+ /* make sure user can't DoS the system by causing Dovecot to create
+ tons of useless namespaces. */
+ mail_user_drop_useless_namespaces(client->user);
+
+ ctx->sync_ctx = mailbox_sync_init(box, flags);
+ ctx->t = mailbox_transaction_begin(box, 0, "Mailbox sync");
+ ctx->mail = mail_alloc(ctx->t, MAIL_FETCH_FLAGS, NULL);
+ ctx->messages_count = client->messages_count;
+ i_array_init(&ctx->tmp_keywords, client->keywords.announce_count + 8);
+
+ if (client_has_enabled(client, imap_feature_qresync)) {
+ i_array_init(&ctx->expunges, 128);
+ /* always send UIDs in FETCH replies */
+ ctx->imap_flags |= IMAP_SYNC_FLAG_SEND_UID;
+ }
+
+ client_send_mailbox_flags(client, FALSE);
+ /* send search updates the first time after sync is initialized.
+ it now contains expunged messages that must be sent before
+ EXPUNGE replies. */
+ if (imap_sync_send_search_updates(ctx, TRUE) == 0)
+ i_unreached();
+ ctx->search_update_idx = 0;
+ return ctx;
+}
+
+static void
+imap_sync_send_highestmodseq(struct imap_sync_context *ctx,
+ struct client_command_context *sync_cmd)
+{
+ struct client *client = ctx->client;
+ uint64_t send_modseq = 0;
+
+ if (ctx->sync_status.sync_delayed_expunges &&
+ client->highest_fetch_modseq > client->sync_last_full_modseq) {
+ /* if client updates highest-modseq using returned MODSEQs
+ it loses expunges. try to avoid this by sending it a lower
+ pre-expunge HIGHESTMODSEQ reply. */
+ send_modseq = client->sync_last_full_modseq;
+ } else if (!ctx->sync_status.sync_delayed_expunges &&
+ ctx->status.highest_modseq > client->sync_last_full_modseq &&
+ ctx->status.highest_modseq > client->highest_fetch_modseq) {
+ /* we've probably sent some VANISHED or EXISTS replies which
+ increased the highest-modseq. notify the client about
+ this. */
+ send_modseq = ctx->status.highest_modseq;
+ }
+
+ if (send_modseq == 0) {
+ /* no sending */
+ } else if (sync_cmd->sync != NULL && /* IDLE doesn't have ->sync */
+ sync_cmd->sync->tagline != NULL && /* NOTIFY doesn't have tagline */
+ str_begins(sync_cmd->sync->tagline, "OK ") &&
+ sync_cmd->sync->tagline[3] != '[') {
+ /* modify the tagged reply directly */
+ sync_cmd->sync->tagline = p_strdup_printf(sync_cmd->pool,
+ "OK [HIGHESTMODSEQ %"PRIu64"] %s",
+ send_modseq, sync_cmd->sync->tagline + 3);
+ } else {
+ /* send an untagged OK reply */
+ client_send_line(client, t_strdup_printf(
+ "* OK [HIGHESTMODSEQ %"PRIu64"] Highest",
+ send_modseq));
+ }
+
+ if (!ctx->sync_status.sync_delayed_expunges) {
+ /* no delayed expunges, remember this for future */
+ client->sync_last_full_modseq = ctx->status.highest_modseq;
+ }
+ client->highest_fetch_modseq = 0;
+}
+
+static int imap_sync_finish(struct imap_sync_context *ctx, bool aborting)
+{
+ struct client *client = ctx->client;
+ int ret = ctx->failed ? -1 : 0;
+
+ if (ctx->finished)
+ return ret;
+ ctx->finished = TRUE;
+
+ mail_free(&ctx->mail);
+ /* the transaction is used only for fetching modseqs/flags.
+ it can't really fail.. */
+ (void)mailbox_transaction_commit(&ctx->t);
+
+ if (array_is_created(&ctx->expunges))
+ array_free(&ctx->expunges);
+
+ if (mailbox_sync_deinit(&ctx->sync_ctx, &ctx->sync_status) < 0 ||
+ ctx->failed) {
+ ctx->failed = TRUE;
+ ret = -1;
+ }
+ mailbox_get_open_status(ctx->box, STATUS_UIDVALIDITY |
+ STATUS_MESSAGES | STATUS_RECENT |
+ STATUS_HIGHESTMODSEQ, &ctx->status);
+
+ if (ctx->status.uidvalidity != client->uidvalidity) {
+ /* most clients would get confused by this. disconnect them. */
+ client_disconnect_with_error(client,
+ "Mailbox UIDVALIDITY changed");
+ }
+ if (mailbox_is_inconsistent(ctx->box)) {
+ client_disconnect_with_error(client,
+ "IMAP session state is inconsistent, please relogin.");
+ /* we can't trust status information anymore, so don't try to
+ sync message counts. */
+ return -1;
+ }
+ if (!ctx->no_newmail && !aborting) {
+ if (ctx->status.messages < ctx->messages_count)
+ i_panic("Message count decreased");
+ if (ctx->status.messages != ctx->messages_count &&
+ client->notify_count_changes) {
+ client_send_line(client,
+ t_strdup_printf("* %u EXISTS", ctx->status.messages));
+ ctx->have_new_mails = TRUE;
+ }
+ if (ctx->status.recent != client->recent_count &&
+ client->notify_count_changes) {
+ client_send_line(client,
+ t_strdup_printf("* %u RECENT", ctx->status.recent));
+ }
+ client->messages_count = ctx->status.messages;
+ client->recent_count = ctx->status.recent;
+ }
+ return ret;
+}
+
+static int imap_sync_notify_more(struct imap_sync_context *ctx)
+{
+ int ret = 1;
+
+ if (ctx->have_new_mails && ctx->client->notify_ctx != NULL) {
+ /* send FETCH replies for the new mails */
+ if ((ret = imap_client_notify_newmails(ctx->client)) == 0)
+ return 0;
+ if (ret < 0)
+ ctx->failed = TRUE;
+ }
+
+ /* send search updates the second time after syncing in done.
+ now it contains added/removed messages. */
+ if ((ret = imap_sync_send_search_updates(ctx, FALSE)) < 0)
+ ctx->failed = TRUE;
+
+ if (ret > 0)
+ ret = ctx->client->v.sync_notify_more(ctx);
+ return ret;
+}
+
+int imap_sync_deinit(struct imap_sync_context *ctx,
+ struct client_command_context *sync_cmd)
+{
+ int ret;
+
+ ret = imap_sync_finish(ctx, TRUE);
+ imap_client_notify_finished(ctx->client);
+
+ if (client_has_enabled(ctx->client, imap_feature_qresync) &&
+ !ctx->client->nonpermanent_modseqs)
+ imap_sync_send_highestmodseq(ctx, sync_cmd);
+
+ if (array_is_created(&ctx->search_removes)) {
+ array_free(&ctx->search_removes);
+ array_free(&ctx->search_adds);
+ }
+
+ array_free(&ctx->tmp_keywords);
+ array_free(&ctx->module_contexts);
+ i_free(ctx);
+ return ret;
+}
+
+static void imap_sync_add_modseq(struct imap_sync_context *ctx, string_t *str)
+{
+ uint64_t modseq;
+
+ modseq = mail_get_modseq(ctx->mail);
+ if (ctx->client->highest_fetch_modseq < modseq)
+ ctx->client->highest_fetch_modseq = modseq;
+ str_printfa(str, "MODSEQ (%"PRIu64")", modseq);
+}
+
+static int imap_sync_send_flags(struct imap_sync_context *ctx, string_t *str)
+{
+ enum mail_flags flags;
+ const char *const *keywords;
+
+ mail_set_seq(ctx->mail, ctx->seq);
+ flags = mail_get_flags(ctx->mail);
+ keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords,
+ mail_get_keyword_indexes(ctx->mail));
+
+ if ((flags & MAIL_DELETED) != 0)
+ ctx->client->sync_seen_deletes = TRUE;
+
+ str_truncate(str, 0);
+ str_printfa(str, "* %u FETCH (", ctx->seq);
+ if ((ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID) != 0)
+ str_printfa(str, "UID %u ", ctx->mail->uid);
+ if (client_has_enabled(ctx->client, imap_feature_condstore) &&
+ !ctx->client->nonpermanent_modseqs) {
+ imap_sync_add_modseq(ctx, str);
+ str_append_c(str, ' ');
+ }
+ str_append(str, "FLAGS (");
+ imap_write_flags(str, flags, keywords);
+ str_append(str, "))");
+ return client_send_line_next(ctx->client, str_c(str));
+}
+
+static int imap_sync_send_modseq(struct imap_sync_context *ctx, string_t *str)
+{
+ mail_set_seq(ctx->mail, ctx->seq);
+
+ str_truncate(str, 0);
+ str_printfa(str, "* %u FETCH (", ctx->seq);
+ if ((ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID) != 0)
+ str_printfa(str, "UID %u ", ctx->mail->uid);
+ imap_sync_add_modseq(ctx, str);
+ str_append_c(str, ')');
+ return client_send_line_next(ctx->client, str_c(str));
+}
+
+static void imap_sync_vanished(struct imap_sync_context *ctx)
+{
+ const struct seq_range *seqs;
+ unsigned int i, count;
+ string_t *line;
+ uint32_t seq, prev_uid, start_uid;
+ bool comma = FALSE;
+
+ /* Convert expunge sequences to UIDs and send them in VANISHED line. */
+ seqs = array_get(&ctx->expunges, &count);
+ if (count == 0)
+ return;
+
+ line = t_str_new(256);
+ str_append(line, "* VANISHED ");
+ for (i = 0; i < count; i++) {
+ start_uid = 0; prev_uid = 0;
+ for (seq = seqs[i].seq1; seq <= seqs[i].seq2; seq++) {
+ mail_set_seq(ctx->mail, seq);
+ if (prev_uid == 0 || prev_uid + 1 != ctx->mail->uid) {
+ if (start_uid != 0) {
+ if (!comma)
+ comma = TRUE;
+ else
+ str_append_c(line, ',');
+ str_printfa(line, "%u", start_uid);
+ if (start_uid != prev_uid) {
+ str_printfa(line, ":%u",
+ prev_uid);
+ }
+ }
+ start_uid = ctx->mail->uid;
+ }
+ prev_uid = ctx->mail->uid;
+ }
+ if (!comma)
+ comma = TRUE;
+ else
+ str_append_c(line, ',');
+ str_printfa(line, "%u", start_uid);
+ if (start_uid != prev_uid)
+ str_printfa(line, ":%u", prev_uid);
+ }
+ str_append(line, "\r\n");
+ o_stream_nsend(ctx->client->output, str_data(line), str_len(line));
+}
+
+static int imap_sync_send_expunges(struct imap_sync_context *ctx, string_t *str)
+{
+ int ret = 1;
+
+ if (!ctx->client->notify_count_changes) {
+ /* NOTIFY: MessageEvent not specified for selected mailbox */
+ return 1;
+ }
+
+ if (array_is_created(&ctx->expunges)) {
+ /* Use a single VANISHED line */
+ seq_range_array_add_range(&ctx->expunges,
+ ctx->sync_rec.seq1,
+ ctx->sync_rec.seq2);
+ return 1;
+ }
+ if (ctx->seq == 0)
+ ctx->seq = ctx->sync_rec.seq2;
+ for (; ctx->seq >= ctx->sync_rec.seq1; ctx->seq--) {
+ if (ret == 0) {
+ /* buffer full, continue later */
+ return 0;
+ }
+
+ str_truncate(str, 0);
+ str_printfa(str, "* %u EXPUNGE", ctx->seq);
+ ret = client_send_line_next(ctx->client, str_c(str));
+ }
+ return 1;
+}
+
+int imap_sync_more(struct imap_sync_context *ctx)
+{
+ string_t *str;
+ int ret = 1;
+
+ if (ctx->finished)
+ return imap_sync_notify_more(ctx);
+
+ /* finish syncing even when client has disconnected. otherwise our
+ internal state (ctx->messages_count) can get messed up and unless
+ we immediately stop handling all commands and syncs we could end up
+ assert-crashing. */
+ str = t_str_new(256);
+ for (;;) {
+ if (ctx->seq == 0) {
+ /* get next one */
+ if (!mailbox_sync_next(ctx->sync_ctx, &ctx->sync_rec)) {
+ /* finished */
+ ret = 1;
+ break;
+ }
+ }
+
+ if (ctx->sync_rec.seq2 > ctx->messages_count) {
+ /* don't send change notifications of messages we
+ haven't even announced to client yet */
+ if (ctx->sync_rec.seq1 > ctx->messages_count) {
+ ctx->seq = 0;
+ continue;
+ }
+ ctx->sync_rec.seq2 = ctx->messages_count;
+ }
+
+ /* EXPUNGEs must come last */
+ i_assert(!array_is_created(&ctx->expunges) ||
+ array_count(&ctx->expunges) == 0 ||
+ ctx->sync_rec.type == MAILBOX_SYNC_TYPE_EXPUNGE);
+ switch (ctx->sync_rec.type) {
+ case MAILBOX_SYNC_TYPE_FLAGS:
+ if (!ctx->client->notify_flag_changes) {
+ /* NOTIFY: FlagChange not specified for
+ selected mailbox */
+ break;
+ }
+ if (ctx->seq == 0)
+ ctx->seq = ctx->sync_rec.seq1;
+
+ ret = 1;
+ for (; ctx->seq <= ctx->sync_rec.seq2; ctx->seq++) {
+ if (ret == 0)
+ break;
+
+ ret = imap_sync_send_flags(ctx, str);
+ }
+ break;
+ case MAILBOX_SYNC_TYPE_EXPUNGE:
+ ret = imap_sync_send_expunges(ctx, str);
+ if (ret > 0) {
+ /* update only after we're finished, so that
+ the seq2 > messages_count check above
+ doesn't break */
+ ctx->messages_count -=
+ ctx->sync_rec.seq2 -
+ ctx->sync_rec.seq1 + 1;
+ }
+ break;
+ case MAILBOX_SYNC_TYPE_MODSEQ:
+ if (!client_has_enabled(ctx->client, imap_feature_condstore))
+ break;
+ if (!ctx->client->notify_flag_changes) {
+ /* NOTIFY: FlagChange not specified for
+ selected mailbox. The RFC doesn't explicitly
+ specify MODSEQ changes, but they're close
+ enough to flag changes. */
+ break;
+ }
+
+ if (ctx->seq == 0)
+ ctx->seq = ctx->sync_rec.seq1;
+
+ ret = 1;
+ for (; ctx->seq <= ctx->sync_rec.seq2; ctx->seq++) {
+ if (ret == 0)
+ break;
+
+ ret = imap_sync_send_modseq(ctx, str);
+ }
+ break;
+ }
+ if (ret == 0) {
+ /* buffer full */
+ break;
+ }
+
+ ctx->seq = 0;
+ }
+ if (ret > 0) {
+ if (array_is_created(&ctx->expunges))
+ imap_sync_vanished(ctx);
+ if (imap_sync_finish(ctx, FALSE) < 0)
+ return -1;
+ return imap_sync_more(ctx);
+ }
+ return ret;
+}
+
+bool imap_sync_is_allowed(struct client *client)
+{
+ if (client->syncing)
+ return FALSE;
+
+ if (client->mailbox != NULL &&
+ mailbox_transaction_get_count(client->mailbox) > 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static bool cmd_finish_sync(struct client_command_context *cmd)
+{
+ if (cmd->sync->tagline != NULL)
+ client_send_tagline(cmd, cmd->sync->tagline);
+ return TRUE;
+}
+
+static bool cmd_sync_continue(struct client_command_context *sync_cmd)
+{
+ struct client_command_context *cmd, *prev;
+ struct client *client = sync_cmd->client;
+ struct imap_sync_context *ctx = sync_cmd->context;
+ int ret;
+
+ i_assert(ctx->client == client);
+
+ if ((ret = imap_sync_more(ctx)) == 0)
+ return FALSE;
+ if (ret < 0)
+ ctx->failed = TRUE;
+
+ client->syncing = FALSE;
+ if (imap_sync_deinit(ctx, sync_cmd) < 0) {
+ client_send_untagged_storage_error(client,
+ mailbox_get_storage(client->mailbox));
+ }
+ sync_cmd->context = NULL;
+
+ /* Finish all commands that waited for this sync. Go through the queue
+ backwards, so that tagged replies are sent in the same order as
+ they were received. This fixes problems with clients that rely on
+ this (Apple Mail 3.2) */
+ for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
+ for (; cmd != NULL; cmd = prev) {
+ prev = cmd->prev;
+
+ if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
+ cmd != sync_cmd &&
+ cmd->sync->counter+1 == client->sync_counter) {
+ cmd_finish_sync(cmd);
+ client_command_free(&cmd);
+ }
+ }
+ cmd_finish_sync(sync_cmd);
+ return TRUE;
+}
+
+static void get_common_sync_flags(struct client *client,
+ enum mailbox_sync_flags *flags_r,
+ enum imap_sync_flags *imap_flags_r)
+{
+ struct client_command_context *cmd;
+ unsigned int count = 0, fast_count = 0, noexpunges_count = 0;
+
+ *flags_r = 0;
+ *imap_flags_r = 0;
+
+ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+ if (cmd->sync != NULL &&
+ cmd->sync->counter == client->sync_counter) {
+ if ((cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0)
+ fast_count++;
+ if ((cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES) != 0)
+ noexpunges_count++;
+ *flags_r |= cmd->sync->flags;
+ *imap_flags_r |= cmd->sync->imap_flags;
+ count++;
+ }
+ }
+ i_assert(noexpunges_count == 0 || noexpunges_count == count);
+ if (fast_count != count)
+ *flags_r &= ENUM_NEGATE(MAILBOX_SYNC_FLAG_FAST);
+
+ i_assert((*flags_r & MAILBOX_SYNC_FLAG_FIX_INCONSISTENT) == 0);
+}
+
+static bool cmd_sync_client(struct client_command_context *sync_cmd)
+{
+ struct client *client = sync_cmd->client;
+ struct imap_sync_context *ctx;
+ enum mailbox_sync_flags flags;
+ enum imap_sync_flags imap_flags;
+ bool no_newmail;
+
+ /* there may be multiple commands waiting. use their combined flags */
+ get_common_sync_flags(client, &flags, &imap_flags);
+ client->sync_counter++;
+
+ no_newmail = (client->set->parsed_workarounds & WORKAROUND_DELAY_NEWMAIL) != 0 &&
+ client->notify_ctx == NULL && /* always disabled with NOTIFY */
+ (imap_flags & IMAP_SYNC_FLAG_SAFE) == 0;
+ if (no_newmail) {
+ /* expunges might break the client just as badly as new mail
+ notifications. */
+ flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
+ }
+
+ client->syncing = TRUE;
+
+ ctx = imap_sync_init(client, client->mailbox, imap_flags, flags);
+ ctx->no_newmail = no_newmail;
+
+ /* handle the syncing using sync_cmd. it doesn't actually matter which
+ one of the pending commands it is. */
+ sync_cmd->func = cmd_sync_continue;
+ sync_cmd->context = ctx;
+ sync_cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+ if (!cmd_sync_continue(sync_cmd)) {
+ o_stream_set_flush_pending(client->output, TRUE);
+ return FALSE;
+ }
+
+ client_command_free(&sync_cmd);
+ cmd_sync_delayed(client);
+ return TRUE;
+}
+
+bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
+ enum imap_sync_flags imap_flags, const char *tagline)
+{
+ struct client *client = cmd->client;
+
+ i_assert(client->output_cmd_lock == NULL);
+
+ if (cmd->cancel)
+ return TRUE;
+
+ cmd->stats.last_run_timeval = ioloop_timeval;
+ if (client->mailbox == NULL) {
+ /* no mailbox selected, no point in delaying the sync */
+ if (tagline != NULL)
+ client_send_tagline(cmd, tagline);
+ return TRUE;
+ }
+ cmd->tagline_reply = p_strdup(cmd->pool, tagline);
+
+ cmd->sync = p_new(cmd->pool, struct imap_client_sync_context, 1);
+ cmd->sync->counter = client->sync_counter;
+ cmd->sync->flags = flags;
+ cmd->sync->imap_flags = imap_flags;
+ cmd->sync->tagline = cmd->tagline_reply;
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_SYNC;
+
+ cmd->func = NULL;
+ cmd->context = NULL;
+
+ if (client->input_lock == cmd)
+ client->input_lock = NULL;
+ return FALSE;
+}
+
+static bool cmd_sync_drop_fast(struct client *client)
+{
+ struct client_command_context *cmd, *prev;
+ bool ret = FALSE;
+
+ if (client->command_queue == NULL)
+ return FALSE;
+
+ for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
+ for (; cmd != NULL; cmd = prev) {
+ prev = cmd->next;
+
+ if (cmd->state != CLIENT_COMMAND_STATE_WAIT_SYNC)
+ continue;
+
+ i_assert(cmd->sync != NULL);
+ if ((cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0) {
+ cmd_finish_sync(cmd);
+ client_command_free(&cmd);
+ ret = TRUE;
+ }
+ }
+ return ret;
+}
+
+static bool cmd_sync_delayed_real(struct client *client)
+{
+ struct client_command_context *cmd, *first_expunge, *first_nonexpunge;
+
+ if (client->output_cmd_lock != NULL) {
+ /* wait until we can send output to client */
+ return FALSE;
+ }
+
+ if (!imap_sync_is_allowed(client)) {
+ /* wait until mailbox can be synced */
+ return cmd_sync_drop_fast(client);
+ }
+
+ /* separate syncs that can send expunges from those that can't */
+ first_expunge = first_nonexpunge = NULL;
+ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+ if (cmd->sync != NULL &&
+ cmd->sync->counter == client->sync_counter) {
+ if ((cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES) != 0) {
+ if (first_nonexpunge == NULL)
+ first_nonexpunge = cmd;
+ } else {
+ if (first_expunge == NULL)
+ first_expunge = cmd;
+ }
+ }
+ }
+ if (first_expunge != NULL && first_nonexpunge != NULL) {
+ /* sync expunges after nonexpunges */
+ for (cmd = first_expunge; cmd != NULL; cmd = cmd->next) {
+ if (cmd->sync != NULL &&
+ cmd->sync->counter == client->sync_counter &&
+ (cmd->sync->flags &
+ MAILBOX_SYNC_FLAG_NO_EXPUNGES) == 0)
+ cmd->sync->counter++;
+ }
+ first_expunge = NULL;
+ }
+ cmd = first_nonexpunge != NULL ? first_nonexpunge : first_expunge;
+
+ if (cmd == NULL)
+ return cmd_sync_drop_fast(client);
+ i_assert(client->mailbox != NULL);
+ return cmd_sync_client(cmd);
+}
+
+bool cmd_sync_delayed(struct client *client)
+{
+ bool ret;
+
+ T_BEGIN {
+ ret = cmd_sync_delayed_real(client);
+ } T_END;
+ return ret;
+}
diff --git a/src/imap/imap-sync.h b/src/imap/imap-sync.h
new file mode 100644
index 0000000..a1c5198
--- /dev/null
+++ b/src/imap/imap-sync.h
@@ -0,0 +1,25 @@
+#ifndef IMAP_SYNC_H
+#define IMAP_SYNC_H
+
+enum imap_sync_flags {
+ IMAP_SYNC_FLAG_SEND_UID = 0x01,
+ IMAP_SYNC_FLAG_SAFE = 0x02
+};
+
+struct client;
+
+struct imap_sync_context *
+imap_sync_init(struct client *client, struct mailbox *box,
+ enum imap_sync_flags imap_flags, enum mailbox_sync_flags flags);
+int imap_sync_deinit(struct imap_sync_context *ctx,
+ struct client_command_context *sync_cmd);
+int imap_sync_more(struct imap_sync_context *ctx);
+
+/* Returns TRUE if syncing would be allowed currently. */
+bool imap_sync_is_allowed(struct client *client);
+
+bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
+ enum imap_sync_flags imap_flags, const char *tagline);
+bool cmd_sync_delayed(struct client *client) ATTR_NOWARN_UNUSED_RESULT;
+
+#endif
diff --git a/src/imap/mail-storage-callbacks.c b/src/imap/mail-storage-callbacks.c
new file mode 100644
index 0000000..16c2d51
--- /dev/null
+++ b/src/imap/mail-storage-callbacks.c
@@ -0,0 +1,45 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ostream.h"
+#include "mail-storage.h"
+#include "imap-commands-util.h"
+
+static void notify_ok(struct mailbox *mailbox ATTR_UNUSED,
+ const char *text, void *context)
+{
+ struct client *client = context;
+
+ if (o_stream_get_buffer_used_size(client->output) != 0)
+ return;
+
+ T_BEGIN {
+ const char *str;
+
+ str = t_strconcat("* OK ", text, "\r\n", NULL);
+ o_stream_nsend_str(client->output, str);
+ (void)o_stream_flush(client->output);
+ } T_END;
+}
+
+static void notify_no(struct mailbox *mailbox ATTR_UNUSED,
+ const char *text, void *context)
+{
+ struct client *client = context;
+
+ if (o_stream_get_buffer_used_size(client->output) != 0)
+ return;
+
+ T_BEGIN {
+ const char *str;
+
+ str = t_strconcat("* NO ", text, "\r\n", NULL);
+ o_stream_nsend_str(client->output, str);
+ (void)o_stream_flush(client->output);
+ } T_END;
+}
+
+struct mail_storage_callbacks mail_storage_callbacks = {
+ notify_ok,
+ notify_no
+};
diff --git a/src/imap/main.c b/src/imap/main.c
new file mode 100644
index 0000000..cca5075
--- /dev/null
+++ b/src/imap/main.c
@@ -0,0 +1,591 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "path-util.h"
+#include "str.h"
+#include "base64.h"
+#include "process-title.h"
+#include "randgen.h"
+#include "restrict-access.h"
+#include "write-full.h"
+#include "settings-parser.h"
+#include "master-interface.h"
+#include "master-service.h"
+#include "master-login.h"
+#include "mail-user.h"
+#include "mail-storage-service.h"
+#include "smtp-submit-settings.h"
+#include "imap-master-client.h"
+#include "imap-resp-code.h"
+#include "imap-commands.h"
+#include "imap-feature.h"
+#include "imap-fetch.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define IS_STANDALONE() \
+ (getenv(MASTER_IS_PARENT_ENV) == NULL)
+
+#define IMAP_DIE_IDLE_SECS 10
+
+static bool verbose_proctitle = FALSE;
+static struct mail_storage_service_ctx *storage_service;
+static struct master_login *master_login = NULL;
+static struct timeout *to_proctitle;
+
+imap_client_created_func_t *hook_client_created = NULL;
+bool imap_debug = FALSE;
+
+struct event_category event_category_imap = {
+ .name = "imap",
+};
+
+imap_client_created_func_t *
+imap_client_created_hook_set(imap_client_created_func_t *new_hook)
+{
+ imap_client_created_func_t *old_hook = hook_client_created;
+
+ hook_client_created = new_hook;
+ return old_hook;
+}
+
+static void imap_refresh_proctitle_callback(void *context ATTR_UNUSED)
+{
+ timeout_remove(&to_proctitle);
+ imap_refresh_proctitle();
+}
+
+void imap_refresh_proctitle_delayed(void)
+{
+ if (to_proctitle == NULL)
+ to_proctitle = timeout_add_short(0,
+ imap_refresh_proctitle_callback, NULL);
+}
+
+void imap_refresh_proctitle(void)
+{
+#define IMAP_PROCTITLE_PREFERRED_LEN 80
+ struct client *client;
+ struct client_command_context *cmd;
+ bool wait_output;
+
+ if (!verbose_proctitle)
+ return;
+ if (imap_client_count == 0) {
+ if (imap_master_clients_refresh_proctitle())
+ return;
+ }
+
+ string_t *title = t_str_new(128);
+ str_append_c(title, '[');
+ switch (imap_client_count) {
+ case 0:
+ str_append(title, "idling");
+ break;
+ case 1:
+ client = imap_clients;
+ str_append(title, client->user->username);
+ if (client->user->conn.remote_ip != NULL) {
+ str_append_c(title, ' ');
+ str_append(title,
+ net_ip2addr(client->user->conn.remote_ip));
+ }
+ wait_output = FALSE;
+ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+ if (cmd->name == NULL)
+ continue;
+
+ if (str_len(title) < IMAP_PROCTITLE_PREFERRED_LEN) {
+ str_append_c(title, ' ');
+ str_append(title, cmd->name);
+ }
+ if (cmd->state == CLIENT_COMMAND_STATE_WAIT_OUTPUT)
+ wait_output = TRUE;
+ }
+ if (wait_output) {
+ str_printfa(title, " - %zu bytes waiting",
+ o_stream_get_buffer_used_size(client->output));
+ if (o_stream_is_corked(client->output))
+ str_append(title, " corked");
+ }
+ if (client->destroyed)
+ str_append(title, " (deinit)");
+ break;
+ default:
+ str_printfa(title, "%u connections", imap_client_count);
+ break;
+ }
+ str_append_c(title, ']');
+ process_title_set(str_c(title));
+}
+
+static void client_kill_idle(struct client *client)
+{
+ if (client->output_cmd_lock != NULL)
+ return;
+
+ mail_storage_service_io_activate_user(client->service_user);
+ client_send_line(client, "* BYE Server shutting down.");
+ client_destroy(client, "Server shutting down.");
+}
+
+static void imap_die(void)
+{
+ struct client *client, *next;
+ time_t last_io, now = time(NULL);
+ time_t stop_timestamp = now - IMAP_DIE_IDLE_SECS;
+ unsigned int stop_msecs;
+
+ for (client = imap_clients; client != NULL; client = next) {
+ next = client->next;
+
+ last_io = I_MAX(client->last_input, client->last_output);
+ if (last_io <= stop_timestamp)
+ client_kill_idle(client);
+ else {
+ timeout_remove(&client->to_idle);
+ stop_msecs = (last_io - stop_timestamp) * 1000;
+ client->to_idle = timeout_add(stop_msecs,
+ client_kill_idle, client);
+ }
+ }
+}
+
+struct imap_login_request {
+ const char *tag;
+
+ const unsigned char *input;
+ size_t input_size;
+ bool send_untagged_capability;
+};
+
+static void
+client_parse_imap_login_request(const unsigned char *data, size_t len,
+ struct imap_login_request *input_r)
+{
+ size_t taglen;
+
+ i_zero(input_r);
+ if (len == 0)
+ return;
+
+ if (data[0] == '1')
+ input_r->send_untagged_capability = TRUE;
+ data++; len--;
+
+ input_r->tag = t_strndup(data, len);
+ taglen = strlen(input_r->tag) + 1;
+
+ if (len > taglen) {
+ input_r->input = data + taglen;
+ input_r->input_size = len - taglen;
+ }
+}
+
+static void
+client_send_login_reply(struct ostream *output, const char *capability_string,
+ const char *preauth_username,
+ const struct imap_login_request *request)
+{
+ string_t *reply = t_str_new(256);
+
+ /* cork/uncork around the OK reply to minimize latency */
+ o_stream_cork(output);
+ if (request->tag == NULL) {
+ str_printfa(reply, "* PREAUTH [CAPABILITY %s] Logged in as %s\r\n",
+ capability_string, preauth_username);
+ } else if (capability_string == NULL) {
+ /* Client initialization failed. There's no need to send
+ capabilities. Just send the tagged OK so the client knows
+ the login itself succeeded, followed by a BYE. */
+ str_printfa(reply, "%s OK Logged in, but initialization failed.\r\n",
+ request->tag);
+ str_append(reply, "* BYE "MAIL_ERRSTR_CRITICAL_MSG"\r\n");
+ } else if (request->send_untagged_capability) {
+ /* client doesn't seem to understand tagged capabilities. send
+ untagged instead and hope that it works. */
+ str_printfa(reply, "* CAPABILITY %s\r\n", capability_string);
+ str_printfa(reply, "%s OK Logged in\r\n", request->tag);
+ } else {
+ str_printfa(reply, "%s OK [CAPABILITY %s] Logged in\r\n",
+ request->tag, capability_string);
+ }
+ o_stream_nsend(output, str_data(reply), str_len(reply));
+ if (o_stream_uncork_flush(output) < 0 &&
+ output->stream_errno != EPIPE &&
+ output->stream_errno != ECONNRESET)
+ i_error("write(client) failed: %s", o_stream_get_error(output));
+}
+
+static void
+client_add_input_finalize(struct client *client)
+{
+ struct ostream *output;
+
+ /* try to condense any responses into as few packets as possible */
+ output = client->output;
+ o_stream_ref(output);
+ o_stream_cork(output);
+ (void)client_handle_input(client);
+ o_stream_uncork(output);
+ o_stream_unref(&output);
+
+ /* we could have already handled LOGOUT, or we might need to continue
+ pending ambiguous commands. */
+ client_continue_pending_input(client);
+}
+
+int client_create_from_input(const struct mail_storage_service_input *input,
+ int fd_in, int fd_out, bool unhibernated,
+ struct client **client_r, const char **error_r)
+{
+ struct mail_storage_service_input service_input;
+ struct mail_storage_service_user *user;
+ struct mail_user *mail_user;
+ struct client *client;
+ struct imap_settings *imap_set;
+ struct smtp_submit_settings *smtp_set;
+ struct event *event;
+ const char *errstr;
+
+ event = event_create(NULL);
+ event_add_category(event, &event_category_imap);
+ event_add_fields(event, (const struct event_add_field []){
+ { .key = "user", .value = input->username },
+ { .key = NULL }
+ });
+ if (input->local_ip.family != 0)
+ event_add_str(event, "local_ip", net_ip2addr(&input->local_ip));
+ if (input->local_port != 0)
+ event_add_int(event, "local_port", input->local_port);
+ if (input->remote_ip.family != 0)
+ event_add_str(event, "remote_ip", net_ip2addr(&input->remote_ip));
+ if (input->remote_port != 0)
+ event_add_int(event, "remote_port", input->remote_port);
+
+ service_input = *input;
+ service_input.event_parent = event;
+ if (mail_storage_service_lookup_next(storage_service, &service_input,
+ &user, &mail_user, error_r) <= 0) {
+ event_unref(&event);
+ return -1;
+ }
+ /* Add the session only after creating the user, because
+ input->session_id may be NULL */
+ event_add_str(event, "session", mail_user->session_id);
+
+ restrict_access_allow_coredumps(TRUE);
+
+ smtp_set = mail_storage_service_user_get_set(user)[1];
+ imap_set = mail_storage_service_user_get_set(user)[2];
+ if (imap_set->verbose_proctitle)
+ verbose_proctitle = TRUE;
+
+ if (settings_var_expand(&smtp_submit_setting_parser_info, smtp_set,
+ mail_user->pool, mail_user_var_expand_table(mail_user),
+ &errstr) <= 0 ||
+ settings_var_expand(&imap_setting_parser_info, imap_set,
+ mail_user->pool, mail_user_var_expand_table(mail_user),
+ &errstr) <= 0) {
+ *error_r = t_strdup_printf("Failed to expand settings: %s", errstr);
+ mail_user_deinit(&mail_user);
+ mail_storage_service_user_unref(&user);
+ event_unref(&event);
+ return -1;
+ }
+
+ client = client_create(fd_in, fd_out, unhibernated,
+ event, mail_user, user, imap_set, smtp_set);
+ client->userdb_fields = input->userdb_fields == NULL ? NULL :
+ p_strarray_dup(client->pool, input->userdb_fields);
+ event_unref(&event);
+ *client_r = client;
+ return 0;
+}
+
+static void main_stdio_run(const char *username)
+{
+ struct client *client;
+ struct mail_storage_service_input input;
+ struct imap_login_request request;
+ const char *value, *error, *input_base64;
+
+ i_zero(&input);
+ input.module = input.service = "imap";
+ input.username = username != NULL ? username : getenv("USER");
+ if (input.username == NULL && IS_STANDALONE())
+ input.username = getlogin();
+ if (input.username == NULL)
+ i_fatal("USER environment missing");
+ if ((value = getenv("IP")) != NULL)
+ (void)net_addr2ip(value, &input.remote_ip);
+ if ((value = getenv("LOCAL_IP")) != NULL)
+ (void)net_addr2ip(value, &input.local_ip);
+
+ if (client_create_from_input(&input, STDIN_FILENO, STDOUT_FILENO,
+ FALSE, &client, &error) < 0)
+ i_fatal("%s", error);
+
+ input_base64 = getenv("CLIENT_INPUT");
+ if (input_base64 == NULL) {
+ /* IMAPLOGINTAG environment is compatible with mailfront */
+ i_zero(&request);
+ request.tag = getenv("IMAPLOGINTAG");
+ } else {
+ const buffer_t *input_buf = t_base64_decode_str(input_base64);
+ client_parse_imap_login_request(input_buf->data, input_buf->used,
+ &request);
+ if (request.input_size > 0) {
+ client_add_istream_prefix(client, request.input,
+ request.input_size);
+ }
+ }
+
+ client_create_finish_io(client);
+ client_send_login_reply(client->output,
+ str_c(client->capability_string),
+ client->user->username, &request);
+ if (client_create_finish(client, &error) < 0)
+ i_fatal("%s", error);
+ client_add_input_finalize(client);
+ /* client may be destroyed now */
+}
+
+static void
+login_client_connected(const struct master_login_client *login_client,
+ const char *username, const char *const *extra_fields)
+{
+#define MSG_BYE_INTERNAL_ERROR "* BYE "MAIL_ERRSTR_CRITICAL_MSG"\r\n"
+ struct mail_storage_service_input input;
+ struct client *client;
+ struct imap_login_request request;
+ enum mail_auth_request_flags flags = login_client->auth_req.flags;
+ const char *error;
+
+ i_zero(&input);
+ input.module = input.service = "imap";
+ input.local_ip = login_client->auth_req.local_ip;
+ input.remote_ip = login_client->auth_req.remote_ip;
+ input.local_port = login_client->auth_req.local_port;
+ input.remote_port = login_client->auth_req.remote_port;
+ input.username = username;
+ input.userdb_fields = extra_fields;
+ input.session_id = login_client->session_id;
+ if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SECURED) != 0)
+ input.conn_secured = TRUE;
+ if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SSL_SECURED) != 0)
+ input.conn_ssl_secured = TRUE;
+
+ client_parse_imap_login_request(login_client->data,
+ login_client->auth_req.data_size,
+ &request);
+
+ if (client_create_from_input(&input, login_client->fd, login_client->fd,
+ FALSE, &client, &error) < 0) {
+ int fd = login_client->fd;
+ struct ostream *output =
+ o_stream_create_fd_autoclose(&fd, IO_BLOCK_SIZE);
+ client_send_login_reply(output, NULL, NULL, &request);
+ o_stream_destroy(&output);
+
+ i_error("%s", error);
+ master_service_client_connection_destroyed(master_service);
+ return;
+ }
+ if ((flags & MAIL_AUTH_REQUEST_FLAG_TLS_COMPRESSION) != 0)
+ client->tls_compression = TRUE;
+ if (request.input_size > 0) {
+ client_add_istream_prefix(client, request.input,
+ request.input_size);
+ }
+
+ /* The order here is important:
+ 1. Finish setting up rawlog, so all input/output is written there.
+ 2. Send tagged reply to login before any potentially long-running
+ work (during which client could disconnect due to timeout).
+ 3. Finish initializing user, which can potentially take a long time.
+ */
+ client_create_finish_io(client);
+ client_send_login_reply(client->output,
+ str_c(client->capability_string),
+ NULL, &request);
+ if (client_create_finish(client, &error) < 0) {
+ if (write_full(login_client->fd, MSG_BYE_INTERNAL_ERROR,
+ strlen(MSG_BYE_INTERNAL_ERROR)) < 0)
+ if (errno != EAGAIN && errno != EPIPE &&
+ errno != ECONNRESET)
+ e_error(client->event,
+ "write_full(client) failed: %m");
+
+ e_error(client->event, "%s", error);
+ client_destroy(client, error);
+ return;
+ }
+
+ client_add_input_finalize(client);
+ /* client may be destroyed now */
+}
+
+static void login_client_failed(const struct master_login_client *client,
+ const char *errormsg)
+{
+ struct imap_login_request request;
+ const char *msg;
+
+ client_parse_imap_login_request(client->data,
+ client->auth_req.data_size, &request);
+ msg = t_strdup_printf("%s NO ["IMAP_RESP_CODE_UNAVAILABLE"] %s\r\n",
+ request.tag, errormsg);
+ if (write(client->fd, msg, strlen(msg)) < 0) {
+ /* ignored */
+ }
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+ /* when running standalone, we shouldn't even get here */
+ i_assert(master_login != NULL);
+
+ master_service_client_connection_accept(conn);
+ if (strcmp(conn->name, "imap-master") == 0) {
+ /* restoring existing IMAP connection (e.g. from imap-idle) */
+ imap_master_client_create(conn->fd);
+ } else {
+ master_login_add(master_login, conn->fd);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ static const struct setting_parser_info *set_roots[] = {
+ &smtp_submit_setting_parser_info,
+ &imap_setting_parser_info,
+ NULL
+ };
+ struct master_login_settings login_set;
+ enum master_service_flags service_flags = 0;
+ enum mail_storage_service_flags storage_service_flags =
+ MAIL_STORAGE_SERVICE_FLAG_NO_SSL_CA |
+ /*
+ * We include MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES so
+ * that the mail_user initialization is fast and we can
+ * quickly send back the OK response to LOGIN/AUTHENTICATE.
+ * Otherwise we risk a very slow namespace initialization to
+ * cause client timeouts on login.
+ */
+ MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES;
+ const char *username = NULL, *auth_socket_path = "auth-master";
+ int c;
+
+ i_zero(&login_set);
+ login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT;
+ login_set.request_auth_token = TRUE;
+
+ if (IS_STANDALONE() && getuid() == 0 &&
+ net_getpeername(1, NULL, NULL) == 0) {
+ printf("* BAD [ALERT] imap binary must not be started from "
+ "inetd, use imap-login instead.\n");
+ return 1;
+ }
+
+ if (IS_STANDALONE()) {
+ service_flags |= MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_STD_CLIENT;
+ } else {
+ service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
+ }
+
+ master_service = master_service_init("imap", service_flags,
+ &argc, &argv, "a:Dt:u:");
+ while ((c = master_getopt(master_service)) > 0) {
+ switch (c) {
+ case 'a':
+ auth_socket_path = optarg;
+ break;
+ case 't':
+ if (str_to_uint(optarg, &login_set.postlogin_timeout_secs) < 0 ||
+ login_set.postlogin_timeout_secs == 0)
+ i_fatal("Invalid -t parameter: %s", optarg);
+ break;
+ case 'u':
+ storage_service_flags |=
+ MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+ username = optarg;
+ break;
+ case 'D':
+ imap_debug = TRUE;
+ break;
+ default:
+ return FATAL_DEFAULT;
+ }
+ }
+
+ master_service_set_die_callback(master_service, imap_die);
+
+ /* plugins may want to add commands, so this needs to be called early */
+ commands_init();
+ imap_fetch_handlers_init();
+ imap_features_init();
+ clients_init();
+ imap_master_clients_init();
+ /* this is needed before settings are read */
+ verbose_proctitle = !IS_STANDALONE() &&
+ getenv(MASTER_VERBOSE_PROCTITLE_ENV) != NULL;
+
+ const char *error;
+ if (t_abspath(auth_socket_path, &login_set.auth_socket_path, &error) < 0)
+ i_fatal("t_abspath(%s) failed: %s", auth_socket_path, error);
+
+ if (argv[optind] != NULL) {
+ if (t_abspath(argv[optind], &login_set.postlogin_socket_path, &error) < 0)
+ i_fatal("t_abspath(%s) failed: %s", argv[optind], error);
+ }
+ login_set.callback = login_client_connected;
+ login_set.failure_callback = login_client_failed;
+ login_set.update_proctitle = verbose_proctitle &&
+ master_service_get_client_limit(master_service) == 1;
+
+ if (!IS_STANDALONE())
+ master_login = master_login_init(master_service, &login_set);
+
+ storage_service =
+ mail_storage_service_init(master_service,
+ set_roots, storage_service_flags);
+ master_service_init_finish(master_service);
+ /* NOTE: login_set.*_socket_path are now invalid due to data stack
+ having been freed */
+
+ /* fake that we're running, so we know if client was destroyed
+ while handling its initial input */
+ io_loop_set_running(current_ioloop);
+
+ if (IS_STANDALONE()) {
+ T_BEGIN {
+ main_stdio_run(username);
+ } T_END;
+ } else {
+ io_loop_set_running(current_ioloop);
+ }
+
+ if (io_loop_is_running(current_ioloop))
+ master_service_run(master_service, client_connected);
+ clients_destroy_all();
+
+ if (master_login != NULL)
+ master_login_deinit(&master_login);
+ mail_storage_service_deinit(&storage_service);
+
+ imap_fetch_handlers_deinit();
+ imap_features_deinit();
+
+ commands_deinit();
+ imap_master_clients_deinit();
+
+ timeout_remove(&to_proctitle);
+ master_service_deinit(&master_service);
+ return 0;
+}
diff --git a/src/imap/test-imap-client-hibernate.c b/src/imap/test-imap-client-hibernate.c
new file mode 100644
index 0000000..9b90e1b
--- /dev/null
+++ b/src/imap/test-imap-client-hibernate.c
@@ -0,0 +1,292 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "test-subprocess.h"
+#include "istream.h"
+#include "istream-unix.h"
+#include "strescape.h"
+#include "path-util.h"
+#include "unlink-directory.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "smtp-submit.h"
+#include "mail-storage-service.h"
+#include "mail-storage-private.h"
+#include "imap-common.h"
+#include "imap-settings.h"
+#include "imap-client.h"
+
+#include <sys/stat.h>
+
+#define TEMP_DIRNAME ".test-imap-client-hibernate"
+
+#define EVILSTR "\t\r\n\001"
+
+struct test_imap_client_hibernate {
+ struct client *client;
+ int fd_listen;
+ bool has_mailbox;
+ const char *reply;
+};
+
+imap_client_created_func_t *hook_client_created = NULL;
+bool imap_debug = FALSE;
+
+static const char *tmpdir;
+static struct mail_storage_service_ctx *storage_service;
+
+void imap_refresh_proctitle(void) { }
+void imap_refresh_proctitle_delayed(void) { }
+int client_create_from_input(const struct mail_storage_service_input *input ATTR_UNUSED,
+ int fd_in ATTR_UNUSED, int fd_out ATTR_UNUSED,
+ bool unhibernated ATTR_UNUSED,
+ struct client **client_r ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED) { return -1; }
+
+static int imap_hibernate_server(struct test_imap_client_hibernate *ctx)
+{
+ i_set_failure_prefix("SERVER: ");
+
+ int fd = net_accept(ctx->fd_listen, NULL, NULL);
+ i_assert(fd > 0);
+ struct istream *input = i_stream_create_unix(fd, SIZE_MAX);
+ i_stream_unix_set_read_fd(input);
+
+ /* send handshake */
+ const char *str = "VERSION\timap-hibernate\t1\t0\n";
+ if (write(fd, str, strlen(str)) != (ssize_t)strlen(str))
+ i_fatal("write(imap-hibernate client handshake) failed: %m");
+
+ /* read handshake */
+ const char *line;
+ if ((line = i_stream_read_next_line(input)) == NULL)
+ i_fatal("read(imap-hibernate client handshake) failed: %s",
+ i_stream_get_error(input));
+ if (strcmp(line, "VERSION\timap-hibernate\t1\t0") != 0)
+ i_fatal("VERSION not received");
+ /* read command */
+ if ((line = i_stream_read_next_line(input)) == NULL)
+ i_fatal("read(imap-hibernate client command) failed: %s",
+ i_stream_get_error(input));
+ int fd2 = i_stream_unix_get_read_fd(input);
+ test_assert(fd2 != -1);
+ i_close_fd(&fd2);
+ const char *const *args = t_strsplit_tabescaped(line);
+
+ /* write reply */
+ if (write(fd, ctx->reply, strlen(ctx->reply)) != (ssize_t)strlen(ctx->reply))
+ i_fatal("write(imap-hibernate client command) failed: %m");
+
+ if (ctx->has_mailbox) {
+ /* read mailbox notify fd */
+ i_stream_unix_set_read_fd(input);
+ if (i_stream_read_next_line(input) == NULL)
+ i_fatal("read(imap-hibernate notify fd) failed: %s",
+ i_stream_get_error(input));
+
+ fd2 = i_stream_unix_get_read_fd(input);
+ test_assert(fd2 != -1);
+ i_close_fd(&fd2);
+
+ if (write(fd, "+\n", 2) != 2)
+ i_fatal("write(imap-hibernate client command) failed: %m");
+ }
+
+ unsigned int i = 0;
+ test_assert_strcmp(args[i++], EVILSTR"testuser");
+ test_assert_strcmp(args[i++], EVILSTR"%u");
+ test_assert_strcmp(args[i++], "idle_notify_interval=120");
+ test_assert(str_begins(args[i++], "peer_dev_major="));
+ test_assert(str_begins(args[i++], "peer_dev_minor="));
+ test_assert(str_begins(args[i++], "peer_ino="));
+ test_assert_strcmp(args[i++], "session="EVILSTR"session");
+ test_assert(str_begins(args[i++], "session_created="));
+ test_assert_strcmp(args[i++], "lip=127.0.0.1");
+ test_assert_strcmp(args[i++], "lport=1234");
+ test_assert_strcmp(args[i++], "rip=127.0.0.2");
+ test_assert_strcmp(args[i++], "rport=5678");
+ test_assert(str_begins(args[i++], "uid="));
+ test_assert(str_begins(args[i++], "gid="));
+ if (ctx->has_mailbox)
+ test_assert_strcmp(args[i++], "mailbox="EVILSTR"mailbox");
+ test_assert_strcmp(args[i++], "tag="EVILSTR"tag");
+ test_assert(str_begins(args[i++], "stats="));
+ test_assert_strcmp(args[i++], "idle-cmd");
+ if (ctx->has_mailbox)
+ test_assert_strcmp(args[i++], "notify_fd");
+ test_assert(str_begins(args[i++], "state="));
+ test_assert(args[i] == NULL);
+
+ i_stream_unref(&input);
+ i_close_fd(&ctx->fd_listen);
+ i_close_fd(&fd);
+
+ ctx->client->hibernated = TRUE; /* prevent disconnect Info message */
+ client_destroy(ctx->client, NULL);
+
+ mail_storage_service_deinit(&storage_service);
+ master_service_deinit_forked(&master_service);
+ return 0;
+}
+
+static void
+mailbox_notify_callback(struct mailbox *box ATTR_UNUSED,
+ struct client *client ATTR_UNUSED)
+{
+}
+
+static void test_imap_client_hibernate(void)
+{
+ struct client *client;
+ struct smtp_submit_settings smtp_set;
+ struct mail_storage_service_user *service_user;
+ struct mail_user *mail_user;
+ struct test_imap_client_hibernate ctx;
+ const char *error;
+
+ storage_service = mail_storage_service_init(master_service, NULL,
+ MAIL_STORAGE_SERVICE_FLAG_ALLOW_ROOT |
+ MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
+ MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR |
+ MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS);
+
+ const char *const input_userdb[] = {
+ "mailbox_list_index=no",
+ t_strdup_printf("mail=mbox:%s/mbox", tmpdir),
+ NULL
+ };
+ struct mail_storage_service_input input = {
+ .username = EVILSTR"testuser",
+ .local_port = 1234,
+ .remote_port = 5678,
+ .userdb_fields = input_userdb,
+ };
+ test_assert(net_addr2ip("127.0.0.1", &input.local_ip) == 0);
+ test_assert(net_addr2ip("127.0.0.2", &input.remote_ip) == 0);
+ test_assert(mail_storage_service_lookup_next(storage_service, &input,
+ &service_user, &mail_user, &error) == 1);
+ mail_user->set->base_dir = tmpdir;
+ mail_user->set->mail_log_prefix = EVILSTR"%u";
+ mail_user->session_id = EVILSTR"session";
+ i_zero(&smtp_set);
+ i_zero(&ctx);
+
+ struct event *event = event_create(NULL);
+ int client_fd = dup(dev_null_fd);
+ client = client_create(client_fd, client_fd, FALSE, event,
+ mail_user, service_user,
+ imap_setting_parser_info.defaults, &smtp_set);
+ ctx.client = client;
+
+ /* can't hibernate without IDLE */
+ test_begin("imap client hibernate: non-IDLE");
+ test_assert(!imap_client_hibernate(&client, &error));
+ test_assert_strcmp(error, "Non-IDLE connections not supported currently");
+ test_end();
+
+ struct client_command_context *cmd = client_command_alloc(client);
+ cmd->tag = EVILSTR"tag";
+ cmd->name = "IDLE";
+ event_unref(&event);
+
+ /* imap-hibernate socket doesn't exist */
+ test_begin("imap client hibernate: socket not found");
+ test_expect_error_string("/"TEMP_DIRNAME"/imap-hibernate) failed: No such file or directory");
+ test_assert(!imap_client_hibernate(&client, &error));
+ test_expect_no_more_errors();
+ test_assert(strstr(error, "net_connect_unix") != NULL);
+ test_end();
+
+ /* imap-hibernate socket times out */
+ const char *socket_path = t_strdup_printf("%s/imap-hibernate", tmpdir);
+ ctx.fd_listen = net_listen_unix(socket_path, 1);
+ if (ctx.fd_listen == -1)
+ i_fatal("net_listen_unix(%s) failed: %m", socket_path);
+ fd_set_nonblock(ctx.fd_listen, FALSE);
+
+ /* imap-hibernate socket returns failure */
+ test_begin("imap client hibernate: error returned");
+ ctx.reply = "-notgood\n";
+ test_subprocess_fork(imap_hibernate_server, &ctx, FALSE);
+
+ test_expect_error_string(TEMP_DIRNAME"/imap-hibernate returned failure: notgood");
+ test_assert(!imap_client_hibernate(&client, &error));
+ test_expect_no_more_errors();
+ test_assert(strstr(error, "notgood") != NULL);
+ test_end();
+
+ /* create and open evil mailbox */
+ client->mailbox = mailbox_alloc(client->user->namespaces->list,
+ "testbox", 0);
+ struct mailbox_update update = {
+ .uid_validity = 12345678,
+ };
+ memset(update.mailbox_guid, 0x12, sizeof(update.mailbox_guid));
+ test_assert(mailbox_create(client->mailbox, &update, FALSE) == 0);
+ test_assert(mailbox_open(client->mailbox) == 0);
+ client->mailbox->vname = EVILSTR"mailbox";
+
+ /* successful hibernation */
+ test_begin("imap client hibernate: success");
+ ctx.reply = "+\n";
+ ctx.has_mailbox = TRUE;
+ test_subprocess_fork(imap_hibernate_server, &ctx, FALSE);
+ /* start notification only after forking or we'll have trouble
+ deinitializing cleanly */
+ mailbox_notify_changes(client->mailbox, mailbox_notify_callback, client);
+ test_assert(imap_client_hibernate(&client, &error));
+ test_end();
+
+ i_close_fd(&ctx.fd_listen);
+ mail_storage_service_deinit(&storage_service);
+}
+
+static void test_cleanup(void)
+{
+ const char *error;
+
+ if (unlink_directory(tmpdir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0)
+ i_error("unlink_directory() failed: %s", error);
+}
+
+static void test_init(void)
+{
+ const char *cwd, *error;
+
+ test_assert(t_get_working_dir(&cwd, &error) == 0);
+ tmpdir = t_strconcat(cwd, "/"TEMP_DIRNAME, NULL);
+
+ test_cleanup();
+ if (mkdir(tmpdir, 0700) < 0)
+ i_fatal("mkdir() failed: %m");
+
+ test_subprocesses_init(FALSE);
+}
+
+int main(int argc, char *argv[])
+{
+ const enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS |
+ MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_STD_CLIENT |
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS;
+ int ret;
+
+ master_service = master_service_init("test-imap-client-hibernate",
+ service_flags, &argc, &argv, "D");
+
+ master_service_init_finish(master_service);
+ test_init();
+
+ static void (*const test_functions[])(void) = {
+ test_imap_client_hibernate,
+ NULL
+ };
+ ret = test_run(test_functions);
+
+ test_subprocesses_deinit();
+ test_cleanup();
+ master_service_deinit(&master_service);
+ return ret;
+}