summaryrefslogtreecommitdiffstats
path: root/src/imap-hibernate
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap-hibernate')
-rw-r--r--src/imap-hibernate/Makefile.am27
-rw-r--r--src/imap-hibernate/Makefile.in828
-rw-r--r--src/imap-hibernate/imap-client.c799
-rw-r--r--src/imap-hibernate/imap-client.h40
-rw-r--r--src/imap-hibernate/imap-hibernate-client.c304
-rw-r--r--src/imap-hibernate/imap-hibernate-client.h9
-rw-r--r--src/imap-hibernate/imap-hibernate-settings.c47
-rw-r--r--src/imap-hibernate/imap-master-connection.c140
-rw-r--r--src/imap-hibernate/imap-master-connection.h24
-rw-r--r--src/imap-hibernate/main.c57
10 files changed, 2275 insertions, 0 deletions
diff --git a/src/imap-hibernate/Makefile.am b/src/imap-hibernate/Makefile.am
new file mode 100644
index 0000000..3641c90
--- /dev/null
+++ b/src/imap-hibernate/Makefile.am
@@ -0,0 +1,27 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = imap-hibernate
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-imap \
+ $(BINARY_CFLAGS)
+
+imap_hibernate_LDADD = $(LIBDOVECOT) \
+ $(BINARY_LDFLAGS)
+
+imap_hibernate_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+
+imap_hibernate_SOURCES = \
+ imap-client.c \
+ imap-hibernate-client.c \
+ imap-hibernate-settings.c \
+ imap-master-connection.c \
+ main.c
+
+noinst_HEADERS = \
+ imap-client.h \
+ imap-hibernate-client.h \
+ imap-master-connection.h
diff --git a/src/imap-hibernate/Makefile.in b/src/imap-hibernate/Makefile.in
new file mode 100644
index 0000000..ba2e63e
--- /dev/null
+++ b/src/imap-hibernate/Makefile.in
@@ -0,0 +1,828 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+pkglibexec_PROGRAMS = imap-hibernate$(EXEEXT)
+subdir = src/imap-hibernate
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(pkglibexecdir)"
+PROGRAMS = $(pkglibexec_PROGRAMS)
+am_imap_hibernate_OBJECTS = imap-client.$(OBJEXT) \
+ imap-hibernate-client.$(OBJEXT) \
+ imap-hibernate-settings.$(OBJEXT) \
+ imap-master-connection.$(OBJEXT) main.$(OBJEXT)
+imap_hibernate_OBJECTS = $(am_imap_hibernate_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 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/imap-client.Po \
+ ./$(DEPDIR)/imap-hibernate-client.Po \
+ ./$(DEPDIR)/imap-hibernate-settings.Po \
+ ./$(DEPDIR)/imap-master-connection.Po ./$(DEPDIR)/main.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_hibernate_SOURCES)
+DIST_SOURCES = $(imap_hibernate_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+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-settings \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-imap \
+ $(BINARY_CFLAGS)
+
+imap_hibernate_LDADD = $(LIBDOVECOT) \
+ $(BINARY_LDFLAGS)
+
+imap_hibernate_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+imap_hibernate_SOURCES = \
+ imap-client.c \
+ imap-hibernate-client.c \
+ imap-hibernate-settings.c \
+ imap-master-connection.c \
+ main.c
+
+noinst_HEADERS = \
+ imap-client.h \
+ imap-hibernate-client.h \
+ imap-master-connection.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/imap-hibernate/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/imap-hibernate/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files
+
+clean-pkglibexecPROGRAMS:
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+imap-hibernate$(EXEEXT): $(imap_hibernate_OBJECTS) $(imap_hibernate_DEPENDENCIES) $(EXTRA_imap_hibernate_DEPENDENCIES)
+ @rm -f imap-hibernate$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(imap_hibernate_OBJECTS) $(imap_hibernate_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-client.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-hibernate-client.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-hibernate-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-master-connection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibexecdir)"; 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-pkglibexecPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imap-client.Po
+ -rm -f ./$(DEPDIR)/imap-hibernate-client.Po
+ -rm -f ./$(DEPDIR)/imap-hibernate-settings.Po
+ -rm -f ./$(DEPDIR)/imap-master-connection.Po
+ -rm -f ./$(DEPDIR)/main.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-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)/imap-client.Po
+ -rm -f ./$(DEPDIR)/imap-hibernate-client.Po
+ -rm -f ./$(DEPDIR)/imap-hibernate-settings.Po
+ -rm -f ./$(DEPDIR)/imap-master-connection.Po
+ -rm -f ./$(DEPDIR)/main.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-pkglibexecPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool 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-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-pkglibexecPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/imap-hibernate/imap-client.c b/src/imap-hibernate/imap-client.c
new file mode 100644
index 0000000..8e0f933
--- /dev/null
+++ b/src/imap-hibernate/imap-client.c
@@ -0,0 +1,799 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "fdpass.h"
+#include "hostpid.h"
+#include "connection.h"
+#include "iostream.h"
+#include "istream.h"
+#include "ostream.h"
+#include "llist.h"
+#include "priorityq.h"
+#include "base64.h"
+#include "str.h"
+#include "strescape.h"
+#include "time-util.h"
+#include "var-expand.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "imap-keepalive.h"
+#include "imap-master-connection.h"
+#include "imap-client.h"
+
+#include <unistd.h>
+
+#define IMAP_MASTER_SOCKET_NAME "imap-master"
+
+/* we only need enough for "DONE\r\n<tag> IDLE\r\n" */
+#define IMAP_MAX_INBUF 12 + 1 + 128 /* DONE\r\nIDLE\r\n + ' ' + <tag> */
+#define IMAP_MAX_OUTBUF 1024
+
+/* If client has sent input and we can't recreate imap process in this
+ many seconds, disconnect the client. */
+#define IMAP_CLIENT_MOVE_BACK_WITH_INPUT_TIMEOUT_SECS 10
+/* If there's a change notification and we can't recreate imap process in this
+ many seconds, disconnect the client. */
+#define IMAP_CLIENT_MOVE_BACK_WITHOUT_INPUT_TIMEOUT_SECS (60*5)
+
+/* How often to try to unhibernate clients. */
+#define IMAP_UNHIBERNATE_RETRY_MSECS 100
+
+#define IMAP_CLIENT_BUFFER_FULL_ERROR "Client output buffer is full"
+#define IMAP_CLIENT_UNHIBERNATE_ERROR "Failed to unhibernate client"
+
+enum imap_client_input_state {
+ IMAP_CLIENT_INPUT_STATE_UNKNOWN,
+ IMAP_CLIENT_INPUT_STATE_BAD,
+ IMAP_CLIENT_INPUT_STATE_DONE_LF,
+ IMAP_CLIENT_INPUT_STATE_DONE_CRLF,
+ IMAP_CLIENT_INPUT_STATE_DONEIDLE
+};
+
+struct imap_client_notify {
+ int fd;
+ struct io *io;
+};
+
+struct imap_client {
+ struct priorityq_item item;
+
+ struct imap_client *prev, *next;
+ pool_t pool;
+ struct event *event;
+ struct imap_client_state state;
+ ARRAY(struct imap_client_notify) notifys;
+
+ time_t move_back_start;
+ struct timeout *to_move_back;
+
+ int fd;
+ struct io *io;
+ struct istream *input;
+ struct ostream *output;
+ struct timeout *to_keepalive;
+ struct imap_master_connection *master_conn;
+ struct ioloop_context *ioloop_ctx;
+ const char *log_prefix;
+ unsigned int next_read_threshold;
+ bool bad_done, idle_done;
+ bool unhibernate_queued;
+ bool input_pending;
+};
+
+static struct imap_client *imap_clients;
+static struct priorityq *unhibernate_queue;
+static struct timeout *to_unhibernate;
+static const char imap_still_here_text[] = "* OK Still here\r\n";
+
+static struct event_category event_category_imap = {
+ .name = "imap",
+};
+static struct event_category event_category_imap_hibernate = {
+ .name = "imap-hibernate",
+ .parent = &event_category_imap,
+};
+
+static void imap_client_stop(struct imap_client *client);
+void imap_client_destroy(struct imap_client **_client, const char *reason);
+static void imap_client_add_idle_keepalive_timeout(struct imap_client *client);
+static void imap_clients_unhibernate(void *context);
+static void imap_client_stop_notify_listening(struct imap_client *client);
+
+static void imap_client_disconnected(struct imap_client **_client)
+{
+ struct imap_client *client = *_client;
+ const char *reason;
+
+ reason = io_stream_get_disconnect_reason(client->input, client->output);
+ imap_client_destroy(_client, reason);
+}
+
+static void
+imap_client_unhibernate_failed(struct imap_client **_client, const char *error)
+{
+ struct imap_client *client = *_client;
+ struct timeval created;
+ event_get_create_time(client->event, &created);
+
+ struct event_passthrough *e =
+ event_create_passthrough(client->event)->
+ set_name("imap_client_unhibernated")->
+ add_int("hibernation_usecs",
+ timeval_diff_usecs(&ioloop_timeval, &created))->
+ add_str("error", error);
+ e_error(e->event(), IMAP_CLIENT_UNHIBERNATE_ERROR": %s", error);
+ imap_client_destroy(_client, IMAP_CLIENT_UNHIBERNATE_ERROR);
+}
+
+static void
+imap_client_parse_userdb_fields(struct imap_client *client,
+ const char **auth_user_r)
+{
+ const char *const *field;
+ unsigned int i;
+
+ *auth_user_r = NULL;
+
+ if (client->state.userdb_fields == NULL)
+ return;
+
+ field = t_strsplit_tabescaped(client->state.userdb_fields);
+ for (i = 0; field[i] != NULL; i++) {
+ if (str_begins(field[i], "auth_user="))
+ *auth_user_r = field[i] + 10;
+ }
+}
+
+static void
+imap_client_move_back_send_callback(void *context, struct ostream *output)
+{
+ struct imap_client *client = context;
+ const struct imap_client_state *state = &client->state;
+ string_t *str = t_str_new(256);
+ struct timeval created;
+ const unsigned char *input_data;
+ size_t input_size;
+ ssize_t ret;
+
+ str_append_tabescaped(str, state->username);
+ event_get_create_time(client->event, &created);
+ str_printfa(str, "\thibernation_started=%"PRIdTIME_T".%06u",
+ created.tv_sec, (unsigned int)created.tv_usec);
+
+ if (state->session_id != NULL) {
+ str_append(str, "\tsession=");
+ str_append_tabescaped(str, state->session_id);
+ }
+ if (state->session_created != 0) {
+ str_printfa(str, "\tsession_created=%s",
+ dec2str(state->session_created));
+ }
+ if (state->tag != NULL)
+ str_printfa(str, "\ttag=%s", client->state.tag);
+ if (state->local_ip.family != 0)
+ str_printfa(str, "\tlip=%s", net_ip2addr(&state->local_ip));
+ if (state->local_port != 0)
+ str_printfa(str, "\tlport=%u", state->local_port);
+ if (state->remote_ip.family != 0)
+ str_printfa(str, "\trip=%s", net_ip2addr(&state->remote_ip));
+ if (state->remote_port != 0)
+ str_printfa(str, "\trport=%u", state->remote_port);
+ if (state->userdb_fields != NULL) {
+ str_append(str, "\tuserdb_fields=");
+ str_append_tabescaped(str, state->userdb_fields);
+ }
+ if (major(state->peer_dev) != 0 || minor(state->peer_dev) != 0) {
+ str_printfa(str, "\tpeer_dev_major=%lu\tpeer_dev_minor=%lu",
+ (unsigned long)major(state->peer_dev),
+ (unsigned long)minor(state->peer_dev));
+ }
+ if (state->peer_ino != 0)
+ str_printfa(str, "\tpeer_ino=%llu", (unsigned long long)state->peer_ino);
+ if (state->state_size > 0) {
+ str_append(str, "\tstate=");
+ base64_encode(state->state, state->state_size, str);
+ }
+ input_data = i_stream_get_data(client->input, &input_size);
+ if (input_size > 0) {
+ str_append(str, "\tclient_input=");
+ base64_encode(input_data, input_size, str);
+ }
+ i_assert(o_stream_get_buffer_used_size(client->output) == 0);
+ if (client->idle_done) {
+ if (client->bad_done)
+ str_append(str, "\tbad-done");
+ } else if (client->state.idle_cmd) {
+ /* IDLE continues after sending changes */
+ str_append(str, "\tidle-continue");
+ }
+ str_append_c(str, '\n');
+
+ /* send the fd first */
+ ret = fd_send(o_stream_get_fd(output), client->fd, str_data(str), 1);
+ if (ret < 0) {
+ const char *error = t_strdup_printf(
+ "fd_send(%s) failed: %m", o_stream_get_name(output));
+ imap_client_unhibernate_failed(&client, error);
+ return;
+ }
+ i_assert(ret > 0);
+ o_stream_nsend(output, str_data(str) + 1, str_len(str) - 1);
+}
+
+static void
+imap_client_move_back_read_callback(void *context, const char *line)
+{
+ struct imap_client *client = context;
+
+ if (line[0] != '+') {
+ /* failed - FIXME: retry later? */
+ imap_client_unhibernate_failed(&client, line+1);
+ } else {
+ imap_client_destroy(&client, NULL);
+ }
+}
+
+static bool imap_move_has_reached_timeout(struct imap_client *client)
+{
+ int max_secs = client->input_pending ?
+ IMAP_CLIENT_MOVE_BACK_WITH_INPUT_TIMEOUT_SECS :
+ IMAP_CLIENT_MOVE_BACK_WITHOUT_INPUT_TIMEOUT_SECS;
+ return client->move_back_start != 0 &&
+ ioloop_time - client->move_back_start > max_secs;
+}
+
+static bool imap_client_try_move_back(struct imap_client *client)
+{
+ const struct master_service_settings *master_set;
+ const char *path, *error;
+ int ret;
+
+ if (o_stream_get_buffer_used_size(client->output) > 0) {
+ /* there is data buffered, so we have to disconnect you */
+ imap_client_destroy(&client, IMAP_CLIENT_BUFFER_FULL_ERROR);
+ return TRUE;
+ }
+
+ master_set = master_service_settings_get(master_service);
+ path = t_strconcat(master_set->base_dir,
+ "/"IMAP_MASTER_SOCKET_NAME, NULL);
+ ret = imap_master_connection_init(path,
+ imap_client_move_back_send_callback,
+ imap_client_move_back_read_callback,
+ client, &client->master_conn, &error);
+ if (ret > 0) {
+ /* success */
+ imap_client_stop(client);
+ return TRUE;
+ } else if (ret < 0 || imap_move_has_reached_timeout(client)) {
+ /* failed to connect to the imap-master socket */
+ imap_client_unhibernate_failed(&client, error);
+ return TRUE;
+ }
+
+ e_debug(event_create_passthrough(client->event)->
+ set_name("imap_client_unhibernate_retried")->
+ add_str("error", error)->event(),
+ "Unhibernation failed: %s - retrying", error);
+ /* Stop listening for client's IOs while waiting for the next
+ reconnection attempt. However if we got here because of an external
+ notification keep waiting to see if client sends any IO, since that
+ will cause the unhibernation to be aborted earlier. */
+ if (client->input_pending)
+ io_remove(&client->io);
+ imap_client_stop_notify_listening(client);
+ return FALSE;
+}
+
+static void imap_client_move_back(struct imap_client *client)
+{
+ if (imap_client_try_move_back(client))
+ return;
+
+ /* imap-master socket is busy. retry in a while. */
+ if (client->move_back_start == 0)
+ client->move_back_start = ioloop_time;
+ if (!client->unhibernate_queued) {
+ client->unhibernate_queued = TRUE;
+ priorityq_add(unhibernate_queue, &client->item);
+ }
+ if (to_unhibernate == NULL) {
+ to_unhibernate = timeout_add_short(IMAP_UNHIBERNATE_RETRY_MSECS,
+ imap_clients_unhibernate, NULL);
+ }
+}
+
+static enum imap_client_input_state
+imap_client_input_parse(const unsigned char *data, size_t size, const char **tag_r)
+{
+ const unsigned char *tag_start, *tag_end;
+
+ enum imap_client_input_state state = IMAP_CLIENT_INPUT_STATE_DONE_LF;
+
+ /* skip over DONE[\r]\n */
+ if (i_memcasecmp(data, "DONE", I_MIN(size, 4)) != 0)
+ return IMAP_CLIENT_INPUT_STATE_BAD;
+ if (size <= 4)
+ return IMAP_CLIENT_INPUT_STATE_UNKNOWN;
+ data += 4; size -= 4;
+
+ if (data[0] == '\r') {
+ state = IMAP_CLIENT_INPUT_STATE_DONE_CRLF;
+ data++; size--;
+ }
+ if (size == 0)
+ return IMAP_CLIENT_INPUT_STATE_UNKNOWN;
+ if (data[0] != '\n')
+ return IMAP_CLIENT_INPUT_STATE_BAD;
+ data++; size--;
+ if (size == 0)
+ return state;
+
+ tag_start = data;
+
+ /* skip over tag */
+ while(data[0] != ' ' &&
+ data[0] != '\r' &&
+ data[0] != '\t' ) { data++; size--; }
+
+ tag_end = data;
+
+ if (size == 0)
+ return state;
+ if (data[0] != ' ')
+ return IMAP_CLIENT_INPUT_STATE_BAD;
+ data++; size--;
+
+ /* skip over IDLE[\r]\n - checking this assumes that the DONE and IDLE
+ are sent in the same IP packet, otherwise we'll unnecessarily
+ recreate the imap process and immediately resume IDLE there. if this
+ becomes an issue we could add a small delay to the imap process
+ creation and wait for the IDLE command during it. */
+ if (size <= 4 || i_memcasecmp(data, "IDLE", 4) != 0)
+ return state;
+ data += 4; size -= 4;
+
+ if (data[0] == '\r') {
+ data++; size--;
+ }
+ if (size == 1 && data[0] == '\n') {
+ *tag_r = t_strdup_until(tag_start, tag_end);
+ return IMAP_CLIENT_INPUT_STATE_DONEIDLE;
+ }
+ return state;
+}
+
+static void imap_client_input_idle_cmd(struct imap_client *client)
+{
+ char *old_tag;
+ const char *new_tag;
+ const char *output;
+ const unsigned char *data;
+ size_t size;
+ bool done = TRUE;
+ int ret;
+
+ /* we should read either DONE or disconnection. also handle if client
+ sends DONE\nIDLE simply to recreate the IDLE. */
+ ret = i_stream_read_bytes(client->input, &data, &size,
+ client->next_read_threshold + 1);
+ if (size == 0) {
+ if (ret < 0)
+ imap_client_disconnected(&client);
+ return;
+ }
+ client->next_read_threshold = 0;
+ switch (imap_client_input_parse(data, size, &new_tag)) {
+ case IMAP_CLIENT_INPUT_STATE_UNKNOWN:
+ /* we haven't received a full DONE[\r]\n yet - wait */
+ client->next_read_threshold = size;
+ return;
+ case IMAP_CLIENT_INPUT_STATE_BAD:
+ /* invalid input - return this to the imap process */
+ client->bad_done = TRUE;
+ break;
+ case IMAP_CLIENT_INPUT_STATE_DONE_LF:
+ i_stream_skip(client->input, 4+1);
+ break;
+ case IMAP_CLIENT_INPUT_STATE_DONE_CRLF:
+ i_stream_skip(client->input, 4+2);
+ break;
+ case IMAP_CLIENT_INPUT_STATE_DONEIDLE:
+ /* we received DONE+IDLE, so the client simply wanted to notify
+ us that it's still there. continue hibernation. */
+ old_tag = client->state.tag;
+ client->state.tag = i_strdup(new_tag);
+ output = t_strdup_printf("%s OK Idle completed.\r\n+ idling\r\n", old_tag);
+ i_free(old_tag);
+ ret = o_stream_flush(client->output);
+ if (ret > 0)
+ ret = o_stream_send_str(client->output, output);
+ if (ret < 0) {
+ imap_client_disconnected(&client);
+ return;
+ }
+ if ((size_t)ret != strlen(output)) {
+ /* disconnect */
+ imap_client_destroy(&client, IMAP_CLIENT_BUFFER_FULL_ERROR);
+ return;
+ } else {
+ done = FALSE;
+ i_stream_skip(client->input, size);
+ }
+ break;
+ }
+
+ if (done) {
+ client->idle_done = TRUE;
+ client->input_pending = TRUE;
+ imap_client_move_back(client);
+ } else
+ imap_client_add_idle_keepalive_timeout(client);
+}
+
+static void imap_client_input_nonidle(struct imap_client *client)
+{
+ if (i_stream_read(client->input) < 0)
+ imap_client_disconnected(&client);
+ else {
+ client->input_pending = TRUE;
+ imap_client_move_back(client);
+ }
+}
+
+static void imap_client_input_notify(struct imap_client *client)
+{
+ imap_client_move_back(client);
+}
+
+static void keepalive_timeout(struct imap_client *client)
+{
+ ssize_t ret;
+
+ /* do not send this if there is data buffered */
+ if ((ret = o_stream_flush(client->output)) < 0) {
+ imap_client_disconnected(&client);
+ return;
+ } else if (ret == 0)
+ return;
+
+ ret = o_stream_send_str(client->output, imap_still_here_text);
+ if (ret < 0) {
+ imap_client_disconnected(&client);
+ return;
+ }
+ /* ostream buffer size is definitely large enough for this text */
+ i_assert((size_t)ret == strlen(imap_still_here_text));
+ imap_client_add_idle_keepalive_timeout(client);
+}
+
+static void imap_client_add_idle_keepalive_timeout(struct imap_client *client)
+{
+ unsigned int interval = client->state.imap_idle_notify_interval;
+
+ if (interval == 0)
+ return;
+
+ interval = imap_keepalive_interval_msecs(client->state.username,
+ &client->state.remote_ip,
+ interval);
+
+ timeout_remove(&client->to_keepalive);
+ client->to_keepalive = timeout_add(interval, keepalive_timeout, client);
+}
+
+static const struct var_expand_table *
+imap_client_get_var_expand_table(struct imap_client *client)
+{
+ const char *username = t_strcut(client->state.username, '@');
+ const char *domain = i_strchr_to_next(client->state.username, '@');
+ const char *local_ip = client->state.local_ip.family == 0 ? NULL :
+ net_ip2addr(&client->state.local_ip);
+ const char *remote_ip = client->state.remote_ip.family == 0 ? NULL :
+ net_ip2addr(&client->state.remote_ip);
+
+ const char *auth_user, *auth_username, *auth_domain;
+ imap_client_parse_userdb_fields(client, &auth_user);
+ if (auth_user == NULL) {
+ auth_user = client->state.username;
+ auth_username = username;
+ auth_domain = domain;
+ } else {
+ auth_username = t_strcut(auth_user, '@');
+ auth_domain = i_strchr_to_next(auth_user, '@');
+ }
+
+ const struct var_expand_table stack_tab[] = {
+ { 'u', client->state.username, "user" },
+ { 'n', username, "username" },
+ { 'd', domain, "domain" },
+ { 's', "imap-hibernate", "service" },
+ { 'h', NULL /* we shouldn't need this */, "home" },
+ { 'l', local_ip, "lip" },
+ { 'r', remote_ip, "rip" },
+ { 'p', my_pid, "pid" },
+ { 'i', dec2str(client->state.uid), "uid" },
+ { '\0', dec2str(client->state.gid), "gid" },
+ { '\0', client->state.session_id, "session" },
+ { '\0', auth_user, "auth_user" },
+ { '\0', auth_username, "auth_username" },
+ { '\0', auth_domain, "auth_domain" },
+ /* aliases: */
+ { '\0', local_ip, "local_ip" },
+ { '\0', remote_ip, "remote_ip" },
+ /* NOTE: keep this synced with lib-storage's
+ mail_user_var_expand_table() */
+ { '\0', NULL, NULL }
+ };
+ struct var_expand_table *tab;
+
+ tab = t_malloc_no0(sizeof(stack_tab));
+ memcpy(tab, stack_tab, sizeof(stack_tab));
+ return tab;
+}
+
+static int
+imap_client_var_expand_func_userdb(const char *data, void *context,
+ const char **value_r, const char **error_r ATTR_UNUSED)
+{
+ const char *const *fields = context;
+ const char *field_name = t_strdup_printf("%s=",t_strcut(data, ':'));
+ const char *default_value = i_strchr_to_next(data, ':');
+ const char *value = NULL;
+
+ for(;*fields != NULL; fields++) {
+ if (str_begins(*fields, field_name)) {
+ value = *fields+strlen(field_name);
+ break;
+ }
+ }
+
+ *value_r = value != NULL ? value : default_value;
+
+ return 1;
+}
+
+static void imap_client_io_activate_user(struct imap_client *client)
+{
+ i_set_failure_prefix("%s", client->log_prefix);
+}
+
+static void imap_client_io_deactivate_user(struct imap_client *client ATTR_UNUSED)
+{
+ i_set_failure_prefix("imap-hibernate: ");
+}
+
+static const char *imap_client_get_anvil_userip_ident(struct imap_client_state *state)
+{
+ if (state->remote_ip.family == 0)
+ return NULL;
+ return t_strconcat(net_ip2addr(&state->remote_ip), "/",
+ str_tabescape(state->username), NULL);
+}
+
+struct imap_client *
+imap_client_create(int fd, const struct imap_client_state *state)
+{
+ const struct var_expand_func_table funcs[] = {
+ { "userdb", imap_client_var_expand_func_userdb },
+ { NULL, NULL }
+ };
+ struct imap_client *client;
+ pool_t pool = pool_alloconly_create("imap client", 256);
+ void *statebuf;
+ const char *ident, *error;
+
+ i_assert(state->username != NULL);
+ i_assert(state->mail_log_prefix != NULL);
+
+ fd_set_nonblock(fd, TRUE); /* it should already be, but be sure */
+
+ client = p_new(pool, struct imap_client, 1);
+ client->pool = pool;
+ client->fd = fd;
+ client->input = i_stream_create_fd(fd, IMAP_MAX_INBUF);
+ client->output = o_stream_create_fd(fd, IMAP_MAX_OUTBUF);
+ client->state = *state;
+ client->state.username = p_strdup(pool, state->username);
+ client->state.session_id = p_strdup(pool, state->session_id);
+ client->state.userdb_fields = p_strdup(pool, state->userdb_fields);
+ client->state.stats = p_strdup(pool, state->stats);
+
+ client->event = event_create(NULL);
+ event_add_category(client->event, &event_category_imap_hibernate);
+ event_add_str(client->event, "user", state->username);
+ event_add_str(client->event, "session", state->session_id);
+ if (state->mailbox_vname != NULL)
+ event_add_str(client->event, "mailbox", state->mailbox_vname);
+ if (state->local_ip.family != 0)
+ event_add_str(client->event, "local_ip",
+ net_ip2addr(&state->local_ip));
+ if (state->local_port != 0)
+ event_add_int(client->event, "local_port", state->local_port);
+ if (state->remote_ip.family != 0)
+ event_add_str(client->event, "remote_ip",
+ net_ip2addr(&state->remote_ip));
+ if (state->remote_port != 0)
+ event_add_int(client->event, "remote_port", state->remote_port);
+
+ if (state->state_size > 0) {
+ client->state.state = statebuf = p_malloc(pool, state->state_size);
+ memcpy(statebuf, state->state, state->state_size);
+ client->state.state_size = state->state_size;
+ }
+ T_BEGIN {
+ string_t *str;
+ char **fields = p_strsplit_tabescaped(unsafe_data_stack_pool,
+ client->state.userdb_fields);
+ str = t_str_new(256);
+ if (var_expand_with_funcs(str, state->mail_log_prefix,
+ imap_client_get_var_expand_table(client),
+ funcs, fields, &error) <= 0) {
+ e_error(client->event,
+ "Failed to expand mail_log_prefix=%s: %s",
+ state->mail_log_prefix, error);
+ }
+ client->log_prefix = p_strdup(pool, str_c(str));
+ } T_END;
+
+ ident = imap_client_get_anvil_userip_ident(&client->state);
+ if (ident != NULL) {
+ master_service_anvil_send(master_service, t_strconcat(
+ "CONNECT\t", my_pid, "\timap/", ident, "\n", NULL));
+ client->state.anvil_sent = TRUE;
+ }
+
+ p_array_init(&client->notifys, pool, 2);
+ DLLIST_PREPEND(&imap_clients, client);
+ return client;
+}
+
+static void imap_client_stop_notify_listening(struct imap_client *client)
+{
+ struct imap_client_notify *notify;
+
+ array_foreach_modifiable(&client->notifys, notify) {
+ io_remove(&notify->io);
+ i_close_fd(&notify->fd);
+ }
+}
+
+static void imap_client_stop(struct imap_client *client)
+{
+ if (client->unhibernate_queued) {
+ priorityq_remove(unhibernate_queue, &client->item);
+ client->unhibernate_queued = FALSE;
+ }
+ io_remove(&client->io);
+ timeout_remove(&client->to_keepalive);
+ imap_client_stop_notify_listening(client);
+}
+
+void imap_client_destroy(struct imap_client **_client, const char *reason)
+{
+ struct imap_client *client = *_client;
+
+ *_client = NULL;
+
+ if (reason != NULL) {
+ /* the client input/output bytes don't count the DONE+IDLE by
+ imap-hibernate, but that shouldn't matter much. */
+ e_info(client->event, "Disconnected: %s %s",
+ reason, client->state.stats);
+ }
+
+ if (client->state.anvil_sent) {
+ master_service_anvil_send(master_service, t_strconcat(
+ "DISCONNECT\t", my_pid, "\timap/",
+ imap_client_get_anvil_userip_ident(&client->state),
+ "\n", NULL));
+ }
+
+ if (client->master_conn != NULL)
+ imap_master_connection_free(&client->master_conn);
+ if (client->ioloop_ctx != NULL) {
+ io_loop_context_remove_callbacks(client->ioloop_ctx,
+ imap_client_io_activate_user,
+ imap_client_io_deactivate_user, client);
+ imap_client_io_deactivate_user(client);
+ io_loop_context_unref(&client->ioloop_ctx);
+ }
+
+ if (client->state.tag != NULL)
+ i_free(client->state.tag);
+
+ DLLIST_REMOVE(&imap_clients, client);
+ imap_client_stop(client);
+ i_stream_destroy(&client->input);
+ o_stream_destroy(&client->output);
+ i_close_fd(&client->fd);
+ event_unref(&client->event);
+ pool_unref(&client->pool);
+
+ master_service_client_connection_destroyed(master_service);
+}
+
+void imap_client_add_notify_fd(struct imap_client *client, int fd)
+{
+ struct imap_client_notify *notify;
+
+ notify = array_append_space(&client->notifys);
+ notify->fd = fd;
+}
+
+void imap_client_create_finish(struct imap_client *client)
+{
+ struct imap_client_notify *notify;
+
+ client->ioloop_ctx = io_loop_context_new(current_ioloop);
+ io_loop_context_add_callbacks(client->ioloop_ctx,
+ imap_client_io_activate_user,
+ imap_client_io_deactivate_user, client);
+ io_loop_context_switch(client->ioloop_ctx);
+
+ if (client->state.idle_cmd) {
+ client->io = io_add(client->fd, IO_READ,
+ imap_client_input_idle_cmd, client);
+ } else {
+ client->io = io_add(client->fd, IO_READ,
+ imap_client_input_nonidle, client);
+ }
+ imap_client_add_idle_keepalive_timeout(client);
+
+ array_foreach_modifiable(&client->notifys, notify) {
+ notify->io = io_add(notify->fd, IO_READ,
+ imap_client_input_notify, client);
+ }
+}
+
+static int client_unhibernate_cmp(const void *p1, const void *p2)
+{
+ const struct imap_client *c1 = p1, *c2 = p2;
+ time_t t1, t2;
+
+ t1 = c1->move_back_start +
+ (c1->input_pending ?
+ IMAP_CLIENT_MOVE_BACK_WITH_INPUT_TIMEOUT_SECS :
+ IMAP_CLIENT_MOVE_BACK_WITHOUT_INPUT_TIMEOUT_SECS);
+ t2 = c2->move_back_start +
+ (c2->input_pending ?
+ IMAP_CLIENT_MOVE_BACK_WITH_INPUT_TIMEOUT_SECS :
+ IMAP_CLIENT_MOVE_BACK_WITHOUT_INPUT_TIMEOUT_SECS);
+ if (t1 < t2)
+ return -1;
+ if (t1 > t2)
+ return 1;
+ return 0;
+}
+
+static void imap_clients_unhibernate(void *context ATTR_UNUSED)
+{
+ struct priorityq_item *item;
+
+ while ((item = priorityq_peek(unhibernate_queue)) != NULL) {
+ struct imap_client *client = (struct imap_client *)item;
+
+ if (!imap_client_try_move_back(client))
+ return;
+ }
+ timeout_remove(&to_unhibernate);
+}
+
+void imap_clients_init(void)
+{
+ unhibernate_queue = priorityq_init(client_unhibernate_cmp, 64);
+}
+
+void imap_clients_deinit(void)
+{
+ while (imap_clients != NULL) {
+ struct imap_client *client = imap_clients;
+
+ imap_client_io_activate_user(client);
+ imap_client_destroy(&client, "Shutting down");
+ }
+ timeout_remove(&to_unhibernate);
+ priorityq_deinit(&unhibernate_queue);
+}
diff --git a/src/imap-hibernate/imap-client.h b/src/imap-hibernate/imap-client.h
new file mode 100644
index 0000000..b6bb0e6
--- /dev/null
+++ b/src/imap-hibernate/imap-client.h
@@ -0,0 +1,40 @@
+#ifndef IMAP_CLIENT_H
+#define IMAP_CLIENT_H
+
+#include "net.h"
+
+struct imap_client_state {
+ /* required: */
+ const char *username, *mail_log_prefix;
+ /* optional: */
+ const char *session_id, *mailbox_vname, *userdb_fields, *stats;
+ struct ip_addr local_ip, remote_ip;
+ in_port_t local_port, remote_port;
+ time_t session_created;
+
+ uid_t uid;
+ gid_t gid;
+
+ dev_t peer_dev;
+ ino_t peer_ino;
+
+ char *tag;
+ const unsigned char *state;
+ size_t state_size;
+
+ unsigned int imap_idle_notify_interval;
+ bool idle_cmd;
+ bool have_notify_fd;
+ bool anvil_sent;
+};
+
+struct imap_client *
+imap_client_create(int fd, const struct imap_client_state *state);
+void imap_client_add_notify_fd(struct imap_client *client, int fd);
+void imap_client_create_finish(struct imap_client *client);
+void imap_client_destroy(struct imap_client **_client, const char *reason);
+
+void imap_clients_init(void);
+void imap_clients_deinit(void);
+
+#endif
diff --git a/src/imap-hibernate/imap-hibernate-client.c b/src/imap-hibernate/imap-hibernate-client.c
new file mode 100644
index 0000000..4065adc
--- /dev/null
+++ b/src/imap-hibernate/imap-hibernate-client.c
@@ -0,0 +1,304 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "connection.h"
+#include "istream.h"
+#include "istream-unix.h"
+#include "ostream.h"
+#include "buffer.h"
+#include "base64.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "imap-client.h"
+#include "imap-hibernate-client.h"
+
+struct imap_hibernate_client {
+ struct connection conn;
+ struct imap_client *imap_client;
+ bool imap_client_created;
+ bool finished;
+ bool debug;
+};
+
+struct imap_hibernate_input {
+ /* input we've already read from the IMAP client. */
+ buffer_t *client_input;
+ /* IMAP connection state */
+ buffer_t *state;
+};
+
+static struct connection_list *hibernate_clients = NULL;
+
+static void imap_hibernate_client_destroy(struct connection *conn)
+{
+ struct imap_hibernate_client *client = (struct imap_hibernate_client *)conn;
+
+ if (!client->imap_client_created)
+ master_service_client_connection_destroyed(master_service);
+ else if (client->finished)
+ imap_client_create_finish(client->imap_client);
+ connection_deinit(conn);
+ i_free(conn);
+}
+
+static int
+imap_hibernate_client_parse_input(const char *const *args, pool_t pool,
+ struct imap_client_state *state_r,
+ const char **error_r)
+{
+ const char *key, *value;
+ unsigned int peer_dev_major = 0, peer_dev_minor = 0;
+
+ i_zero(state_r);
+ if (args[0] == NULL) {
+ *error_r = "Missing username in input";
+ return -1;
+ }
+ state_r->username = args[0]; args++;
+ if (args[0] == NULL) {
+ *error_r = "Missing mail_log_prefix in input";
+ return -1;
+ }
+ state_r->mail_log_prefix = args[0]; args++;
+
+ for (; *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, &state_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, &state_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, &state_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, &state_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, &state_r->peer_ino) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_ino value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "uid") == 0) {
+ if (str_to_uid(value, &state_r->uid) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid uid value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "gid") == 0) {
+ if (str_to_gid(value, &state_r->gid) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid gid value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "stats") == 0) {
+ state_r->stats = value;
+ } else if (strcmp(key, "idle-cmd") == 0) {
+ state_r->idle_cmd = TRUE;
+ } else if (strcmp(key, "session") == 0) {
+ state_r->session_id = value;
+ } else if (strcmp(key, "mailbox") == 0) {
+ state_r->mailbox_vname = value;
+ } else if (strcmp(key, "session_created") == 0) {
+ if (str_to_time(value, &state_r->session_created) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid session_created value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "userdb_fields") == 0) {
+ state_r->userdb_fields = value;
+ } else if (strcmp(key, "notify_fd") == 0) {
+ state_r->have_notify_fd = TRUE;
+ } else if (strcmp(key, "idle_notify_interval") == 0) {
+ if (str_to_uint(value, &state_r->imap_idle_notify_interval) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid idle_notify_interval value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "tag") == 0) {
+ state_r->tag = i_strdup(value);
+ } else if (strcmp(key, "state") == 0) {
+ buffer_t *state_buf;
+
+ state_buf = buffer_create_dynamic(pool, 1024);
+ if (base64_decode(value, strlen(value), NULL,
+ state_buf) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid state base64 value: %s", value);
+ return -1;
+ }
+ state_r->state = state_buf->data;
+ state_r->state_size = state_buf->used;
+ }
+ }
+ if (state_r->tag == NULL) {
+ *error_r = "Missing tag";
+ return -1;
+ }
+ if (peer_dev_major != 0 || peer_dev_minor != 0)
+ state_r->peer_dev = makedev(peer_dev_major, peer_dev_minor);
+ return 0;
+}
+
+static int
+imap_hibernate_client_input_args(struct connection *conn,
+ const char *const *args, int fd, pool_t pool)
+{
+ struct imap_hibernate_client *client =
+ (struct imap_hibernate_client *)conn;
+ struct imap_client_state state;
+ const char *error;
+
+ if (imap_hibernate_client_parse_input(args, pool, &state, &error) < 0) {
+ e_error(conn->event, "Failed to parse client input: %s", error);
+ o_stream_nsend_str(conn->output, t_strdup_printf(
+ "-Failed to parse client input: %s\n", error));
+ return -1;
+ }
+ client->imap_client = imap_client_create(fd, &state);
+ /* the transferred imap client fd is now counted as the client. */
+ client->imap_client_created = TRUE;
+ return state.have_notify_fd ? 0 : 1;
+}
+
+static int
+imap_hibernate_client_input_line(struct connection *conn, const char *line)
+{
+ struct imap_hibernate_client *client =
+ (struct imap_hibernate_client *)conn;
+ int fd = -1, ret;
+
+ if (!conn->version_received) {
+ if (connection_handshake_args_default(
+ conn, t_strsplit_tabescaped(line)) < 0)
+ return -1;
+ conn->version_received = TRUE;
+ return 1;
+ }
+ if (client->finished) {
+ e_error(conn->event, "Received unexpected line: %s", line);
+ return -1;
+ }
+
+ if (client->imap_client == NULL) {
+ char *const *args;
+ pool_t pool;
+
+ fd = i_stream_unix_get_read_fd(conn->input);
+ if (fd == -1) {
+ e_error(conn->event, "IMAP client fd not received");
+ return -1;
+ }
+
+ pool = pool_alloconly_create("client cmd", 1024);
+ args = p_strsplit_tabescaped(pool, line);
+ ret = imap_hibernate_client_input_args(conn, (const void *)args,
+ fd, pool);
+ if (ret >= 0 && client->debug)
+ e_debug(conn->event, "Create client with input: %s", line);
+ pool_unref(&pool);
+ } else {
+ fd = i_stream_unix_get_read_fd(conn->input);
+ if (fd == -1) {
+ e_error(conn->event,
+ "IMAP notify fd not received (input: %s)", line);
+ ret = -1;
+ } else if (line[0] != '\0') {
+ e_error(conn->event,
+ "Expected empty notify fd line from client, but got: %s", line);
+ o_stream_nsend_str(conn->output,
+ "Expected empty notify fd line");
+ ret = -1;
+ } else {
+ imap_client_add_notify_fd(client->imap_client, fd);
+ ret = 1;
+ }
+ }
+
+ if (ret < 0) {
+ if (client->imap_client != NULL)
+ imap_client_destroy(&client->imap_client, NULL);
+ i_close_fd(&fd);
+ return -1;
+ } else if (ret == 0) {
+ /* still need to read another fd */
+ i_stream_unix_set_read_fd(conn->input);
+ } else {
+ /* finished - wait for disconnection from imap before finishing.
+ this way the old imap process will have time to destroy
+ itself before we have a chance to create another one. */
+ client->finished = TRUE;
+ }
+ o_stream_nsend_str(conn->output, "+\n");
+ return 1;
+}
+
+void imap_hibernate_client_create(int fd, bool debug)
+{
+ struct imap_hibernate_client *client;
+
+ client = i_new(struct imap_hibernate_client, 1);
+ client->debug = debug;
+ client->conn.unix_socket = TRUE;
+ connection_init_server(hibernate_clients, &client->conn,
+ "imap-hibernate", fd, fd);
+ i_stream_unix_set_read_fd(client->conn.input);
+}
+
+static struct connection_settings client_set = {
+ .service_name_in = "imap-hibernate",
+ .service_name_out = "imap-hibernate",
+ .major_version = 1,
+ .minor_version = 0,
+
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = imap_hibernate_client_destroy,
+ .input_line = imap_hibernate_client_input_line
+};
+
+void imap_hibernate_clients_init(void)
+{
+ hibernate_clients = connection_list_init(&client_set, &client_vfuncs);
+}
+
+void imap_hibernate_clients_deinit(void)
+{
+ connection_list_deinit(&hibernate_clients);
+}
diff --git a/src/imap-hibernate/imap-hibernate-client.h b/src/imap-hibernate/imap-hibernate-client.h
new file mode 100644
index 0000000..c7bec73
--- /dev/null
+++ b/src/imap-hibernate/imap-hibernate-client.h
@@ -0,0 +1,9 @@
+#ifndef IMAP_HIBERNATE_CLIENT_H
+#define IMAP_HIBERNATE_CLIENT_H
+
+void imap_hibernate_client_create(int fd, bool debug);
+
+void imap_hibernate_clients_init(void);
+void imap_hibernate_clients_deinit(void);
+
+#endif
diff --git a/src/imap-hibernate/imap-hibernate-settings.c b/src/imap-hibernate/imap-hibernate-settings.c
new file mode 100644
index 0000000..d7618c0
--- /dev/null
+++ b/src/imap-hibernate/imap-hibernate-settings.c
@@ -0,0 +1,47 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+/* <settings checks> */
+static struct file_listener_settings imap_hibernate_unix_listeners_array[] = {
+ { "imap-hibernate", 0660, "", "$default_internal_group" }
+};
+static struct file_listener_settings *imap_hibernate_unix_listeners[] = {
+ &imap_hibernate_unix_listeners_array[0]
+};
+static buffer_t imap_hibernate_unix_listeners_buf = {
+ { { imap_hibernate_unix_listeners, sizeof(imap_hibernate_unix_listeners) } }
+};
+/* </settings checks> */
+
+struct service_settings imap_hibernate_service_settings = {
+ .name = "imap-hibernate",
+ .protocol = "imap",
+ .type = "",
+ .executable = "imap-hibernate",
+ .user = "$default_internal_user",
+ .group = "",
+ .privileged_group = "",
+ .extra_groups = "",
+ .chroot = "",
+
+ .drop_priv_before_exec = FALSE,
+
+ .process_min_avail = 0,
+ .process_limit = 0,
+ .client_limit = 0,
+ .service_count = 0,
+ .idle_kill = 0,
+ .vsz_limit = UOFF_T_MAX,
+
+ .unix_listeners = { { &imap_hibernate_unix_listeners_buf,
+ sizeof(imap_hibernate_unix_listeners[0]) } },
+ .fifo_listeners = ARRAY_INIT,
+ .inet_listeners = ARRAY_INIT
+};
diff --git a/src/imap-hibernate/imap-master-connection.c b/src/imap-hibernate/imap-master-connection.c
new file mode 100644
index 0000000..71fa4dd
--- /dev/null
+++ b/src/imap-hibernate/imap-master-connection.c
@@ -0,0 +1,140 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "connection.h"
+#include "imap-master-connection.h"
+
+#define IMAP_MASTER_CONNECTION_TIMEOUT_MSECS 30000
+
+struct imap_master_connection {
+ struct connection conn;
+ struct timeout *to;
+
+ imap_master_connection_send_callback_t *send_callback;
+ imap_master_connection_read_callback_t *read_callback;
+ void *context;
+};
+
+static struct connection_list *master_clients;
+
+static void imap_master_connection_timeout(struct imap_master_connection *conn)
+{
+ e_error(conn->conn.event,
+ "Timeout communicating with %s (version %sreceived)",
+ conn->conn.name, conn->conn.version_received ? "" : "not ");
+ imap_master_connection_deinit(&conn);
+}
+
+int imap_master_connection_init(const char *path,
+ imap_master_connection_send_callback_t *send_callback,
+ imap_master_connection_read_callback_t *read_callback,
+ void *context,
+ struct imap_master_connection **conn_r,
+ const char **error_r)
+{
+ struct imap_master_connection *conn;
+
+ conn = i_new(struct imap_master_connection, 1);
+ conn->send_callback = send_callback;
+ conn->read_callback = read_callback;
+ conn->context = context;
+ connection_init_client_unix(master_clients, &conn->conn, path);
+ if (connection_client_connect(&conn->conn) < 0) {
+ int ret = errno == EAGAIN ? 0 : -1;
+
+ *error_r = t_strdup_printf(
+ "net_connect_unix(%s) failed: %m", path);
+ connection_deinit(&conn->conn);
+ i_free(conn);
+ return ret;
+ }
+ conn->to = timeout_add(IMAP_MASTER_CONNECTION_TIMEOUT_MSECS,
+ imap_master_connection_timeout, conn);
+ *conn_r = conn;
+ return 1;
+}
+
+static void
+imap_master_read_callback(struct imap_master_connection **_conn,
+ const char *line)
+{
+ struct imap_master_connection *conn = *_conn;
+ imap_master_connection_read_callback_t *read_callback =
+ conn->read_callback;
+
+ *_conn = NULL;
+ conn->read_callback = NULL;
+ read_callback(conn->context, line);
+ /* connection is destroyed now */
+}
+
+void imap_master_connection_deinit(struct imap_master_connection **_conn)
+{
+ imap_master_read_callback(_conn, t_strdup_printf(
+ "-%s", connection_disconnect_reason(&(*_conn)->conn)));
+}
+
+void imap_master_connection_free(struct imap_master_connection **_conn)
+{
+ struct imap_master_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ timeout_remove(&conn->to);
+ connection_deinit(&conn->conn);
+ i_free(conn);
+}
+
+static void imap_master_client_destroy(struct connection *_conn)
+{
+ struct imap_master_connection *conn =
+ (struct imap_master_connection *)_conn;
+
+ imap_master_connection_deinit(&conn);
+}
+
+static int
+imap_master_client_input_line(struct connection *_conn, const char *line)
+{
+ struct imap_master_connection *conn =
+ (struct imap_master_connection *)_conn;
+
+ if (!_conn->version_received) {
+ if (connection_input_line_default(_conn, line) < 0)
+ return -1;
+
+ conn->send_callback(conn->context, _conn->output);
+ return 1;
+ } else {
+ imap_master_read_callback(&conn, line);
+ /* we're finished now with this connection - disconnect it */
+ return -1;
+ }
+}
+
+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 = TRUE
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = imap_master_client_destroy,
+ .input_line = imap_master_client_input_line
+};
+
+void imap_master_connections_init(void)
+{
+ master_clients = connection_list_init(&client_set, &client_vfuncs);
+}
+
+void imap_master_connections_deinit(void)
+{
+ connection_list_deinit(&master_clients);
+}
diff --git a/src/imap-hibernate/imap-master-connection.h b/src/imap-hibernate/imap-master-connection.h
new file mode 100644
index 0000000..0ff2f3c
--- /dev/null
+++ b/src/imap-hibernate/imap-master-connection.h
@@ -0,0 +1,24 @@
+#ifndef IMAP_MASTER_CONNECTION_H
+#define IMAP_MASTER_CONNECTION_H
+
+struct imap_master_connection;
+
+typedef void
+imap_master_connection_send_callback_t(void *context, struct ostream *output);
+typedef void
+imap_master_connection_read_callback_t(void *context, const char *reply);
+
+/* Returns 1 = success, 0 = retry later, -1 = error */
+int imap_master_connection_init(const char *path,
+ imap_master_connection_send_callback_t *send_callback,
+ imap_master_connection_read_callback_t *read_callback,
+ void *context,
+ struct imap_master_connection **conn_r,
+ const char **error_r);
+void imap_master_connection_deinit(struct imap_master_connection **conn);
+void imap_master_connection_free(struct imap_master_connection **conn);
+
+void imap_master_connections_init(void);
+void imap_master_connections_deinit(void);
+
+#endif
diff --git a/src/imap-hibernate/main.c b/src/imap-hibernate/main.c
new file mode 100644
index 0000000..dce14e0
--- /dev/null
+++ b/src/imap-hibernate/main.c
@@ -0,0 +1,57 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "restrict-access.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "imap-client.h"
+#include "imap-hibernate-client.h"
+#include "imap-master-connection.h"
+
+static bool debug = FALSE;
+
+static void client_connected(struct master_service_connection *conn)
+{
+ master_service_client_connection_accept(conn);
+ imap_hibernate_client_create(conn->fd, debug);
+}
+
+int main(int argc, char *argv[])
+{
+ enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_UPDATE_PROCTITLE;
+ const char *error;
+ int c;
+
+ master_service = master_service_init("imap-hibernate", service_flags,
+ &argc, &argv, "D");
+ while ((c = master_getopt(master_service)) > 0) {
+ switch (c) {
+ case 'D':
+ debug = TRUE;
+ break;
+ default:
+ return FATAL_DEFAULT;
+ }
+ }
+
+ if (master_service_settings_read_simple(master_service, NULL, &error) < 0)
+ i_fatal("Error reading configuration: %s", error);
+
+ master_service_init_log(master_service);
+ restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL);
+ restrict_access_allow_coredumps(TRUE);
+
+ imap_clients_init();
+ imap_master_connections_init();
+ imap_hibernate_clients_init();
+ master_service_init_finish(master_service);
+
+ master_service_run(master_service, client_connected);
+
+ imap_master_connections_deinit();
+ imap_hibernate_clients_deinit();
+ imap_clients_deinit();
+ master_service_deinit(&master_service);
+ return 0;
+}