summaryrefslogtreecommitdiffstats
path: root/src/lib-master
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-master
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-master')
-rw-r--r--src/lib-master/Makefile.am83
-rw-r--r--src/lib-master/Makefile.in967
-rw-r--r--src/lib-master/anvil-client.c275
-rw-r--r--src/lib-master/anvil-client.h37
-rw-r--r--src/lib-master/ipc-client.c220
-rw-r--r--src/lib-master/ipc-client.h24
-rw-r--r--src/lib-master/ipc-server.c202
-rw-r--r--src/lib-master/ipc-server.h20
-rw-r--r--src/lib-master/master-auth.c286
-rw-r--r--src/lib-master/master-auth.h112
-rw-r--r--src/lib-master/master-instance.c369
-rw-r--r--src/lib-master/master-instance.h42
-rw-r--r--src/lib-master/master-interface.h117
-rw-r--r--src/lib-master/master-login-auth.c642
-rw-r--r--src/lib-master/master-login-auth.h28
-rw-r--r--src/lib-master/master-login.c564
-rw-r--r--src/lib-master/master-login.h50
-rw-r--r--src/lib-master/master-service-haproxy.c689
-rw-r--r--src/lib-master/master-service-private.h109
-rw-r--r--src/lib-master/master-service-settings-cache.c410
-rw-r--r--src/lib-master/master-service-settings-cache.h16
-rw-r--r--src/lib-master/master-service-settings.c816
-rw-r--r--src/lib-master/master-service-settings.h115
-rw-r--r--src/lib-master/master-service-ssl-settings.c275
-rw-r--r--src/lib-master/master-service-ssl-settings.h65
-rw-r--r--src/lib-master/master-service-ssl.c105
-rw-r--r--src/lib-master/master-service-ssl.h16
-rw-r--r--src/lib-master/master-service.c1548
-rw-r--r--src/lib-master/master-service.h262
-rw-r--r--src/lib-master/service-settings.h82
-rw-r--r--src/lib-master/stats-client.c373
-rw-r--r--src/lib-master/stats-client.h8
-rw-r--r--src/lib-master/syslog-util.c65
-rw-r--r--src/lib-master/syslog-util.h14
-rw-r--r--src/lib-master/test-event-stats.c784
-rw-r--r--src/lib-master/test-master-service-settings-cache.c125
36 files changed, 9915 insertions, 0 deletions
diff --git a/src/lib-master/Makefile.am b/src/lib-master/Makefile.am
new file mode 100644
index 0000000..8133c05
--- /dev/null
+++ b/src/lib-master/Makefile.am
@@ -0,0 +1,83 @@
+pkgsysconfdir = $(sysconfdir)/dovecot
+
+noinst_LTLIBRARIES = libmaster.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-auth \
+ -I$(top_srcdir)/src/lib-dns \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -DPKG_RUNDIR=\""$(rundir)"\" \
+ -DPKG_STATEDIR=\""$(statedir)"\" \
+ -DSYSCONFDIR=\""$(pkgsysconfdir)"\" \
+ -DBINDIR=\""$(bindir)"\"
+
+libmaster_la_SOURCES = \
+ anvil-client.c \
+ ipc-client.c \
+ ipc-server.c \
+ master-auth.c \
+ master-instance.c \
+ master-login.c \
+ master-login-auth.c \
+ master-service.c \
+ master-service-haproxy.c \
+ master-service-settings.c \
+ master-service-settings-cache.c \
+ master-service-ssl.c \
+ master-service-ssl-settings.c \
+ stats-client.c \
+ syslog-util.c
+
+headers = \
+ anvil-client.h \
+ ipc-client.h \
+ ipc-server.h \
+ master-auth.h \
+ master-instance.h \
+ master-interface.h \
+ master-login.h \
+ master-login-auth.h \
+ master-service.h \
+ master-service-private.h \
+ master-service-settings.h \
+ master-service-settings-cache.h \
+ master-service-ssl.h \
+ master-service-ssl-settings.h \
+ service-settings.h \
+ stats-client.h \
+ syslog-util.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+ test-master-service-settings-cache \
+ test-event-stats
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+ ../lib-test/libtest.la \
+ ../lib-dns/libdns.la \
+ ../lib/liblib.la
+
+test_event_stats_libs = \
+ libmaster.la
+
+test_deps = $(noinst_LTLIBRARIES) $(test_libs)
+
+test_master_service_settings_cache_SOURCES = test-master-service-settings-cache.c
+test_master_service_settings_cache_LDADD = master-service-settings-cache.lo ../lib-settings/libsettings.la $(test_libs)
+test_master_service_settings_cache_DEPENDENCIES = $(test_deps) ../lib-settings/libsettings.la
+
+test_event_stats_SOURCES = test-event-stats.c
+test_event_stats_LDADD = $(test_event_stats_libs) $(test_libs)
+test_event_stats_DEPENDENCIES = $(test_deps)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/lib-master/Makefile.in b/src/lib-master/Makefile.in
new file mode 100644
index 0000000..e835d76
--- /dev/null
+++ b/src/lib-master/Makefile.in
@@ -0,0 +1,967 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/lib-master
+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-master-service-settings-cache$(EXEEXT) \
+ test-event-stats$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmaster_la_LIBADD =
+am_libmaster_la_OBJECTS = anvil-client.lo ipc-client.lo ipc-server.lo \
+ master-auth.lo master-instance.lo master-login.lo \
+ master-login-auth.lo master-service.lo \
+ master-service-haproxy.lo master-service-settings.lo \
+ master-service-settings-cache.lo master-service-ssl.lo \
+ master-service-ssl-settings.lo stats-client.lo syslog-util.lo
+libmaster_la_OBJECTS = $(am_libmaster_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_test_event_stats_OBJECTS = test-event-stats.$(OBJEXT)
+test_event_stats_OBJECTS = $(am_test_event_stats_OBJECTS)
+am_test_master_service_settings_cache_OBJECTS = \
+ test-master-service-settings-cache.$(OBJEXT)
+test_master_service_settings_cache_OBJECTS = \
+ $(am_test_master_service_settings_cache_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/anvil-client.Plo \
+ ./$(DEPDIR)/ipc-client.Plo ./$(DEPDIR)/ipc-server.Plo \
+ ./$(DEPDIR)/master-auth.Plo ./$(DEPDIR)/master-instance.Plo \
+ ./$(DEPDIR)/master-login-auth.Plo ./$(DEPDIR)/master-login.Plo \
+ ./$(DEPDIR)/master-service-haproxy.Plo \
+ ./$(DEPDIR)/master-service-settings-cache.Plo \
+ ./$(DEPDIR)/master-service-settings.Plo \
+ ./$(DEPDIR)/master-service-ssl-settings.Plo \
+ ./$(DEPDIR)/master-service-ssl.Plo \
+ ./$(DEPDIR)/master-service.Plo ./$(DEPDIR)/stats-client.Plo \
+ ./$(DEPDIR)/syslog-util.Plo ./$(DEPDIR)/test-event-stats.Po \
+ ./$(DEPDIR)/test-master-service-settings-cache.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 = $(libmaster_la_SOURCES) $(test_event_stats_SOURCES) \
+ $(test_master_service_settings_cache_SOURCES)
+DIST_SOURCES = $(libmaster_la_SOURCES) $(test_event_stats_SOURCES) \
+ $(test_master_service_settings_cache_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+pkgsysconfdir = $(sysconfdir)/dovecot
+noinst_LTLIBRARIES = libmaster.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-auth \
+ -I$(top_srcdir)/src/lib-dns \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -DPKG_RUNDIR=\""$(rundir)"\" \
+ -DPKG_STATEDIR=\""$(statedir)"\" \
+ -DSYSCONFDIR=\""$(pkgsysconfdir)"\" \
+ -DBINDIR=\""$(bindir)"\"
+
+libmaster_la_SOURCES = \
+ anvil-client.c \
+ ipc-client.c \
+ ipc-server.c \
+ master-auth.c \
+ master-instance.c \
+ master-login.c \
+ master-login-auth.c \
+ master-service.c \
+ master-service-haproxy.c \
+ master-service-settings.c \
+ master-service-settings-cache.c \
+ master-service-ssl.c \
+ master-service-ssl-settings.c \
+ stats-client.c \
+ syslog-util.c
+
+headers = \
+ anvil-client.h \
+ ipc-client.h \
+ ipc-server.h \
+ master-auth.h \
+ master-instance.h \
+ master-interface.h \
+ master-login.h \
+ master-login-auth.h \
+ master-service.h \
+ master-service-private.h \
+ master-service-settings.h \
+ master-service-settings-cache.h \
+ master-service-ssl.h \
+ master-service-ssl-settings.h \
+ service-settings.h \
+ stats-client.h \
+ syslog-util.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+test_programs = \
+ test-master-service-settings-cache \
+ test-event-stats
+
+test_libs = \
+ ../lib-test/libtest.la \
+ ../lib-dns/libdns.la \
+ ../lib/liblib.la
+
+test_event_stats_libs = \
+ libmaster.la
+
+test_deps = $(noinst_LTLIBRARIES) $(test_libs)
+test_master_service_settings_cache_SOURCES = test-master-service-settings-cache.c
+test_master_service_settings_cache_LDADD = master-service-settings-cache.lo ../lib-settings/libsettings.la $(test_libs)
+test_master_service_settings_cache_DEPENDENCIES = $(test_deps) ../lib-settings/libsettings.la
+test_event_stats_SOURCES = test-event-stats.c
+test_event_stats_LDADD = $(test_event_stats_libs) $(test_libs)
+test_event_stats_DEPENDENCIES = $(test_deps)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-master/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-master/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
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmaster.la: $(libmaster_la_OBJECTS) $(libmaster_la_DEPENDENCIES) $(EXTRA_libmaster_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmaster_la_OBJECTS) $(libmaster_la_LIBADD) $(LIBS)
+
+test-event-stats$(EXEEXT): $(test_event_stats_OBJECTS) $(test_event_stats_DEPENDENCIES) $(EXTRA_test_event_stats_DEPENDENCIES)
+ @rm -f test-event-stats$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_event_stats_OBJECTS) $(test_event_stats_LDADD) $(LIBS)
+
+test-master-service-settings-cache$(EXEEXT): $(test_master_service_settings_cache_OBJECTS) $(test_master_service_settings_cache_DEPENDENCIES) $(EXTRA_test_master_service_settings_cache_DEPENDENCIES)
+ @rm -f test-master-service-settings-cache$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_master_service_settings_cache_OBJECTS) $(test_master_service_settings_cache_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/anvil-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipc-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipc-server.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-auth.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-instance.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-login-auth.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-login.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-service-haproxy.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-service-settings-cache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-service-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-service-ssl-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-service-ssl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-service.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/syslog-util.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-event-stats.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-master-service-settings-cache.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/anvil-client.Plo
+ -rm -f ./$(DEPDIR)/ipc-client.Plo
+ -rm -f ./$(DEPDIR)/ipc-server.Plo
+ -rm -f ./$(DEPDIR)/master-auth.Plo
+ -rm -f ./$(DEPDIR)/master-instance.Plo
+ -rm -f ./$(DEPDIR)/master-login-auth.Plo
+ -rm -f ./$(DEPDIR)/master-login.Plo
+ -rm -f ./$(DEPDIR)/master-service-haproxy.Plo
+ -rm -f ./$(DEPDIR)/master-service-settings-cache.Plo
+ -rm -f ./$(DEPDIR)/master-service-settings.Plo
+ -rm -f ./$(DEPDIR)/master-service-ssl-settings.Plo
+ -rm -f ./$(DEPDIR)/master-service-ssl.Plo
+ -rm -f ./$(DEPDIR)/master-service.Plo
+ -rm -f ./$(DEPDIR)/stats-client.Plo
+ -rm -f ./$(DEPDIR)/syslog-util.Plo
+ -rm -f ./$(DEPDIR)/test-event-stats.Po
+ -rm -f ./$(DEPDIR)/test-master-service-settings-cache.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-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)/anvil-client.Plo
+ -rm -f ./$(DEPDIR)/ipc-client.Plo
+ -rm -f ./$(DEPDIR)/ipc-server.Plo
+ -rm -f ./$(DEPDIR)/master-auth.Plo
+ -rm -f ./$(DEPDIR)/master-instance.Plo
+ -rm -f ./$(DEPDIR)/master-login-auth.Plo
+ -rm -f ./$(DEPDIR)/master-login.Plo
+ -rm -f ./$(DEPDIR)/master-service-haproxy.Plo
+ -rm -f ./$(DEPDIR)/master-service-settings-cache.Plo
+ -rm -f ./$(DEPDIR)/master-service-settings.Plo
+ -rm -f ./$(DEPDIR)/master-service-ssl-settings.Plo
+ -rm -f ./$(DEPDIR)/master-service-ssl.Plo
+ -rm -f ./$(DEPDIR)/master-service.Plo
+ -rm -f ./$(DEPDIR)/stats-client.Plo
+ -rm -f ./$(DEPDIR)/syslog-util.Plo
+ -rm -f ./$(DEPDIR)/test-event-stats.Po
+ -rm -f ./$(DEPDIR)/test-master-service-settings-cache.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
+
+.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-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+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/lib-master/anvil-client.c b/src/lib-master/anvil-client.c
new file mode 100644
index 0000000..0cda77f
--- /dev/null
+++ b/src/lib-master/anvil-client.c
@@ -0,0 +1,275 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "array.h"
+#include "aqueue.h"
+#include "anvil-client.h"
+
+struct anvil_query {
+ anvil_callback_t *callback;
+ void *context;
+};
+
+struct anvil_client {
+ char *path;
+ int fd;
+ struct istream *input;
+ struct ostream *output;
+ struct io *io;
+ struct timeout *to_query;
+
+ struct timeout *to_reconnect;
+ time_t last_reconnect;
+
+ ARRAY(struct anvil_query *) queries_arr;
+ struct aqueue *queries;
+
+ bool (*reconnect_callback)(void);
+ enum anvil_client_flags flags;
+};
+
+#define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
+#define ANVIL_INBUF_SIZE 1024
+#define ANVIL_RECONNECT_MIN_SECS 5
+#define ANVIL_QUERY_TIMEOUT_MSECS (1000*5)
+
+static void anvil_client_disconnect(struct anvil_client *client);
+
+struct anvil_client *
+anvil_client_init(const char *path, bool (*reconnect_callback)(void),
+ enum anvil_client_flags flags)
+{
+ struct anvil_client *client;
+
+ client = i_new(struct anvil_client, 1);
+ client->path = i_strdup(path);
+ client->reconnect_callback = reconnect_callback;
+ client->flags = flags;
+ client->fd = -1;
+ i_array_init(&client->queries_arr, 32);
+ client->queries = aqueue_init(&client->queries_arr.arr);
+ return client;
+}
+
+void anvil_client_deinit(struct anvil_client **_client)
+{
+ struct anvil_client *client = *_client;
+
+ *_client = NULL;
+
+ anvil_client_disconnect(client);
+ array_free(&client->queries_arr);
+ aqueue_deinit(&client->queries);
+ i_free(client->path);
+ i_assert(client->to_reconnect == NULL);
+ i_free(client);
+}
+
+static void anvil_reconnect(struct anvil_client *client)
+{
+ anvil_client_disconnect(client);
+ if (client->reconnect_callback != NULL) {
+ if (!client->reconnect_callback()) {
+ /* no reconnection */
+ return;
+ }
+ }
+
+ if (ioloop_time - client->last_reconnect < ANVIL_RECONNECT_MIN_SECS) {
+ if (client->to_reconnect == NULL) {
+ client->to_reconnect =
+ timeout_add(ANVIL_RECONNECT_MIN_SECS*1000,
+ anvil_reconnect, client);
+ }
+ } else {
+ client->last_reconnect = ioloop_time;
+ (void)anvil_client_connect(client, FALSE);
+ }
+}
+
+static void anvil_input(struct anvil_client *client)
+{
+ struct anvil_query *const *queries;
+ struct anvil_query *query;
+ const char *line;
+ unsigned int count;
+
+ queries = array_get(&client->queries_arr, &count);
+ while ((line = i_stream_read_next_line(client->input)) != NULL) {
+ if (aqueue_count(client->queries) == 0) {
+ i_error("anvil: Unexpected input: %s", line);
+ continue;
+ }
+
+ query = queries[aqueue_idx(client->queries, 0)];
+ if (query->callback != NULL) T_BEGIN {
+ query->callback(line, query->context);
+ } T_END;
+ i_free(query);
+ aqueue_delete_tail(client->queries);
+ }
+ if (client->input->stream_errno != 0) {
+ i_error("read(%s) failed: %s", client->path,
+ i_stream_get_error(client->input));
+ anvil_reconnect(client);
+ } else if (client->input->eof) {
+ i_error("read(%s) failed: EOF", client->path);
+ anvil_reconnect(client);
+ } else if (client->to_query != NULL) {
+ if (aqueue_count(client->queries) == 0)
+ timeout_remove(&client->to_query);
+ else
+ timeout_reset(client->to_query);
+ }
+}
+
+int anvil_client_connect(struct anvil_client *client, bool retry)
+{
+ int fd;
+
+ i_assert(client->fd == -1);
+
+ fd = retry ? net_connect_unix_with_retries(client->path, 5000) :
+ net_connect_unix(client->path);
+ if (fd == -1) {
+ if (errno != ENOENT ||
+ (client->flags & ANVIL_CLIENT_FLAG_HIDE_ENOENT) == 0) {
+ i_error("net_connect_unix(%s) failed: %m",
+ client->path);
+ }
+ return -1;
+ }
+
+ timeout_remove(&client->to_reconnect);
+
+ client->fd = fd;
+ client->input = i_stream_create_fd(fd, ANVIL_INBUF_SIZE);
+ client->output = o_stream_create_fd(fd, SIZE_MAX);
+ client->io = io_add(fd, IO_READ, anvil_input, client);
+ if (o_stream_send_str(client->output, ANVIL_HANDSHAKE) < 0) {
+ i_error("write(%s) failed: %s", client->path,
+ o_stream_get_error(client->output));
+ anvil_reconnect(client);
+ return -1;
+ }
+ return 0;
+}
+
+static void anvil_client_cancel_queries(struct anvil_client *client)
+{
+ struct anvil_query *const *queries, *query;
+ unsigned int count;
+
+ queries = array_get(&client->queries_arr, &count);
+ while (aqueue_count(client->queries) > 0) {
+ query = queries[aqueue_idx(client->queries, 0)];
+ if (query->callback != NULL)
+ query->callback(NULL, query->context);
+ i_free(query);
+ aqueue_delete_tail(client->queries);
+ }
+ timeout_remove(&client->to_query);
+}
+
+static void anvil_client_disconnect(struct anvil_client *client)
+{
+ anvil_client_cancel_queries(client);
+ if (client->fd != -1) {
+ io_remove(&client->io);
+ i_stream_destroy(&client->input);
+ o_stream_destroy(&client->output);
+ net_disconnect(client->fd);
+ client->fd = -1;
+ }
+ timeout_remove(&client->to_reconnect);
+}
+
+static void anvil_client_timeout(struct anvil_client *client)
+{
+ i_assert(aqueue_count(client->queries) > 0);
+
+ i_error("%s: Anvil queries timed out after %u secs - aborting queries",
+ client->path, ANVIL_QUERY_TIMEOUT_MSECS/1000);
+ /* perhaps reconnect helps */
+ anvil_reconnect(client);
+}
+
+static int anvil_client_send(struct anvil_client *client, const char *cmd)
+{
+ struct const_iovec iov[2];
+
+ if (client->fd == -1) {
+ if (anvil_client_connect(client, FALSE) < 0)
+ return -1;
+ }
+
+ iov[0].iov_base = cmd;
+ iov[0].iov_len = strlen(cmd);
+ iov[1].iov_base = "\n";
+ iov[1].iov_len = 1;
+ if (o_stream_sendv(client->output, iov, 2) < 0) {
+ i_error("write(%s) failed: %s", client->path,
+ o_stream_get_error(client->output));
+ anvil_reconnect(client);
+ return -1;
+ }
+ return 0;
+}
+
+struct anvil_query *
+anvil_client_query(struct anvil_client *client, const char *query,
+ anvil_callback_t *callback, void *context)
+{
+ struct anvil_query *anvil_query;
+
+ anvil_query = i_new(struct anvil_query, 1);
+ anvil_query->callback = callback;
+ anvil_query->context = context;
+ aqueue_append(client->queries, &anvil_query);
+ if (anvil_client_send(client, query) < 0) {
+ /* connection failure. add a delayed failure callback.
+ the caller may not expect the callback to be called
+ immediately. */
+ timeout_remove(&client->to_query);
+ client->to_query =
+ timeout_add_short(0, anvil_client_cancel_queries, client);
+ } else if (client->to_query == NULL) {
+ client->to_query = timeout_add(ANVIL_QUERY_TIMEOUT_MSECS,
+ anvil_client_timeout, client);
+ }
+ return anvil_query;
+}
+
+void anvil_client_query_abort(struct anvil_client *client,
+ struct anvil_query **_query)
+{
+ struct anvil_query *query = *_query;
+ struct anvil_query *const *queries;
+ unsigned int i, count;
+
+ *_query = NULL;
+
+ count = aqueue_count(client->queries);
+ queries = array_front(&client->queries_arr);
+ for (i = 0; i < count; i++) {
+ if (queries[aqueue_idx(client->queries, i)] == query) {
+ query->callback = NULL;
+ return;
+ }
+ }
+ i_panic("anvil query to be aborted doesn't exist");
+}
+
+void anvil_client_cmd(struct anvil_client *client, const char *cmd)
+{
+ (void)anvil_client_send(client, cmd);
+}
+
+bool anvil_client_is_connected(struct anvil_client *client)
+{
+ return client->fd != -1;
+}
diff --git a/src/lib-master/anvil-client.h b/src/lib-master/anvil-client.h
new file mode 100644
index 0000000..3433041
--- /dev/null
+++ b/src/lib-master/anvil-client.h
@@ -0,0 +1,37 @@
+#ifndef ANVIL_CLIENT_H
+#define ANVIL_CLIENT_H
+
+enum anvil_client_flags {
+ /* if connect() fails with ENOENT, hide the error */
+ ANVIL_CLIENT_FLAG_HIDE_ENOENT = 0x01
+};
+
+/* reply=NULL if query failed */
+typedef void anvil_callback_t(const char *reply, void *context);
+
+/* If reconnect_callback is specified, it's called when connection is lost.
+ If the callback returns FALSE, reconnection isn't attempted. */
+struct anvil_client *
+anvil_client_init(const char *path, bool (*reconnect_callback)(void),
+ enum anvil_client_flags flags) ATTR_NULL(2);
+void anvil_client_deinit(struct anvil_client **client);
+
+/* Connect to anvil. If retry=TRUE, try connecting for a while */
+int anvil_client_connect(struct anvil_client *client, bool retry);
+
+/* Send a query to anvil, expect a one line reply. The returned pointer can be
+ used to abort the query later. It becomes invalid when callback is
+ called (= the callback must not call it). Returns NULL if the query couldn't
+ be sent. */
+struct anvil_query *
+anvil_client_query(struct anvil_client *client, const char *query,
+ anvil_callback_t *callback, void *context);
+void anvil_client_query_abort(struct anvil_client *client,
+ struct anvil_query **query);
+/* Send a command to anvil, don't expect any replies. */
+void anvil_client_cmd(struct anvil_client *client, const char *cmd);
+
+/* Returns TRUE if anvil is connected to. */
+bool anvil_client_is_connected(struct anvil_client *client);
+
+#endif
diff --git a/src/lib-master/ipc-client.c b/src/lib-master/ipc-client.c
new file mode 100644
index 0000000..b734c1b
--- /dev/null
+++ b/src/lib-master/ipc-client.c
@@ -0,0 +1,220 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "net.h"
+#include "llist.h"
+#include "istream.h"
+#include "ostream.h"
+#include "hostpid.h"
+#include "master-service.h"
+#include "ipc-client.h"
+
+#include <unistd.h>
+
+struct ipc_client_cmd {
+ struct ipc_client_cmd *prev, *next;
+
+ ipc_client_callback_t *callback;
+ void *context;
+};
+
+struct ipc_client {
+ char *path;
+ ipc_client_callback_t *callback;
+
+ int fd;
+ struct io *io;
+ struct timeout *to_failed;
+ struct istream *input;
+ struct ostream *output;
+ struct ipc_client_cmd *cmds_head, *cmds_tail;
+ unsigned int aborted_cmds_count;
+};
+
+static void ipc_client_disconnect(struct ipc_client *client);
+
+static void ipc_client_input_line(struct ipc_client *client, const char *line)
+{
+ struct ipc_client_cmd *cmd = client->cmds_head;
+ enum ipc_client_cmd_state state;
+ bool disconnect = FALSE;
+
+ if (client->aborted_cmds_count > 0) {
+ /* the command was already aborted */
+ cmd = NULL;
+ } else if (cmd == NULL) {
+ i_error("IPC proxy sent unexpected input: %s", line);
+ return;
+ }
+
+ switch (*line++) {
+ case ':':
+ state = IPC_CLIENT_CMD_STATE_REPLY;
+ break;
+ case '+':
+ state = IPC_CLIENT_CMD_STATE_OK;
+ break;
+ case '-':
+ state = IPC_CLIENT_CMD_STATE_ERROR;
+ break;
+ default:
+ i_error("IPC proxy sent invalid input: %s", line);
+ line = "Invalid input";
+ disconnect = TRUE;
+ state = IPC_CLIENT_CMD_STATE_ERROR;
+ break;
+ }
+
+ if (state != IPC_CLIENT_CMD_STATE_REPLY) {
+ if (cmd != NULL)
+ DLLIST2_REMOVE(&client->cmds_head,
+ &client->cmds_tail, cmd);
+ else
+ client->aborted_cmds_count--;
+ }
+ if (cmd != NULL)
+ cmd->callback(state, line, cmd->context);
+ if (state != IPC_CLIENT_CMD_STATE_REPLY)
+ i_free(cmd);
+ if (disconnect)
+ ipc_client_disconnect(client);
+}
+
+static void ipc_client_input(struct ipc_client *client)
+{
+ const char *line;
+
+ if (i_stream_read(client->input) < 0) {
+ ipc_client_disconnect(client);
+ return;
+ }
+ while ((line = i_stream_next_line(client->input)) != NULL) T_BEGIN {
+ ipc_client_input_line(client, line);
+ } T_END;
+}
+
+static int ipc_client_connect(struct ipc_client *client)
+{
+ if (client->fd != -1)
+ return 0;
+
+ client->fd = net_connect_unix(client->path);
+ if (client->fd == -1) {
+ i_error("connect(%s) failed: %m", client->path);
+ return -1;
+ }
+
+ client->io = io_add(client->fd, IO_READ, ipc_client_input, client);
+ client->input = i_stream_create_fd(client->fd, SIZE_MAX);
+ client->output = o_stream_create_fd(client->fd, SIZE_MAX);
+ o_stream_set_no_error_handling(client->output, TRUE);
+ return 0;
+}
+
+static void ipc_client_abort_commands(struct ipc_client *client,
+ const char *reason)
+{
+ struct ipc_client_cmd *cmd, *next;
+
+ cmd = client->cmds_head;
+ client->cmds_head = client->cmds_tail = NULL;
+ for (; cmd != NULL; cmd = next) {
+ cmd->callback(IPC_CLIENT_CMD_STATE_ERROR, reason, cmd->context);
+ next = cmd->next;
+ i_free(cmd);
+ }
+}
+
+static void ipc_client_disconnect(struct ipc_client *client)
+{
+ timeout_remove(&client->to_failed);
+ ipc_client_abort_commands(client, "Disconnected");
+
+ if (client->fd == -1)
+ return;
+
+ io_remove(&client->io);
+ i_stream_destroy(&client->input);
+ o_stream_destroy(&client->output);
+ if (close(client->fd) < 0)
+ i_error("close(%s) failed: %m", client->path);
+ client->fd = -1;
+}
+
+struct ipc_client *
+ipc_client_init(const char *ipc_socket_path)
+{
+ struct ipc_client *client;
+
+ client = i_new(struct ipc_client, 1);
+ client->path = i_strdup(ipc_socket_path);
+ client->fd = -1;
+ return client;
+}
+
+void ipc_client_deinit(struct ipc_client **_client)
+{
+ struct ipc_client *client = *_client;
+
+ *_client = NULL;
+
+ ipc_client_disconnect(client);
+ i_free(client->path);
+ i_free(client);
+}
+
+static void ipc_client_cmd_connect_failed(struct ipc_client *client)
+{
+ ipc_client_abort_commands(client, "ipc connect failed");
+ timeout_remove(&client->to_failed);
+}
+
+struct ipc_client_cmd *
+ipc_client_cmd(struct ipc_client *client, const char *cmd,
+ ipc_client_callback_t *callback, void *context)
+{
+ struct ipc_client_cmd *ipc_cmd;
+ struct const_iovec iov[2];
+
+ ipc_cmd = i_new(struct ipc_client_cmd, 1);
+ ipc_cmd->callback = callback;
+ ipc_cmd->context = context;
+ DLLIST2_APPEND(&client->cmds_head, &client->cmds_tail, ipc_cmd);
+
+ if (client->to_failed != NULL ||
+ ipc_client_connect(client) < 0) {
+ /* Delay calling the failure callback. Fail all commands until
+ the callback is called. */
+ if (client->to_failed == NULL) {
+ client->to_failed = timeout_add_short(0,
+ ipc_client_cmd_connect_failed, client);
+ }
+ } else {
+ iov[0].iov_base = cmd;
+ iov[0].iov_len = strlen(cmd);
+ iov[1].iov_base = "\n";
+ iov[1].iov_len = 1;
+ o_stream_nsendv(client->output, iov, N_ELEMENTS(iov));
+ }
+ return ipc_cmd;
+}
+
+void ipc_client_cmd_abort(struct ipc_client *client,
+ struct ipc_client_cmd **_cmd)
+{
+ struct ipc_client_cmd *cmd = *_cmd;
+
+ *_cmd = NULL;
+ cmd->callback = NULL;
+ /* Free the command only if it's the oldest. Free also other such
+ commands in case they were aborted earlier. */
+ while (client->cmds_head != NULL &&
+ client->cmds_head->callback == NULL) {
+ struct ipc_client_cmd *head = client->cmds_head;
+
+ client->aborted_cmds_count++;
+ DLLIST2_REMOVE(&client->cmds_head, &client->cmds_tail, head);
+ i_free(head);
+ }
+}
diff --git a/src/lib-master/ipc-client.h b/src/lib-master/ipc-client.h
new file mode 100644
index 0000000..99bce16
--- /dev/null
+++ b/src/lib-master/ipc-client.h
@@ -0,0 +1,24 @@
+#ifndef IPC_CLIENT_H
+#define IPC_CLIENT_H
+
+enum ipc_client_cmd_state {
+ IPC_CLIENT_CMD_STATE_REPLY,
+ IPC_CLIENT_CMD_STATE_OK,
+ IPC_CLIENT_CMD_STATE_ERROR
+};
+
+typedef void ipc_client_callback_t(enum ipc_client_cmd_state state,
+ const char *data, void *context);
+
+struct ipc_client *
+ipc_client_init(const char *ipc_socket_path);
+void ipc_client_deinit(struct ipc_client **client);
+
+struct ipc_client_cmd *
+ipc_client_cmd(struct ipc_client *client, const char *cmd,
+ ipc_client_callback_t *callback, void *context)
+ ATTR_NULL(4);
+void ipc_client_cmd_abort(struct ipc_client *client,
+ struct ipc_client_cmd **cmd);
+
+#endif
diff --git a/src/lib-master/ipc-server.c b/src/lib-master/ipc-server.c
new file mode 100644
index 0000000..8bd8c23
--- /dev/null
+++ b/src/lib-master/ipc-server.c
@@ -0,0 +1,202 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "hostpid.h"
+#include "master-service.h"
+#include "ipc-server.h"
+
+#include <unistd.h>
+
+#define IPC_SERVER_RECONNECT_MSECS (10*1000)
+#define IPC_SERVER_PROTOCOL_MAJOR_VERSION 1
+#define IPC_SERVER_PROTOCOL_MINOR_VERSION 0
+#define IPC_SERVER_HANDSHAKE "VERSION\tipc-server\t1\t0\nHANDSHAKE\t%s\t%s\n"
+
+struct ipc_cmd {
+ struct ipc_server *server;
+ unsigned int tag;
+};
+
+struct ipc_server {
+ char *name, *path;
+ ipc_command_callback_t *callback;
+
+ int ipc_cmd_refcount;
+
+ int fd;
+ struct io *io;
+ struct timeout *to;
+ struct istream *input;
+ struct ostream *output;
+
+ bool version_received:1;
+};
+
+static void ipc_server_disconnect(struct ipc_server *server);
+static void ipc_server_connect(struct ipc_server *server);
+
+static void ipc_server_input_line(struct ipc_server *server, char *line)
+{
+ struct ipc_cmd *cmd;
+ unsigned int tag = 0;
+ char *p;
+
+ /* tag cmd */
+ p = strchr(line, '\t');
+ if (p != NULL) {
+ *p++ = '\0';
+ if (str_to_uint(line, &tag) < 0)
+ p = NULL;
+ }
+ if (p == NULL || *p == '\0') {
+ i_error("IPC proxy sent invalid input: %s", line);
+ return;
+ }
+
+ cmd = i_new(struct ipc_cmd, 1);
+ cmd->server = server;
+ cmd->tag = tag;
+
+ server->ipc_cmd_refcount++;
+ T_BEGIN {
+ server->callback(cmd, p);
+ } T_END;
+}
+
+static void ipc_server_input(struct ipc_server *server)
+{
+ char *line;
+
+ if (i_stream_read(server->input) < 0) {
+ ipc_server_disconnect(server);
+ ipc_server_connect(server);
+ return;
+ }
+
+ if (!server->version_received) {
+ if ((line = i_stream_next_line(server->input)) == NULL)
+ return;
+
+ if (!version_string_verify(line, "ipc-proxy",
+ IPC_SERVER_PROTOCOL_MAJOR_VERSION)) {
+ i_error("IPC proxy not compatible with this server "
+ "(mixed old and new binaries?)");
+ ipc_server_disconnect(server);
+ return;
+ }
+ server->version_received = TRUE;
+ }
+
+ while ((line = i_stream_next_line(server->input)) != NULL)
+ ipc_server_input_line(server, line);
+}
+
+static void ipc_server_connect(struct ipc_server *server)
+{
+ i_assert(server->fd == -1);
+
+ timeout_remove(&server->to);
+
+ server->fd = net_connect_unix(server->path);
+ if (server->fd == -1) {
+ i_error("connect(%s) failed: %m", server->path);
+ server->to = timeout_add(IPC_SERVER_RECONNECT_MSECS,
+ ipc_server_connect, server);
+ return;
+ }
+
+ server->io = io_add(server->fd, IO_READ, ipc_server_input, server);
+ server->input = i_stream_create_fd(server->fd, SIZE_MAX);
+ server->output = o_stream_create_fd(server->fd, SIZE_MAX);
+ o_stream_set_no_error_handling(server->output, TRUE);
+ o_stream_nsend_str(server->output,
+ t_strdup_printf(IPC_SERVER_HANDSHAKE, server->name, my_pid));
+ o_stream_cork(server->output);
+}
+
+static void ipc_server_disconnect(struct ipc_server *server)
+{
+ if (server->fd == -1)
+ return;
+
+ io_remove(&server->io);
+ i_stream_destroy(&server->input);
+ o_stream_destroy(&server->output);
+ if (close(server->fd) < 0)
+ i_error("close(%s) failed: %m", server->path);
+ server->fd = -1;
+}
+
+struct ipc_server *
+ipc_server_init(const char *ipc_socket_path, const char *name,
+ ipc_command_callback_t *callback)
+{
+ struct ipc_server *server;
+
+ server = i_new(struct ipc_server, 1);
+ server->name = i_strdup(name);
+ server->path = i_strdup(ipc_socket_path);
+ server->callback = callback;
+ server->fd = -1;
+ ipc_server_connect(server);
+ return server;
+}
+
+void ipc_server_deinit(struct ipc_server **_server)
+{
+ struct ipc_server *server = *_server;
+
+ *_server = NULL;
+
+ i_assert(server->ipc_cmd_refcount == 0);
+
+ ipc_server_disconnect(server);
+ timeout_remove(&server->to);
+ i_free(server->name);
+ i_free(server->path);
+ i_free(server);
+}
+
+void ipc_cmd_send(struct ipc_cmd *cmd, const char *data)
+{
+ o_stream_nsend_str(cmd->server->output,
+ t_strdup_printf("%u\t:%s\n", cmd->tag, data));
+}
+
+static void ipc_cmd_finish(struct ipc_cmd *cmd, const char *line)
+{
+ o_stream_nsend_str(cmd->server->output,
+ t_strdup_printf("%u\t%s\n", cmd->tag, line));
+ o_stream_uncork(cmd->server->output);
+
+ i_assert(cmd->server->ipc_cmd_refcount > 0);
+ cmd->server->ipc_cmd_refcount--;
+ i_free(cmd);
+}
+
+void ipc_cmd_success(struct ipc_cmd **_cmd)
+{
+ ipc_cmd_success_reply(_cmd, "");
+}
+
+void ipc_cmd_success_reply(struct ipc_cmd **_cmd, const char *data)
+{
+ struct ipc_cmd *cmd = *_cmd;
+
+ *_cmd = NULL;
+ ipc_cmd_finish(cmd, t_strconcat("+", data, NULL));
+}
+
+void ipc_cmd_fail(struct ipc_cmd **_cmd, const char *errormsg)
+{
+ struct ipc_cmd *cmd = *_cmd;
+
+ i_assert(errormsg != NULL);
+
+ *_cmd = NULL;
+ ipc_cmd_finish(cmd, t_strconcat("-", errormsg, NULL));
+}
diff --git a/src/lib-master/ipc-server.h b/src/lib-master/ipc-server.h
new file mode 100644
index 0000000..e9e2736
--- /dev/null
+++ b/src/lib-master/ipc-server.h
@@ -0,0 +1,20 @@
+#ifndef IPC_SERVER_H
+#define IPC_SERVER_H
+
+struct ipc_cmd;
+
+/* The callback must eventually free the cmd by calling ip_cmd_success/fail().
+ line is guaranteed to be non-empty. */
+typedef void ipc_command_callback_t(struct ipc_cmd *cmd, const char *line);
+
+struct ipc_server *
+ipc_server_init(const char *ipc_socket_path, const char *name,
+ ipc_command_callback_t *callback);
+void ipc_server_deinit(struct ipc_server **server);
+
+void ipc_cmd_send(struct ipc_cmd *cmd, const char *data);
+void ipc_cmd_success(struct ipc_cmd **cmd);
+void ipc_cmd_success_reply(struct ipc_cmd **cmd, const char *data);
+void ipc_cmd_fail(struct ipc_cmd **cmd, const char *errormsg);
+
+#endif
diff --git a/src/lib-master/master-auth.c b/src/lib-master/master-auth.c
new file mode 100644
index 0000000..36781c4
--- /dev/null
+++ b/src/lib-master/master-auth.c
@@ -0,0 +1,286 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "fdpass.h"
+#include "buffer.h"
+#include "hash.h"
+#include "time-util.h"
+#include "master-service-private.h"
+#include "master-auth.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define SOCKET_CONNECT_RETRY_MSECS 500
+#define SOCKET_CONNECT_RETRY_WARNING_INTERVAL_SECS 2
+#define MASTER_AUTH_REQUEST_TIMEOUT_MSECS (MASTER_LOGIN_TIMEOUT_SECS/2*1000)
+
+struct master_auth_connection {
+ struct master_auth *auth;
+ unsigned int tag;
+
+ unsigned int client_pid, auth_id;
+ struct ip_addr remote_ip;
+ struct timeval create_time;
+
+ char *path;
+ int fd;
+ struct io *io;
+ struct timeout *to;
+
+ char buf[sizeof(struct master_auth_reply)];
+ unsigned int buf_pos;
+
+ master_auth_callback_t *callback;
+ void *context;
+};
+
+struct master_auth {
+ struct master_service *service;
+ pool_t pool;
+
+ const char *default_path;
+ time_t last_connect_warning;
+
+ unsigned int tag_counter;
+ HASH_TABLE(void *, struct master_auth_connection *) connections;
+};
+
+struct master_auth *
+master_auth_init(struct master_service *service, const char *path)
+{
+ struct master_auth *auth;
+ pool_t pool;
+
+ pool = pool_alloconly_create("master auth", 1024);
+ auth = p_new(pool, struct master_auth, 1);
+ auth->pool = pool;
+ auth->service = service;
+ auth->default_path = p_strdup(pool, path);
+ hash_table_create_direct(&auth->connections, pool, 0);
+ return auth;
+}
+
+static void
+master_auth_connection_deinit(struct master_auth_connection **_conn)
+{
+ struct master_auth_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ if (conn->tag != 0)
+ hash_table_remove(conn->auth->connections,
+ POINTER_CAST(conn->tag));
+
+ if (conn->callback != NULL)
+ conn->callback(NULL, conn->context);
+
+ timeout_remove(&conn->to);
+ io_remove(&conn->io);
+ if (conn->fd != -1) {
+ if (close(conn->fd) < 0)
+ i_fatal("close(%s) failed: %m", conn->path);
+ conn->fd = -1;
+ }
+ i_free(conn->path);
+ i_free(conn);
+}
+
+static void ATTR_FORMAT(2, 3)
+conn_error(struct master_auth_connection *conn, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ i_error("master(%s): %s (client-pid=%u, client-id=%u, rip=%s, created %u msecs ago, received %u/%zu bytes)",
+ conn->path, t_strdup_vprintf(fmt, args),
+ conn->client_pid, conn->auth_id, net_ip2addr(&conn->remote_ip),
+ timeval_diff_msecs(&ioloop_timeval, &conn->create_time),
+ conn->buf_pos, sizeof(conn->buf_pos));
+ va_end(args);
+}
+
+void master_auth_deinit(struct master_auth **_auth)
+{
+ struct master_auth *auth = *_auth;
+ struct hash_iterate_context *iter;
+ void *key;
+ struct master_auth_connection *conn;
+
+ *_auth = NULL;
+
+ iter = hash_table_iterate_init(auth->connections);
+ while (hash_table_iterate(iter, auth->connections, &key, &conn)) {
+ conn->tag = 0;
+ master_auth_connection_deinit(&conn);
+ }
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&auth->connections);
+ pool_unref(&auth->pool);
+}
+
+static void master_auth_connection_input(struct master_auth_connection *conn)
+{
+ const struct master_auth_reply *reply;
+ int ret;
+
+ ret = read(conn->fd, conn->buf + conn->buf_pos,
+ sizeof(conn->buf) - conn->buf_pos);
+ if (ret <= 0) {
+ if (ret == 0 || errno == ECONNRESET) {
+ conn_error(conn, "read() failed: Remote closed connection "
+ "(destination service { process_limit } reached?)");
+ } else {
+ if (errno == EAGAIN)
+ return;
+ conn_error(conn, "read() failed: %m");
+ }
+ master_auth_connection_deinit(&conn);
+ return;
+ }
+
+ conn->buf_pos += ret;
+ if (conn->buf_pos < sizeof(conn->buf))
+ return;
+
+ /* reply is now read */
+ reply = (const void *)conn->buf;
+ conn->buf_pos = 0;
+
+ if (conn->tag != reply->tag)
+ conn_error(conn, "Received reply with unknown tag %u", reply->tag);
+ else if (conn->callback == NULL) {
+ /* request aborted */
+ } else {
+ conn->callback(reply, conn->context);
+ conn->callback = NULL;
+ }
+ master_auth_connection_deinit(&conn);
+}
+
+static void master_auth_connection_timeout(struct master_auth_connection *conn)
+{
+ conn_error(conn, "Auth request timed out");
+ master_auth_connection_deinit(&conn);
+}
+
+void master_auth_request_full(struct master_auth *auth,
+ const struct master_auth_request_params *params,
+ master_auth_callback_t *callback, void *context,
+ unsigned int *tag_r)
+{
+ struct master_auth_connection *conn;
+ struct master_auth_request req;
+ buffer_t *buf;
+ struct stat st;
+ ssize_t ret;
+
+ i_assert(params->request.client_pid != 0);
+ i_assert(params->request.auth_pid != 0);
+
+ conn = i_new(struct master_auth_connection, 1);
+ conn->auth = auth;
+ conn->create_time = ioloop_timeval;
+ conn->callback = callback;
+ conn->context = context;
+ conn->path = params->socket_path != NULL ?
+ i_strdup(params->socket_path) : i_strdup(auth->default_path);
+
+ req = params->request;
+ req.tag = ++auth->tag_counter;
+ if (req.tag == 0)
+ req.tag = ++auth->tag_counter;
+
+ conn->client_pid = req.client_pid;
+ conn->auth_id = req.auth_id;
+ conn->remote_ip = req.remote_ip;
+
+ if (fstat(params->client_fd, &st) < 0)
+ i_fatal("fstat(auth dest fd) failed: %m");
+ req.ino = st.st_ino;
+
+ buf = t_buffer_create(sizeof(req) + req.data_size);
+ buffer_append(buf, &req, sizeof(req));
+ buffer_append(buf, params->data, req.data_size);
+
+ conn->fd = net_connect_unix(conn->path);
+ if (conn->fd == -1 && errno == EAGAIN) {
+ /* Couldn't connect to the socket immediately. This will add
+ a delay that causes hangs to the whole process, which won't
+ be obvious unless we log a warning. FIXME: The wait could
+ be asynchronous. */
+ struct timeval start_time;
+
+ io_loop_time_refresh();
+ start_time = ioloop_timeval;
+ conn->fd = net_connect_unix_with_retries(conn->path,
+ SOCKET_CONNECT_RETRY_MSECS);
+ io_loop_time_refresh();
+ if (conn->fd != -1 &&
+ ioloop_time - auth->last_connect_warning >=
+ SOCKET_CONNECT_RETRY_WARNING_INTERVAL_SECS) {
+ i_warning("net_connect_unix(%s) succeeded only after retrying - "
+ "took %lld us", conn->path,
+ timeval_diff_usecs(&ioloop_timeval, &start_time));
+ auth->last_connect_warning = ioloop_time;
+ }
+ }
+ if (conn->fd == -1) {
+ conn_error(conn, "net_connect_unix(%s) failed: %m%s",
+ conn->path, errno != EAGAIN ? "" :
+ " - http://wiki2.dovecot.org/SocketUnavailable");
+ master_auth_connection_deinit(&conn);
+ return;
+ }
+
+ ret = fd_send(conn->fd, params->client_fd, buf->data, buf->used);
+ if (ret < 0) {
+ conn_error(conn, "fd_send(fd=%d) failed: %m",
+ params->client_fd);
+ } else if ((size_t)ret != buf->used) {
+ conn_error(conn, "fd_send() sent only %d of %d bytes",
+ (int)ret, (int)buf->used);
+ ret = -1;
+ }
+ if (ret < 0) {
+ master_auth_connection_deinit(&conn);
+ return;
+ }
+
+ conn->tag = req.tag;
+ conn->to = timeout_add(MASTER_AUTH_REQUEST_TIMEOUT_MSECS,
+ master_auth_connection_timeout, conn);
+ conn->io = io_add(conn->fd, IO_READ,
+ master_auth_connection_input, conn);
+ i_assert(hash_table_lookup(auth->connections, POINTER_CAST(req.tag)) == NULL);
+ hash_table_insert(auth->connections, POINTER_CAST(req.tag), conn);
+ *tag_r = req.tag;
+}
+
+void master_auth_request(struct master_auth *auth, int fd,
+ const struct master_auth_request *request,
+ const unsigned char *data,
+ master_auth_callback_t *callback,
+ void *context, unsigned int *tag_r)
+{
+ struct master_auth_request_params params;
+
+ i_zero(&params);
+ params.client_fd = fd;
+ params.request = *request;
+ params.data = data;
+
+ master_auth_request_full(auth, &params, callback, context, tag_r);
+}
+
+void master_auth_request_abort(struct master_auth *auth, unsigned int tag)
+{
+ struct master_auth_connection *conn;
+
+ conn = hash_table_lookup(auth->connections, POINTER_CAST(tag));
+ if (conn == NULL)
+ i_panic("master_auth_request_abort(): tag %u not found", tag);
+
+ conn->callback = NULL;
+}
diff --git a/src/lib-master/master-auth.h b/src/lib-master/master-auth.h
new file mode 100644
index 0000000..8e0db74
--- /dev/null
+++ b/src/lib-master/master-auth.h
@@ -0,0 +1,112 @@
+#ifndef MASTER_AUTH_H
+#define MASTER_AUTH_H
+
+#include "net.h"
+
+struct master_service;
+
+/* Major version changes are not backwards compatible,
+ minor version numbers can be ignored. */
+#define AUTH_MASTER_PROTOCOL_MAJOR_VERSION 1
+#define AUTH_MASTER_PROTOCOL_MINOR_VERSION 1
+
+/* Authentication client process's cookie size */
+#define MASTER_AUTH_COOKIE_SIZE (128/8)
+
+/* LOGIN_MAX_INBUF_SIZE should be based on this. Keep this large enough so that
+ LOGIN_MAX_INBUF_SIZE will be 1024+2 bytes. This is because IMAP ID command's
+ values may be max. 1024 bytes plus 2 for "" quotes. (Although it could be
+ even double of that when value is full of \" quotes, but for now lets not
+ make it too easy to waste memory..) */
+#define MASTER_AUTH_MAX_DATA_SIZE (1024 + 128 + 64 + 2)
+
+#define MASTER_AUTH_ERRMSG_INTERNAL_FAILURE \
+ "Internal error occurred. Refer to server log for more information."
+
+enum mail_auth_request_flags {
+ /* Connection has TLS compression enabled */
+ MAIL_AUTH_REQUEST_FLAG_TLS_COMPRESSION = BIT(0),
+ /* Connection is secure (SSL or just trusted) */
+ MAIL_AUTH_REQUEST_FLAG_CONN_SECURED = BIT(1),
+ /* Connection is secured using SSL specifically */
+ MAIL_AUTH_REQUEST_FLAG_CONN_SSL_SECURED = BIT(2),
+ /* This login is implicit; no command reply is expected */
+ MAIL_AUTH_REQUEST_FLAG_IMPLICIT = BIT(3),
+};
+
+/* Authentication request. File descriptor may be sent along with the
+ request. */
+struct master_auth_request {
+ /* Request tag. Reply is sent back using same tag. */
+ unsigned int tag;
+
+ /* Authentication process, authentication ID and auth cookie. */
+ pid_t auth_pid;
+ unsigned int auth_id;
+ unsigned int client_pid;
+ uint8_t cookie[MASTER_AUTH_COOKIE_SIZE];
+
+ /* Properties of the connection. The file descriptor
+ itself may be a local socketpair. */
+ struct ip_addr local_ip, remote_ip;
+ in_port_t local_port, remote_port;
+
+ uint32_t flags;
+
+ /* request follows this many bytes of client input */
+ uint32_t data_size;
+ /* inode of the transferred fd. verified just to be sure that the
+ correct fd is mapped to the correct struct. */
+ ino_t ino;
+};
+
+enum master_auth_status {
+ MASTER_AUTH_STATUS_OK,
+ MASTER_AUTH_STATUS_INTERNAL_ERROR
+};
+
+struct master_auth_reply {
+ /* tag=0 are notifications from master */
+ unsigned int tag;
+ enum master_auth_status status;
+ /* PID of the post-login mail process handling this connection */
+ pid_t mail_pid;
+};
+
+struct master_auth_request_params {
+ /* Client fd to transfer to post-login process or -1 if no fd is
+ wanted to be transferred. */
+ int client_fd;
+ /* Override master_auth->default_path if non-NULL */
+ const char *socket_path;
+
+ /* Authentication request that is sent to post-login process.
+ tag is ignored. */
+ struct master_auth_request request;
+ /* Client input of size request.data_size */
+ const unsigned char *data;
+};
+
+/* reply=NULL if the auth lookup was cancelled due to some error */
+typedef void master_auth_callback_t(const struct master_auth_reply *reply,
+ void *context);
+
+struct master_auth *
+master_auth_init(struct master_service *service, const char *path);
+void master_auth_deinit(struct master_auth **auth);
+
+/* Send an authentication request. Returns tag which can be used to abort the
+ request (ie. ignore the reply from master). */
+void master_auth_request_full(struct master_auth *auth,
+ const struct master_auth_request_params *params,
+ master_auth_callback_t *callback, void *context,
+ unsigned int *tag_r);
+/* For backwards compatibility: */
+void master_auth_request(struct master_auth *auth, int fd,
+ const struct master_auth_request *request,
+ const unsigned char *data,
+ master_auth_callback_t *callback,
+ void *context, unsigned int *tag_r);
+void master_auth_request_abort(struct master_auth *auth, unsigned int tag);
+
+#endif
diff --git a/src/lib-master/master-instance.c b/src/lib-master/master-instance.c
new file mode 100644
index 0000000..86fb1c1
--- /dev/null
+++ b/src/lib-master/master-instance.c
@@ -0,0 +1,369 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "path-util.h"
+#include "array.h"
+#include "istream.h"
+#include "ostream.h"
+#include "file-dotlock.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-instance.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+struct master_instance_list {
+ pool_t pool;
+ const char *path;
+
+ ARRAY(struct master_instance) instances;
+
+ bool locked:1;
+ bool config_paths_changed:1;
+};
+
+struct master_instance_list_iter {
+ struct master_instance_list *list;
+ unsigned int idx;
+};
+
+static const struct dotlock_settings dotlock_set = {
+ .timeout = 10,
+ .stale_timeout = 60,
+ .use_io_notify = TRUE,
+};
+
+struct master_instance_list *master_instance_list_init(const char *path)
+{
+ struct master_instance_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"master instances", 256);
+ list = p_new(pool, struct master_instance_list, 1);
+ list->pool = pool;
+ list->path = p_strdup(pool, path);
+ p_array_init(&list->instances, pool, 8);
+ return list;
+}
+
+void master_instance_list_deinit(struct master_instance_list **_list)
+{
+ struct master_instance_list *list = *_list;
+
+ *_list = NULL;
+ pool_unref(&list->pool);
+}
+
+static void
+master_instance_update_config_path(struct master_instance_list *list,
+ struct master_instance *inst)
+{
+ const char *path, *config_path, *error;
+
+ /* update instance's config path if it has changed */
+ path = t_strconcat(inst->base_dir, "/"PACKAGE".conf", NULL);
+ if (t_readlink(path, &config_path, &error) < 0) {
+ /* The link may not exist, ignore the error. */
+ if (errno != ENOENT)
+ i_error("t_readlink(%s) failed: %s", path, error);
+ return;
+ }
+ if (null_strcmp(inst->config_path, config_path) != 0) {
+ inst->config_path = p_strdup(list->pool, config_path);
+ list->config_paths_changed = TRUE;
+ }
+}
+
+static int
+master_instance_list_add_line(struct master_instance_list *list,
+ const char *line)
+{
+ struct master_instance *inst;
+ const char *const *args;
+ time_t last_used;
+
+ /* <last used> <name> <base dir> [<config path>] */
+ args = t_strsplit_tabescaped(line);
+ if (str_array_length(args) < 3)
+ return -1;
+ if (str_to_time(args[0], &last_used) < 0)
+ return -1;
+
+ inst = array_append_space(&list->instances);
+ inst->last_used = last_used;
+ inst->name = p_strdup(list->pool, args[1]);
+ inst->base_dir = p_strdup(list->pool, args[2]);
+ inst->config_path = p_strdup_empty(list->pool, args[3]);
+ master_instance_update_config_path(list, inst);
+ return 0;
+}
+
+static int master_instance_list_refresh(struct master_instance_list *list)
+{
+ struct istream *input;
+ const char *line;
+ int fd, ret = 0;
+
+ array_clear(&list->instances);
+
+ fd = open(list->path, O_RDONLY);
+ if (fd == -1) {
+ if (errno == ENOENT)
+ return 0;
+
+ i_error("open(%s) failed: %m", list->path);
+ return -1;
+ }
+ input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
+ while ((line = i_stream_read_next_line(input)) != NULL) T_BEGIN {
+ if (master_instance_list_add_line(list, line) < 0)
+ i_error("Invalid line in %s: %s", list->path, line);
+ } T_END;
+ if (input->stream_errno != 0) {
+ i_error("read(%s) failed: %s", list->path,
+ i_stream_get_error(input));
+ ret = -1;
+ }
+ i_stream_destroy(&input);
+ return ret;
+}
+
+static int
+master_instance_list_write(struct master_instance_list *list,
+ int fd, const char *path)
+{
+ struct ostream *output;
+ const struct master_instance *inst;
+ string_t *str = t_str_new(128);
+ int ret = 0;
+
+ output = o_stream_create_fd(fd, 0);
+ o_stream_cork(output);
+ array_foreach(&list->instances, inst) {
+ str_truncate(str, 0);
+ str_printfa(str, "%ld\t", (long)inst->last_used);
+ str_append_tabescaped(str, inst->name);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, inst->base_dir);
+ str_append_c(str, '\t');
+ if (inst->config_path != NULL)
+ str_append_tabescaped(str, inst->config_path);
+ str_append_c(str, '\n');
+ o_stream_nsend(output, str_data(str), str_len(str));
+ }
+ if (o_stream_finish(output) < 0) {
+ i_error("write(%s) failed: %s", path, o_stream_get_error(output));
+ ret = -1;
+ }
+ o_stream_destroy(&output);
+ return ret;
+}
+
+static int master_instance_write_init(struct master_instance_list *list,
+ struct dotlock **dotlock_r)
+{
+ int fd;
+
+ i_assert(!list->locked);
+
+ *dotlock_r = NULL;
+
+ fd = file_dotlock_open_mode(&dotlock_set, list->path, 0, 0644,
+ (uid_t)-1, (gid_t)-1, dotlock_r);
+ if (fd == -1) {
+ i_error("file_dotlock_open(%s) failed: %m", list->path);
+ return -1;
+ }
+ if (master_instance_list_refresh(list) < 0) {
+ file_dotlock_delete(dotlock_r);
+ return -1;
+ }
+ list->locked = TRUE;
+ return fd;
+}
+
+static int master_instance_write_finish(struct master_instance_list *list,
+ int fd, struct dotlock **dotlock)
+{
+ const char *lock_path = file_dotlock_get_lock_path(*dotlock);
+ int ret;
+
+ i_assert(list->locked);
+
+ T_BEGIN {
+ ret = master_instance_list_write(list, fd, lock_path);
+ } T_END;
+
+ list->locked = FALSE;
+ if (ret < 0) {
+ file_dotlock_delete(dotlock);
+ return -1;
+ }
+ if (fdatasync(fd) < 0) {
+ i_error("fdatasync(%s) failed: %m", lock_path);
+ file_dotlock_delete(dotlock);
+ return -1;
+ }
+ list->config_paths_changed = FALSE;
+ return file_dotlock_replace(dotlock, 0);
+}
+
+static struct master_instance *
+master_instance_find(struct master_instance_list *list,
+ const char *base_dir)
+{
+ struct master_instance *inst;
+
+ array_foreach_modifiable(&list->instances, inst) {
+ if (strcmp(inst->base_dir, base_dir) == 0)
+ return inst;
+ }
+ return NULL;
+}
+
+int master_instance_list_update(struct master_instance_list *list,
+ const char *base_dir)
+{
+ struct master_instance *inst;
+ struct dotlock *dotlock;
+ int fd;
+
+ if ((fd = master_instance_write_init(list, &dotlock)) == -1)
+ return -1;
+
+ inst = master_instance_find(list, base_dir);
+ if (inst == NULL) {
+ inst = array_append_space(&list->instances);
+ inst->name = "";
+ inst->base_dir = p_strdup(list->pool, base_dir);
+ }
+ inst->last_used = time(NULL);
+ master_instance_update_config_path(list, inst);
+
+ return master_instance_write_finish(list, fd, &dotlock);
+}
+
+int master_instance_list_set_name(struct master_instance_list *list,
+ const char *base_dir, const char *name)
+{
+ const struct master_instance *orig_inst;
+ struct master_instance *inst;
+ struct dotlock *dotlock;
+ int fd;
+
+ i_assert(*name != '\0');
+
+ if ((fd = master_instance_write_init(list, &dotlock)) == -1)
+ return -1;
+
+ orig_inst = master_instance_list_find_by_name(list, name);
+ if (orig_inst != NULL &&
+ strcmp(orig_inst->base_dir, base_dir) != 0) {
+ /* name already used */
+ file_dotlock_delete(&dotlock);
+ list->locked = FALSE;
+ return 0;
+ }
+
+ inst = master_instance_find(list, base_dir);
+ if (inst == NULL) {
+ inst = array_append_space(&list->instances);
+ inst->base_dir = p_strdup(list->pool, base_dir);
+ }
+ inst->name = p_strdup(list->pool, name);
+ inst->last_used = time(NULL);
+
+ return master_instance_write_finish(list, fd, &dotlock) < 0 ? -1 : 1;
+}
+
+int master_instance_list_remove(struct master_instance_list *list,
+ const char *base_dir)
+{
+ struct dotlock *dotlock;
+ const struct master_instance *instances;
+ unsigned int i, count;
+ int fd;
+
+ if ((fd = master_instance_write_init(list, &dotlock)) == -1)
+ return -1;
+
+ instances = array_get(&list->instances, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(instances[i].base_dir, base_dir) == 0) {
+ array_delete(&list->instances, i, 1);
+ break;
+ }
+ }
+
+ if (i == count) {
+ file_dotlock_delete(&dotlock);
+ list->locked = FALSE;
+ return 0;
+ }
+ return master_instance_write_finish(list, fd, &dotlock) < 0 ? -1 : 1;
+}
+
+static int
+master_instance_list_refresh_and_update(struct master_instance_list *list)
+{
+ struct dotlock *dotlock;
+ int fd;
+
+ if (master_instance_list_refresh(list) < 0)
+ return -1;
+ if (list->config_paths_changed && !list->locked) {
+ /* write new config paths */
+ if ((fd = master_instance_write_init(list, &dotlock)) == -1)
+ return -1;
+ if (master_instance_write_finish(list, fd, &dotlock) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+const struct master_instance *
+master_instance_list_find_by_name(struct master_instance_list *list,
+ const char *name)
+{
+ const struct master_instance *inst;
+
+ i_assert(*name != '\0');
+
+ if (array_count(&list->instances) == 0)
+ (void)master_instance_list_refresh_and_update(list);
+
+ array_foreach(&list->instances, inst) {
+ if (strcmp(inst->name, name) == 0)
+ return inst;
+ }
+ return NULL;
+}
+
+struct master_instance_list_iter *
+master_instance_list_iterate_init(struct master_instance_list *list)
+{
+ struct master_instance_list_iter *iter;
+
+ iter = i_new(struct master_instance_list_iter, 1);
+ iter->list = list;
+ (void)master_instance_list_refresh_and_update(list);
+ return iter;
+}
+
+const struct master_instance *
+master_instance_iterate_list_next(struct master_instance_list_iter *iter)
+{
+ if (iter->idx == array_count(&iter->list->instances))
+ return NULL;
+ return array_idx(&iter->list->instances, iter->idx++);
+}
+
+void master_instance_iterate_list_deinit(struct master_instance_list_iter **_iter)
+{
+ struct master_instance_list_iter *iter = *_iter;
+
+ *_iter = NULL;
+
+ i_free(iter);
+}
diff --git a/src/lib-master/master-instance.h b/src/lib-master/master-instance.h
new file mode 100644
index 0000000..2b25978
--- /dev/null
+++ b/src/lib-master/master-instance.h
@@ -0,0 +1,42 @@
+#ifndef MASTER_INSTANCE_H
+#define MASTER_INSTANCE_H
+
+#define MASTER_INSTANCE_FNAME "instances"
+
+struct master_instance_list;
+
+struct master_instance {
+ time_t last_used;
+ const char *name;
+ const char *base_dir;
+ const char *config_path;
+};
+
+struct master_instance_list *master_instance_list_init(const char *path);
+void master_instance_list_deinit(struct master_instance_list **list);
+
+/* Add/update last_used timestamp for an instance. Returns 0 if ok,
+ -1 if I/O error. */
+int master_instance_list_update(struct master_instance_list *list,
+ const char *base_dir);
+/* Set instance's name. Returns 1 if ok, 0 if name was already used for
+ another instance (base_dir) or -1 if I/O error. */
+int master_instance_list_set_name(struct master_instance_list *list,
+ const char *base_dir, const char *name);
+/* Remove instance. Returns 1 if ok, 0 if it didn't exist or -1 if I/O error. */
+int master_instance_list_remove(struct master_instance_list *list,
+ const char *base_dir);
+
+/* Find instance by its name. */
+const struct master_instance *
+master_instance_list_find_by_name(struct master_instance_list *list,
+ const char *name);
+
+/* Iterate through existing instances. */
+struct master_instance_list_iter *
+master_instance_list_iterate_init(struct master_instance_list *list);
+const struct master_instance *
+master_instance_iterate_list_next(struct master_instance_list_iter *iter);
+void master_instance_iterate_list_deinit(struct master_instance_list_iter **iter);
+
+#endif
diff --git a/src/lib-master/master-interface.h b/src/lib-master/master-interface.h
new file mode 100644
index 0000000..e5db7ce
--- /dev/null
+++ b/src/lib-master/master-interface.h
@@ -0,0 +1,117 @@
+#ifndef MASTER_INTERFACE_H
+#define MASTER_INTERFACE_H
+
+/* We are attempting semi-compatibility with Postfix's master process here.
+ Whether this is useful or not remains to be seen. */
+
+/* Child processes should send status updates whenever they accept a new
+ connection (decrease available_count) and when they close existing
+ connection (increase available_count). */
+struct master_status {
+ pid_t pid;
+ /* uid is used to check for old/invalid status messages */
+ unsigned int uid;
+ /* number of new connections process is currently accepting */
+ unsigned int available_count;
+};
+
+/* When connecting to log service, send this handshake first */
+struct log_service_handshake {
+ /* If magic is invalid, assume the data is already what we want
+ to log */
+#define MASTER_LOG_MAGIC 0x02ff03fe
+ unsigned int log_magic;
+
+ /* Add this prefix to each logged line */
+#define MASTER_LOG_PREFIX_NAME "MASTER"
+ unsigned int prefix_len;
+ /* unsigned char prefix[]; */
+};
+
+enum master_login_state {
+ MASTER_LOGIN_STATE_NONFULL = 0,
+ MASTER_LOGIN_STATE_FULL
+};
+
+/* getenv(MASTER_IS_PARENT_ENV) != NULL if process was started by
+ Dovecot master */
+#define MASTER_IS_PARENT_ENV "DOVECOT_CHILD_PROCESS"
+
+/* getenv(MASTER_UID_ENV) provides master_status.uid value */
+#define MASTER_UID_ENV "GENERATION"
+
+/* getenv(MASTER_SERVICE_NAME) provides the service's name */
+#define MASTER_SERVICE_ENV "SERVICE_NAME"
+
+/* getenv(MASTER_CLIENT_LIMIT_ENV) provides maximum
+ master_status.available_count as specified in configuration file */
+#define MASTER_CLIENT_LIMIT_ENV "CLIENT_LIMIT"
+
+/* getenv(MASTER_PROCESS_LIMIT_ENV) specifies how many processes of this type
+ can be created before reaching the limit */
+#define MASTER_PROCESS_LIMIT_ENV "PROCESS_LIMIT"
+
+/* getenv(MASTER_PROCESS_MIN_AVAIL_ENV) specifies how many processes of this
+ type are created at startup and are kept running all the time */
+#define MASTER_PROCESS_MIN_AVAIL_ENV "PROCESS_MIN_AVAIL"
+
+/* getenv(MASTER_SERVICE_COUNT_ENV) specifies how many client connections the
+ process can finish handling before it should kill itself. */
+#define MASTER_SERVICE_COUNT_ENV "SERVICE_COUNT"
+
+/* getenv(MASTER_SERVICE_IDLE_KILL_ENV) specifies service's idle_kill timeout
+ in seconds. */
+#define MASTER_SERVICE_IDLE_KILL_ENV "IDLE_KILL"
+
+/* getenv(MASTER_CONFIG_FILE_ENV) provides path to configuration file/socket */
+#define MASTER_CONFIG_FILE_ENV "CONFIG_FILE"
+
+/* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number
+ (unset if version_ignore=yes) */
+#define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION"
+
+/* getenv(MASTER_SSL_KEY_PASSWORD_ENV) returns manually typed SSL key password,
+ if dovecot was started with -p parameter. */
+#define MASTER_SSL_KEY_PASSWORD_ENV "SSL_KEY_PASSWORD"
+
+/* getenv(DOVECOT_PRESERVE_ENVS_ENV) returns a space separated list of
+ environments that should be preserved. */
+#define DOVECOT_PRESERVE_ENVS_ENV "DOVECOT_PRESERVE_ENVS"
+
+/* getenv(DOVECOT_LOG_DEBUG_ENV) returns the global log_debug setting. This can
+ be used to initialize debug logging immediately at startup. */
+#define DOVECOT_LOG_DEBUG_ENV "LOG_DEBUG"
+
+/* getenv(DOVECOT_STATS_WRITER_SOCKET_PATH) returns path to the stats-writer
+ socket. */
+#define DOVECOT_STATS_WRITER_SOCKET_PATH "STATS_WRITER_SOCKET_PATH"
+
+/* Write pipe to anvil. */
+#define MASTER_ANVIL_FD 3
+/* Anvil reads new log fds from this fd */
+#define MASTER_ANVIL_LOG_FDPASS_FD 4
+/* Master's "all processes full" notification fd for login processes */
+#define MASTER_LOGIN_NOTIFY_FD 4
+
+/* Shared pipe to master, used to send master_status reports */
+#define MASTER_STATUS_FD 5
+/* Pipe to master, used to detect when it dies. (MASTER_STATUS_FD would have
+ been fine for this, except it's inefficient in Linux) */
+#define MASTER_DEAD_FD 6
+/* First file descriptor where process is expected to be listening.
+ The file descriptor count is given in -s parameter, defaulting to 1.
+
+ master_status.available_count reports how many accept()s we're still
+ accepting. Once no children are listening, master will do it and create
+ new child processes when needed. */
+#define MASTER_LISTEN_FD_FIRST 7
+
+/* Timeouts: base everything on how long we can wait for login clients. */
+#define MASTER_LOGIN_TIMEOUT_SECS (3*60)
+/* auth server should abort auth requests before that happens */
+#define MASTER_AUTH_SERVER_TIMEOUT_SECS (MASTER_LOGIN_TIMEOUT_SECS - 30)
+/* auth clients should abort auth lookups after server was supposed to have
+ done that */
+#define MASTER_AUTH_LOOKUP_TIMEOUT_SECS (MASTER_AUTH_SERVER_TIMEOUT_SECS + 5)
+
+#endif
diff --git a/src/lib-master/master-login-auth.c b/src/lib-master/master-login-auth.c
new file mode 100644
index 0000000..42de091
--- /dev/null
+++ b/src/lib-master/master-login-auth.c
@@ -0,0 +1,642 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "ioloop.h"
+#include "eacces-error.h"
+#include "hostpid.h"
+#include "istream.h"
+#include "ostream.h"
+#include "llist.h"
+#include "hex-binary.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "time-util.h"
+#include "connection.h"
+#include "auth-client-private.h"
+#include "master-interface.h"
+#include "master-service.h"
+#include "master-auth.h"
+#include "master-login-auth.h"
+
+
+#define AUTH_MAX_INBUF_SIZE 8192
+
+struct master_login_auth_request {
+ struct master_login_auth_request *prev, *next;
+ struct event *event;
+
+ unsigned int id;
+ struct timeval create_stamp;
+
+ pid_t auth_pid;
+ unsigned int auth_id;
+ unsigned int client_pid;
+ uint8_t cookie[MASTER_AUTH_COOKIE_SIZE];
+
+ master_login_auth_request_callback_t *callback;
+ void *context;
+
+ bool aborted:1;
+};
+
+struct master_login_auth {
+ struct connection conn;
+ struct connection_list *clist;
+ struct event *event;
+ pool_t pool;
+ int refcount;
+
+ const char *auth_socket_path;
+
+ struct timeval connect_time, handshake_time;
+
+ struct timeout *to;
+
+ unsigned int id_counter;
+ HASH_TABLE(void *, struct master_login_auth_request *) requests;
+ /* linked list of requests, ordered by create_stamp */
+ struct master_login_auth_request *request_head, *request_tail;
+
+ pid_t auth_server_pid;
+
+ unsigned int timeout_msecs;
+
+ bool connected:1;
+ bool request_auth_token:1;
+};
+
+static int
+master_login_auth_input_args(struct connection *_conn, const char *const *args);
+static int
+master_login_auth_handshake_line(struct connection *_conn, const char *line);
+static void master_login_auth_destroy(struct connection *_conn);
+
+static void master_login_auth_update_timeout(struct master_login_auth *auth);
+static void master_login_auth_check_spids(struct master_login_auth *auth);
+
+static const struct connection_vfuncs master_login_auth_vfuncs = {
+ .destroy = master_login_auth_destroy,
+ .handshake_line = master_login_auth_handshake_line,
+ .input_args = master_login_auth_input_args,
+};
+
+static const struct connection_settings master_login_auth_set = {
+ .dont_send_version = TRUE,
+ .service_name_in = "auth-master",
+ .service_name_out = "auth-master",
+ .major_version = AUTH_MASTER_PROTOCOL_MAJOR_VERSION,
+ .minor_version = AUTH_MASTER_PROTOCOL_MINOR_VERSION,
+ .unix_client_connect_msecs = 1000,
+ .input_max_size = AUTH_MAX_INBUF_SIZE,
+ .output_max_size = SIZE_MAX,
+ .client = TRUE,
+};
+
+static int
+master_login_auth_connect(struct master_login_auth *auth);
+
+struct master_login_auth *
+master_login_auth_init(const char *auth_socket_path, bool request_auth_token)
+{
+ struct master_login_auth *auth;
+ pool_t pool;
+
+ pool = pool_alloconly_create("master login auth", 1024);
+ auth = p_new(pool, struct master_login_auth, 1);
+ auth->pool = pool;
+ auth->auth_socket_path = p_strdup(pool, auth_socket_path);
+ auth->request_auth_token = request_auth_token;
+ auth->refcount = 1;
+ hash_table_create_direct(&auth->requests, pool, 0);
+ auth->id_counter = i_rand_limit(32767) * 131072U;
+
+ auth->clist = connection_list_init(&master_login_auth_set,
+ &master_login_auth_vfuncs);
+
+ auth->event = event_create(NULL);
+ event_add_category(auth->event, &event_category_auth_client);
+ event_set_append_log_prefix(auth->event, "auth-master: login: ");
+
+ auth->conn.event_parent = auth->event;
+ connection_init_client_unix(auth->clist, &auth->conn,
+ auth->auth_socket_path);
+
+ auth->timeout_msecs = 1000 * MASTER_AUTH_LOOKUP_TIMEOUT_SECS;
+ master_login_auth_connect(auth);
+ return auth;
+}
+
+static void request_failure(struct master_login_auth *auth,
+ struct master_login_auth_request *request,
+ const char *log_reason, const char *client_reason)
+{
+ string_t *str = t_str_new(128);
+
+ str_printfa(str, "auth connected %u msecs ago",
+ timeval_diff_msecs(&ioloop_timeval, &auth->connect_time));
+ if (auth->handshake_time.tv_sec != 0) {
+ str_printfa(str, ", handshake %u msecs ago",
+ timeval_diff_msecs(&ioloop_timeval, &auth->handshake_time));
+ }
+ str_printfa(str, ", request took %u msecs, client-pid=%u client-id=%u",
+ timeval_diff_msecs(&ioloop_timeval, &request->create_stamp),
+ request->client_pid, request->auth_id);
+
+ struct event_passthrough *e =
+ event_create_passthrough(request->event)->
+ set_name("auth_master_client_login_finished");
+ e->add_str("error", log_reason);
+ e_error(e->event(), "Login auth request failed: %s (%s)",
+ log_reason, str_c(str));
+
+ request->callback(NULL, client_reason, request->context);
+}
+
+static void
+request_internal_failure(struct master_login_auth *auth,
+ struct master_login_auth_request *request,
+ const char *reason)
+{
+ request_failure(auth, request, reason, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE);
+}
+
+static void request_free(struct master_login_auth_request **_request)
+{
+ struct master_login_auth_request *request = *_request;
+
+ *_request = NULL;
+
+ event_unref(&request->event);
+ i_free(request);
+}
+
+static void
+master_login_auth_fail(struct master_login_auth *auth,
+ const char *reason) ATTR_NULL(2)
+{
+ struct master_login_auth_request *request;
+
+ if (reason == NULL)
+ reason = "Disconnected from auth server, aborting";
+
+ if (auth->connected)
+ connection_disconnect(&auth->conn);
+ auth->connected = FALSE;
+
+ while (auth->request_head != NULL) {
+ request = auth->request_head;
+ DLLIST2_REMOVE(&auth->request_head,
+ &auth->request_tail, request);
+
+ request_internal_failure(auth, request, reason);
+ request_free(&request);
+ }
+ hash_table_clear(auth->requests, FALSE);
+
+ timeout_remove(&auth->to);
+ i_zero(&auth->connect_time);
+ i_zero(&auth->handshake_time);
+}
+
+void master_login_auth_disconnect(struct master_login_auth *auth)
+{
+ master_login_auth_fail(auth, NULL);
+}
+
+static void master_login_auth_unref(struct master_login_auth **_auth)
+{
+ struct master_login_auth *auth = *_auth;
+ struct connection_list *clist = auth->clist;
+
+ *_auth = NULL;
+
+ i_assert(auth->refcount > 0);
+ if (--auth->refcount > 0)
+ return;
+
+ hash_table_destroy(&auth->requests);
+ connection_deinit(&auth->conn);
+ connection_list_deinit(&clist);
+ event_unref(&auth->event);
+ pool_unref(&auth->pool);
+}
+
+void master_login_auth_deinit(struct master_login_auth **_auth)
+{
+ struct master_login_auth *auth = *_auth;
+
+ *_auth = NULL;
+
+ master_login_auth_disconnect(auth);
+ master_login_auth_unref(&auth);
+}
+
+void master_login_auth_set_timeout(struct master_login_auth *auth,
+ unsigned int msecs)
+{
+ auth->timeout_msecs = msecs;
+}
+
+static void master_login_auth_destroy(struct connection *_conn)
+{
+ struct master_login_auth *auth =
+ container_of(_conn, struct master_login_auth, conn);
+
+ switch (_conn->disconnect_reason) {
+ case CONNECTION_DISCONNECT_HANDSHAKE_FAILED:
+ master_login_auth_fail(auth,
+ "Handshake with auth service failed");
+ break;
+ case CONNECTION_DISCONNECT_BUFFER_FULL:
+ /* buffer full */
+ e_error(auth->event, "Auth server sent us too long line");
+ master_login_auth_fail(auth, NULL);
+ break;
+ default:
+ /* disconnected. stop accepting new connections, because in
+ default configuration we no longer have permissions to
+ connect back to auth-master */
+ master_service_stop_new_connections(master_service);
+ master_login_auth_fail(auth, NULL);
+ }
+}
+
+static unsigned int auth_get_next_timeout_msecs(struct master_login_auth *auth)
+{
+ struct timeval expires;
+ int diff;
+
+ expires = auth->request_head->create_stamp;
+ timeval_add_msecs(&expires, auth->timeout_msecs);
+
+ diff = timeval_diff_msecs(&expires, &ioloop_timeval);
+ return (diff <= 0 ? 0 : (unsigned int)diff);
+}
+
+static void master_login_auth_timeout(struct master_login_auth *auth)
+{
+ struct master_login_auth_request *request;
+ const char *reason;
+
+ while (auth->request_head != NULL &&
+ auth_get_next_timeout_msecs(auth) == 0) {
+ int msecs;
+
+ request = auth->request_head;
+ DLLIST2_REMOVE(&auth->request_head,
+ &auth->request_tail, request);
+ hash_table_remove(auth->requests, POINTER_CAST(request->id));
+
+ msecs = timeval_diff_msecs(&ioloop_timeval,
+ &request->create_stamp);
+ reason = t_strdup_printf(
+ "Auth server request timed out after %u.%03u secs",
+ msecs/1000, msecs%1000);
+ request_internal_failure(auth, request, reason);
+ request_free(&request);
+ }
+ timeout_remove(&auth->to);
+ master_login_auth_update_timeout(auth);
+}
+
+static void master_login_auth_update_timeout(struct master_login_auth *auth)
+{
+ i_assert(auth->to == NULL);
+
+ if (auth->request_head != NULL) {
+ auth->to = timeout_add(auth_get_next_timeout_msecs(auth),
+ master_login_auth_timeout, auth);
+ }
+}
+
+static int
+master_login_auth_handshake_line(struct connection *_conn, const char *line)
+{
+ struct master_login_auth *auth =
+ container_of(_conn, struct master_login_auth, conn);
+ const char *const *tmp;
+ unsigned int major_version, minor_version;
+
+ tmp = t_strsplit_tabescaped(line);
+ if (!auth->conn.version_received && strcmp(tmp[0], "VERSION") == 0 &&
+ tmp[1] != NULL && tmp[2] != NULL) {
+ if (str_to_uint(tmp[1], &major_version) < 0 ||
+ str_to_uint(tmp[2], &minor_version) < 0) {
+ e_error(auth->event,
+ "Auth server sent invalid version line: %s",
+ line);
+ return -1;
+ }
+
+ if (connection_verify_version(_conn, "auth-master",
+ major_version,
+ minor_version) < 0)
+ return -1;
+ return 0;
+ }
+ if (strcmp(tmp[0], "SPID") != 0 ||
+ str_to_pid(tmp[1], &auth->auth_server_pid) < 0) {
+ e_error(auth->event,
+ "Auth server did not send valid SPID: %s", line);
+ return -1;
+ }
+
+ master_login_auth_check_spids(auth);
+ return 1;
+}
+
+static void
+master_login_auth_request_remove(struct master_login_auth *auth,
+ struct master_login_auth_request *request)
+{
+ bool update_timeout;
+
+ update_timeout = request->prev == NULL;
+
+ hash_table_remove(auth->requests, POINTER_CAST(request->id));
+ DLLIST2_REMOVE(&auth->request_head, &auth->request_tail, request);
+
+ if (update_timeout) {
+ timeout_remove(&auth->to);
+ master_login_auth_update_timeout(auth);
+ }
+}
+
+static struct master_login_auth_request *
+master_login_auth_lookup_request(struct master_login_auth *auth,
+ unsigned int id)
+{
+ struct master_login_auth_request *request;
+
+ request = hash_table_lookup(auth->requests, POINTER_CAST(id));
+ if (request == NULL) {
+ e_error(auth->event,
+ "Auth server sent reply with unknown ID %u", id);
+ return NULL;
+ }
+ master_login_auth_request_remove(auth, request);
+ if (request->aborted) {
+ request->callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE,
+ request->context);
+ request_free(&request);
+ return NULL;
+ }
+ return request;
+}
+
+static void
+master_login_auth_input_user(struct master_login_auth *auth, unsigned int id,
+ const char *const *args)
+{
+ struct master_login_auth_request *request;
+
+ /* USER <id> <userid> [..] */
+ request = master_login_auth_lookup_request(auth, id);
+ if (request != NULL) {
+ struct event_passthrough *e =
+ event_create_passthrough(request->event)->
+ set_name("auth_master_client_login_finished");
+ if (args[0] != NULL && *args[0] != '\0')
+ e->add_str("user", args[0]);
+ e_debug(e->event(), "Login auth request successful");
+
+ request->callback(args, NULL, request->context);
+ request_free(&request);
+ }
+}
+
+static void
+master_login_auth_input_notfound(struct master_login_auth *auth,
+ unsigned int id,
+ const char *const *args ATTR_UNUSED)
+{
+ struct master_login_auth_request *request;
+
+ /* NOTFOUND <id> */
+ request = master_login_auth_lookup_request(auth, id);
+ if (request != NULL) {
+ const char *reason = t_strdup_printf(
+ "Authenticated user not found from userdb, "
+ "auth lookup id=%u", id);
+ request_internal_failure(auth, request, reason);
+ request_free(&request);
+ }
+}
+
+static void
+master_login_auth_input_fail(struct master_login_auth *auth, unsigned int id,
+ const char *const *args)
+{
+ struct master_login_auth_request *request;
+ const char *error = NULL;
+ unsigned int i;
+
+ /* FAIL <id> [..] [reason=<error>] [..] */
+ for (i = 0; args[i] != NULL; i++) {
+ if (str_begins(args[i], "reason="))
+ error = args[i] + 7;
+ }
+
+ request = master_login_auth_lookup_request(auth, id);
+ if (request != NULL) {
+ if (error == NULL) {
+ request_internal_failure(auth, request,
+ "Internal auth failure");
+ } else {
+ const char *log_reason = t_strdup_printf(
+ "Internal auth failure: %s", error);
+ request_failure(auth, request, log_reason, error);
+ }
+ request_free(&request);
+ }
+}
+
+static int
+master_login_auth_input_args(struct connection *_conn, const char *const *args)
+{
+ struct master_login_auth *auth =
+ container_of(_conn, struct master_login_auth, conn);
+ unsigned int id;
+
+ if (args[0] != NULL && strcmp(args[0], "CUID") == 0) {
+ e_error(auth->event, "%s is an auth client socket. "
+ "It should be a master socket.",
+ auth->auth_socket_path);
+ return -1;
+ }
+
+ if (args[0] == NULL || args[1] == NULL ||
+ str_to_uint(args[1], &id) < 0) {
+ e_error(auth->event, "BUG: Unexpected input: %s",
+ t_strarray_join(args, "\t"));
+ return -1;
+ }
+
+ auth->refcount++;
+ if (strcmp(args[0], "USER") == 0)
+ master_login_auth_input_user(auth, id, &args[2]);
+ else if (strcmp(args[0], "NOTFOUND") == 0)
+ master_login_auth_input_notfound(auth, id, &args[2]);
+ else if (strcmp(args[0], "FAIL") == 0)
+ master_login_auth_input_fail(auth, id, &args[2]);
+ master_login_auth_unref(&auth);
+
+ return 1;
+}
+
+static int
+master_login_auth_connect(struct master_login_auth *auth)
+{
+ i_assert(!auth->connected);
+
+ if (connection_client_connect(&auth->conn) < 0) {
+ if (errno == EACCES) {
+ e_error(auth->event, "%s",
+ eacces_error_get("connect",
+ auth->auth_socket_path));
+ } else {
+ e_error(auth->event, "connect(%s) failed: %m",
+ auth->auth_socket_path);
+ }
+ return -1;
+ }
+ io_loop_time_refresh();
+ auth->connect_time = ioloop_timeval;
+ auth->connected = TRUE;
+
+ o_stream_nsend_str(auth->conn.output,
+ t_strdup_printf("VERSION\t%u\t%u\n",
+ AUTH_MASTER_PROTOCOL_MAJOR_VERSION,
+ AUTH_MASTER_PROTOCOL_MINOR_VERSION));
+ return 0;
+}
+
+static bool
+auth_request_check_spid(struct master_login_auth *auth,
+ struct master_login_auth_request *req)
+{
+ if (auth->auth_server_pid != req->auth_pid &&
+ auth->conn.handshake_received) {
+ /* auth server was restarted. don't even attempt a login. */
+ e_warning(auth->event,
+ "Auth server restarted (pid %u -> %u), aborting auth",
+ (unsigned int)req->auth_pid,
+ (unsigned int)auth->auth_server_pid);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void master_login_auth_check_spids(struct master_login_auth *auth)
+{
+ struct master_login_auth_request *req, *next;
+
+ for (req = auth->request_head; req != NULL; req = next) {
+ next = req->next;
+ if (!auth_request_check_spid(auth, req))
+ req->aborted = TRUE;
+ }
+}
+
+static void
+master_login_auth_send_request(struct master_login_auth *auth,
+ struct master_login_auth_request *req)
+{
+ string_t *str;
+
+ if (!auth_request_check_spid(auth, req)) {
+ master_login_auth_request_remove(auth, req);
+ req->callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE,
+ req->context);
+ request_free(&req);
+ return;
+ }
+
+ str = t_str_new(128);
+ str_printfa(str, "REQUEST\t%u\t%u\t%u\t", req->id,
+ req->client_pid, req->auth_id);
+ binary_to_hex_append(str, req->cookie, sizeof(req->cookie));
+ str_printfa(str, "\tsession_pid=%s", my_pid);
+ if (auth->request_auth_token)
+ str_append(str, "\trequest_auth_token");
+ str_append_c(str, '\n');
+ o_stream_nsend(auth->conn.output, str_data(str), str_len(str));
+}
+
+void master_login_auth_request(struct master_login_auth *auth,
+ const struct master_auth_request *req,
+ master_login_auth_request_callback_t *callback,
+ void *context)
+{
+ struct master_login_auth_request *login_req;
+ unsigned int id;
+
+ if (!auth->connected) {
+ if (master_login_auth_connect(auth) < 0) {
+ /* we couldn't connect to auth now,
+ so we probably can't in future either. */
+ master_service_stop_new_connections(master_service);
+ callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE,
+ context);
+ return;
+ }
+ }
+
+ id = ++auth->id_counter;
+ if (id == 0)
+ id++;
+
+ io_loop_time_refresh();
+ login_req = i_new(struct master_login_auth_request, 1);
+ login_req->create_stamp = ioloop_timeval;
+ login_req->id = id;
+ login_req->auth_pid = req->auth_pid;
+ login_req->client_pid = req->client_pid;
+ login_req->auth_id = req->auth_id;
+ memcpy(login_req->cookie, req->cookie, sizeof(login_req->cookie));
+ login_req->callback = callback;
+ login_req->context = context;
+ i_assert(hash_table_lookup(auth->requests, POINTER_CAST(id)) == NULL);
+ hash_table_insert(auth->requests, POINTER_CAST(id), login_req);
+ DLLIST2_APPEND(&auth->request_head, &auth->request_tail, login_req);
+
+ login_req->event = event_create(auth->event);
+ event_add_int(login_req->event, "id", login_req->id);
+ event_set_append_log_prefix(login_req->event,
+ t_strdup_printf("request [%u]: ",
+ login_req->id));
+
+ if (req->local_ip.family != 0) {
+ event_add_str(login_req->event, "local_ip",
+ net_ip2addr(&req->local_ip));
+ }
+ if (req->local_port != 0) {
+ event_add_int(login_req->event, "local_port",
+ req->local_port);
+ }
+ if (req->remote_ip.family != 0) {
+ event_add_str(login_req->event, "remote_ip",
+ net_ip2addr(&req->remote_ip));
+ }
+ if (req->remote_port != 0) {
+ event_add_int(login_req->event, "remote_port",
+ req->remote_port);
+ }
+
+ struct event_passthrough *e =
+ event_create_passthrough(login_req->event)->
+ set_name("auth_master_client_login_started");
+ e_debug(e->event(), "Started login auth request");
+
+ if (auth->to == NULL)
+ master_login_auth_update_timeout(auth);
+
+ master_login_auth_send_request(auth, login_req);
+}
+
+unsigned int master_login_auth_request_count(struct master_login_auth *auth)
+{
+ return hash_table_count(auth->requests);
+}
diff --git a/src/lib-master/master-login-auth.h b/src/lib-master/master-login-auth.h
new file mode 100644
index 0000000..d08732b
--- /dev/null
+++ b/src/lib-master/master-login-auth.h
@@ -0,0 +1,28 @@
+#ifndef MASTER_LOGIN_AUTH_H
+#define MASTER_LOGIN_AUTH_H
+
+struct master_auth_request;
+
+typedef void
+master_login_auth_request_callback_t(const char *const *auth_args,
+ const char *errormsg, void *context);
+
+struct master_login_auth *
+master_login_auth_init(const char *auth_socket_path, bool request_auth_token);
+void master_login_auth_deinit(struct master_login_auth **auth);
+void master_login_auth_disconnect(struct master_login_auth *auth);
+
+/* Set timeout for requests. */
+void master_login_auth_set_timeout(struct master_login_auth *auth,
+ unsigned int msecs);
+
+/* req has been sent by login process. this function finishes authentication
+ by performing verifying from auth that req is valid and doing the userdb
+ lookup. */
+void master_login_auth_request(struct master_login_auth *auth,
+ const struct master_auth_request *req,
+ master_login_auth_request_callback_t *callback,
+ void *context);
+unsigned int master_login_auth_request_count(struct master_login_auth *auth);
+
+#endif
diff --git a/src/lib-master/master-login.c b/src/lib-master/master-login.c
new file mode 100644
index 0000000..e25ce35
--- /dev/null
+++ b/src/lib-master/master-login.c
@@ -0,0 +1,564 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "ostream.h"
+#include "fdpass.h"
+#include "llist.h"
+#include "str.h"
+#include "strescape.h"
+#include "time-util.h"
+#include "master-service-private.h"
+#include "master-login.h"
+#include "master-login-auth.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define master_login_conn_is_closed(conn) \
+ ((conn)->fd == -1)
+#define master_login_conn_has_clients(conn) \
+ ((conn)->refcount > 1)
+
+struct master_login_connection {
+ struct master_login_connection *prev, *next;
+
+ struct master_login *login;
+ struct master_login_client *clients;
+ struct timeval create_time;
+ int refcount;
+ int fd;
+ struct io *io;
+ struct ostream *output;
+
+ bool login_success:1;
+};
+
+struct master_login_postlogin {
+ struct master_login_client *client;
+
+ int fd;
+ struct timeval create_time;
+ struct io *io;
+ struct timeout *to;
+ string_t *input;
+ char *username;
+ char *socket_path;
+};
+
+struct master_login {
+ struct master_service *service;
+ master_login_callback_t *callback;
+ master_login_failure_callback_t *failure_callback;
+ struct master_login_connection *conns;
+ struct master_login_auth *auth;
+ char *postlogin_socket_path;
+ unsigned int postlogin_timeout_secs;
+
+ bool stopping:1;
+};
+
+static void master_login_conn_close(struct master_login_connection *conn);
+static void master_login_conn_unref(struct master_login_connection **_conn);
+
+struct master_login *
+master_login_init(struct master_service *service,
+ const struct master_login_settings *set)
+{
+ struct master_login *login;
+
+ i_assert(set->postlogin_socket_path == NULL ||
+ set->postlogin_timeout_secs > 0);
+
+ login = i_new(struct master_login, 1);
+ login->service = service;
+ login->callback = set->callback;
+ login->failure_callback = set->failure_callback;
+ login->auth = master_login_auth_init(set->auth_socket_path,
+ set->request_auth_token);
+ login->postlogin_socket_path = i_strdup(set->postlogin_socket_path);
+ login->postlogin_timeout_secs = set->postlogin_timeout_secs;
+
+ i_assert(service->login == NULL);
+ service->login = login;
+ return login;
+}
+
+void master_login_deinit(struct master_login **_login)
+{
+ struct master_login *login = *_login;
+
+ *_login = NULL;
+
+ i_assert(login->service->login == login);
+ login->service->login = NULL;
+
+ master_login_auth_deinit(&login->auth);
+ while (login->conns != NULL) {
+ struct master_login_connection *conn = login->conns;
+
+ master_login_conn_close(conn);
+ master_login_conn_unref(&conn);
+ }
+ i_free(login->postlogin_socket_path);
+ i_free(login);
+}
+
+static void ATTR_FORMAT(2, 3)
+conn_error(struct master_login_connection *conn, const char *fmt, ...)
+{
+ string_t *str = t_str_new(128);
+ va_list args;
+
+ va_start(args, fmt);
+ str_printfa(str, "connection created %d msecs ago",
+ timeval_diff_msecs(&ioloop_timeval, &conn->create_time));
+ if (conn->clients != NULL) {
+ struct master_login_client *client = conn->clients;
+
+ str_append(str, ", ");
+ if (client->next != NULL)
+ str_printfa(str, "%u clients, first ", conn->refcount-1);
+ str_printfa(str, "client created %d msecs ago: ",
+ timeval_diff_msecs(&ioloop_timeval,
+ &client->create_time));
+ str_printfa(str, "session=%s, rip=%s, auth_pid=%ld, "
+ "client-pid=%u, client-id=%u",
+ client->session_id,
+ net_ip2addr(&client->auth_req.remote_ip),
+ (long)client->auth_req.auth_pid,
+ client->auth_req.client_pid,
+ client->auth_req.auth_id);
+ if (client->postlogin_client != NULL) {
+ struct master_login_postlogin *pl =
+ client->postlogin_client;
+ str_printfa(str, ", post-login script %s started %d msecs ago",
+ pl->socket_path,
+ timeval_diff_msecs(&ioloop_timeval,
+ &pl->create_time));
+ }
+ }
+ i_error("%s (%s)", t_strdup_vprintf(fmt, args), str_c(str));
+ va_end(args);
+}
+
+static int
+master_login_conn_read_request(struct master_login_connection *conn,
+ struct master_auth_request *req_r,
+ unsigned char data[MASTER_AUTH_MAX_DATA_SIZE],
+ int *client_fd_r)
+{
+ struct stat st;
+ ssize_t ret;
+
+ *client_fd_r = -1;
+
+ ret = fd_read(conn->fd, req_r, sizeof(*req_r), client_fd_r);
+ if (ret != sizeof(*req_r)) {
+ if (ret == 0) {
+ /* disconnected */
+ if (master_login_conn_has_clients(conn))
+ conn_error(conn, "Login client disconnected too early");
+ } else if (ret > 0) {
+ /* request wasn't fully read */
+ conn_error(conn, "fd_read() partial input (%d/%d)",
+ (int)ret, (int)sizeof(*req_r));
+ } else {
+ if (errno == EAGAIN)
+ return 0;
+
+ conn_error(conn, "fd_read() failed: %m");
+ }
+ return -1;
+ }
+
+ if (req_r->data_size != 0) {
+ if (req_r->data_size > MASTER_AUTH_MAX_DATA_SIZE) {
+ conn_error(conn, "Too large auth data_size sent");
+ return -1;
+ }
+ /* @UNSAFE */
+ ret = read(conn->fd, data, req_r->data_size);
+ if (ret != (ssize_t)req_r->data_size) {
+ if (ret == 0) {
+ /* disconnected */
+ if (master_login_conn_has_clients(conn)) {
+ conn_error(conn, "Login client disconnected too early "
+ "(while reading data)");
+ }
+ } else if (ret > 0) {
+ /* request wasn't fully read */
+ conn_error(conn, "Data read partially %d/%u",
+ (int)ret, req_r->data_size);
+ } else {
+ conn_error(conn, "read(data) failed: %m");
+ }
+ return -1;
+ }
+ }
+
+ if (*client_fd_r == -1) {
+ conn_error(conn, "Auth request missing a file descriptor");
+ return -1;
+ }
+
+ if (fstat(*client_fd_r, &st) < 0) {
+ conn_error(conn, "fstat(fd_read client) failed: %m");
+ return -1;
+ }
+ if (st.st_ino != req_r->ino) {
+ conn_error(conn, "Auth request inode mismatch: %s != %s",
+ dec2str(st.st_ino), dec2str(req_r->ino));
+ return -1;
+ }
+ return 1;
+}
+
+static void master_login_client_free(struct master_login_client **_client)
+{
+ struct master_login_client *client = *_client;
+
+ *_client = NULL;
+ if (client->fd != -1) {
+ if (close(client->fd) < 0)
+ i_error("close(fd_read client) failed: %m");
+ /* this client failed (login callback wasn't called).
+ reset prefix to default. */
+ i_set_failure_prefix("%s: ", client->conn->login->service->name);
+ }
+
+ /* FIXME: currently we create a separate connection for each request,
+ so close the connection after we're done with this client */
+ if (!master_login_conn_is_closed(client->conn)) {
+ i_assert(client->conn->refcount > 1);
+ client->conn->refcount--;
+ }
+ DLLIST_REMOVE(&client->conn->clients, client);
+ master_login_conn_unref(&client->conn);
+ i_free(client->session_id);
+ i_free(client);
+}
+
+static void master_login_auth_finish(struct master_login_client *client,
+ const char *const *auth_args)
+{
+ struct master_login *login = client->conn->login;
+ struct master_service *service = login->service;
+ bool close_sockets;
+
+ close_sockets = service->master_status.available_count == 0 &&
+ service->service_count_left == 1;
+
+ client->conn->login_success = TRUE;
+ login->callback(client, auth_args[0], auth_args+1);
+
+ if (close_sockets) {
+ /* we're dying as soon as this connection closes. */
+ i_assert(master_login_auth_request_count(login->auth) == 0);
+ master_login_auth_disconnect(login->auth);
+
+ master_service_close_config_fd(service);
+ } else if (login->stopping) {
+ /* try stopping again */
+ master_login_stop(login);
+ }
+
+ client->fd = -1;
+ master_login_client_free(&client);
+}
+
+static void master_login_postlogin_free(struct master_login_postlogin *pl)
+{
+ if (pl->client != NULL) {
+ i_assert(pl->client->postlogin_client == pl);
+ master_login_client_free(&pl->client);
+ }
+ timeout_remove(&pl->to);
+ io_remove(&pl->io);
+ if (close(pl->fd) < 0)
+ i_error("close(postlogin) failed: %m");
+ str_free(&pl->input);
+ i_free(pl->socket_path);
+ i_free(pl->username);
+ i_free(pl);
+}
+
+static void master_login_postlogin_input(struct master_login_postlogin *pl)
+{
+ struct master_login_connection *conn = pl->client->conn;
+ char buf[1024];
+ const char *const *auth_args;
+ size_t len;
+ ssize_t ret;
+ int fd = -1;
+
+ while ((ret = fd_read(pl->fd, buf, sizeof(buf), &fd)) > 0) {
+ if (fd != -1) {
+ /* post-login script replaced fd */
+ if (close(pl->client->fd) < 0)
+ conn_error(conn, "close(client) failed: %m");
+ pl->client->fd = fd;
+ }
+ str_append_data(pl->input, buf, ret);
+ }
+
+ len = str_len(pl->input);
+ if (len > 0 && str_c(pl->input)[len-1] == '\n') {
+ /* finished reading the input */
+ str_truncate(pl->input, len-1);
+ } else {
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ return;
+
+ conn_error(conn, "fd_read(%s) failed: %m", pl->socket_path);
+ } else if (str_len(pl->input) > 0) {
+ conn_error(conn, "fd_read(%s) failed: disconnected",
+ pl->socket_path);
+ } else {
+ conn_error(conn, "Post-login script denied access to user %s",
+ pl->username);
+ }
+ master_login_postlogin_free(pl);
+ return;
+ }
+
+ auth_args = t_strsplit_tabescaped(str_c(pl->input));
+ pl->client->postlogin_client = NULL;
+ master_login_auth_finish(pl->client, auth_args);
+
+ pl->client = NULL;
+ master_login_postlogin_free(pl);
+}
+
+static void master_login_postlogin_timeout(struct master_login_postlogin *pl)
+{
+ conn_error(pl->client->conn,
+ "Timeout waiting for post-login script to finish, aborting");
+
+ master_login_postlogin_free(pl);
+}
+
+static int master_login_postlogin(struct master_login_client *client,
+ const char *const *auth_args,
+ const char *socket_path)
+{
+ struct master_login *login = client->conn->login;
+ struct master_login_postlogin *pl;
+ string_t *str;
+ unsigned int i;
+ int fd;
+ ssize_t ret;
+
+ fd = net_connect_unix_with_retries(socket_path, 1000);
+ if (fd == -1) {
+ conn_error(client->conn, "net_connect_unix(%s) failed: %m%s",
+ socket_path, errno != EAGAIN ? "" :
+ " - http://wiki2.dovecot.org/SocketUnavailable");
+ return -1;
+ }
+
+ str = t_str_new(256);
+ str_printfa(str, "VERSION\tscript-login\t1\t0\n"
+ "%s\t%s", net_ip2addr(&client->auth_req.local_ip),
+ net_ip2addr(&client->auth_req.remote_ip));
+ for (i = 0; auth_args[i] != NULL; i++) {
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, auth_args[i]);
+ }
+ str_append_c(str, '\n');
+ ret = fd_send(fd, client->fd, str_data(str), str_len(str));
+ if (ret != (ssize_t)str_len(str)) {
+ if (ret < 0) {
+ conn_error(client->conn, "write(%s) failed: %m", socket_path);
+ } else {
+ conn_error(client->conn, "write(%s) failed: partial write", socket_path);
+ }
+ i_close_fd(&fd);
+ return -1;
+ }
+ net_set_nonblock(fd, TRUE);
+ io_loop_time_refresh();
+
+ pl = i_new(struct master_login_postlogin, 1);
+ pl->client = client;
+ pl->username = i_strdup(auth_args[0]);
+ pl->socket_path = i_strdup(socket_path);
+ pl->create_time = ioloop_timeval;
+ pl->fd = fd;
+ pl->io = io_add(fd, IO_READ, master_login_postlogin_input, pl);
+ pl->to = timeout_add(login->postlogin_timeout_secs * 1000,
+ master_login_postlogin_timeout, pl);
+ pl->input = str_new(default_pool, 512);
+
+ i_assert(client->postlogin_client == NULL);
+ client->postlogin_client = pl;
+ return 0;
+}
+
+static const char *
+auth_args_find_postlogin_socket(const char *const *auth_args)
+{
+ for (unsigned int i = 0; auth_args[i] != NULL; i++) {
+ if (str_begins(auth_args[i], "postlogin="))
+ return auth_args[i]+10;
+ }
+ return NULL;
+}
+
+static void
+master_login_auth_callback(const char *const *auth_args, const char *errormsg,
+ void *context)
+{
+ struct master_login_client *client = context;
+ struct master_login_connection *conn = client->conn;
+ struct master_auth_reply reply;
+ const char *postlogin_socket_path;
+
+ i_assert(errormsg != NULL || auth_args != NULL);
+
+ i_zero(&reply);
+ reply.tag = client->auth_req.tag;
+ reply.status = errormsg == NULL ? MASTER_AUTH_STATUS_OK :
+ MASTER_AUTH_STATUS_INTERNAL_ERROR;
+ reply.mail_pid = getpid();
+ o_stream_nsend(conn->output, &reply, sizeof(reply));
+
+ if (errormsg != NULL || auth_args[0] == NULL) {
+ if (auth_args != NULL) {
+ i_error("login client: Username missing from auth reply");
+ errormsg = MASTER_AUTH_ERRMSG_INTERNAL_FAILURE;
+ }
+ conn->login->failure_callback(client, errormsg);
+ master_login_client_free(&client);
+ return;
+ }
+ i_set_failure_prefix("%s(%s): ", client->conn->login->service->name,
+ auth_args[0]);
+
+ postlogin_socket_path = auth_args_find_postlogin_socket(auth_args);
+ if (postlogin_socket_path == NULL)
+ postlogin_socket_path = conn->login->postlogin_socket_path;
+
+ if (postlogin_socket_path == NULL)
+ master_login_auth_finish(client, auth_args);
+ else {
+ /* we've sent the reply. the connection is no longer needed,
+ so disconnect it (before login process disconnects us and
+ logs an error) */
+ if (!master_login_conn_is_closed(conn)) {
+ master_login_conn_close(conn);
+ master_login_conn_unref(&conn);
+ }
+
+ /* execute post-login scripts before finishing auth */
+ if (master_login_postlogin(client, auth_args,
+ postlogin_socket_path) < 0)
+ master_login_client_free(&client);
+ }
+}
+
+static void master_login_conn_input(struct master_login_connection *conn)
+{
+ struct master_auth_request req;
+ struct master_login_client *client;
+ struct master_login *login = conn->login;
+ unsigned char data[MASTER_AUTH_MAX_DATA_SIZE];
+ size_t i, session_len = 0;
+ int ret, client_fd;
+
+ ret = master_login_conn_read_request(conn, &req, data, &client_fd);
+ if (ret <= 0) {
+ if (ret < 0) {
+ master_login_conn_close(conn);
+ master_login_conn_unref(&conn);
+ }
+ i_close_fd(&client_fd);
+ return;
+ }
+ fd_close_on_exec(client_fd, TRUE);
+
+ /* extract the session ID from the request data */
+ for (i = 0; i < req.data_size; i++) {
+ if (data[i] == '\0') {
+ session_len = i++;
+ break;
+ }
+ }
+ io_loop_time_refresh();
+
+ /* @UNSAFE: we have a request. do userdb lookup for it. */
+ req.data_size -= i;
+ client = i_malloc(MALLOC_ADD(sizeof(struct master_login_client), req.data_size));
+ client->create_time = ioloop_timeval;
+ client->conn = conn;
+ client->fd = client_fd;
+ client->auth_req = req;
+ client->session_id = i_strndup(data, session_len);
+ memcpy(client->data, data+i, req.data_size);
+ conn->refcount++;
+ DLLIST_PREPEND(&conn->clients, client);
+
+ master_login_auth_request(login->auth, &req,
+ master_login_auth_callback, client);
+}
+
+void master_login_add(struct master_login *login, int fd)
+{
+ struct master_login_connection *conn;
+
+ conn = i_new(struct master_login_connection, 1);
+ conn->refcount = 1;
+ conn->login = login;
+ conn->create_time = ioloop_timeval;
+ conn->fd = fd;
+ conn->io = io_add(conn->fd, IO_READ, master_login_conn_input, conn);
+ conn->output = o_stream_create_fd(fd, SIZE_MAX);
+ o_stream_set_no_error_handling(conn->output, TRUE);
+
+ DLLIST_PREPEND(&login->conns, conn);
+
+ /* NOTE: currently there's a separate connection for each request. */
+}
+
+static void master_login_conn_close(struct master_login_connection *conn)
+{
+ if (master_login_conn_is_closed(conn))
+ return;
+
+ DLLIST_REMOVE(&conn->login->conns, conn);
+
+ io_remove(&conn->io);
+ o_stream_close(conn->output);
+ if (close(conn->fd) < 0)
+ i_error("close(master login) failed: %m");
+ conn->fd = -1;
+}
+
+static void master_login_conn_unref(struct master_login_connection **_conn)
+{
+ struct master_login_connection *conn = *_conn;
+
+ i_assert(conn->refcount > 0);
+
+ if (--conn->refcount > 0)
+ return;
+
+ *_conn = NULL;
+ i_assert(conn->clients == NULL);
+ master_login_conn_close(conn);
+ o_stream_unref(&conn->output);
+
+ if (!conn->login_success)
+ master_service_client_connection_destroyed(conn->login->service);
+ i_free(conn);
+}
+
+void master_login_stop(struct master_login *login)
+{
+ login->stopping = TRUE;
+ if (master_login_auth_request_count(login->auth) == 0) {
+ master_login_auth_disconnect(login->auth);
+ master_service_close_config_fd(login->service);
+ }
+}
diff --git a/src/lib-master/master-login.h b/src/lib-master/master-login.h
new file mode 100644
index 0000000..7029aa9
--- /dev/null
+++ b/src/lib-master/master-login.h
@@ -0,0 +1,50 @@
+#ifndef MASTER_LOGIN_H
+#define MASTER_LOGIN_H
+
+#include "master-auth.h"
+
+#define MASTER_POSTLOGIN_TIMEOUT_DEFAULT 60
+
+struct master_login_client {
+ /* parent connection */
+ struct master_login_connection *conn;
+ /* linked list of all clients within the connection */
+ struct master_login_client *prev, *next;
+ /* non-NULL while running postlogin script */
+ struct master_login_postlogin *postlogin_client;
+
+ int fd;
+ struct timeval create_time;
+
+ struct master_auth_request auth_req;
+ char *session_id;
+ unsigned char data[FLEXIBLE_ARRAY_MEMBER];
+};
+
+typedef void
+master_login_callback_t(const struct master_login_client *client,
+ const char *username, const char *const *extra_fields);
+typedef void
+master_login_failure_callback_t(const struct master_login_client *client,
+ const char *errormsg);
+
+struct master_login_settings {
+ const char *auth_socket_path;
+ const char *postlogin_socket_path;
+ unsigned int postlogin_timeout_secs;
+
+ master_login_callback_t *callback;
+ master_login_failure_callback_t *failure_callback;
+
+ bool request_auth_token:1;
+};
+
+struct master_login *
+master_login_init(struct master_service *service,
+ const struct master_login_settings *set);
+void master_login_deinit(struct master_login **login);
+
+void master_login_add(struct master_login *login, int fd);
+void master_login_stop(struct master_login *login);
+
+#endif
diff --git a/src/lib-master/master-service-haproxy.c b/src/lib-master/master-service-haproxy.c
new file mode 100644
index 0000000..c36935d
--- /dev/null
+++ b/src/lib-master/master-service-haproxy.c
@@ -0,0 +1,689 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+
+#define HAPROXY_V1_MAX_HEADER_SIZE (108)
+
+#define PP2_TYPE_ALPN 0x01
+#define PP2_TYPE_AUTHORITY 0x02
+#define PP2_TYPE_CRC32C 0x03
+#define PP2_TYPE_NOOP 0x04
+#define PP2_TYPE_SSL 0x20
+#define PP2_SUBTYPE_SSL_VERSION 0x21
+#define PP2_SUBTYPE_SSL_CN 0x22
+#define PP2_SUBTYPE_SSL_CIPHER 0x23
+#define PP2_SUBTYPE_SSL_SIG_ALG 0x24
+#define PP2_SUBTYPE_SSL_KEY_ALG 0x25
+#define PP2_TYPE_NETNS 0x30
+
+#define PP2_CLIENT_SSL 0x01
+#define PP2_CLIENT_CERT_CONN 0x02
+#define PP2_CLIENT_CERT_SESS 0x04
+
+enum haproxy_version_t {
+ HAPROXY_VERSION_1,
+ HAPROXY_VERSION_2,
+};
+
+enum {
+ HAPROXY_CMD_LOCAL = 0x00,
+ HAPROXY_CMD_PROXY = 0x01
+};
+
+enum {
+ HAPROXY_AF_UNSPEC = 0x00,
+ HAPROXY_AF_INET = 0x01,
+ HAPROXY_AF_INET6 = 0x02,
+ HAPROXY_AF_UNIX = 0x03
+};
+
+enum {
+ HAPROXY_SOCK_UNSPEC = 0x00,
+ HAPROXY_SOCK_STREAM = 0x01,
+ HAPROXY_SOCK_DGRAM = 0x02
+};
+
+static const char haproxy_v2sig[12] =
+ "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
+
+struct haproxy_header_v2 {
+ uint8_t sig[12];
+ uint8_t ver_cmd;
+ uint8_t fam;
+ uint16_t len;
+};
+
+struct haproxy_data_v2 {
+ union {
+ struct { /* for TCP/UDP over IPv4, len = 12 */
+ uint32_t src_addr;
+ uint32_t dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip4;
+ struct { /* for TCP/UDP over IPv6, len = 36 */
+ uint8_t src_addr[16];
+ uint8_t dst_addr[16];
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip6;
+ struct { /* for AF_UNIX sockets, len = 216 */
+ uint8_t src_addr[108];
+ uint8_t dst_addr[108];
+ } unx;
+ } addr;
+};
+
+#define SIZEOF_PP2_TLV (1U+2U)
+struct haproxy_pp2_tlv {
+ uint8_t type;
+ uint16_t len;
+ const unsigned char *data;
+};
+
+#define SIZEOF_PP2_TLV_SSL (1U+4U)
+struct haproxy_pp2_tlv_ssl {
+ uint8_t client;
+ uint32_t verify;
+
+ size_t len;
+ const unsigned char *data;
+};
+
+struct master_service_haproxy_conn {
+ struct master_service_connection conn;
+
+ pool_t pool;
+
+ struct master_service_haproxy_conn *prev, *next;
+
+ struct master_service *service;
+
+ struct io *io;
+ struct timeout *to;
+};
+
+static void
+master_service_haproxy_conn_free(struct master_service_haproxy_conn *hpconn)
+{
+ struct master_service *service = hpconn->service;
+
+ DLLIST_REMOVE(&service->haproxy_conns, hpconn);
+
+ io_remove(&hpconn->io);
+ timeout_remove(&hpconn->to);
+ pool_unref(&hpconn->pool);
+}
+
+static void
+master_service_haproxy_conn_failure(struct master_service_haproxy_conn *hpconn)
+{
+ struct master_service *service = hpconn->service;
+ struct master_service_connection conn = hpconn->conn;
+
+ master_service_haproxy_conn_free(hpconn);
+ master_service_client_connection_handled(service, &conn);
+}
+
+static void
+master_service_haproxy_conn_success(struct master_service_haproxy_conn *hpconn)
+{
+ struct master_service *service = hpconn->service;
+ struct master_service_connection conn = hpconn->conn;
+
+ master_service_haproxy_conn_free(hpconn);
+ master_service_client_connection_callback(service, &conn);
+}
+
+static void
+master_service_haproxy_timeout(struct master_service_haproxy_conn *hpconn)
+{
+ i_error("haproxy: Client timed out (rip=%s)",
+ net_ip2addr(&hpconn->conn.remote_ip));
+ master_service_haproxy_conn_failure(hpconn);
+}
+
+static int
+master_service_haproxy_recv(int fd, void *buf, size_t len, int flags)
+{
+ ssize_t ret;
+
+ do {
+ ret = recv(fd, buf, len, flags);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0 && errno == EAGAIN)
+ return 0;
+ if (ret <= 0) {
+ if (ret == 0)
+ errno = ECONNRESET;
+ return -1;
+ }
+
+ return ret;
+}
+
+static int get_ssl_tlv(const unsigned char *kvdata, size_t dlen,
+ struct haproxy_pp2_tlv_ssl *kv)
+{
+ if (dlen < SIZEOF_PP2_TLV_SSL)
+ return -1;
+ kv->client = kvdata[0];
+ /* spec does not specify the endianess of this field */
+ kv->verify = cpu32_to_cpu_unaligned(kvdata+1);
+ kv->data = kvdata+SIZEOF_PP2_TLV_SSL;
+ kv->len = dlen - SIZEOF_PP2_TLV_SSL;
+ return 0;
+}
+
+static int get_tlv(const unsigned char *kvdata, size_t dlen,
+ struct haproxy_pp2_tlv *kv)
+{
+ if (dlen < SIZEOF_PP2_TLV)
+ return -1;
+
+ /* spec says
+ uint8_t type
+ uint8_t len_hi
+ uint8_t len_lo
+ so we combine the hi and lo here. */
+ kv->type = kvdata[0];
+ kv->len = (kvdata[1]<<8)+kvdata[2];
+ kv->data = kvdata + SIZEOF_PP2_TLV;
+
+ if (kv->len + SIZEOF_PP2_TLV > dlen)
+ return -1;
+
+ return 0;
+}
+
+static int
+master_service_haproxy_parse_ssl_tlv(struct master_service_haproxy_conn *hpconn,
+ const struct haproxy_pp2_tlv_ssl *ssl_kv,
+ const char **error_r)
+{
+ hpconn->conn.proxy.ssl = (ssl_kv->client & (PP2_CLIENT_SSL)) != 0;
+
+ /* try parse some more */
+ for(size_t i = 0; i < ssl_kv->len;) {
+ struct haproxy_pp2_tlv kv;
+ if (get_tlv(ssl_kv->data + i, ssl_kv->len - i, &kv) < 0) {
+ *error_r = t_strdup_printf("get_tlv(%zu) failed: "
+ "Truncated data", i);
+ return -1;
+ }
+ i += SIZEOF_PP2_TLV + kv.len;
+ switch(kv.type) {
+ /* we don't care about these */
+ case PP2_SUBTYPE_SSL_CIPHER:
+ case PP2_SUBTYPE_SSL_SIG_ALG:
+ case PP2_SUBTYPE_SSL_KEY_ALG:
+ break;
+ case PP2_SUBTYPE_SSL_CN:
+ hpconn->conn.proxy.cert_common_name =
+ p_strndup(hpconn->pool, kv.data, kv.len);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+master_service_haproxy_parse_tlv(struct master_service_haproxy_conn *hpconn,
+ const unsigned char *buf, size_t blen,
+ const char **error_r)
+{
+ for(size_t i = 0; i < blen;) {
+ struct haproxy_pp2_tlv kv;
+ struct haproxy_pp2_tlv_ssl ssl_kv;
+
+ if (get_tlv(buf + i, blen - i, &kv) < 0) {
+ *error_r = t_strdup_printf("get_tlv(%zu) failed: "
+ "Truncated data", i);
+ return -1;
+ }
+
+ /* skip unsupported values */
+ switch(kv.type) {
+ case PP2_TYPE_ALPN:
+ hpconn->conn.proxy.alpn_size = kv.len;
+ hpconn->conn.proxy.alpn =
+ p_memdup(hpconn->pool, kv.data, kv.len);
+ break;
+ case PP2_TYPE_AUTHORITY:
+ /* store hostname somewhere */
+ hpconn->conn.proxy.hostname =
+ p_strndup(hpconn->pool, kv.data, kv.len);
+ break;
+ case PP2_TYPE_SSL:
+ if (get_ssl_tlv(kv.data, kv.len, &ssl_kv) < 0) {
+ *error_r = t_strdup_printf("get_ssl_tlv(%zu) failed: "
+ "Truncated data", i);
+ return -1;
+ }
+ if (master_service_haproxy_parse_ssl_tlv(hpconn, &ssl_kv, error_r)<0)
+ return -1;
+ break;
+ }
+ i += SIZEOF_PP2_TLV + kv.len;
+ }
+ return 0;
+}
+
+static int
+master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
+{
+ /* reasonable max size for haproxy data */
+ unsigned char rbuf[1500];
+ const char *error;
+ static union {
+ unsigned char v1_data[HAPROXY_V1_MAX_HEADER_SIZE];
+ struct {
+ const struct haproxy_header_v2 hdr;
+ const struct haproxy_data_v2 data;
+ } v2;
+ } buf;
+ struct ip_addr *real_remote_ip = &hpconn->conn.remote_ip;
+ int fd = hpconn->conn.fd;
+ struct ip_addr local_ip, remote_ip;
+ in_port_t local_port, remote_port;
+ size_t size,i,want;
+ ssize_t ret;
+ enum haproxy_version_t version;
+
+ /* the protocol specification explicitly states that the protocol header
+ must be sent as one TCP frame, meaning that we will get it in full
+ with the first recv() call.
+ */
+ i_zero(&buf);
+ i_zero(&rbuf);
+
+ /* see if there is a HAPROXY protocol command waiting */
+ if ((ret = master_service_haproxy_recv(fd, &buf, sizeof(buf), MSG_PEEK))<=0) {
+ if (ret < 0)
+ i_info("haproxy: Client disconnected (rip=%s): %m",
+ net_ip2addr(real_remote_ip));
+ return ret;
+ /* see if there is a haproxy command, 8 is used later on as well */
+ } else if (ret >= 8 && memcmp(buf.v1_data, "PROXY", 5) == 0) {
+ /* fine */
+ version = HAPROXY_VERSION_1;
+ } else if ((size_t)ret >= sizeof(buf.v2.hdr) &&
+ memcmp(buf.v2.hdr.sig, haproxy_v2sig, sizeof(haproxy_v2sig)) == 0) {
+ want = ntohs(buf.v2.hdr.len) + sizeof(buf.v2.hdr);
+ if (want > sizeof(rbuf)) {
+ i_error("haproxy: Client disconnected: Too long header (rip=%s)",
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+
+ if ((ret = master_service_haproxy_recv(fd, rbuf, want, MSG_WAITALL))<=0) {
+ if (ret < 0)
+ i_info("haproxy: Client disconnected (rip=%s): %m",
+ net_ip2addr(real_remote_ip));
+ return ret;
+ }
+
+ if (ret != (ssize_t)want) {
+ i_info("haproxy: Client disconnected: Failed to read full header (rip=%s)",
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ memcpy(&buf, rbuf, sizeof(buf));
+ version = HAPROXY_VERSION_2;
+ } else {
+ /* it wasn't haproxy data */
+ i_error("haproxy: Client disconnected: "
+ "Failed to read valid HAproxy data (rip=%s)",
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+
+ /* don't update true connection data until we succeed */
+ local_ip = hpconn->conn.local_ip;
+ remote_ip = hpconn->conn.remote_ip;
+ local_port = hpconn->conn.local_port;
+ remote_port = hpconn->conn.remote_port;
+
+ /* protocol version 2 */
+ if (version == HAPROXY_VERSION_2) {
+ const struct haproxy_header_v2 *hdr = &buf.v2.hdr;
+ const struct haproxy_data_v2 *data = &buf.v2.data;
+ size_t hdr_len;
+
+ i_assert(ret >= (ssize_t)sizeof(buf.v2.hdr));
+
+ if ((hdr->ver_cmd & 0xf0) != 0x20) {
+ i_error("haproxy: Client disconnected: "
+ "Unsupported protocol version (version=%02x, rip=%s)",
+ (hdr->ver_cmd & 0xf0) >> 4,
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+
+ hdr_len = ntohs(hdr->len);
+ size = sizeof(*hdr) + hdr_len;
+ /* keep tab of how much address data there really is because
+ because TLVs begin after that. */
+ i = 0;
+
+ if (ret < (ssize_t)size) {
+ i_error("haproxy(v2): Client disconnected: "
+ "Protocol payload length does not match header "
+ "(got=%zu, expect=%zu, rip=%s)",
+ (size_t)ret, size, net_ip2addr(real_remote_ip));
+ return -1;
+ }
+
+ i += sizeof(*hdr);
+
+ switch (hdr->ver_cmd & 0x0f) {
+ case HAPROXY_CMD_LOCAL:
+ /* keep local connection address for LOCAL */
+ /*i_debug("haproxy(v2): Local connection (rip=%s)",
+ net_ip2addr(real_remote_ip));*/
+ i = size; /* we should skip all the remaining data which can be present in PROXY protocol */
+ break;
+ case HAPROXY_CMD_PROXY:
+ if ((hdr->fam & 0x0f) != HAPROXY_SOCK_STREAM) {
+ /* UDP makes no sense currently */
+ i_error("haproxy(v2): Client disconnected: "
+ "Not using TCP (type=%02x, rip=%s)",
+ (hdr->fam & 0x0f), net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ switch ((hdr->fam & 0xf0) >> 4) {
+ case HAPROXY_AF_INET:
+ /* IPv4 */
+ if (hdr_len < sizeof(data->addr.ip4)) {
+ i_error("haproxy(v2): Client disconnected: "
+ "IPv4 data is incomplete (rip=%s)",
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ local_ip.family = AF_INET;
+ local_ip.u.ip4.s_addr = data->addr.ip4.dst_addr;
+ local_port = ntohs(data->addr.ip4.dst_port);
+ remote_ip.family = AF_INET;
+ remote_ip.u.ip4.s_addr = data->addr.ip4.src_addr;
+ remote_port = ntohs(data->addr.ip4.src_port);
+ i += sizeof(data->addr.ip4);
+ break;
+ case HAPROXY_AF_INET6:
+ /* IPv6 */
+ if (hdr_len < sizeof(data->addr.ip6)) {
+ i_error("haproxy(v2): Client disconnected: "
+ "IPv6 data is incomplete (rip=%s)",
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ local_ip.family = AF_INET6;
+ memcpy(&local_ip.u.ip6.s6_addr, data->addr.ip6.dst_addr, 16);
+ local_port = ntohs(data->addr.ip6.dst_port);
+ remote_ip.family = AF_INET6;
+ memcpy(&remote_ip.u.ip6.s6_addr, data->addr.ip6.src_addr, 16);
+ remote_port = ntohs(data->addr.ip6.src_port);
+ i += sizeof(data->addr.ip6);
+ break;
+ case HAPROXY_AF_UNSPEC:
+ case HAPROXY_AF_UNIX:
+ /* unsupported; ignored */
+ i_error("haproxy(v2): Unsupported address family "
+ "(family=%02x, rip=%s)", (hdr->fam & 0xf0) >> 4,
+ net_ip2addr(real_remote_ip));
+ break;
+ default:
+ /* unsupported; error */
+ i_error("haproxy(v2): Client disconnected: "
+ "Unknown address family "
+ "(family=%02x, rip=%s)", (hdr->fam & 0xf0) >> 4,
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ break;
+ default:
+ i_error("haproxy(v2): Client disconnected: "
+ "Invalid command (cmd=%02x, rip=%s)",
+ (hdr->ver_cmd & 0x0f),
+ net_ip2addr(real_remote_ip));
+ return -1; /* not a supported command */
+ }
+
+ if (i > size) {
+ i_error("haproxy(v2): Client disconnected: "
+ "Invalid header size (size=%zu, tlv offset=%zu)",
+ size, i);
+ return -1; /* not a supported command */
+ }
+ if (master_service_haproxy_parse_tlv(hpconn, rbuf+i, size-i, &error) < 0) {
+ i_error("haproxy(v2): Client disconnected: "
+ "Invalid TLV: %s (cmd=%02x, rip=%s)",
+ error,
+ (hdr->ver_cmd & 0x0f),
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ /* protocol version 1 (soon obsolete) */
+ } else if (version == HAPROXY_VERSION_1) {
+ unsigned char *data = buf.v1_data, *end;
+ const char *const *fields;
+ unsigned int family = 0;
+
+ i_assert(ret >= 8);
+
+ /* find end of header line */
+ end = memchr(data, '\r', ret - 1);
+ if (end == NULL || end[1] != '\n')
+ return -1;
+ *end = '\0';
+ size = end + 2 - data;
+
+ /* magic */
+ fields = t_strsplit((char *)data, " ");
+ i_assert(strcmp(*fields, "PROXY") == 0);
+ fields++;
+
+ /* protocol */
+ if (*fields == NULL) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Field for proxied protocol is missing "
+ "(rip=%s)", net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ if (strcmp(*fields, "TCP4") == 0) {
+ family = AF_INET;
+ } else if (strcmp(*fields, "TCP6") == 0) {
+ family = AF_INET6;
+ } else if (strcmp(*fields, "UNKNOWN") == 0) {
+ family = 0;
+ } else {
+ i_error("haproxy(v1): Client disconnected: "
+ "Unknown proxied protocol "
+ "(protocol=`%s', rip=%s)", str_sanitize(*fields, 64),
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ fields++;
+
+ if (family != 0) {
+ /* remote address */
+ if (*fields == NULL) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Field for proxied remote address is missing "
+ "(rip=%s)", net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ if (net_addr2ip(*fields, &remote_ip) < 0 ||
+ remote_ip.family != family) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Proxied remote address is invalid "
+ "(address=`%s', rip=%s)", str_sanitize(*fields, 64),
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ fields++;
+
+ /* local address */
+ if (*fields == NULL) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Field for proxied local address is missing "
+ "(rip=%s)", net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ if (net_addr2ip(*fields, &local_ip) < 0 ||
+ local_ip.family != family) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Proxied local address is invalid "
+ "(address=`%s', rip=%s)", str_sanitize(*fields, 64),
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ fields++;
+
+ /* remote port */
+ if (*fields == NULL) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Field for proxied local port is missing "
+ "(rip=%s)", net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ if (net_str2port(*fields, &remote_port) < 0) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Proxied remote port is invalid "
+ "(port=`%s', rip=%s)", str_sanitize(*fields, 64),
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ fields++;
+
+ /* local port */
+ if (*fields == NULL) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Field for proxied local port is missing "
+ "(rip=%s)", net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ if (net_str2port(*fields, &local_port) < 0) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Proxied local port is invalid "
+ "(port=`%s', rip=%s)", str_sanitize(*fields, 64),
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ fields++;
+
+ if (*fields != NULL) {
+ i_error("haproxy(v1): Client disconnected: "
+ "Header line has spurius extra field "
+ "(field=`%s', rip=%s)", str_sanitize(*fields, 64),
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ }
+ i_assert(size <= sizeof(buf));
+
+ if ((ret = master_service_haproxy_recv(fd, &buf, size, 0))<=0) {
+ if (ret < 0)
+ i_info("haproxy: Client disconnected (rip=%s): %m",
+ net_ip2addr(real_remote_ip));
+ return ret;
+ } else if (ret != (ssize_t)size) {
+ i_error("haproxy: Client disconnected: "
+ "Failed to read full header (rip=%s)",
+ net_ip2addr(real_remote_ip));
+ return -1;
+ }
+ /* invalid protocol */
+ } else {
+ i_unreached();
+ }
+
+ /* assign data from proxy */
+ hpconn->conn.local_ip = local_ip;
+ hpconn->conn.remote_ip = remote_ip;
+ hpconn->conn.local_port = local_port;
+ hpconn->conn.remote_port = remote_port;
+ hpconn->conn.proxied = TRUE;
+
+ return 1;
+}
+
+static void
+master_service_haproxy_input(struct master_service_haproxy_conn *hpconn)
+{
+ int ret;
+
+ if ((ret = master_service_haproxy_read(hpconn)) <= 0) {
+ if (ret < 0)
+ master_service_haproxy_conn_failure(hpconn);
+ } else {
+ master_service_haproxy_conn_success(hpconn);
+ }
+}
+
+static bool
+master_service_haproxy_conn_is_trusted(struct master_service *service,
+ struct master_service_connection *conn)
+{
+ const char *const *net;
+ struct ip_addr net_ip;
+ unsigned int bits;
+
+ if (service->set->haproxy_trusted_networks == NULL)
+ return FALSE;
+
+ net = t_strsplit_spaces(service->set->haproxy_trusted_networks, ", ");
+ for (; *net != NULL; net++) {
+ if (net_parse_range(*net, &net_ip, &bits) < 0) {
+ i_error("haproxy_trusted_networks: "
+ "Invalid network '%s'", *net);
+ break;
+ }
+
+ if (net_is_in_network(&conn->real_remote_ip, &net_ip, bits))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void master_service_haproxy_new(struct master_service *service,
+ struct master_service_connection *conn)
+{
+ struct master_service_haproxy_conn *hpconn;
+ pool_t pool;
+
+ if (!master_service_haproxy_conn_is_trusted(service, conn)) {
+ i_warning("haproxy: Client not trusted (rip=%s)",
+ net_ip2addr(&conn->real_remote_ip));
+ master_service_client_connection_handled(service, conn);
+ return;
+ }
+
+ pool = pool_alloconly_create("haproxy connection", 128);
+ hpconn = p_new(pool, struct master_service_haproxy_conn, 1);
+ hpconn->pool = pool;
+ hpconn->conn = *conn;
+ hpconn->service = service;
+ DLLIST_PREPEND(&service->haproxy_conns, hpconn);
+
+ hpconn->io = io_add(conn->fd, IO_READ,
+ master_service_haproxy_input, hpconn);
+ hpconn->to = timeout_add(service->set->haproxy_timeout*1000,
+ master_service_haproxy_timeout, hpconn);
+}
+
+void master_service_haproxy_abort(struct master_service *service)
+{
+ while (service->haproxy_conns != NULL) {
+ int fd = service->haproxy_conns->conn.fd;
+
+ master_service_haproxy_conn_free(service->haproxy_conns);
+ i_close_fd(&fd);
+ }
+}
+
diff --git a/src/lib-master/master-service-private.h b/src/lib-master/master-service-private.h
new file mode 100644
index 0000000..429f2eb
--- /dev/null
+++ b/src/lib-master/master-service-private.h
@@ -0,0 +1,109 @@
+#ifndef MASTER_SERVICE_PRIVATE_H
+#define MASTER_SERVICE_PRIVATE_H
+
+#include "master-interface.h"
+#include "master-service.h"
+
+struct master_service_haproxy_conn;
+
+struct master_service_listener {
+ struct master_service *service;
+ char *name;
+
+ /* settings */
+ bool ssl;
+ bool haproxy;
+
+ /* state */
+ bool closed;
+ int fd;
+ struct io *io;
+};
+
+struct master_service {
+ struct ioloop *ioloop;
+
+ char *name;
+ char *configured_name;
+ char *getopt_str;
+ enum master_service_flags flags;
+
+ int argc;
+ char **argv;
+
+ const char *version_string;
+ char *config_path;
+ ARRAY_TYPE(const_string) config_overrides;
+ int config_fd;
+ int syslog_facility;
+ data_stack_frame_t datastack_frame_id;
+
+ struct master_service_listener *listeners;
+ unsigned int socket_count;
+
+ struct io *io_status_write, *io_status_error;
+ unsigned int service_count_left;
+ unsigned int total_available_count;
+ unsigned int process_limit;
+ unsigned int process_min_avail;
+ unsigned int idle_kill_secs;
+
+ struct master_status master_status;
+ unsigned int last_sent_status_avail_count;
+ time_t last_sent_status_time;
+ struct timeout *to_status;
+
+ bool (*idle_die_callback)(void);
+ void (*die_callback)(void);
+ struct timeout *to_die;
+
+ master_service_avail_overflow_callback_t *avail_overflow_callback;
+ struct timeout *to_overflow_state, *to_overflow_call;
+
+ struct master_login *login;
+
+ master_service_connection_callback_t *callback;
+
+ pool_t set_pool;
+ const struct master_service_settings *set;
+ struct setting_parser_context *set_parser;
+
+ struct ssl_iostream_context *ssl_ctx;
+ time_t ssl_params_last_refresh;
+
+ struct stats_client *stats_client;
+ struct master_service_haproxy_conn *haproxy_conns;
+ struct event_filter *process_shutdown_filter;
+
+ bool killed:1;
+ bool stopping:1;
+ bool keep_environment:1;
+ bool log_directly:1;
+ bool initial_status_sent:1;
+ bool die_with_master:1;
+ bool call_avail_overflow:1;
+ bool config_path_changed_with_param:1;
+ bool want_ssl_server:1;
+ bool ssl_ctx_initialized:1;
+ bool config_path_from_master:1;
+ bool log_initialized:1;
+ bool init_finished:1;
+};
+
+void master_service_io_listeners_add(struct master_service *service);
+void master_status_update(struct master_service *service);
+void master_service_close_config_fd(struct master_service *service);
+
+void master_service_io_listeners_remove(struct master_service *service);
+void master_service_ssl_io_listeners_remove(struct master_service *service);
+
+void master_service_client_connection_handled(struct master_service *service,
+ struct master_service_connection *conn);
+void master_service_client_connection_callback(struct master_service *service,
+ struct master_service_connection *conn);
+
+void master_service_haproxy_new(struct master_service *service,
+ struct master_service_connection *conn);
+void master_service_haproxy_abort(struct master_service *service);
+
+#endif
diff --git a/src/lib-master/master-service-settings-cache.c b/src/lib-master/master-service-settings-cache.c
new file mode 100644
index 0000000..11dd66b
--- /dev/null
+++ b/src/lib-master/master-service-settings-cache.c
@@ -0,0 +1,410 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "wildcard-match.h"
+#include "hash.h"
+#include "llist.h"
+#include "settings-parser.h"
+#include "dns-util.h"
+#include "strescape.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+#include "master-service-settings-cache.h"
+
+/* we start with just a guess. it's updated later. */
+#define CACHE_INITIAL_ENTRY_POOL_SIZE (1024*16)
+#define CACHE_ADD_ENTRY_POOL_SIZE 1024
+
+struct config_filter {
+ struct config_filter *prev, *next;
+
+ const char *local_name;
+ struct ip_addr local_ip, remote_ip;
+ unsigned int local_bits, remote_bits;
+};
+
+struct settings_entry {
+ struct settings_entry *prev, *next;
+
+ pool_t pool;
+ const char *local_name;
+ struct ip_addr local_ip;
+
+ struct setting_parser_context *parser;
+};
+
+struct master_service_settings_cache {
+ pool_t pool;
+
+ struct master_service *service;
+ const char *module;
+ const char *service_name;
+ size_t max_cache_size;
+
+ /* global settings for this service (after they've been read) */
+ struct setting_parser_context *global_parser;
+
+ /* cache for other settings (local_ip/local_name set) */
+ struct settings_entry *oldest, *newest;
+ /* separate list for entries whose parser=global_parser */
+ struct settings_entry *oldest_global, *newest_global;
+ /* local_name, local_ip => struct settings_entry */
+ HASH_TABLE(char *, struct settings_entry *) local_name_hash;
+ HASH_TABLE(struct ip_addr *, struct settings_entry *) local_ip_hash;
+
+ struct config_filter *filters;
+
+ /* Initial size for new settings entry pools */
+ size_t approx_entry_pool_size;
+ /* number of bytes malloced by cached settings entries
+ (doesn't count memory used by hash table or global sets) */
+ size_t cache_malloc_size;
+
+ bool done_initial_lookup:1;
+ bool service_uses_local:1;
+ bool service_uses_remote:1;
+};
+
+struct master_service_settings_cache *
+master_service_settings_cache_init(struct master_service *service,
+ const char *module, const char *service_name)
+{
+ struct master_service_settings_cache *cache;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"master service settings cache",
+ 1024*12);
+ cache = p_new(pool, struct master_service_settings_cache, 1);
+ cache->pool = pool;
+ cache->service = service;
+ cache->module = p_strdup(pool, module);
+ cache->service_name = p_strdup(pool, service_name);
+ cache->max_cache_size = SIZE_MAX;
+ return cache;
+}
+
+int master_service_settings_cache_init_filter(struct master_service_settings_cache *cache)
+{
+ const char *const *filters;
+ const char *error;
+
+ if (cache->filters != NULL)
+ return 0;
+ if (master_service_settings_get_filters(cache->service, &filters, &error) < 0) {
+ i_error("master-service: cannot get filters: %s", error);
+ return -1;
+ }
+
+ /* parse filters */
+ while(*filters != NULL) {
+ const char *const *keys = t_strsplit_tabescaped(*filters);
+ struct config_filter *filter =
+ p_new(cache->pool, struct config_filter, 1);
+ while(*keys != NULL) {
+ if (str_begins(*keys, "local-net=")) {
+ (void)net_parse_range((*keys)+10,
+ &filter->local_ip, &filter->local_bits);
+ } else if (str_begins(*keys, "remote-net=")) {
+ (void)net_parse_range((*keys)+11,
+ &filter->remote_ip, &filter->remote_bits);
+ } else if (str_begins(*keys, "local-name=")) {
+ filter->local_name = p_strdup(cache->pool, (*keys)+11);
+ }
+ keys++;
+ }
+ DLLIST_PREPEND(&cache->filters, filter);
+ filters++;
+ }
+ return 0;
+}
+
+static bool
+match_local_name(const char *local_name,
+ const char *filter_local_name)
+{
+ /* Handle multiple names separated by spaces in local_name
+ * Ex: local_name "mail.domain.tld domain.tld mx.domain.tld" { ... } */
+ const char *ptr;
+ while((ptr = strchr(filter_local_name, ' ')) != NULL) {
+ if (dns_match_wildcard(local_name,
+ t_strdup_until(filter_local_name, ptr)) == 0)
+ return TRUE;
+ filter_local_name = ptr+1;
+ }
+ return dns_match_wildcard(local_name, filter_local_name) == 0;
+}
+
+/* Remove any elements which there is no filter for */
+static void
+master_service_settings_cache_fix_input(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input,
+ struct master_service_settings_input *new_input)
+{
+ bool found_lip, found_rip, found_local_name;
+
+ found_lip = found_rip = found_local_name = FALSE;
+
+ struct config_filter *filter = cache->filters;
+ while(filter != NULL) {
+ if (filter->local_bits > 0 &&
+ net_is_in_network(&input->local_ip, &filter->local_ip,
+ filter->local_bits))
+ found_lip = TRUE;
+ if (filter->remote_bits > 0 &&
+ net_is_in_network(&input->remote_ip, &filter->remote_ip,
+ filter->remote_bits))
+ found_rip = TRUE;
+ if (input->local_name != NULL && filter->local_name != NULL &&
+ match_local_name(input->local_name, filter->local_name))
+ found_local_name = TRUE;
+ filter = filter->next;
+ };
+
+ *new_input = *input;
+
+ if (!found_lip)
+ i_zero(&new_input->local_ip);
+ if (!found_rip)
+ i_zero(&new_input->remote_ip);
+ if (!found_local_name)
+ new_input->local_name = NULL;
+}
+
+
+void master_service_settings_cache_deinit(struct master_service_settings_cache **_cache)
+{
+ struct master_service_settings_cache *cache = *_cache;
+ struct settings_entry *entry, *next;
+
+ /* parsers need to be deinitialized, because they reference the pool */
+ for (entry = cache->oldest_global; entry != NULL; entry = next) {
+ next = entry->next;
+ i_assert(entry->parser == cache->global_parser);
+ pool_unref(&entry->pool);
+ }
+ for (entry = cache->oldest; entry != NULL; entry = next) {
+ next = entry->next;
+ i_assert(entry->parser != cache->global_parser);
+ settings_parser_deinit(&entry->parser);
+ pool_unref(&entry->pool);
+ }
+ hash_table_destroy(&cache->local_name_hash);
+ hash_table_destroy(&cache->local_ip_hash);
+ if (cache->global_parser != NULL)
+ settings_parser_deinit(&cache->global_parser);
+ pool_unref(&cache->pool);
+}
+
+static bool
+cache_can_return_global(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input)
+{
+ if (cache->service_uses_local) {
+ if (input->local_name != NULL || input->local_ip.family != 0)
+ return FALSE;
+ }
+ if (cache->service_uses_remote) {
+ if (input->remote_ip.family != 0)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+cache_find(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input,
+ const struct setting_parser_context **parser_r)
+{
+ struct settings_entry *entry = NULL;
+
+ if (!cache->done_initial_lookup)
+ return FALSE;
+
+ if (cache_can_return_global(cache, input)) {
+ if (cache->global_parser != NULL) {
+ *parser_r = cache->global_parser;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ if (cache->service_uses_remote)
+ return FALSE;
+
+ /* see if we have it already in cache. if local_name is specified,
+ don't even try to use local_ip (even though we have it), because
+ there may be different settings specifically for local_name */
+ if (input->local_name != NULL) {
+ if (hash_table_is_created(cache->local_name_hash)) {
+ entry = hash_table_lookup(cache->local_name_hash,
+ input->local_name);
+ }
+ } else if (hash_table_is_created(cache->local_ip_hash) &&
+ input->local_ip.family != 0) {
+ entry = hash_table_lookup(cache->local_ip_hash,
+ &input->local_ip);
+ }
+
+ if (entry != NULL) {
+ if (entry->parser != cache->global_parser) {
+ DLLIST2_REMOVE(&cache->oldest, &cache->newest, entry);
+ DLLIST2_APPEND(&cache->oldest, &cache->newest, entry);
+ }
+ *parser_r = entry->parser;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+setting_entry_detach(struct master_service_settings_cache *cache,
+ struct settings_entry *entry)
+{
+ DLLIST2_REMOVE(&cache->oldest, &cache->newest, entry);
+ cache->cache_malloc_size -=
+ pool_alloconly_get_total_alloc_size(entry->pool);
+
+ if (entry->local_name != NULL)
+ hash_table_remove(cache->local_name_hash, entry->local_name);
+ else if (entry->local_ip.family != 0)
+ hash_table_remove(cache->local_ip_hash, &entry->local_ip);
+ settings_parser_deinit(&entry->parser);
+}
+
+static struct setting_parser_context *
+cache_add(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input,
+ const struct master_service_settings_output *output,
+ struct setting_parser_context *parser)
+{
+ struct settings_entry *entry;
+ pool_t pool;
+ size_t pool_size;
+ char *entry_local_name;
+
+ if (!output->used_local && !output->used_remote) {
+ /* these are same as global settings */
+ if (cache->global_parser == NULL) {
+ cache->global_parser =
+ settings_parser_dup(parser, cache->pool);
+ }
+ }
+ if (cache->service_uses_remote) {
+ /* for now we don't try to handle caching remote IPs */
+ return parser;
+ }
+
+ if (input->local_name == NULL && input->local_ip.family == 0)
+ return parser;
+
+ if (!output->used_local) {
+ /* use global settings, but add local_ip/host to hash tables
+ so we'll find them */
+ pool = pool_alloconly_create("settings global entry", 256);
+ } else if (cache->cache_malloc_size >= cache->max_cache_size) {
+ /* free the oldest and reuse its pool */
+ pool = cache->oldest->pool;
+ setting_entry_detach(cache, cache->oldest);
+ p_clear(pool); /* note: frees also entry */
+ } else {
+ pool_size = cache->approx_entry_pool_size != 0 ?
+ cache->approx_entry_pool_size :
+ CACHE_INITIAL_ENTRY_POOL_SIZE;
+ pool = pool_alloconly_create("settings entry", pool_size);
+ }
+ entry = p_new(pool, struct settings_entry, 1);
+ entry->pool = pool;
+ entry_local_name = p_strdup(pool, input->local_name);
+ entry->local_name = entry_local_name;
+ entry->local_ip = input->local_ip;
+ if (!output->used_local) {
+ entry->parser = cache->global_parser;
+ DLLIST2_APPEND(&cache->oldest_global, &cache->newest_global,
+ entry);
+ } else {
+ entry->parser = settings_parser_dup(parser, entry->pool);
+ DLLIST2_APPEND(&cache->oldest, &cache->newest, entry);
+
+ pool_size = pool_alloconly_get_total_used_size(pool);
+ if (pool_size > cache->approx_entry_pool_size) {
+ cache->approx_entry_pool_size = pool_size +
+ CACHE_ADD_ENTRY_POOL_SIZE;
+ }
+ }
+ cache->cache_malloc_size += pool_alloconly_get_total_alloc_size(pool);
+
+ if (input->local_name != NULL) {
+ if (!hash_table_is_created(cache->local_name_hash)) {
+ hash_table_create(&cache->local_name_hash,
+ cache->pool, 0, str_hash, strcmp);
+ }
+ i_assert(hash_table_lookup(cache->local_name_hash,
+ entry_local_name) == NULL);
+ hash_table_insert(cache->local_name_hash,
+ entry_local_name, entry);
+ } else if (input->local_ip.family != 0) {
+ if (!hash_table_is_created(cache->local_ip_hash)) {
+ hash_table_create(&cache->local_ip_hash, cache->pool, 0,
+ net_ip_hash, net_ip_cmp);
+ }
+ i_assert(hash_table_lookup(cache->local_ip_hash,
+ &entry->local_ip) == NULL);
+ hash_table_insert(cache->local_ip_hash,
+ &entry->local_ip, entry);
+ }
+ return entry->parser;
+}
+
+int master_service_settings_cache_read(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input,
+ const struct dynamic_settings_parser *dyn_parsers,
+ const struct setting_parser_context **parser_r,
+ const char **error_r)
+{
+ struct master_service_settings_output output;
+ struct master_service_settings_input new_input;
+ const struct master_service_settings *set;
+
+ i_assert(null_strcmp(input->module, cache->module) == 0);
+ i_assert(null_strcmp(input->service, cache->service_name) == 0);
+
+ if (cache_find(cache, input, parser_r))
+ return 0;
+
+ new_input = *input;
+ if (cache->filters != NULL) {
+ master_service_settings_cache_fix_input(cache, input, &new_input);
+ if (cache_find(cache, &new_input, parser_r))
+ return 0;
+ }
+
+ if (dyn_parsers != NULL) {
+ settings_parser_dyn_update(cache->pool, &new_input.roots,
+ dyn_parsers);
+ }
+ if (master_service_settings_read(cache->service, &new_input,
+ &output, error_r) < 0)
+ return -1;
+
+ if (!cache->done_initial_lookup) {
+ cache->done_initial_lookup = TRUE;
+ cache->service_uses_local = output.service_uses_local;
+ cache->service_uses_remote = output.service_uses_remote;
+
+ set = master_service_settings_get(cache->service);
+ cache->max_cache_size = set->config_cache_size;
+ }
+
+ if (output.used_local && !cache->service_uses_local) {
+ *error_r = "BUG: config unexpectedly returned local settings";
+ return -1;
+ }
+ if (output.used_remote && !cache->service_uses_remote) {
+ *error_r = "BUG: config unexpectedly returned remote settings";
+ return -1;
+ }
+
+ *parser_r = cache_add(cache, &new_input, &output,
+ cache->service->set_parser);
+ return 0;
+}
diff --git a/src/lib-master/master-service-settings-cache.h b/src/lib-master/master-service-settings-cache.h
new file mode 100644
index 0000000..157132e
--- /dev/null
+++ b/src/lib-master/master-service-settings-cache.h
@@ -0,0 +1,16 @@
+#ifndef MASTER_SERVICE_SETTINGS_CACHE_H
+#define MASTER_SERVICE_SETTINGS_CACHE_H
+
+struct master_service_settings_cache *
+master_service_settings_cache_init(struct master_service *service,
+ const char *module,
+ const char *service_name);
+void master_service_settings_cache_deinit(struct master_service_settings_cache **cache);
+int master_service_settings_cache_init_filter(struct master_service_settings_cache *cache);
+int master_service_settings_cache_read(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input,
+ const struct dynamic_settings_parser *dyn_parsers,
+ const struct setting_parser_context **parser_r,
+ const char **error_r) ATTR_NULL(3);
+
+#endif
diff --git a/src/lib-master/master-service-settings.c b/src/lib-master/master-service-settings.c
new file mode 100644
index 0000000..f0f0797
--- /dev/null
+++ b/src/lib-master/master-service-settings.c
@@ -0,0 +1,816 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "event-filter.h"
+#include "path-util.h"
+#include "istream.h"
+#include "write-full.h"
+#include "str.h"
+#include "strescape.h"
+#include "syslog-util.h"
+#include "eacces-error.h"
+#include "env-util.h"
+#include "execv-const.h"
+#include "settings-parser.h"
+#include "stats-client.h"
+#include "master-service-private.h"
+#include "master-service-ssl-settings.h"
+#include "master-service-settings.h"
+
+#include <stddef.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#define DOVECOT_CONFIG_BIN_PATH BINDIR"/doveconf"
+#define DOVECOT_CONFIG_SOCKET_PATH PKG_RUNDIR"/config"
+
+#define CONFIG_READ_TIMEOUT_SECS 10
+#define CONFIG_HANDSHAKE "VERSION\tconfig\t2\t0\n"
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct master_service_settings)
+
+static bool
+master_service_settings_check(void *_set, pool_t pool, const char **error_r);
+
+static const struct setting_define master_service_setting_defines[] = {
+ DEF(STR, base_dir),
+ DEF(STR, state_dir),
+ DEF(STR, instance_name),
+ DEF(STR, log_path),
+ DEF(STR, info_log_path),
+ DEF(STR, debug_log_path),
+ DEF(STR, log_timestamp),
+ DEF(STR, log_debug),
+ DEF(STR, log_core_filter),
+ DEF(STR, process_shutdown_filter),
+ DEF(STR, syslog_facility),
+ DEF(STR, import_environment),
+ DEF(STR, stats_writer_socket_path),
+ DEF(SIZE, config_cache_size),
+ DEF(BOOL, version_ignore),
+ DEF(BOOL, shutdown_clients),
+ DEF(BOOL, verbose_proctitle),
+
+ DEF(STR, haproxy_trusted_networks),
+ DEF(TIME, haproxy_timeout),
+
+ SETTING_DEFINE_LIST_END
+};
+
+/* <settings checks> */
+#ifdef HAVE_LIBSYSTEMD
+# define ENV_SYSTEMD " LISTEN_PID LISTEN_FDS NOTIFY_SOCKET"
+#else
+# define ENV_SYSTEMD ""
+#endif
+#ifdef DEBUG
+# define ENV_GDB " GDB DEBUG_SILENT"
+#else
+# define ENV_GDB ""
+#endif
+/* </settings checks> */
+
+static const struct master_service_settings master_service_default_settings = {
+ .base_dir = PKG_RUNDIR,
+ .state_dir = PKG_STATEDIR,
+ .instance_name = PACKAGE,
+ .log_path = "syslog",
+ .info_log_path = "",
+ .debug_log_path = "",
+ .log_timestamp = DEFAULT_FAILURE_STAMP_FORMAT,
+ .log_debug = "",
+ .log_core_filter = "",
+ .process_shutdown_filter = "",
+ .syslog_facility = "mail",
+ .import_environment = "TZ CORE_OUTOFMEM CORE_ERROR" ENV_SYSTEMD ENV_GDB,
+ .stats_writer_socket_path = "stats-writer",
+ .config_cache_size = 1024*1024,
+ .version_ignore = FALSE,
+ .shutdown_clients = TRUE,
+ .verbose_proctitle = FALSE,
+
+ .haproxy_trusted_networks = "",
+ .haproxy_timeout = 3
+};
+
+const struct setting_parser_info master_service_setting_parser_info = {
+ .module_name = "master",
+ .defines = master_service_setting_defines,
+ .defaults = &master_service_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct master_service_settings),
+
+ .parent_offset = SIZE_MAX,
+ .check_func = master_service_settings_check
+};
+
+/* <settings checks> */
+static bool
+setting_filter_parse(const char *set_name, const char *set_value,
+ void (*handle_filter)(struct event_filter *) ATTR_UNUSED,
+ const char **error_r)
+{
+ struct event_filter *filter;
+ const char *error;
+
+ if (set_value[0] == '\0')
+ return TRUE;
+
+ filter = event_filter_create();
+ if (event_filter_parse(set_value, filter, &error) < 0) {
+ *error_r = t_strdup_printf("Invalid %s: %s", set_name, error);
+ event_filter_unref(&filter);
+ return FALSE;
+ }
+#ifndef CONFIG_BINARY
+ handle_filter(filter);
+#endif
+ event_filter_unref(&filter);
+ return TRUE;
+}
+
+static void
+master_service_set_process_shutdown_filter_wrapper(struct event_filter *filter)
+{
+ master_service_set_process_shutdown_filter(master_service, filter);
+}
+
+static bool
+master_service_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+ const char **error_r)
+{
+ struct master_service_settings *set = _set;
+ int facility;
+
+ if (*set->log_path == '\0') {
+ /* default to syslog logging */
+ set->log_path = "syslog";
+ }
+ if (!syslog_facility_find(set->syslog_facility, &facility)) {
+ *error_r = t_strdup_printf("Unknown syslog_facility: %s",
+ set->syslog_facility);
+ return FALSE;
+ }
+
+ if (!setting_filter_parse("log_debug", set->log_debug,
+ event_set_global_debug_log_filter, error_r))
+ return FALSE;
+ if (!setting_filter_parse("log_core_filter", set->log_core_filter,
+ event_set_global_core_log_filter, error_r))
+ return FALSE;
+ if (!setting_filter_parse("process_shutdown_filter",
+ set->process_shutdown_filter,
+ master_service_set_process_shutdown_filter_wrapper,
+ error_r))
+ return FALSE;
+ return TRUE;
+}
+/* </settings checks> */
+
+static void strarr_push(ARRAY_TYPE(const_string) *argv, const char *str)
+{
+ array_push_back(argv, &str);
+}
+
+static void ATTR_NORETURN
+master_service_exec_config(struct master_service *service,
+ const struct master_service_settings_input *input)
+{
+ ARRAY_TYPE(const_string) conf_argv;
+ const char *binary_path = service->argv[0];
+ const char *error = NULL;
+
+ if (!t_binary_abspath(&binary_path, &error)) {
+ i_fatal("t_binary_abspath(%s) failed: %s", binary_path, error);
+ }
+
+ if (!service->keep_environment && !input->preserve_environment) {
+ if (input->preserve_home)
+ master_service_import_environment("HOME");
+ if (input->preserve_user)
+ master_service_import_environment("USER");
+ if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0)
+ master_service_import_environment("LOG_STDERR_TIMESTAMP");
+
+ /* doveconf empties the environment before exec()ing us back
+ if DOVECOT_PRESERVE_ENVS is set, so make sure it is. */
+ if (getenv(DOVECOT_PRESERVE_ENVS_ENV) == NULL)
+ env_put(DOVECOT_PRESERVE_ENVS_ENV, "");
+ } else {
+ /* make sure doveconf doesn't remove any environment */
+ env_remove(DOVECOT_PRESERVE_ENVS_ENV);
+ }
+ if (input->use_sysexits)
+ env_put("USE_SYSEXITS", "1");
+
+ t_array_init(&conf_argv, 11 + (service->argc + 1) + 1);
+ strarr_push(&conf_argv, DOVECOT_CONFIG_BIN_PATH);
+ if (input->service != NULL) {
+ strarr_push(&conf_argv, "-f");
+ strarr_push(&conf_argv,
+ t_strconcat("service=", input->service, NULL));
+ }
+ strarr_push(&conf_argv, "-c");
+ strarr_push(&conf_argv, service->config_path);
+ if (input->module != NULL) {
+ strarr_push(&conf_argv, "-m");
+ strarr_push(&conf_argv, input->module);
+ }
+ if (input->extra_modules != NULL) {
+ for (unsigned int i = 0; input->extra_modules[i] != NULL; i++) {
+ strarr_push(&conf_argv, "-m");
+ strarr_push(&conf_argv, input->extra_modules[i]);
+ }
+ }
+ if ((service->flags & MASTER_SERVICE_FLAG_DISABLE_SSL_SET) == 0 &&
+ (input->module != NULL || input->extra_modules != NULL)) {
+ strarr_push(&conf_argv, "-m");
+ if (service->want_ssl_server)
+ strarr_push(&conf_argv, "ssl-server");
+ else
+ strarr_push(&conf_argv, "ssl");
+ }
+ if (input->parse_full_config)
+ strarr_push(&conf_argv, "-p");
+
+ strarr_push(&conf_argv, "-e");
+ strarr_push(&conf_argv, binary_path);
+ array_append(&conf_argv, (const char *const *)service->argv + 1,
+ service->argc);
+ array_append_zero(&conf_argv);
+
+ const char *const *argv = array_front(&conf_argv);
+ execv_const(argv[0], argv);
+}
+
+static void
+config_error_update_path_source(struct master_service *service,
+ const struct master_service_settings_input *input,
+ const char **error)
+{
+ if (input->config_path == NULL && service->config_path_from_master) {
+ *error = t_strdup_printf("%s (path is from %s environment)",
+ *error, MASTER_CONFIG_FILE_ENV);
+ }
+}
+
+static void
+config_exec_fallback(struct master_service *service,
+ const struct master_service_settings_input *input,
+ const char **error)
+{
+ const char *path, *stat_error;
+ struct stat st;
+ int saved_errno = errno;
+
+ if (input->never_exec) {
+ *error = t_strdup_printf(
+ "%s - doveconf execution fallback is disabled", *error);
+ return;
+ }
+
+ path = input->config_path != NULL ? input->config_path :
+ master_service_get_config_path(service);
+ if (stat(path, &st) < 0)
+ stat_error = t_strdup_printf("stat(%s) failed: %m", path);
+ else if (S_ISSOCK(st.st_mode))
+ stat_error = t_strdup_printf("%s is a UNIX socket", path);
+ else if (S_ISFIFO(st.st_mode))
+ stat_error = t_strdup_printf("%s is a FIFO", path);
+ else {
+ /* it's a file, not a socket/pipe */
+ master_service_exec_config(service, input);
+ }
+ *error = t_strdup_printf(
+ "%s - Also failed to read config by executing doveconf: %s",
+ *error, stat_error);
+ config_error_update_path_source(service, input, error);
+ errno = saved_errno;
+}
+
+static int
+master_service_open_config(struct master_service *service,
+ const struct master_service_settings_input *input,
+ const char **path_r, const char **error_r)
+{
+ struct stat st;
+ const char *path;
+ int fd;
+
+ *path_r = path = input->config_path != NULL ? input->config_path :
+ master_service_get_config_path(service);
+
+ if (service->config_fd != -1 && input->config_path == NULL &&
+ !service->config_path_changed_with_param) {
+ /* use the already opened config socket */
+ fd = service->config_fd;
+ service->config_fd = -1;
+ return fd;
+ }
+
+ if (!service->config_path_from_master &&
+ !service->config_path_changed_with_param &&
+ input->config_path == NULL) {
+ /* first try to connect to the default config socket.
+ configuration may contain secrets, so in default config
+ this fails because the socket is 0600. it's useful for
+ developers though. :) */
+ fd = net_connect_unix(DOVECOT_CONFIG_SOCKET_PATH);
+ if (fd >= 0) {
+ *path_r = DOVECOT_CONFIG_SOCKET_PATH;
+ net_set_nonblock(fd, FALSE);
+ return fd;
+ }
+ /* fallback to executing doveconf */
+ }
+
+ if (stat(path, &st) < 0) {
+ *error_r = errno == EACCES ? eacces_error_get("stat", path) :
+ t_strdup_printf("stat(%s) failed: %m", path);
+ config_error_update_path_source(service, input, error_r);
+ return -1;
+ }
+
+ if (!S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode)) {
+ /* it's not an UNIX socket, don't even try to connect */
+ fd = -1;
+ errno = ENOTSOCK;
+ } else {
+ fd = net_connect_unix_with_retries(path, 1000);
+ }
+ if (fd < 0) {
+ *error_r = t_strdup_printf("net_connect_unix(%s) failed: %m",
+ path);
+ config_exec_fallback(service, input, error_r);
+ return -1;
+ }
+ net_set_nonblock(fd, FALSE);
+ return fd;
+}
+
+static void
+config_build_request(struct master_service *service, string_t *str,
+ const struct master_service_settings_input *input)
+{
+ str_append(str, "REQ");
+ if (input->module != NULL)
+ str_printfa(str, "\tmodule=%s", input->module);
+ if (input->extra_modules != NULL) {
+ for (unsigned int i = 0; input->extra_modules[i] != NULL; i++)
+ str_printfa(str, "\tmodule=%s", input->extra_modules[i]);
+ }
+ if ((service->flags & MASTER_SERVICE_FLAG_DISABLE_SSL_SET) == 0 &&
+ (input->module != NULL || input->extra_modules != NULL)) {
+ str_printfa(str, "\tmodule=%s",
+ service->want_ssl_server ? "ssl-server" : "ssl");
+ }
+ if (input->no_ssl_ca)
+ str_append(str, "\texclude=ssl_ca\texclude=ssl_verify_client_cert");
+ if (input->service != NULL)
+ str_printfa(str, "\tservice=%s", input->service);
+ if (input->username != NULL)
+ str_printfa(str, "\tuser=%s", input->username);
+ if (input->local_ip.family != 0)
+ str_printfa(str, "\tlip=%s", net_ip2addr(&input->local_ip));
+ if (input->remote_ip.family != 0)
+ str_printfa(str, "\trip=%s", net_ip2addr(&input->remote_ip));
+ if (input->local_name != NULL)
+ str_printfa(str, "\tlname=%s", input->local_name);
+ str_append_c(str, '\n');
+}
+
+static int
+config_send_request(struct master_service *service,
+ const struct master_service_settings_input *input,
+ int fd, const char *path, const char **error_r)
+{
+ int ret;
+
+ T_BEGIN {
+ string_t *str;
+
+ str = t_str_new(128);
+ str_append(str, CONFIG_HANDSHAKE);
+ config_build_request(service, str, input);
+ ret = write_full(fd, str_data(str), str_len(str));
+ } T_END;
+ if (ret < 0) {
+ *error_r = t_strdup_printf("write_full(%s) failed: %m", path);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+config_send_filters_request(int fd, const char *path, const char **error_r)
+{
+ int ret;
+ ret = write_full(fd, CONFIG_HANDSHAKE"FILTERS\n", strlen(CONFIG_HANDSHAKE"FILTERS\n"));
+ if (ret < 0) {
+ *error_r = t_strdup_printf("write_full(%s) failed: %m", path);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+master_service_apply_config_overrides(struct master_service *service,
+ struct setting_parser_context *parser,
+ const char **error_r)
+{
+ const char *const *overrides;
+ unsigned int i, count;
+
+ overrides = array_get(&service->config_overrides, &count);
+ for (i = 0; i < count; i++) {
+ if (settings_parse_line(parser, overrides[i]) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid -o parameter %s: %s", overrides[i],
+ settings_parser_get_error(parser));
+ return -1;
+ }
+ settings_parse_set_key_expanded(parser, service->set_pool,
+ t_strcut(overrides[i], '='));
+ }
+ return 0;
+}
+
+static int
+config_read_reply_header(struct istream *istream, const char *path, pool_t pool,
+ const struct master_service_settings_input *input,
+ struct master_service_settings_output *output_r,
+ const char **error_r)
+{
+ const char *line;
+ ssize_t ret;
+
+ while ((ret = i_stream_read(istream)) > 0) {
+ line = i_stream_next_line(istream);
+ if (line != NULL)
+ break;
+ }
+ if (ret <= 0) {
+ if (ret == 0)
+ return 1;
+ *error_r = istream->stream_errno != 0 ?
+ t_strdup_printf("read(%s) failed: %s", path,
+ i_stream_get_error(istream)) :
+ t_strdup_printf("read(%s) failed: EOF", path);
+ return -1;
+ }
+
+ T_BEGIN {
+ const char *const *arg = t_strsplit_tabescaped(line);
+ ARRAY_TYPE(const_string) services;
+
+ p_array_init(&services, pool, 8);
+ for (; *arg != NULL; arg++) {
+ if (strcmp(*arg, "service-uses-local") == 0)
+ output_r->service_uses_local = TRUE;
+ else if (strcmp(*arg, "service-uses-remote") == 0)
+ output_r->service_uses_remote = TRUE;
+ if (strcmp(*arg, "used-local") == 0)
+ output_r->used_local = TRUE;
+ else if (strcmp(*arg, "used-remote") == 0)
+ output_r->used_remote = TRUE;
+ else if (str_begins(*arg, "service=")) {
+ const char *name = p_strdup(pool, *arg + 8);
+ array_push_back(&services, &name);
+ }
+ }
+ if (input->service == NULL) {
+ array_append_zero(&services);
+ output_r->specific_services = array_front(&services);
+ }
+ } T_END;
+ return 0;
+}
+
+void master_service_config_socket_try_open(struct master_service *service)
+{
+ struct master_service_settings_input input;
+ const char *path, *error;
+ int fd;
+
+ /* we'll get here before command line parameters have been parsed,
+ so -O, -c and -i parameters haven't been handled yet at this point.
+ this means we could end up opening config socket connection
+ unnecessarily, but this isn't a problem. we'll just have to
+ ignore it later on. (unfortunately there isn't a master_service_*()
+ call where this function would be better called.) */
+ if (getenv("DOVECONF_ENV") != NULL ||
+ (service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) != 0)
+ return;
+
+ i_zero(&input);
+ input.never_exec = TRUE;
+ fd = master_service_open_config(service, &input, &path, &error);
+ if (fd != -1)
+ service->config_fd = fd;
+}
+
+int master_service_settings_get_filters(struct master_service *service,
+ const char *const **filters,
+ const char **error_r)
+{
+ struct master_service_settings_input input;
+ int fd;
+ bool retry = TRUE;
+ const char *path = NULL;
+ ARRAY_TYPE(const_string) filters_tmp;
+ t_array_init(&filters_tmp, 8);
+ i_zero(&input);
+
+ if (getenv("DOVECONF_ENV") == NULL &&
+ (service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) == 0) {
+ retry = service->config_fd != -1;
+ for (;;) {
+ fd = master_service_open_config(service, &input, &path, error_r);
+ if (fd == -1) {
+ return -1;
+ }
+ if (config_send_filters_request(fd, path, error_r) == 0)
+ break;
+
+ i_close_fd(&fd);
+ if (!retry)
+ return -1;
+ retry = FALSE;
+ }
+ service->config_fd = fd;
+ struct istream *is = i_stream_create_fd(fd, SIZE_MAX);
+ const char *line;
+ /* try read response */
+ while((line = i_stream_read_next_line(is)) != NULL) {
+ if (*line == '\0')
+ break;
+ if (str_begins(line, "FILTER\t")) {
+ line = t_strdup(line+7);
+ array_push_back(&filters_tmp, &line);
+ }
+ }
+ i_stream_unref(&is);
+ }
+
+ array_append_zero(&filters_tmp);
+ *filters = array_front(&filters_tmp);
+ return 0;
+}
+
+int master_service_settings_read(struct master_service *service,
+ const struct master_service_settings_input *input,
+ struct master_service_settings_output *output_r,
+ const char **error_r)
+{
+ ARRAY(const struct setting_parser_info *) all_roots;
+ const struct setting_parser_info *tmp_root;
+ struct setting_parser_context *parser;
+ struct istream *istream;
+ const char *path = NULL, *error;
+ void **sets;
+ unsigned int i;
+ int ret, fd = -1;
+ time_t now, timeout;
+ bool use_environment, retry;
+
+ i_zero(output_r);
+
+ if (getenv("DOVECONF_ENV") == NULL &&
+ (service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) == 0) {
+ retry = service->config_fd != -1;
+ for (;;) {
+ fd = master_service_open_config(service, input,
+ &path, error_r);
+ if (fd == -1) {
+ if (errno == EACCES)
+ output_r->permission_denied = TRUE;
+ return -1;
+ }
+
+ if (config_send_request(service, input, fd,
+ path, error_r) == 0)
+ break;
+ i_close_fd(&fd);
+ if (!retry) {
+ config_exec_fallback(service, input, error_r);
+ return -1;
+ }
+ /* config process died, retry connecting */
+ retry = FALSE;
+ }
+ }
+
+ if (service->set_pool != NULL) {
+ if (service->set_parser != NULL)
+ settings_parser_deinit(&service->set_parser);
+ p_clear(service->set_pool);
+ } else {
+ service->set_pool =
+ pool_alloconly_create("master service settings", 16384);
+ }
+
+ p_array_init(&all_roots, service->set_pool, 8);
+ tmp_root = &master_service_setting_parser_info;
+ array_push_back(&all_roots, &tmp_root);
+ tmp_root = &master_service_ssl_setting_parser_info;
+ array_push_back(&all_roots, &tmp_root);
+ if (service->want_ssl_server) {
+ tmp_root = &master_service_ssl_server_setting_parser_info;
+ array_push_back(&all_roots, &tmp_root);
+ }
+ if (input->roots != NULL) {
+ for (i = 0; input->roots[i] != NULL; i++)
+ array_push_back(&all_roots, &input->roots[i]);
+ }
+
+ parser = settings_parser_init_list(service->set_pool,
+ array_front(&all_roots), array_count(&all_roots),
+ SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS);
+
+ if (fd != -1) {
+ istream = i_stream_create_fd(fd, SIZE_MAX);
+ now = time(NULL);
+ timeout = now + CONFIG_READ_TIMEOUT_SECS;
+ do {
+ alarm(timeout - now);
+ ret = config_read_reply_header(istream, path,
+ service->set_pool, input,
+ output_r, error_r);
+ if (ret == 0) {
+ ret = settings_parse_stream_read(parser,
+ istream);
+ if (ret < 0)
+ *error_r = t_strdup(
+ settings_parser_get_error(parser));
+ }
+ alarm(0);
+ if (ret <= 0)
+ break;
+
+ /* most likely timed out, but just in case some other
+ signal was delivered early check if we need to
+ continue */
+ now = time(NULL);
+ } while (now < timeout);
+ i_stream_unref(&istream);
+
+ if (ret != 0) {
+ if (ret > 0) {
+ *error_r = t_strdup_printf(
+ "Timeout reading config from %s", path);
+ }
+ i_close_fd(&fd);
+ config_exec_fallback(service, input, error_r);
+ settings_parser_deinit(&parser);
+ return -1;
+ }
+
+ if ((service->flags & MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN) != 0 &&
+ service->config_fd == -1 && input->config_path == NULL)
+ service->config_fd = fd;
+ else
+ i_close_fd(&fd);
+ use_environment = FALSE;
+ } else {
+ use_environment = TRUE;
+ }
+
+ if (use_environment || service->keep_environment) {
+ if (settings_parse_environ(parser) < 0) {
+ *error_r = t_strdup(settings_parser_get_error(parser));
+ settings_parser_deinit(&parser);
+ return -1;
+ }
+ }
+
+ if (array_is_created(&service->config_overrides)) {
+ if (master_service_apply_config_overrides(service, parser,
+ error_r) < 0) {
+ settings_parser_deinit(&parser);
+ return -1;
+ }
+ }
+
+ if (!settings_parser_check(parser, service->set_pool, &error)) {
+ *error_r = t_strdup_printf("Invalid settings: %s", error);
+ settings_parser_deinit(&parser);
+ return -1;
+ }
+
+ sets = settings_parser_get_list(parser);
+ service->set = sets[0];
+ service->set_parser = parser;
+
+ if (service->set->version_ignore &&
+ (service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) {
+ /* running standalone. we want to ignore plugin versions. */
+ service->version_string = NULL;
+ }
+ if ((service->flags & MASTER_SERVICE_FLAG_DONT_SEND_STATS) == 0 &&
+ (service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) {
+ /* When running standalone (e.g. doveadm) try to connect to the
+ stats socket, but don't log an error if it's not running.
+ It may be intentional. Non-standalone stats-client
+ initialization was already done earlier. */
+ master_service_init_stats_client(service, TRUE);
+ }
+
+ if (service->set->shutdown_clients)
+ master_service_set_die_with_master(master_service, TRUE);
+
+ /* if we change any settings afterwards, they're in expanded form.
+ especially all settings from userdb are already expanded. */
+ settings_parse_set_expanded(service->set_parser, TRUE);
+ return 0;
+}
+
+int master_service_settings_read_simple(struct master_service *service,
+ const struct setting_parser_info **roots,
+ const char **error_r)
+{
+ struct master_service_settings_input input;
+ struct master_service_settings_output output;
+
+ i_zero(&input);
+ input.roots = roots;
+ input.module = service->name;
+ return master_service_settings_read(service, &input, &output, error_r);
+}
+
+pool_t master_service_settings_detach(struct master_service *service)
+{
+ pool_t pool = service->set_pool;
+
+ settings_parser_deinit(&service->set_parser);
+ service->set_pool = NULL;
+ return pool;
+}
+
+const struct master_service_settings *
+master_service_settings_get(struct master_service *service)
+{
+ void **sets;
+
+ sets = settings_parser_get_list(service->set_parser);
+ return sets[0];
+}
+
+void **master_service_settings_get_others(struct master_service *service)
+{
+ return master_service_settings_parser_get_others(service,
+ service->set_parser);
+}
+
+void **master_service_settings_parser_get_others(struct master_service *service,
+ const struct setting_parser_context *set_parser)
+{
+ return settings_parser_get_list(set_parser) + 2 +
+ (service->want_ssl_server ? 1 : 0);
+}
+
+struct setting_parser_context *
+master_service_get_settings_parser(struct master_service *service)
+{
+ return service->set_parser;
+}
+
+int master_service_set(struct master_service *service, const char *line)
+{
+ return settings_parse_line(service->set_parser, line);
+}
+
+bool master_service_set_has_config_override(struct master_service *service,
+ const char *key)
+{
+ const char *override, *key_root;
+ bool ret;
+
+ if (!array_is_created(&service->config_overrides))
+ return FALSE;
+
+ key_root = settings_parse_unalias(service->set_parser, key);
+ if (key_root == NULL)
+ key_root = key;
+
+ array_foreach_elem(&service->config_overrides, override) {
+ T_BEGIN {
+ const char *okey, *okey_root;
+
+ okey = t_strcut(override, '=');
+ okey_root = settings_parse_unalias(service->set_parser,
+ okey);
+ if (okey_root == NULL)
+ okey_root = okey;
+ ret = strcmp(okey_root, key_root) == 0;
+ } T_END;
+
+ if (ret)
+ return TRUE;
+ }
+ return FALSE;
+}
diff --git a/src/lib-master/master-service-settings.h b/src/lib-master/master-service-settings.h
new file mode 100644
index 0000000..eceec0c
--- /dev/null
+++ b/src/lib-master/master-service-settings.h
@@ -0,0 +1,115 @@
+#ifndef MASTER_SERVICE_SETTINGS_H
+#define MASTER_SERVICE_SETTINGS_H
+
+#include "net.h"
+
+struct setting_parser_info;
+struct setting_parser_context;
+struct master_service;
+
+struct master_service_settings {
+ /* NOTE: log process won't see any new settings unless they're
+ explicitly sent via environment variables by master process. */
+ const char *base_dir;
+ const char *state_dir;
+ const char *instance_name;
+ const char *log_path;
+ const char *info_log_path;
+ const char *debug_log_path;
+ const char *log_timestamp;
+ const char *log_debug;
+ const char *log_core_filter;
+ const char *process_shutdown_filter;
+ const char *syslog_facility;
+ const char *import_environment;
+ const char *stats_writer_socket_path;
+ uoff_t config_cache_size;
+ bool version_ignore;
+ bool shutdown_clients;
+ bool verbose_proctitle;
+
+ const char *haproxy_trusted_networks;
+ unsigned int haproxy_timeout;
+};
+
+struct master_service_settings_input {
+ const struct setting_parser_info *const *roots;
+ const char *config_path;
+ bool preserve_environment;
+ bool preserve_user;
+ bool preserve_home;
+ bool never_exec;
+ bool use_sysexits;
+ bool parse_full_config;
+
+ /* Either/both module and extra_modules can be set. Usually just one
+ is needed, so module is simpler to set. */
+ const char *module;
+ const char *const *extra_modules;
+ const char *service;
+ const char *username;
+ struct ip_addr local_ip, remote_ip;
+ const char *local_name;
+
+ /* A bit of a memory saving kludge: Mail processes (especially imap)
+ shouldn't read ssl_ca setting since it's likely not needed and it
+ can use a lot of memory. */
+ bool no_ssl_ca;
+};
+
+struct master_service_settings_output {
+ /* if service was not given for lookup, this contains names of services
+ that have more specific settings */
+ const char *const *specific_services;
+
+ /* some settings for this service (or if service was not given,
+ all services) contain local/remote ip/host specific settings
+ (but this lookup didn't necessarily return any of them). */
+ bool service_uses_local:1;
+ bool service_uses_remote:1;
+ /* returned settings contain settings specific to given
+ local/remote ip/host */
+ bool used_local:1;
+ bool used_remote:1;
+ /* Config couldn't be read because we don't have enough permissions.
+ The process probably should be restarted and the settings read
+ before dropping privileges. */
+ bool permission_denied:1;
+};
+
+extern const struct setting_parser_info master_service_setting_parser_info;
+
+/* Try to open the config socket if it's going to be needed later by
+ master_service_settings_read*() */
+void master_service_config_socket_try_open(struct master_service *service);
+int master_service_settings_get_filters(struct master_service *service,
+ const char *const **filters,
+ const char **error_r);
+int master_service_settings_read(struct master_service *service,
+ const struct master_service_settings_input *input,
+ struct master_service_settings_output *output_r,
+ const char **error_r);
+int master_service_settings_read_simple(struct master_service *service,
+ const struct setting_parser_info **roots,
+ const char **error_r) ATTR_NULL(2);
+/* destroy settings parser and clear service's set_pool, so that
+ master_service_settings_read*() can be called without freeing memory used
+ by existing settings structures. */
+pool_t master_service_settings_detach(struct master_service *service);
+
+const struct master_service_settings *
+master_service_settings_get(struct master_service *service);
+void **master_service_settings_get_others(struct master_service *service);
+void **master_service_settings_parser_get_others(struct master_service *service,
+ const struct setting_parser_context *set_parser);
+struct setting_parser_context *
+master_service_get_settings_parser(struct master_service *service);
+
+int master_service_set(struct master_service *service, const char *line);
+
+/* Returns TRUE if -o key=value parameter was used. Setting keys in overrides
+ and parameter are unaliased before comparing. */
+bool master_service_set_has_config_override(struct master_service *service,
+ const char *key);
+
+#endif
diff --git a/src/lib-master/master-service-ssl-settings.c b/src/lib-master/master-service-ssl-settings.c
new file mode 100644
index 0000000..5ddf18c
--- /dev/null
+++ b/src/lib-master/master-service-ssl-settings.c
@@ -0,0 +1,275 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "settings-parser.h"
+#include "master-service-private.h"
+#include "master-service-ssl-settings.h"
+#include "iostream-ssl.h"
+
+#include <stddef.h>
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct master_service_ssl_settings)
+
+static bool
+master_service_ssl_settings_check(void *_set, pool_t pool, const char **error_r);
+
+static const struct setting_define master_service_ssl_setting_defines[] = {
+ DEF(ENUM, ssl),
+ DEF(STR, ssl_ca),
+ DEF(STR, ssl_client_ca_file),
+ DEF(STR, ssl_client_ca_dir),
+ DEF(STR, ssl_client_cert),
+ DEF(STR, ssl_client_key),
+ DEF(STR, ssl_cipher_list),
+ DEF(STR, ssl_cipher_suites),
+ DEF(STR, ssl_curve_list),
+ DEF(STR, ssl_min_protocol),
+ DEF(STR, ssl_cert_username_field),
+ DEF(STR, ssl_crypto_device),
+ DEF(BOOL, ssl_verify_client_cert),
+ DEF(BOOL, ssl_client_require_valid_cert),
+ DEF(BOOL, ssl_require_crl),
+ DEF(BOOL, verbose_ssl),
+ DEF(BOOL, ssl_prefer_server_ciphers),
+ DEF(STR, ssl_options), /* parsed as a string to set bools */
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct master_service_ssl_settings master_service_ssl_default_settings = {
+#ifdef HAVE_SSL
+ .ssl = "yes:no:required",
+#else
+ .ssl = "no:yes:required",
+#endif
+ .ssl_ca = "",
+ .ssl_client_ca_file = "",
+ .ssl_client_ca_dir = "",
+ .ssl_client_cert = "",
+ .ssl_client_key = "",
+ .ssl_cipher_list = "ALL:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!LOW@STRENGTH",
+ .ssl_cipher_suites = "", /* Use TLS library provided value */
+ .ssl_curve_list = "",
+ .ssl_min_protocol = "TLSv1.2",
+ .ssl_cert_username_field = "commonName",
+ .ssl_crypto_device = "",
+ .ssl_verify_client_cert = FALSE,
+ .ssl_client_require_valid_cert = TRUE,
+ .ssl_require_crl = TRUE,
+ .verbose_ssl = FALSE,
+ .ssl_prefer_server_ciphers = FALSE,
+ .ssl_options = "",
+};
+
+const struct setting_parser_info master_service_ssl_setting_parser_info = {
+ .module_name = "ssl",
+ .defines = master_service_ssl_setting_defines,
+ .defaults = &master_service_ssl_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct master_service_ssl_settings),
+
+ .parent_offset = SIZE_MAX,
+ .check_func = master_service_ssl_settings_check
+};
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct master_service_ssl_server_settings)
+
+static const struct setting_define master_service_ssl_server_setting_defines[] = {
+ DEF(STR, ssl_cert),
+ DEF(STR, ssl_key),
+ DEF(STR, ssl_alt_cert),
+ DEF(STR, ssl_alt_key),
+ DEF(STR, ssl_key_password),
+ DEF(STR, ssl_dh),
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct master_service_ssl_server_settings master_service_ssl_server_default_settings = {
+ .ssl_cert = "",
+ .ssl_key = "",
+ .ssl_alt_cert = "",
+ .ssl_alt_key = "",
+ .ssl_key_password = "",
+ .ssl_dh = "",
+};
+
+static const struct setting_parser_info *master_service_ssl_server_setting_dependencies[] = {
+ &master_service_ssl_setting_parser_info,
+ NULL
+};
+
+const struct setting_parser_info master_service_ssl_server_setting_parser_info = {
+ .module_name = "ssl-server",
+ .defines = master_service_ssl_server_setting_defines,
+ .defaults = &master_service_ssl_server_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct master_service_ssl_server_settings),
+
+ .parent_offset = SIZE_MAX,
+ .dependencies = master_service_ssl_server_setting_dependencies,
+};
+
+/* <settings checks> */
+static bool
+master_service_ssl_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+ const char **error_r)
+{
+ struct master_service_ssl_settings *set = _set;
+
+ if (strcmp(set->ssl, "no") == 0) {
+ /* disabled */
+ return TRUE;
+ }
+#ifndef HAVE_SSL
+ *error_r = t_strdup_printf("SSL support not compiled in but ssl=%s",
+ set->ssl);
+ return FALSE;
+#else
+ /* we get called from many different tools, possibly with -O parameter,
+ and few of those tools care about SSL settings. so don't check
+ ssl_cert/ssl_key/etc validity here except in doveconf, because it
+ usually is just an extra annoyance. */
+#ifdef CONFIG_BINARY
+ if (*set->ssl_cert == '\0') {
+ *error_r = "ssl enabled, but ssl_cert not set";
+ return FALSE;
+ }
+ if (*set->ssl_key == '\0') {
+ *error_r = "ssl enabled, but ssl_key not set";
+ return FALSE;
+ }
+#endif
+ if (set->ssl_verify_client_cert && *set->ssl_ca == '\0') {
+ *error_r = "ssl_verify_client_cert set, but ssl_ca not";
+ return FALSE;
+ }
+
+ /* Now explode the ssl_options string into individual flags */
+ /* First set them all to defaults */
+ set->parsed_opts.compression = FALSE;
+ set->parsed_opts.tickets = TRUE;
+
+ /* Then modify anything specified in the string */
+ const char **opts = t_strsplit_spaces(set->ssl_options, ", ");
+ const char *opt;
+ while ((opt = *opts++) != NULL) {
+ if (strcasecmp(opt, "compression") == 0) {
+ set->parsed_opts.compression = TRUE;
+ } else if (strcasecmp(opt, "no_compression") == 0) {
+#ifdef CONFIG_BINARY
+ i_warning("DEPRECATED: no_compression is default, "
+ "so it is redundant in ssl_options");
+#endif
+ } else if (strcasecmp(opt, "no_ticket") == 0) {
+ set->parsed_opts.tickets = FALSE;
+ } else {
+ *error_r = t_strdup_printf("ssl_options: unknown flag: '%s'",
+ opt);
+ return FALSE;
+ }
+ }
+
+#ifndef HAVE_SSL_CTX_SET1_CURVES_LIST
+ if (*set->ssl_curve_list != '\0') {
+ *error_r = "ssl_curve_list is set, but the linked openssl "
+ "version does not support it";
+ return FALSE;
+ }
+#endif
+
+ return TRUE;
+#endif
+}
+/* </settings checks> */
+
+const struct master_service_ssl_settings *
+master_service_ssl_settings_get(struct master_service *service)
+{
+ return master_service_ssl_settings_get_from_parser(service->set_parser);
+}
+
+const struct master_service_ssl_settings *
+master_service_ssl_settings_get_from_parser(struct setting_parser_context *set_parser)
+{
+ void **sets;
+
+ sets = settings_parser_get_list(set_parser);
+ return sets[1];
+}
+
+const struct master_service_ssl_server_settings *
+master_service_ssl_server_settings_get(struct master_service *service)
+{
+ void **sets;
+
+ i_assert(service->want_ssl_server);
+ sets = settings_parser_get_list(service->set_parser);
+ return sets[2];
+}
+
+static void master_service_ssl_common_settings_to_iostream_set(
+ const struct master_service_ssl_settings *ssl_set, pool_t pool,
+ struct ssl_iostream_settings *set_r)
+{
+ i_zero(set_r);
+ set_r->min_protocol = p_strdup(pool, ssl_set->ssl_min_protocol);
+ set_r->cipher_list = p_strdup(pool, ssl_set->ssl_cipher_list);
+ /* leave NULL if empty - let library decide */
+ set_r->ciphersuites = p_strdup_empty(pool, ssl_set->ssl_cipher_suites);
+ /* NOTE: It's a bit questionable whether ssl_ca should be used for
+ clients. But at least for now it's needed for login-proxy. */
+ set_r->ca = p_strdup_empty(pool, ssl_set->ssl_ca);
+
+ set_r->crypto_device = p_strdup(pool, ssl_set->ssl_crypto_device);
+ set_r->cert_username_field = p_strdup(pool, ssl_set->ssl_cert_username_field);
+
+ set_r->verbose = ssl_set->verbose_ssl;
+ set_r->verbose_invalid_cert = ssl_set->verbose_ssl;
+ set_r->skip_crl_check = !ssl_set->ssl_require_crl;
+ set_r->prefer_server_ciphers = ssl_set->ssl_prefer_server_ciphers;
+ set_r->compression = ssl_set->parsed_opts.compression;
+ set_r->tickets = ssl_set->parsed_opts.tickets;
+ set_r->curve_list = p_strdup(pool, ssl_set->ssl_curve_list);
+}
+
+void master_service_ssl_client_settings_to_iostream_set(
+ const struct master_service_ssl_settings *ssl_set, pool_t pool,
+ struct ssl_iostream_settings *set_r)
+{
+ master_service_ssl_common_settings_to_iostream_set(ssl_set, pool, set_r);
+
+ set_r->ca_file = p_strdup_empty(pool, ssl_set->ssl_client_ca_file);
+ set_r->ca_dir = p_strdup_empty(pool, ssl_set->ssl_client_ca_dir);
+ set_r->cert.cert = p_strdup_empty(pool, ssl_set->ssl_client_cert);
+ set_r->cert.key = p_strdup_empty(pool, ssl_set->ssl_client_key);
+ set_r->verify_remote_cert = ssl_set->ssl_client_require_valid_cert;
+ set_r->allow_invalid_cert = !set_r->verify_remote_cert;
+}
+
+void master_service_ssl_server_settings_to_iostream_set(
+ const struct master_service_ssl_settings *ssl_set,
+ const struct master_service_ssl_server_settings *ssl_server_set,
+ pool_t pool, struct ssl_iostream_settings *set_r)
+{
+ master_service_ssl_common_settings_to_iostream_set(ssl_set, pool, set_r);
+
+ set_r->cert.cert = p_strdup(pool, ssl_server_set->ssl_cert);
+ set_r->cert.key = p_strdup(pool, ssl_server_set->ssl_key);
+ set_r->cert.key_password = p_strdup(pool, ssl_server_set->ssl_key_password);
+ if (ssl_server_set->ssl_alt_cert != NULL &&
+ *ssl_server_set->ssl_alt_cert != '\0') {
+ set_r->alt_cert.cert = p_strdup(pool, ssl_server_set->ssl_alt_cert);
+ set_r->alt_cert.key = p_strdup(pool, ssl_server_set->ssl_alt_key);
+ set_r->alt_cert.key_password = p_strdup(pool, ssl_server_set->ssl_key_password);
+ }
+ set_r->dh = p_strdup(pool, ssl_server_set->ssl_dh);
+ set_r->verify_remote_cert = ssl_set->ssl_verify_client_cert;
+ set_r->allow_invalid_cert = !set_r->verify_remote_cert;
+}
diff --git a/src/lib-master/master-service-ssl-settings.h b/src/lib-master/master-service-ssl-settings.h
new file mode 100644
index 0000000..01290ab
--- /dev/null
+++ b/src/lib-master/master-service-ssl-settings.h
@@ -0,0 +1,65 @@
+#ifndef MASTER_SERVICE_SSL_SETTINGS_H
+#define MASTER_SERVICE_SSL_SETTINGS_H
+
+struct master_service;
+struct setting_parser_context;
+struct ssl_iostream_settings;
+
+struct master_service_ssl_settings {
+ const char *ssl;
+ const char *ssl_ca;
+ const char *ssl_client_ca_file;
+ const char *ssl_client_ca_dir;
+ const char *ssl_client_cert;
+ const char *ssl_client_key;
+ const char *ssl_cipher_list;
+ const char *ssl_cipher_suites;
+ const char *ssl_curve_list;
+ const char *ssl_min_protocol;
+ const char *ssl_cert_username_field;
+ const char *ssl_crypto_device;
+ const char *ssl_options;
+
+ bool ssl_verify_client_cert;
+ bool ssl_client_require_valid_cert;
+ bool ssl_require_crl;
+ bool verbose_ssl;
+ bool ssl_prefer_server_ciphers;
+
+ /* These are derived from ssl_options, not set directly */
+ struct {
+ bool compression;
+ bool tickets;
+ } parsed_opts;
+};
+
+struct master_service_ssl_server_settings {
+ const char *ssl_cert;
+ const char *ssl_alt_cert;
+ const char *ssl_key;
+ const char *ssl_alt_key;
+ const char *ssl_key_password;
+ const char *ssl_dh;
+};
+
+extern const struct setting_parser_info master_service_ssl_setting_parser_info;
+extern const struct setting_parser_info master_service_ssl_server_setting_parser_info;
+
+const struct master_service_ssl_settings *
+master_service_ssl_settings_get(struct master_service *service);
+const struct master_service_ssl_settings *
+master_service_ssl_settings_get_from_parser(struct setting_parser_context *set_parser);
+
+const struct master_service_ssl_server_settings *
+master_service_ssl_server_settings_get(struct master_service *service);
+
+/* Provides master service ssl settings to iostream settings */
+void master_service_ssl_client_settings_to_iostream_set(
+ const struct master_service_ssl_settings *ssl_set, pool_t pool,
+ struct ssl_iostream_settings *set_r);
+void master_service_ssl_server_settings_to_iostream_set(
+ const struct master_service_ssl_settings *ssl_set,
+ const struct master_service_ssl_server_settings *ssl_server_set,
+ pool_t pool, struct ssl_iostream_settings *set_r);
+
+#endif
diff --git a/src/lib-master/master-service-ssl.c b/src/lib-master/master-service-ssl.c
new file mode 100644
index 0000000..5d1b515
--- /dev/null
+++ b/src/lib-master/master-service-ssl.c
@@ -0,0 +1,105 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "buffer.h"
+#include "iostream-ssl.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+#include "master-service-ssl-settings.h"
+#include "master-service-ssl.h"
+
+#include <unistd.h>
+
+int master_service_ssl_init(struct master_service *service,
+ struct istream **input, struct ostream **output,
+ struct ssl_iostream **ssl_iostream_r,
+ const char **error_r)
+{
+ const struct master_service_ssl_settings *set;
+ struct ssl_iostream_settings ssl_set;
+
+ i_assert(service->ssl_ctx_initialized);
+
+ set = master_service_ssl_settings_get(service);
+ if (service->ssl_ctx == NULL) {
+ if (strcmp(set->ssl, "no") == 0)
+ *error_r = "SSL is disabled (ssl=no)";
+ else
+ *error_r = "Failed to initialize SSL context";
+ return -1;
+ }
+
+ i_zero(&ssl_set);
+ ssl_set.verbose = set->verbose_ssl;
+ ssl_set.verify_remote_cert = set->ssl_verify_client_cert;
+ return io_stream_create_ssl_server(service->ssl_ctx, &ssl_set,
+ input, output, ssl_iostream_r, error_r);
+}
+
+bool master_service_ssl_is_enabled(struct master_service *service)
+{
+ return service->ssl_ctx != NULL;
+}
+
+void master_service_ssl_ctx_init(struct master_service *service)
+{
+ const struct master_service_ssl_settings *set;
+ const struct master_service_ssl_server_settings *server_set;
+ struct ssl_iostream_settings ssl_set;
+ const char *error;
+
+ if (service->ssl_ctx_initialized)
+ return;
+ service->ssl_ctx_initialized = TRUE;
+
+ /* must be called after master_service_init_finish() so that if
+ initialization fails we can close the SSL listeners */
+ i_assert(service->listeners != NULL || service->socket_count == 0);
+
+ set = master_service_ssl_settings_get(service);
+ server_set = master_service_ssl_server_settings_get(service);
+ if (strcmp(set->ssl, "no") == 0) {
+ /* SSL disabled, don't use it */
+ return;
+ }
+
+ i_zero(&ssl_set);
+ ssl_set.min_protocol = set->ssl_min_protocol;
+ ssl_set.cipher_list = set->ssl_cipher_list;
+ ssl_set.curve_list = set->ssl_curve_list;
+ ssl_set.ca = set->ssl_ca;
+ ssl_set.cert.cert = server_set->ssl_cert;
+ ssl_set.cert.key = server_set->ssl_key;
+ ssl_set.dh = server_set->ssl_dh;
+ ssl_set.cert.key_password = server_set->ssl_key_password;
+ ssl_set.cert_username_field = set->ssl_cert_username_field;
+ if (server_set->ssl_alt_cert != NULL &&
+ *server_set->ssl_alt_cert != '\0') {
+ ssl_set.alt_cert.cert = server_set->ssl_alt_cert;
+ ssl_set.alt_cert.key = server_set->ssl_alt_key;
+ ssl_set.alt_cert.key_password = server_set->ssl_key_password;
+ }
+ ssl_set.crypto_device = set->ssl_crypto_device;
+ ssl_set.skip_crl_check = !set->ssl_require_crl;
+
+ ssl_set.verbose = set->verbose_ssl;
+ ssl_set.verify_remote_cert = set->ssl_verify_client_cert;
+ ssl_set.prefer_server_ciphers = set->ssl_prefer_server_ciphers;
+ ssl_set.compression = set->parsed_opts.compression;
+
+ if (ssl_iostream_context_init_server(&ssl_set, &service->ssl_ctx,
+ &error) < 0) {
+ i_error("SSL context initialization failed, disabling SSL: %s",
+ error);
+ master_service_ssl_io_listeners_remove(service);
+ return;
+ }
+}
+
+void master_service_ssl_ctx_deinit(struct master_service *service)
+{
+ if (service->ssl_ctx != NULL)
+ ssl_iostream_context_unref(&service->ssl_ctx);
+ service->ssl_ctx_initialized = FALSE;
+}
diff --git a/src/lib-master/master-service-ssl.h b/src/lib-master/master-service-ssl.h
new file mode 100644
index 0000000..3e95145
--- /dev/null
+++ b/src/lib-master/master-service-ssl.h
@@ -0,0 +1,16 @@
+#ifndef MASTER_SERVICE_SSL_H
+#define MASTER_SERVICE_SSL_H
+
+struct ssl_iostream;
+
+int master_service_ssl_init(struct master_service *service,
+ struct istream **input, struct ostream **output,
+ struct ssl_iostream **ssl_iostream_r,
+ const char **error_r);
+
+bool master_service_ssl_is_enabled(struct master_service *service);
+
+void master_service_ssl_ctx_init(struct master_service *service);
+void master_service_ssl_ctx_deinit(struct master_service *service);
+
+#endif
diff --git a/src/lib-master/master-service.c b/src/lib-master/master-service.c
new file mode 100644
index 0000000..a92c0a8
--- /dev/null
+++ b/src/lib-master/master-service.c
@@ -0,0 +1,1548 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "lib-event-private.h"
+#include "event-filter.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "path-util.h"
+#include "array.h"
+#include "strescape.h"
+#include "env-util.h"
+#include "home-expand.h"
+#include "process-title.h"
+#include "time-util.h"
+#include "restrict-access.h"
+#include "settings-parser.h"
+#include "syslog-util.h"
+#include "stats-client.h"
+#include "master-instance.h"
+#include "master-login.h"
+#include "master-service-ssl.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+#include "iostream-ssl.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#define DEFAULT_CONFIG_FILE_PATH SYSCONFDIR"/dovecot.conf"
+
+/* getenv(MASTER_CONFIG_FILE_ENV) provides path to configuration file/socket */
+#define MASTER_CONFIG_FILE_ENV "CONFIG_FILE"
+
+/* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number */
+#define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION"
+
+/* when we're full of connections, how often to check if login state has
+ changed. we normally notice it immediately because of a signal, so this is
+ just a fallback against race conditions. */
+#define MASTER_SERVICE_STATE_CHECK_MSECS 1000
+
+/* If die callback hasn't managed to stop the service for this many seconds,
+ force it. */
+#define MASTER_SERVICE_DIE_TIMEOUT_MSECS (30*1000)
+
+struct master_service *master_service;
+
+static struct event_category master_service_category = {
+ .name = NULL, /* set dynamically later */
+};
+static char *master_service_category_name;
+
+static void master_service_io_listeners_close(struct master_service *service);
+static int master_service_get_login_state(enum master_login_state *state_r);
+static void master_service_refresh_login_state(struct master_service *service);
+static void
+master_status_send(struct master_service *service, bool important_update);
+
+const char *master_service_getopt_string(void)
+{
+ return "c:i:ko:OL";
+}
+
+static void sig_die(const siginfo_t *si, void *context)
+{
+ struct master_service *service = context;
+
+ /* SIGINT comes either from master process or from keyboard. we don't
+ want to log it in either case.*/
+ if (si->si_signo != SIGINT) {
+ i_warning("Killed with signal %d (by pid=%s uid=%s code=%s)",
+ si->si_signo, dec2str(si->si_pid),
+ dec2str(si->si_uid),
+ lib_signal_code_to_str(si->si_signo, si->si_code));
+ } else if ((service->flags & MASTER_SERVICE_FLAG_NO_IDLE_DIE) != 0) {
+ /* never die when idling */
+ return;
+ } else if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
+ /* SIGINT came from master. die only if we're not handling
+ any clients currently. */
+ if (service->master_status.available_count !=
+ service->total_available_count)
+ return;
+
+ if (service->idle_die_callback != NULL &&
+ !service->idle_die_callback()) {
+ /* we don't want to die - send a notification to master
+ so it doesn't think we're ignoring it completely. */
+ master_status_send(service, FALSE);
+ return;
+ }
+ }
+
+ service->killed = TRUE;
+ io_loop_stop(service->ioloop);
+}
+
+static void sig_close_listeners(const siginfo_t *si ATTR_UNUSED, void *context)
+{
+ struct master_service *service = context;
+
+ /* We're in a signal handler: Close listeners immediately so master
+ can successfully restart. We can safely close only those listeners
+ that don't have an io, but this shouldn't be a big problem. If there
+ is an active io, the service is unlikely to be unresposive for
+ longer periods of time, so the listener gets closed soon enough via
+ master_status_error().
+
+ For extra safety we don't actually close() the fd, but instead
+ replace it with /dev/null. This way it won't be replaced with some
+ other new fd and attempted to be used in unexpected ways. */
+ for (unsigned int i = 0; i < service->socket_count; i++) {
+ if (service->listeners[i].fd != -1 &&
+ service->listeners[i].io == NULL) {
+ if (dup2(dev_null_fd, service->listeners[i].fd) < 0)
+ lib_signals_syscall_error("signal: dup2(/dev/null, listener) failed: ");
+ service->listeners[i].closed = TRUE;
+ }
+ }
+}
+
+static void
+sig_state_changed(const siginfo_t *si ATTR_UNUSED, void *context)
+{
+ struct master_service *service = context;
+
+ master_service_refresh_login_state(service);
+}
+
+static bool
+master_service_event_callback(struct event *event,
+ enum event_callback_type type,
+ struct failure_context *ctx,
+ const char *fmt ATTR_UNUSED,
+ va_list args ATTR_UNUSED)
+{
+ if (type == EVENT_CALLBACK_TYPE_CREATE && event->parent == NULL) {
+ /* Add service:<name> category for all events. It's enough
+ to do it only for root events, because all other events
+ inherit the category from them. */
+ event_add_category(event, &master_service_category);
+ }
+ /* This callback may be called while still in master_service_init().
+ In that case master_service is NULL. */
+ if (type == EVENT_CALLBACK_TYPE_SEND && master_service != NULL &&
+ event_filter_match(master_service->process_shutdown_filter,
+ event, ctx))
+ master_service_stop_new_connections(master_service);
+ return TRUE;
+}
+
+static void master_service_verify_version_string(struct master_service *service)
+{
+ if (service->version_string != NULL &&
+ strcmp(service->version_string, PACKAGE_VERSION) != 0) {
+ i_fatal("Dovecot version mismatch: "
+ "Master is v%s, %s is v"PACKAGE_VERSION" "
+ "(if you don't care, set version_ignore=yes)",
+ service->version_string, service->name);
+ }
+}
+
+static void master_service_init_socket_listeners(struct master_service *service)
+{
+ unsigned int i;
+ const char *value;
+ bool have_ssl_sockets = FALSE;
+
+ if (service->socket_count == 0)
+ return;
+
+ service->listeners =
+ i_new(struct master_service_listener, service->socket_count);
+
+ for (i = 0; i < service->socket_count; i++) {
+ struct master_service_listener *l = &service->listeners[i];
+
+ l->service = service;
+ l->fd = MASTER_LISTEN_FD_FIRST + i;
+
+ value = getenv(t_strdup_printf("SOCKET%u_SETTINGS", i));
+ if (value != NULL) {
+ const char *const *settings =
+ t_strsplit_tabescaped(value);
+
+ if (*settings != NULL) {
+ l->name = i_strdup_empty(*settings);
+ settings++;
+ }
+ while (*settings != NULL) {
+ if (strcmp(*settings, "ssl") == 0) {
+ l->ssl = TRUE;
+ have_ssl_sockets = TRUE;
+ } else if (strcmp(*settings, "haproxy") == 0) {
+ l->haproxy = TRUE;
+ }
+ settings++;
+ }
+ }
+ }
+ service->want_ssl_server = have_ssl_sockets ||
+ (service->flags & MASTER_SERVICE_FLAG_HAVE_STARTTLS) != 0;
+}
+
+struct master_service *
+master_service_init(const char *name, enum master_service_flags flags,
+ int *argc, char **argv[], const char *getopt_str)
+{
+ struct master_service *service;
+ data_stack_frame_t datastack_frame_id = 0;
+ unsigned int count;
+ const char *service_configured_name, *value;
+
+ i_assert(name != NULL);
+
+#ifdef DEBUG
+ if (getenv("GDB") == NULL &&
+ (flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
+ value = getenv("SOCKET_COUNT");
+ if (value == NULL || str_to_uint(value, &count) < 0)
+ count = 0;
+ fd_debug_verify_leaks(MASTER_LISTEN_FD_FIRST + count, 1024);
+ }
+#endif
+ if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
+ /* make sure we can dump core, at least until
+ privileges are dropped. (i'm not really sure why this
+ is needed, because doing the same just before exec
+ doesn't help, and exec shouldn't affect this with
+ non-setuid/gid binaries..) */
+ restrict_access_allow_coredumps(TRUE);
+ }
+
+ /* NOTE: we start rooted, so keep the code minimal until
+ restrict_access_by_env() is called */
+ lib_init();
+ /* Get the service name from environment. This usually differs from the
+ service name parameter if the executable is used for multiple
+ services. For example "auth" vs "auth-worker". It can also be a
+ service with slightly different settings, like "lmtp" vs
+ "lmtp-no-quota". We don't want to use the configured name as the
+ service's primary name, because that could break some lookups (e.g.
+ auth would suddenly see service=lmtp-no-quota. However, this can be
+ very useful in events to differentiate e.g. auth master and
+ auth-worker events which might otherwise look very similar. It's
+ also useful in log prefixes. */
+ service_configured_name = getenv(MASTER_SERVICE_ENV);
+ if (service_configured_name == NULL)
+ service_configured_name = name;
+ /* Set a logging prefix temporarily. This will be ignored once the log
+ is properly initialized */
+ i_set_failure_prefix("%s(init): ", service_configured_name);
+
+ /* make sure all the data stack allocations during init will be freed
+ before we get to ioloop. the corresponding t_pop() is in
+ master_service_init_finish(). */
+ if ((flags & MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME) == 0)
+ datastack_frame_id = t_push("master_service_init");
+
+ /* ignore these signals as early as possible */
+ lib_signals_init();
+ lib_signals_ignore(SIGPIPE, TRUE);
+ lib_signals_ignore(SIGALRM, FALSE);
+
+ if (getenv(MASTER_UID_ENV) == NULL)
+ flags |= MASTER_SERVICE_FLAG_STANDALONE;
+
+ process_title_init(*argc, argv);
+
+ /* process_title_init() might destroy all environments.
+ Need to look this up again. */
+ service_configured_name = getenv(MASTER_SERVICE_ENV);
+ if (service_configured_name == NULL)
+ service_configured_name = name;
+
+ service = i_new(struct master_service, 1);
+ service->argc = *argc;
+ service->argv = *argv;
+ service->name = i_strdup(name);
+ service->configured_name = i_strdup(service_configured_name);
+ /* keep getopt_str first in case it contains "+" */
+ service->getopt_str = *getopt_str == '\0' ?
+ i_strdup(master_service_getopt_string()) :
+ i_strconcat(getopt_str, master_service_getopt_string(), NULL);
+ service->flags = flags;
+ service->ioloop = io_loop_create();
+ service->service_count_left = UINT_MAX;
+ service->config_fd = -1;
+ service->datastack_frame_id = datastack_frame_id;
+
+ service->config_path = i_strdup(getenv(MASTER_CONFIG_FILE_ENV));
+ if (service->config_path == NULL)
+ service->config_path = i_strdup(DEFAULT_CONFIG_FILE_PATH);
+ else
+ service->config_path_from_master = TRUE;
+
+ if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
+ service->version_string = getenv(MASTER_DOVECOT_VERSION_ENV);
+ service->socket_count = 1;
+ } else {
+ service->version_string = PACKAGE_VERSION;
+ }
+
+ /* listener configuration */
+ value = getenv("SOCKET_COUNT");
+ if (value != NULL && str_to_uint(value, &service->socket_count) < 0)
+ i_fatal("Invalid SOCKET_COUNT environment");
+ T_BEGIN {
+ master_service_init_socket_listeners(service);
+ } T_END;
+
+#ifdef HAVE_SSL
+ /* Load the SSL module if we already know it is necessary. It can also
+ get loaded later on-demand. */
+ if (service->want_ssl_server) {
+ const char *error;
+ if (ssl_module_load(&error) < 0)
+ i_fatal("Cannot load SSL module: %s", error);
+ }
+#endif
+
+ /* set up some kind of logging until we know exactly how and where
+ we want to log */
+ if (getenv("LOG_SERVICE") != NULL)
+ i_set_failure_internal();
+ if (getenv("USER") != NULL) {
+ i_set_failure_prefix("%s(%s): ", service->configured_name,
+ getenv("USER"));
+ } else {
+ i_set_failure_prefix("%s: ", service->configured_name);
+ }
+
+ master_service_category_name =
+ i_strdup_printf("service:%s", service->configured_name);
+ master_service_category.name = master_service_category_name;
+ event_register_callback(master_service_event_callback);
+
+ /* Initialize debug logging */
+ value = getenv(DOVECOT_LOG_DEBUG_ENV);
+ if (value != NULL) {
+ struct event_filter *filter;
+ const char *error;
+ filter = event_filter_create();
+ if (event_filter_parse(value, filter, &error) < 0) {
+ i_error("Invalid "DOVECOT_LOG_DEBUG_ENV" - ignoring: %s",
+ error);
+ } else {
+ event_set_global_debug_log_filter(filter);
+ }
+ event_filter_unref(&filter);
+ }
+
+ if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
+ /* initialize master_status structure */
+ value = getenv(MASTER_UID_ENV);
+ if (value == NULL ||
+ str_to_uint(value, &service->master_status.uid) < 0)
+ i_fatal(MASTER_UID_ENV" missing");
+ service->master_status.pid = getpid();
+
+ /* set the default limit */
+ value = getenv(MASTER_CLIENT_LIMIT_ENV);
+ if (value == NULL || str_to_uint(value, &count) < 0 ||
+ count == 0)
+ i_fatal(MASTER_CLIENT_LIMIT_ENV" missing");
+ master_service_set_client_limit(service, count);
+
+ /* seve the process limit */
+ value = getenv(MASTER_PROCESS_LIMIT_ENV);
+ if (value != NULL && str_to_uint(value, &count) == 0 &&
+ count > 0)
+ service->process_limit = count;
+
+ value = getenv(MASTER_PROCESS_MIN_AVAIL_ENV);
+ if (value != NULL && str_to_uint(value, &count) == 0 &&
+ count > 0)
+ service->process_min_avail = count;
+
+ /* set the default service count */
+ value = getenv(MASTER_SERVICE_COUNT_ENV);
+ if (value != NULL && str_to_uint(value, &count) == 0 &&
+ count > 0)
+ master_service_set_service_count(service, count);
+
+ /* set the idle kill timeout */
+ value = getenv(MASTER_SERVICE_IDLE_KILL_ENV);
+ if (value != NULL && str_to_uint(value, &count) == 0)
+ service->idle_kill_secs = count;
+ } else {
+ master_service_set_client_limit(service, 1);
+ master_service_set_service_count(service, 1);
+ }
+ if ((flags & MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN) != 0) {
+ /* since we're going to keep the config socket open anyway,
+ open it now so we can read settings even after privileges
+ are dropped. */
+ master_service_config_socket_try_open(service);
+ }
+ if ((flags & MASTER_SERVICE_FLAG_DONT_SEND_STATS) == 0) {
+ /* Initialize stats-client early so it can see all events. */
+ value = getenv(DOVECOT_STATS_WRITER_SOCKET_PATH);
+ if (value != NULL && value[0] != '\0')
+ service->stats_client = stats_client_init(value, FALSE);
+ }
+
+ master_service_verify_version_string(service);
+ return service;
+}
+
+int master_getopt(struct master_service *service)
+{
+ int c;
+
+ i_assert(master_getopt_str_is_valid(service->getopt_str));
+
+ while ((c = getopt(service->argc, service->argv,
+ service->getopt_str)) > 0) {
+ if (!master_service_parse_option(service, c, optarg))
+ break;
+ }
+ return c;
+}
+
+bool master_getopt_str_is_valid(const char *str)
+{
+ unsigned int i, j;
+
+ /* make sure there are no duplicates. there are few enough characters
+ that this should be fast enough. */
+ for (i = 0; str[i] != '\0'; i++) {
+ if (str[i] == ':' || str[i] == '+' || str[i] == '-')
+ continue;
+ for (j = i+1; str[j] != '\0'; j++) {
+ if (str[i] == str[j])
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static bool
+master_service_try_init_log(struct master_service *service,
+ const char *prefix)
+{
+ const char *path, *timestamp;
+
+ if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0 &&
+ (service->flags & MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR) == 0) {
+ timestamp = getenv("LOG_STDERR_TIMESTAMP");
+ if (timestamp != NULL)
+ i_set_failure_timestamp_format(timestamp);
+ i_set_failure_file("/dev/stderr", "");
+ return TRUE;
+ }
+
+ if (getenv("LOG_SERVICE") != NULL && !service->log_directly) {
+ /* logging via log service */
+ i_set_failure_internal();
+ i_set_failure_prefix("%s", prefix);
+ return TRUE;
+ }
+
+ if (service->set == NULL) {
+ i_set_failure_file("/dev/stderr", prefix);
+ /* may be called again after we have settings */
+ return FALSE;
+ }
+
+ if (strcmp(service->set->log_path, "syslog") != 0) {
+ /* error logging goes to file or stderr */
+ path = home_expand(service->set->log_path);
+ i_set_failure_file(path, prefix);
+ }
+
+ if (strcmp(service->set->log_path, "syslog") == 0 ||
+ strcmp(service->set->info_log_path, "syslog") == 0 ||
+ strcmp(service->set->debug_log_path, "syslog") == 0) {
+ /* something gets logged to syslog */
+ int facility;
+
+ if (!syslog_facility_find(service->set->syslog_facility,
+ &facility))
+ facility = LOG_MAIL;
+ i_set_failure_syslog(service->set->instance_name, LOG_NDELAY,
+ facility);
+ i_set_failure_prefix("%s", prefix);
+
+ if (strcmp(service->set->log_path, "syslog") != 0) {
+ /* set error handlers back to file */
+ i_set_fatal_handler(default_fatal_handler);
+ i_set_error_handler(default_error_handler);
+ }
+ }
+
+ if (*service->set->info_log_path != '\0' &&
+ strcmp(service->set->info_log_path, "syslog") != 0) {
+ path = home_expand(service->set->info_log_path);
+ if (*path != '\0')
+ i_set_info_file(path);
+ }
+
+ if (*service->set->debug_log_path != '\0' &&
+ strcmp(service->set->debug_log_path, "syslog") != 0) {
+ path = home_expand(service->set->debug_log_path);
+ if (*path != '\0')
+ i_set_debug_file(path);
+ }
+ i_set_failure_timestamp_format(service->set->log_timestamp);
+ return TRUE;
+}
+
+void master_service_init_log(struct master_service *service)
+{
+ master_service_init_log_with_prefix(service, t_strdup_printf(
+ "%s: ", service->configured_name));
+}
+
+void master_service_init_log_with_prefix(struct master_service *service,
+ const char *prefix)
+{
+ if (service->log_initialized) {
+ /* change only the prefix */
+ i_set_failure_prefix("%s", prefix);
+ return;
+ }
+ if (master_service_try_init_log(service, prefix))
+ service->log_initialized = TRUE;
+}
+
+void master_service_init_log_with_pid(struct master_service *service)
+{
+ master_service_init_log_with_prefix(service, t_strdup_printf(
+ "%s(%s): ", service->configured_name, my_pid));
+}
+
+void master_service_init_stats_client(struct master_service *service,
+ bool silent_notfound_errors)
+{
+ if (service->stats_client == NULL &&
+ service->set->stats_writer_socket_path[0] != '\0') T_BEGIN {
+ const char *path = t_strdup_printf("%s/%s",
+ service->set->base_dir,
+ service->set->stats_writer_socket_path);
+ service->stats_client =
+ stats_client_init(path, silent_notfound_errors);
+ } T_END;
+}
+
+void master_service_set_die_with_master(struct master_service *service,
+ bool set)
+{
+ service->die_with_master = set;
+}
+
+void master_service_set_die_callback(struct master_service *service,
+ void (*callback)(void))
+{
+ service->die_callback = callback;
+}
+
+void master_service_set_idle_die_callback(struct master_service *service,
+ bool (*callback)(void))
+{
+ service->idle_die_callback = callback;
+}
+
+static bool get_instance_config(const char *name, const char **config_path_r)
+{
+ struct master_instance_list *list;
+ const struct master_instance *inst;
+ const char *instance_path, *path;
+
+ /* note that we don't have any settings yet. we're just finding out
+ which dovecot.conf we even want to read! so we must use the
+ hardcoded state_dir path. */
+ instance_path = t_strconcat(PKG_STATEDIR"/"MASTER_INSTANCE_FNAME, NULL);
+ list = master_instance_list_init(instance_path);
+ inst = master_instance_list_find_by_name(list, name);
+ if (inst != NULL) {
+ path = t_strdup_printf("%s/dovecot.conf", inst->base_dir);
+ const char *error;
+ if (t_readlink(path, config_path_r, &error) < 0)
+ i_fatal("t_readlink(%s) failed: %s", path, error);
+ }
+ master_instance_list_deinit(&list);
+ return inst != NULL;
+}
+
+bool master_service_parse_option(struct master_service *service,
+ int opt, const char *arg)
+{
+ const char *path;
+
+ switch (opt) {
+ case 'c':
+ i_free(service->config_path);
+ service->config_path = i_strdup(arg);
+ service->config_path_changed_with_param = TRUE;
+ service->config_path_from_master = FALSE;
+ break;
+ case 'i':
+ if (!get_instance_config(arg, &path))
+ i_fatal("Unknown instance name: %s", arg);
+ service->config_path = i_strdup(path);
+ service->config_path_changed_with_param = TRUE;
+ break;
+ case 'k':
+ service->keep_environment = TRUE;
+ break;
+ case 'o':
+ if (!array_is_created(&service->config_overrides))
+ i_array_init(&service->config_overrides, 16);
+ array_push_back(&service->config_overrides, &arg);
+ break;
+ case 'O':
+ service->flags |= MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS;
+ break;
+ case 'L':
+ service->log_directly = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void master_service_error(struct master_service *service)
+{
+ master_service_stop_new_connections(service);
+ if (service->master_status.available_count ==
+ service->total_available_count || service->die_with_master) {
+ if (service->die_callback == NULL)
+ master_service_stop(service);
+ else {
+ service->to_die =
+ timeout_add(MASTER_SERVICE_DIE_TIMEOUT_MSECS,
+ master_service_stop,
+ service);
+ service->die_callback();
+ }
+ }
+}
+
+static void master_status_error(struct master_service *service)
+{
+ /* status fd is a write-only pipe, so if we're here it means the
+ master wants us to die (or died itself). don't die until all
+ service connections are finished. */
+ io_remove(&service->io_status_error);
+
+ /* the log fd may also be closed already, don't die when trying to
+ log later */
+ i_set_failure_ignore_errors(TRUE);
+
+ master_service_error(service);
+}
+
+void master_service_init_finish(struct master_service *service)
+{
+ enum libsig_flags sigint_flags = LIBSIG_FLAG_DELAYED;
+ struct stat st;
+
+ i_assert(!service->init_finished);
+ service->init_finished = TRUE;
+
+ /* From now on we'll abort() if exit() is called unexpectedly. */
+ lib_set_clean_exit(FALSE);
+
+ /* set default signal handlers */
+ if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0)
+ sigint_flags |= LIBSIG_FLAG_RESTART;
+ lib_signals_set_handler(SIGINT, sigint_flags, sig_die, service);
+ lib_signals_set_handler(SIGTERM, LIBSIG_FLAG_DELAYED, sig_die, service);
+ if ((service->flags & MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE) != 0) {
+ lib_signals_set_handler(SIGUSR1, LIBSIG_FLAGS_SAFE,
+ sig_state_changed, service);
+ }
+
+ if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
+ if (fstat(MASTER_STATUS_FD, &st) < 0 || !S_ISFIFO(st.st_mode))
+ i_fatal("Must be started by dovecot master process");
+
+ /* start listening errors for status fd, it means master died */
+ service->io_status_error = io_add(MASTER_DEAD_FD, IO_ERROR,
+ master_status_error, service);
+ lib_signals_set_handler(SIGQUIT, 0, sig_close_listeners, service);
+ }
+ master_service_io_listeners_add(service);
+ if (service->want_ssl_server &&
+ (service->flags & MASTER_SERVICE_FLAG_NO_SSL_INIT) == 0)
+ master_service_ssl_ctx_init(service);
+
+ if ((service->flags & MASTER_SERVICE_FLAG_STD_CLIENT) != 0) {
+ /* we already have a connection to be served */
+ service->master_status.available_count--;
+ }
+ master_status_update(service);
+
+ /* close data stack frame opened by master_service_init() */
+ if ((service->flags & MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME) == 0) {
+ if (!t_pop(&service->datastack_frame_id))
+ i_panic("Leaked t_pop() call");
+ }
+}
+
+static void master_service_import_environment_real(const char *import_environment)
+{
+ const char *const *envs, *key, *value;
+ ARRAY_TYPE(const_string) keys;
+
+ if (*import_environment == '\0')
+ return;
+
+ t_array_init(&keys, 8);
+ /* preserve existing DOVECOT_PRESERVE_ENVS */
+ value = getenv(DOVECOT_PRESERVE_ENVS_ENV);
+ if (value != NULL)
+ array_push_back(&keys, &value);
+#ifdef HAVE_LIBSYSTEMD
+ /* Always import systemd variables, otherwise it is possible to break
+ systemd startup in obscure ways. */
+ value = "NOTIFY_SOCKET LISTEN_FDS LISTEN_PID";
+ array_push_back(&keys, &value);
+#endif
+ /* add new environments */
+ envs = t_strsplit_spaces(import_environment, " ");
+ for (; *envs != NULL; envs++) {
+ value = strchr(*envs, '=');
+ if (value == NULL)
+ key = *envs;
+ else {
+ key = t_strdup_until(*envs, value++);
+ env_put(key, value);
+ }
+ array_push_back(&keys, &key);
+ }
+ array_append_zero(&keys);
+
+ value = t_strarray_join(array_front(&keys), " ");
+ env_put(DOVECOT_PRESERVE_ENVS_ENV, value);
+}
+
+void master_service_import_environment(const char *import_environment)
+{
+ T_BEGIN {
+ master_service_import_environment_real(import_environment);
+ } T_END;
+}
+
+void master_service_env_clean(void)
+{
+ const char *value = getenv(DOVECOT_PRESERVE_ENVS_ENV);
+
+ if (value == NULL || *value == '\0')
+ env_clean();
+ else T_BEGIN {
+ value = t_strconcat(value, " "DOVECOT_PRESERVE_ENVS_ENV, NULL);
+ env_clean_except(t_strsplit_spaces(value, " "));
+ } T_END;
+}
+
+void master_service_set_client_limit(struct master_service *service,
+ unsigned int client_limit)
+{
+ unsigned int used;
+
+ i_assert(service->master_status.available_count ==
+ service->total_available_count);
+
+ used = service->total_available_count -
+ service->master_status.available_count;
+ i_assert(client_limit >= used);
+
+ service->total_available_count = client_limit;
+ service->master_status.available_count = client_limit - used;
+}
+
+unsigned int master_service_get_client_limit(struct master_service *service)
+{
+ return service->total_available_count;
+}
+
+unsigned int master_service_get_process_limit(struct master_service *service)
+{
+ return service->process_limit;
+}
+
+unsigned int master_service_get_process_min_avail(struct master_service *service)
+{
+ return service->process_min_avail;
+}
+
+unsigned int master_service_get_idle_kill_secs(struct master_service *service)
+{
+ return service->idle_kill_secs;
+}
+
+void master_service_set_service_count(struct master_service *service,
+ unsigned int count)
+{
+ unsigned int used;
+
+ used = service->total_available_count -
+ service->master_status.available_count;
+ i_assert(count >= used);
+
+ if (service->total_available_count > count) {
+ service->total_available_count = count;
+ service->master_status.available_count = count - used;
+ }
+ service->service_count_left = count;
+}
+
+unsigned int master_service_get_service_count(struct master_service *service)
+{
+ return service->service_count_left;
+}
+
+unsigned int master_service_get_socket_count(struct master_service *service)
+{
+ return service->socket_count;
+}
+
+const char *master_service_get_socket_name(struct master_service *service,
+ int listen_fd)
+{
+ unsigned int i;
+
+ i_assert(listen_fd >= MASTER_LISTEN_FD_FIRST);
+
+ i = listen_fd - MASTER_LISTEN_FD_FIRST;
+ i_assert(i < service->socket_count);
+ return service->listeners[i].name != NULL ?
+ service->listeners[i].name : "";
+}
+
+void master_service_set_avail_overflow_callback(struct master_service *service,
+ master_service_avail_overflow_callback_t *callback)
+{
+ service->avail_overflow_callback = callback;
+}
+
+const char *master_service_get_config_path(struct master_service *service)
+{
+ return service->config_path;
+}
+
+const char *master_service_get_version_string(struct master_service *service)
+{
+ return service->version_string;
+}
+
+const char *master_service_get_name(struct master_service *service)
+{
+ return service->name;
+}
+
+const char *master_service_get_configured_name(struct master_service *service)
+{
+ return service->configured_name;
+}
+
+void master_service_run(struct master_service *service,
+ master_service_connection_callback_t *callback)
+{
+ service->callback = callback;
+ io_loop_run(service->ioloop);
+ service->callback = NULL;
+}
+
+void master_service_stop(struct master_service *service)
+{
+ io_loop_stop(service->ioloop);
+}
+
+void master_service_stop_new_connections(struct master_service *service)
+{
+ unsigned int current_count;
+
+ if (service->stopping)
+ return;
+
+ service->stopping = TRUE;
+ master_service_io_listeners_remove(service);
+ master_service_io_listeners_close(service);
+
+ /* make sure we stop after servicing current connections */
+ current_count = service->total_available_count -
+ service->master_status.available_count;
+ service->service_count_left = current_count;
+ service->total_available_count = current_count;
+
+ if (current_count == 0)
+ master_service_stop(service);
+ else {
+ /* notify master that we're not accepting any more
+ connections */
+ service->master_status.available_count = 0;
+ master_status_update(service);
+ }
+ if (service->login != NULL)
+ master_login_stop(service->login);
+}
+
+bool master_service_is_killed(struct master_service *service)
+{
+ return service->killed;
+}
+
+bool master_service_is_master_stopped(struct master_service *service)
+{
+ return service->io_status_error == NULL &&
+ (service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0;
+}
+
+void master_service_anvil_send(struct master_service *service, const char *cmd)
+{
+ ssize_t ret;
+
+ if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0)
+ return;
+
+ ret = write(MASTER_ANVIL_FD, cmd, strlen(cmd));
+ if (ret < 0) {
+ if (errno == EPIPE) {
+ /* anvil process was probably recreated, don't bother
+ logging an error about losing connection to it */
+ return;
+ }
+ i_error("write(anvil) failed: %m");
+ } else if (ret == 0)
+ i_error("write(anvil) failed: EOF");
+ else {
+ i_assert((size_t)ret == strlen(cmd));
+ }
+}
+
+void master_service_client_connection_created(struct master_service *service)
+{
+ i_assert(service->master_status.available_count > 0);
+ service->master_status.available_count--;
+ master_status_update(service);
+}
+
+static bool master_service_want_listener(struct master_service *service)
+{
+ if (service->master_status.available_count > 0) {
+ /* more concurrent clients can still be added */
+ return TRUE;
+ }
+ if (service->service_count_left == 1) {
+ /* after handling this client, the whole process will stop. */
+ return FALSE;
+ }
+ if (service->avail_overflow_callback != NULL) {
+ /* overflow callback is set. it's possible that the current
+ existing client may be replaced by a new client, which needs
+ the listener to try to accept new connections. */
+ return TRUE;
+ }
+ /* the listener isn't needed until the current client is disconnected */
+ return FALSE;
+}
+
+void master_service_client_connection_handled(struct master_service *service,
+ struct master_service_connection *conn)
+{
+ if (!conn->accepted) {
+ if (close(conn->fd) < 0)
+ i_error("close(service connection) failed: %m");
+ master_service_client_connection_destroyed(service);
+ } else if (conn->fifo) {
+ /* reading FIFOs stays open forever, don't count them
+ as real clients */
+ master_service_client_connection_destroyed(service);
+ }
+ if (!master_service_want_listener(service)) {
+ i_assert(service->listeners != NULL);
+ master_service_io_listeners_remove(service);
+ if (service->service_count_left == 1 &&
+ service->avail_overflow_callback == NULL) {
+ /* we're not going to accept any more connections after
+ this. go ahead and close the connection early. don't
+ do this before calling callback, because it may want
+ to access the listen_fd (e.g. to check socket
+ permissions).
+
+ Don't do this if overflow callback is set, because
+ otherwise it's never called with service_count=1.
+ Actually this isn't important anymore to do with
+ any service, since nowadays master can request the
+ listeners to be closed via SIGQUIT. Still, closing
+ the fd when possible saves a little bit of memory. */
+ master_service_io_listeners_close(service);
+ }
+ }
+}
+
+void master_service_client_connection_callback(struct master_service *service,
+ struct master_service_connection *conn)
+{
+ service->callback(conn);
+
+ master_service_client_connection_handled(service, conn);
+}
+
+void master_service_client_connection_accept(struct master_service_connection *conn)
+{
+ conn->accepted = TRUE;
+}
+
+void master_service_client_connection_destroyed(struct master_service *service)
+{
+ /* we can listen again */
+ master_service_io_listeners_add(service);
+
+ i_assert(service->total_available_count > 0);
+ i_assert(service->service_count_left > 0);
+
+ if (service->service_count_left == service->total_available_count) {
+ service->total_available_count--;
+ service->service_count_left--;
+ } else {
+ if (service->service_count_left != UINT_MAX)
+ service->service_count_left--;
+
+ i_assert(service->master_status.available_count <
+ service->total_available_count);
+ service->master_status.available_count++;
+ }
+
+ if (service->service_count_left == 0) {
+ i_assert(service->master_status.available_count ==
+ service->total_available_count);
+ master_service_stop(service);
+ } else if ((service->io_status_error == NULL ||
+ service->listeners == NULL) &&
+ service->master_status.available_count ==
+ service->total_available_count) {
+ /* we've finished handling all clients, and
+ a) master has closed the connection
+ b) there are no listeners (std-client?) */
+ master_service_stop(service);
+ } else {
+ master_status_update(service);
+ }
+}
+
+static void master_service_set_login_state(struct master_service *service,
+ enum master_login_state state)
+{
+ timeout_remove(&service->to_overflow_state);
+
+ switch (state) {
+ case MASTER_LOGIN_STATE_NONFULL:
+ service->call_avail_overflow = FALSE;
+ if (service->master_status.available_count > 0)
+ return;
+
+ /* some processes should now be able to handle new connections,
+ although we can't. but there may be race conditions, so
+ make sure that we'll check again soon if the state has
+ changed to "full" without our knowledge. */
+ service->to_overflow_state =
+ timeout_add(MASTER_SERVICE_STATE_CHECK_MSECS,
+ master_service_refresh_login_state,
+ service);
+ return;
+ case MASTER_LOGIN_STATE_FULL:
+ /* make sure we're listening for more connections */
+ service->call_avail_overflow = TRUE;
+ master_service_io_listeners_add(service);
+ return;
+ }
+ i_error("Invalid master login state: %d", state);
+}
+
+static int master_service_get_login_state(enum master_login_state *state_r)
+{
+ off_t ret;
+
+ ret = lseek(MASTER_LOGIN_NOTIFY_FD, 0, SEEK_CUR);
+ if (ret < 0) {
+ i_error("lseek(login notify fd) failed: %m");
+ return -1;
+ }
+ *state_r = ret == MASTER_LOGIN_STATE_FULL ?
+ MASTER_LOGIN_STATE_FULL : MASTER_LOGIN_STATE_NONFULL;
+ return 0;
+}
+
+static void master_service_refresh_login_state(struct master_service *service)
+{
+ enum master_login_state state;
+
+ if (master_service_get_login_state(&state) == 0)
+ master_service_set_login_state(service, state);
+}
+
+void master_service_close_config_fd(struct master_service *service)
+{
+ i_close_fd(&service->config_fd);
+}
+
+static void master_service_deinit_real(struct master_service **_service)
+{
+ struct master_service *service = *_service;
+
+ *_service = NULL;
+
+ if (!service->init_finished &&
+ (service->flags & MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME) == 0) {
+ if (!t_pop(&service->datastack_frame_id))
+ i_panic("Leaked t_pop() call");
+ }
+ master_service_haproxy_abort(service);
+
+ master_service_io_listeners_remove(service);
+ master_service_ssl_ctx_deinit(service);
+
+ if (service->stats_client != NULL)
+ stats_client_deinit(&service->stats_client);
+ master_service_close_config_fd(service);
+ timeout_remove(&service->to_overflow_call);
+ timeout_remove(&service->to_die);
+ timeout_remove(&service->to_overflow_state);
+ timeout_remove(&service->to_status);
+ io_remove(&service->io_status_error);
+ io_remove(&service->io_status_write);
+ if (array_is_created(&service->config_overrides))
+ array_free(&service->config_overrides);
+
+ if (service->set_parser != NULL) {
+ settings_parser_deinit(&service->set_parser);
+ pool_unref(&service->set_pool);
+ }
+ i_free(master_service_category_name);
+ master_service_category.name = NULL;
+ event_unregister_callback(master_service_event_callback);
+ master_service_unset_process_shutdown_filter(service);
+}
+
+static void master_service_free(struct master_service *service)
+{
+ unsigned int i;
+
+ for (i = 0; i < service->socket_count; i++)
+ i_free(service->listeners[i].name);
+ i_free(service->listeners);
+ i_free(service->getopt_str);
+ i_free(service->configured_name);
+ i_free(service->name);
+ i_free(service->config_path);
+ i_free(service);
+}
+
+void master_service_deinit(struct master_service **_service)
+{
+ struct master_service *service = *_service;
+
+ master_service_deinit_real(_service);
+
+ lib_signals_deinit();
+ /* run atexit callbacks before destroying ioloop */
+ lib_atexit_run();
+ io_loop_destroy(&service->ioloop);
+
+ master_service_free(service);
+ lib_deinit();
+}
+
+void master_service_deinit_forked(struct master_service **_service)
+{
+ struct master_service *service = *_service;
+
+ master_service_deinit_real(_service);
+ io_loop_destroy(&service->ioloop);
+
+ master_service_free(service);
+}
+
+static void master_service_overflow(struct master_service *service)
+{
+ enum master_login_state state;
+ struct timeval created;
+
+ timeout_remove(&service->to_overflow_call);
+
+ if (master_service_get_login_state(&state) < 0 ||
+ state != MASTER_LOGIN_STATE_FULL) {
+ /* service is no longer full (or we couldn't check if it is) */
+ return;
+ }
+
+ if (!service->avail_overflow_callback(TRUE, &created)) {
+ /* can't kill the client anymore after all */
+ return;
+ }
+ if (service->master_status.available_count == 0) {
+ /* Client was destroyed, but service_count is now 0.
+ The servive was already stopped, so the process will
+ shutdown and a new process can handle the waiting client
+ connection. */
+ i_assert(service->service_count_left == 0);
+ i_assert(!io_loop_is_running(service->ioloop));
+ return;
+ }
+ master_service_io_listeners_add(service);
+
+ /* The connection is soon accepted by the listener IO callback.
+ Note that this often results in killing two connections, because
+ after the first process has accepted the new client the service is
+ full again. The second process sees this and kills another client.
+ After this the other processes see that the service is no longer
+ full and kill no more clients. */
+}
+
+static unsigned int
+master_service_overflow_timeout_msecs(const struct timeval *created)
+{
+ /* Returns a value between 0..max_wait. The oldest clients return the
+ lowest wait so they get killed before newer clients. For simplicity
+ this code treats all clients older than 10 seconds the same. */
+ const unsigned int max_wait = 100;
+ const int max_since = 10*1000;
+ int created_since = timeval_diff_msecs(&ioloop_timeval, created);
+ unsigned int msecs;
+
+ created_since = I_MAX(created_since, 0);
+ created_since = I_MIN(created_since, max_since);
+
+ msecs = created_since * max_wait / max_since;
+ i_assert(msecs <= max_wait);
+ msecs = max_wait - msecs;
+
+ /* Add some extra randomness, so even if all clients have exactly the
+ same creation time they won't all be killed. */
+ return msecs + i_rand_limit(10);
+}
+
+static bool master_service_full(struct master_service *service)
+{
+ struct timeval created;
+
+ /* This process can't handle any more connections. */
+ if (!service->call_avail_overflow ||
+ service->avail_overflow_callback == NULL)
+ return TRUE;
+
+ /* Master has notified us that all processes are full, and
+ we have the ability to kill old connections. */
+ if (service->total_available_count > 1) {
+ /* This process can still create multiple concurrent
+ clients if we just kill some of the existing ones.
+ Do it immediately. */
+ return !service->avail_overflow_callback(TRUE, &created);
+ }
+
+ /* This process can't create more than a single client. Most likely
+ running with service_count=1. Check the overflow again after a short
+ delay before killing anything. This way only some of the connections
+ get killed instead of all of them. The delay is based on the
+ connection age with a bit of randomness, so the oldest connections
+ should die first, but even if all the connections have time same
+ timestamp they still don't all die at once. */
+ if (!service->avail_overflow_callback(FALSE, &created)) {
+ /* can't kill any clients */
+ return TRUE;
+ }
+ i_assert(service->to_overflow_call == NULL);
+ service->to_overflow_call =
+ timeout_add(master_service_overflow_timeout_msecs(&created),
+ master_service_overflow, service);
+ return TRUE;
+}
+
+static void master_service_listen(struct master_service_listener *l)
+{
+ struct master_service *service = l->service;
+ struct master_service_connection conn;
+
+ if (service->master_status.available_count == 0) {
+ if (master_service_full(service)) {
+ /* Stop the listener until a client has disconnected or
+ overflow callback has killed one. */
+ master_service_io_listeners_remove(service);
+ return;
+ }
+ /* we can accept another client */
+ i_assert(service->master_status.available_count > 0);
+ }
+
+ i_zero(&conn);
+ conn.listen_fd = l->fd;
+ conn.fd = net_accept(l->fd, &conn.remote_ip, &conn.remote_port);
+ if (conn.fd < 0) {
+ struct stat st;
+ int orig_errno = errno;
+
+ if (conn.fd == -1)
+ return;
+
+ if (errno == ENOTSOCK) {
+ /* it's not a socket. should be a fifo. */
+ } else if (errno == EINVAL &&
+ (fstat(l->fd, &st) == 0 && S_ISFIFO(st.st_mode))) {
+ /* BSDI fails accept(fifo) with EINVAL. */
+ } else {
+ errno = orig_errno;
+ i_error("net_accept() failed: %m");
+ /* try again later after one of the existing
+ connections has died */
+ master_service_io_listeners_remove(service);
+ return;
+ }
+ /* use the "listener" as the connection fd and stop the
+ listener. */
+ conn.fd = l->fd;
+ conn.listen_fd = l->fd;
+ conn.fifo = TRUE;
+
+ io_remove(&l->io);
+ l->fd = -1;
+ }
+ conn.ssl = l->ssl;
+ conn.name = master_service_get_socket_name(service, conn.listen_fd);
+
+ (void)net_getsockname(conn.fd, &conn.local_ip, &conn.local_port);
+ conn.real_remote_ip = conn.remote_ip;
+ conn.real_remote_port = conn.remote_port;
+ conn.real_local_ip = conn.local_ip;
+ conn.real_local_port = conn.local_port;
+
+ net_set_nonblock(conn.fd, TRUE);
+
+ master_service_client_connection_created(service);
+ if (l->haproxy)
+ master_service_haproxy_new(service, &conn);
+ else
+ master_service_client_connection_callback(service, &conn);
+}
+
+void master_service_io_listeners_add(struct master_service *service)
+{
+ unsigned int i;
+
+ /* If there's a pending overflow call, remove it now since new
+ clients just became available. */
+ timeout_remove(&service->to_overflow_call);
+
+ if (service->stopping)
+ return;
+
+ for (i = 0; i < service->socket_count; i++) {
+ struct master_service_listener *l = &service->listeners[i];
+
+ if (l->io == NULL && l->fd != -1 && !l->closed) {
+ l->io = io_add(MASTER_LISTEN_FD_FIRST + i, IO_READ,
+ master_service_listen, l);
+ }
+ }
+}
+
+void master_service_io_listeners_remove(struct master_service *service)
+{
+ unsigned int i;
+
+ for (i = 0; i < service->socket_count; i++) {
+ io_remove(&service->listeners[i].io);
+ }
+}
+
+void master_service_ssl_io_listeners_remove(struct master_service *service)
+{
+ unsigned int i;
+
+ for (i = 0; i < service->socket_count; i++) {
+ if (service->listeners[i].io != NULL &&
+ service->listeners[i].ssl)
+ io_remove(&service->listeners[i].io);
+ }
+}
+
+static void master_service_io_listeners_close(struct master_service *service)
+{
+ unsigned int i;
+
+ /* close via listeners. some fds might be pipes that are
+ currently handled as clients. we don't want to close them. */
+ for (i = 0; i < service->socket_count; i++) {
+ if (service->listeners[i].fd != -1) {
+ if (close(service->listeners[i].fd) < 0) {
+ i_error("close(listener %d) failed: %m",
+ service->listeners[i].fd);
+ }
+ service->listeners[i].fd = -1;
+ }
+ }
+}
+
+static bool master_status_update_is_important(struct master_service *service)
+{
+ if (service->master_status.available_count == 0) {
+ /* client_limit reached for this process */
+ return TRUE;
+ }
+ if (service->last_sent_status_avail_count == 0) {
+ /* This process can now handle more clients. This is important
+ to know for master if all the existing processes have
+ avail_count=0 so it doesn't unnecessarily create more
+ processes. */
+ return TRUE;
+ }
+ /* The previous check should have triggered also for the initial
+ status notification. */
+ i_assert(service->initial_status_sent);
+ return FALSE;
+}
+
+static void
+master_status_send(struct master_service *service, bool important_update)
+{
+ ssize_t ret;
+
+ timeout_remove(&service->to_status);
+
+ ret = write(MASTER_STATUS_FD, &service->master_status,
+ sizeof(service->master_status));
+ if (ret == (ssize_t)sizeof(service->master_status)) {
+ /* success */
+ io_remove(&service->io_status_write);
+ service->last_sent_status_time = ioloop_time;
+ service->last_sent_status_avail_count =
+ service->master_status.available_count;
+ service->initial_status_sent = TRUE;
+ } else if (ret >= 0) {
+ /* shouldn't happen? */
+ i_error("write(master_status_fd) returned %d", (int)ret);
+ service->master_status.pid = 0;
+ } else if (errno != EAGAIN) {
+ /* failure */
+ if (errno != EPIPE)
+ i_error("write(master_status_fd) failed: %m");
+ service->master_status.pid = 0;
+ } else if (important_update) {
+ /* reader is busy, but it's important to get this notification
+ through. send it when possible. */
+ if (service->io_status_write == NULL) {
+ service->io_status_write =
+ io_add(MASTER_STATUS_FD, IO_WRITE,
+ master_status_update, service);
+ }
+ }
+}
+
+void master_status_update(struct master_service *service)
+{
+ bool important_update;
+
+ if ((service->flags & MASTER_SERVICE_FLAG_UPDATE_PROCTITLE) != 0 &&
+ service->set != NULL && service->set->verbose_proctitle) T_BEGIN {
+ unsigned int used_count = service->total_available_count -
+ service->master_status.available_count;
+
+ process_title_set(t_strdup_printf("[%u connections]",
+ used_count));
+ } T_END;
+
+ important_update = master_status_update_is_important(service);
+ if (service->master_status.pid == 0 ||
+ service->master_status.available_count ==
+ service->last_sent_status_avail_count) {
+ /* a) closed, b) updating to same state */
+ timeout_remove(&service->to_status);
+ io_remove(&service->io_status_write);
+ return;
+ }
+ if (ioloop_time == service->last_sent_status_time &&
+ !important_update) {
+ /* don't spam master */
+ if (service->to_status != NULL)
+ timeout_reset(service->to_status);
+ else {
+ service->to_status =
+ timeout_add(1000, master_status_update,
+ service);
+ }
+ if (service->io_status_write != NULL)
+ io_remove(&service->io_status_write);
+ return;
+ }
+ master_status_send(service, important_update);
+}
+
+bool version_string_verify(const char *line, const char *service_name,
+ unsigned major_version)
+{
+ unsigned int minor_version;
+
+ return version_string_verify_full(line, service_name,
+ major_version, &minor_version);
+}
+
+bool version_string_verify_full(const char *line, const char *service_name,
+ unsigned major_version,
+ unsigned int *minor_version_r)
+{
+ size_t service_name_len = strlen(service_name);
+ bool ret;
+
+ if (!str_begins(line, "VERSION\t"))
+ return FALSE;
+ line += 8;
+
+ if (strncmp(line, service_name, service_name_len) != 0 ||
+ line[service_name_len] != '\t')
+ return FALSE;
+ line += service_name_len + 1;
+
+ T_BEGIN {
+ const char *p = strchr(line, '\t');
+
+ if (p == NULL)
+ ret = FALSE;
+ else {
+ ret = str_uint_equals(t_strdup_until(line, p),
+ major_version);
+ if (str_to_uint(p+1, minor_version_r) < 0)
+ ret = FALSE;
+ }
+ } T_END;
+ return ret;
+}
+
+void master_service_set_process_shutdown_filter(struct master_service *service,
+ struct event_filter *filter)
+{
+ master_service_unset_process_shutdown_filter(service);
+ service->process_shutdown_filter = filter;
+ event_filter_ref(service->process_shutdown_filter);
+}
+
+void master_service_unset_process_shutdown_filter(struct master_service *service)
+{
+ event_filter_unref(&service->process_shutdown_filter);
+}
diff --git a/src/lib-master/master-service.h b/src/lib-master/master-service.h
new file mode 100644
index 0000000..bd32ad0
--- /dev/null
+++ b/src/lib-master/master-service.h
@@ -0,0 +1,262 @@
+#ifndef MASTER_SERVICE_H
+#define MASTER_SERVICE_H
+
+#include "net.h"
+
+#include <unistd.h> /* for getopt() opt* variables */
+#include <stdio.h> /* for getopt() opt* variables in Solaris */
+
+enum master_service_flags {
+ /* stdin/stdout already contains a client which we want to serve */
+ MASTER_SERVICE_FLAG_STD_CLIENT = 0x01,
+ /* this process is currently running standalone without a master */
+ MASTER_SERVICE_FLAG_STANDALONE = 0x02,
+ /* Log to configured log file instead of stderr. By default when
+ _FLAG_STANDALONE is set, logging is done to stderr. */
+ MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR = 0x04,
+ /* Service is going to do multiple configuration lookups,
+ keep the connection to config service open. Also opens the config
+ socket before dropping privileges. */
+ MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN = 0x08,
+ /* Don't read settings, but use whatever is in environment */
+ MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS = 0x10,
+ /* Use MASTER_LOGIN_NOTIFY_FD to track login overflow state */
+ MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE = 0x40,
+ /* If master sends SIGINT, don't die even if we don't have clients */
+ MASTER_SERVICE_FLAG_NO_IDLE_DIE = 0x80,
+ /* Show number of connections in process title
+ (only if verbose_proctitle setting is enabled) */
+ MASTER_SERVICE_FLAG_UPDATE_PROCTITLE = 0x100,
+ /* Don't read any SSL settings. This is mainly needed to prevent master
+ process from trying to pass through huge list of SSL CA certificates
+ through environment for ssl_ca setting, which could fail. Although
+ the same problem can still happen with standalone doveadm if it
+ reads settings via doveconf instead of config socket. */
+ MASTER_SERVICE_FLAG_DISABLE_SSL_SET = 0x200,
+ /* Don't initialize SSL context automatically. */
+ MASTER_SERVICE_FLAG_NO_SSL_INIT = 0x400,
+ /* Don't create a data stack frame between master_service_init() and
+ master_service_init_finish(). By default this is done to make sure
+ initialization doesn't unnecessarily use up memory in data stack. */
+ MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME = 0x800,
+ /* Don't connect at startup to the stats process. */
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS = 0x1000,
+ /* Service supports STARTTLS-like feature. SSL server must be
+ initialized even if there are no ssl=yes listeners. */
+ MASTER_SERVICE_FLAG_HAVE_STARTTLS = 0x2000,
+};
+
+struct master_service_connection_proxy {
+ /* only set if ssl is TRUE */
+ const char *hostname;
+ const char *cert_common_name;
+ const unsigned char *alpn;
+ unsigned int alpn_size;
+
+ bool ssl:1;
+ bool ssl_client_cert:1;
+};
+
+struct master_service_connection {
+ /* fd of the new connection. */
+ int fd;
+ /* fd of the socket listener. Same as fd for a FIFO. */
+ int listen_fd;
+ /* listener name as in configuration file, or "" if unnamed. */
+ const char *name;
+
+ /* Original client/server IP/port. Both of these may have been changed
+ by the haproxy protocol. */
+ struct ip_addr remote_ip, local_ip;
+ in_port_t remote_port, local_port;
+
+ /* The real client/server IP/port, unchanged by haproxy protocol. */
+ struct ip_addr real_remote_ip, real_local_ip;
+ in_port_t real_remote_port, real_local_port;
+
+ /* filled if connection is proxied */
+ struct master_service_connection_proxy proxy;
+
+ /* This is a connection proxied wit HAproxy (or similar) */
+ bool proxied:1;
+
+ /* This is a FIFO fd. Only a single "connection" is ever received from
+ a FIFO after the first writer sends something to it. */
+ bool fifo:1;
+ /* Perform immediate SSL handshake for this connection. Currently this
+ needs to be performed explicitly by each service. */
+ bool ssl:1;
+
+ /* Internal: master_service_client_connection_accept() has been
+ called for this connection. */
+ bool accepted:1;
+};
+
+typedef void
+master_service_connection_callback_t(struct master_service_connection *conn);
+
+/* If kill==TRUE, the callback should kill one of the existing connections
+ (likely the oldest). If kill==FALSE, it's just a request to check what is
+ the creation timestamp for the connection to be killed. Returns TRUE if
+ a connection was/could be killed, FALSE if not. */
+typedef bool
+master_service_avail_overflow_callback_t(bool kill, struct timeval *created_r);
+
+extern struct master_service *master_service;
+
+const char *master_service_getopt_string(void);
+
+/* Start service initialization. */
+struct master_service *
+master_service_init(const char *name, enum master_service_flags flags,
+ int *argc, char **argv[], const char *getopt_str);
+/* Call getopt() and handle internal parameters. Return values are the same as
+ getopt()'s. */
+int master_getopt(struct master_service *service);
+/* Returns TRUE if str is a valid getopt_str. Currently this only checks for
+ duplicate args so they aren't accidentally added. */
+bool master_getopt_str_is_valid(const char *str);
+/* Parser command line option. Returns TRUE if processed. */
+bool master_service_parse_option(struct master_service *service,
+ int opt, const char *arg);
+/* Finish service initialization. The caller should drop privileges
+ before calling this. This also notifies the master that the service was
+ successfully started and there shouldn't be any service throttling even if
+ it crashes afterwards, so this should be called after all of the
+ initialization code is finished. */
+void master_service_init_finish(struct master_service *service);
+
+/* import_environment is a space-separated list of environment keys or
+ key=values. The key=values are immediately added to the environment.
+ All the keys are added to DOVECOT_PRESERVE_ENVS environment so they're
+ preserved by master_service_env_clean(). */
+void master_service_import_environment(const char *import_environment);
+/* Clean environment from everything except the ones listed in
+ DOVECOT_PRESERVE_ENVS environment. */
+void master_service_env_clean(void);
+
+/* Initialize logging. Only the first call changes the actual logging
+ functions. The following calls change the log prefix. */
+void master_service_init_log(struct master_service *service);
+/* Initialize/change log prefix to the given log prefix. */
+void master_service_init_log_with_prefix(struct master_service *service,
+ const char *prefix);
+/* Initialize/change log prefix to "configured_name(my_pid): " */
+void master_service_init_log_with_pid(struct master_service *service);
+/* Initialize stats client (if it's not already initialized). This is called
+ automatically if MASTER_SERVICE_FLAG_SEND_STATS is enabled. If
+ silent_notfound_errors is set, connect() errors aren't logged if they're
+ happening because the stats service isn't running. */
+void master_service_init_stats_client(struct master_service *service,
+ bool silent_notfound_errors);
+
+/* If set, die immediately when connection to master is lost.
+ Normally all existing clients are handled first. */
+void master_service_set_die_with_master(struct master_service *service,
+ bool set);
+/* Call the given when master connection dies and die_with_master is TRUE.
+ The callback is expected to shut down the service somewhat soon or it's
+ done forcibly. If NULL, the service is stopped immediately. */
+void master_service_set_die_callback(struct master_service *service,
+ void (*callback)(void));
+/* "idle callback" is called when master thinks we're idling and asks us to
+ die. We'll do it only if the idle callback returns TRUE. This callback isn't
+ even called if the master service code knows that we're handling clients. */
+void master_service_set_idle_die_callback(struct master_service *service,
+ bool (*callback)(void));
+/* Call the given callback when there are no available connections and master
+ has indicated that it can't create any more processes to handle requests. */
+void master_service_set_avail_overflow_callback(struct master_service *service,
+ master_service_avail_overflow_callback_t *callback);
+
+/* Set maximum number of clients we can handle. Default is given by master. */
+void master_service_set_client_limit(struct master_service *service,
+ unsigned int client_limit);
+/* Returns the maximum number of clients we can handle. */
+unsigned int master_service_get_client_limit(struct master_service *service);
+/* Returns how many processes of this type can be created before reaching the
+ limit. */
+unsigned int master_service_get_process_limit(struct master_service *service);
+/* Returns service { process_min_avail } */
+unsigned int master_service_get_process_min_avail(struct master_service *service);
+/* Returns the service's idle_kill timeout in seconds. Normally master handles
+ sending the kill request when the process has no clients, but some services
+ with permanent client connections may need to handle this themselves. */
+unsigned int master_service_get_idle_kill_secs(struct master_service *service);
+
+/* Set maximum number of client connections we will handle before shutting
+ down. */
+void master_service_set_service_count(struct master_service *service,
+ unsigned int count);
+/* Returns the number of client connections we will handle before shutting
+ down. The value is decreased only after connection has been closed. */
+unsigned int master_service_get_service_count(struct master_service *service);
+/* Return the number of listener sockets. */
+unsigned int master_service_get_socket_count(struct master_service *service);
+/* Returns the name of the listener socket, or "" if none is specified. */
+const char *master_service_get_socket_name(struct master_service *service,
+ int listen_fd);
+
+/* Returns configuration file path. */
+const char *master_service_get_config_path(struct master_service *service);
+/* Returns PACKAGE_VERSION or NULL if version_ignore=yes. This function is
+ useful mostly as parameter to module_dir_load(). */
+const char *master_service_get_version_string(struct master_service *service);
+/* Returns name of the service, as given in name parameter to _init(). */
+const char *master_service_get_name(struct master_service *service);
+/* Returns name of the service, as given in the configuration file. For example
+ service name=auth, but configured_name=auth-worker. This is preferred in
+ e.g. log prefixes. */
+const char *master_service_get_configured_name(struct master_service *service);
+
+/* Start the service. Blocks until finished */
+void master_service_run(struct master_service *service,
+ master_service_connection_callback_t *callback)
+ ATTR_NULL(2);
+/* Stop a running service. */
+void master_service_stop(struct master_service *service);
+/* Stop once we're done serving existing new connections, but don't accept
+ any new ones. */
+void master_service_stop_new_connections(struct master_service *service);
+/* Returns TRUE if we've received a SIGINT/SIGTERM and we've decided to stop. */
+bool master_service_is_killed(struct master_service *service);
+/* Returns TRUE if our master process is already stopped. This process may or
+ may not be dying itself. Returns FALSE always if the process was started
+ standalone. */
+bool master_service_is_master_stopped(struct master_service *service);
+
+/* Send command to anvil process, if we have fd to it. */
+void master_service_anvil_send(struct master_service *service, const char *cmd);
+/* Call to accept the client connection. Otherwise the connection is closed. */
+void master_service_client_connection_accept(struct master_service_connection *conn);
+/* Used to create "extra client connections" outside the common accept()
+ method. */
+void master_service_client_connection_created(struct master_service *service);
+/* Call whenever a client connection is destroyed. */
+void master_service_client_connection_destroyed(struct master_service *service);
+
+/* Deinitialize the service. */
+void master_service_deinit(struct master_service **service);
+/* Deinitialize the service for a forked child process. Currently, the only
+ difference with master_service_deinit() is that lib_deinit() and
+ lib_signals_deinit() are not called.
+ */
+void master_service_deinit_forked(struct master_service **_service);
+
+/* Returns TRUE if line contains compatible service name and major version.
+ The line is expected to be in format:
+ VERSION <tab> service_name <tab> major version <tab> minor version */
+bool version_string_verify(const char *line, const char *service_name,
+ unsigned major_version);
+/* Same as version_string_verify(), but return the minor version. */
+bool version_string_verify_full(const char *line, const char *service_name,
+ unsigned major_version,
+ unsigned int *minor_version_r);
+
+/* Sets process shutdown filter */
+void master_service_set_process_shutdown_filter(struct master_service *service,
+ struct event_filter *filter);
+/* Unsets process shutdown filter, if it exists */
+void master_service_unset_process_shutdown_filter(struct master_service *service);
+
+#endif
diff --git a/src/lib-master/service-settings.h b/src/lib-master/service-settings.h
new file mode 100644
index 0000000..c023585
--- /dev/null
+++ b/src/lib-master/service-settings.h
@@ -0,0 +1,82 @@
+#ifndef SERVICE_SETTINGS_H
+#define SERVICE_SETTINGS_H
+
+#include "net.h"
+
+/* <settings checks> */
+enum service_user_default {
+ SERVICE_USER_DEFAULT_NONE = 0,
+ SERVICE_USER_DEFAULT_INTERNAL,
+ SERVICE_USER_DEFAULT_LOGIN
+};
+
+enum service_type {
+ SERVICE_TYPE_UNKNOWN,
+ SERVICE_TYPE_LOG,
+ SERVICE_TYPE_ANVIL,
+ SERVICE_TYPE_CONFIG,
+ SERVICE_TYPE_LOGIN,
+ SERVICE_TYPE_STARTUP,
+ /* Worker processes are intentionally limited to their process_limit,
+ and they can regularly reach it. There shouldn't be unnecessary
+ warnings about temporarily reaching the limit. */
+ SERVICE_TYPE_WORKER,
+};
+/* </settings checks> */
+
+struct file_listener_settings {
+ const char *path;
+ unsigned int mode;
+ const char *user;
+ const char *group;
+};
+ARRAY_DEFINE_TYPE(file_listener_settings, struct file_listener_settings *);
+
+struct inet_listener_settings {
+ const char *name;
+ const char *address;
+ in_port_t port;
+ bool ssl;
+ bool reuse_port;
+ bool haproxy;
+};
+ARRAY_DEFINE_TYPE(inet_listener_settings, struct inet_listener_settings *);
+
+struct service_settings {
+ const char *name;
+ const char *protocol;
+ const char *type;
+ const char *executable;
+ const char *user;
+ const char *group;
+ const char *privileged_group;
+ const char *extra_groups;
+ const char *chroot;
+
+ bool drop_priv_before_exec;
+
+ unsigned int process_min_avail;
+ unsigned int process_limit;
+ unsigned int client_limit;
+ unsigned int service_count;
+ unsigned int idle_kill;
+ uoff_t vsz_limit;
+
+ ARRAY_TYPE(file_listener_settings) unix_listeners;
+ ARRAY_TYPE(file_listener_settings) fifo_listeners;
+ ARRAY_TYPE(inet_listener_settings) inet_listeners;
+
+ /* internal to master: */
+ struct master_settings *master_set;
+ enum service_type parsed_type;
+ enum service_user_default user_default;
+ bool login_dump_core:1;
+
+ /* -- flags that can be set internally -- */
+
+ /* process_limit must not be higher than 1 */
+ bool process_limit_1:1;
+};
+ARRAY_DEFINE_TYPE(service_settings, struct service_settings *);
+
+#endif
diff --git a/src/lib-master/stats-client.c b/src/lib-master/stats-client.c
new file mode 100644
index 0000000..e6b24c7
--- /dev/null
+++ b/src/lib-master/stats-client.c
@@ -0,0 +1,373 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "strescape.h"
+#include "ostream.h"
+#include "time-util.h"
+#include "lib-event-private.h"
+#include "event-filter.h"
+#include "connection.h"
+#include "stats-client.h"
+
+#define STATS_CLIENT_TIMEOUT_MSECS (5*1000)
+#define STATS_CLIENT_RECONNECT_INTERVAL_MSECS (10*1000)
+
+struct stats_client {
+ struct connection conn;
+ struct event_filter *filter;
+ struct ioloop *ioloop;
+ struct timeout *to_reconnect;
+ bool handshaked;
+ bool handshake_received_at_least_once;
+ bool silent_notfound_errors;
+};
+
+static struct connection_list *stats_clients;
+
+static void stats_client_connect(struct stats_client *client);
+
+static int
+client_handshake_filter(const char *const *args, struct event_filter **filter_r,
+ const char **error_r)
+{
+ if (strcmp(args[0], "FILTER") != 0) {
+ *error_r = "Expected FILTER";
+ return -1;
+ }
+ if (args[1] == NULL || args[1][0] == '\0') {
+ *filter_r = NULL;
+ return 0;
+ }
+
+ *filter_r = event_filter_create();
+ if (!event_filter_import(*filter_r, t_str_tabunescape(args[1]), error_r)) {
+ event_filter_unref(filter_r);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+stats_client_handshake(struct stats_client *client, const char *const *args)
+{
+ struct event_filter *filter;
+ const char *error;
+
+ if (client_handshake_filter(args, &filter, &error) < 0) {
+ i_error("stats: Received invalid handshake: %s (input: %s)",
+ error, t_strarray_join(args, "\t"));
+ return -1;
+ }
+ client->handshaked = TRUE;
+ client->handshake_received_at_least_once = TRUE;
+ if (client->ioloop != NULL)
+ io_loop_stop(client->ioloop);
+
+ if (filter == NULL)
+ filter = event_filter_create();
+
+ event_filter_unref(&client->filter);
+ client->filter = filter;
+ event_set_global_debug_send_filter(client->filter);
+ return 1;
+}
+
+static int
+stats_client_input_args(struct connection *conn, const char *const *args)
+{
+ struct stats_client *client = (struct stats_client *)conn;
+
+ return stats_client_handshake(client, args);
+
+}
+
+static void stats_client_reconnect(struct stats_client *client)
+{
+ timeout_remove(&client->to_reconnect);
+ stats_client_connect(client);
+}
+
+static void stats_client_destroy(struct connection *conn)
+{
+ struct stats_client *client = (struct stats_client *)conn;
+ struct event *event;
+ unsigned int reconnect_msecs = STATS_CLIENT_RECONNECT_INTERVAL_MSECS;
+
+ /* after reconnection the IDs need to be re-sent */
+ for (event = events_get_head(); event != NULL; event = event->next)
+ event->sent_to_stats_id = 0;
+
+ client->handshaked = FALSE;
+ connection_disconnect(conn);
+ if (client->ioloop != NULL) {
+ /* waiting for stats handshake to finish */
+ io_loop_stop(client->ioloop);
+ } else if (conn->connect_finished.tv_sec != 0) {
+ int msecs_since_connected =
+ timeval_diff_msecs(&ioloop_timeval,
+ &conn->connect_finished);
+ if (msecs_since_connected >= STATS_CLIENT_RECONNECT_INTERVAL_MSECS) {
+ /* reconnect immdiately */
+ reconnect_msecs = 0;
+ } else {
+ /* wait for reconnect interval since we last
+ were connected. */
+ reconnect_msecs = STATS_CLIENT_RECONNECT_INTERVAL_MSECS -
+ msecs_since_connected;
+ }
+ }
+ if (client->to_reconnect == NULL) {
+ client->to_reconnect =
+ timeout_add(reconnect_msecs,
+ stats_client_reconnect, client);
+ }
+}
+
+static const struct connection_settings stats_client_set = {
+ .service_name_in = "stats-server",
+ .service_name_out = "stats-client",
+ .major_version = 4,
+ .minor_version = 0,
+
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = TRUE
+};
+
+static const struct connection_vfuncs stats_client_vfuncs = {
+ .destroy = stats_client_destroy,
+ .input_args = stats_client_input_args,
+};
+
+static void
+stats_event_write(struct stats_client *client,
+ struct event *event, struct event *global_event,
+ const struct failure_context *ctx, string_t *str, bool begin)
+{
+ struct event *merged_event;
+ struct event *parent_event;
+ bool update = FALSE, flush_output = FALSE;
+
+ merged_event = begin ? event_ref(event) : event_minimize(event);
+ parent_event = merged_event->parent;
+
+ if (parent_event != NULL) {
+ if (parent_event->sent_to_stats_id !=
+ parent_event->change_id) {
+ stats_event_write(client, parent_event, NULL,
+ ctx, str, TRUE);
+ }
+ i_assert(parent_event->sent_to_stats_id != 0);
+ }
+ if (begin) {
+ i_assert(event == merged_event);
+ update = (event->sent_to_stats_id != 0);
+ const char *cmd = !update ? "BEGIN" : "UPDATE";
+ str_printfa(str, "%s\t%"PRIu64"\t", cmd, event->id);
+ event->sent_to_stats_id = event->change_id;
+ /* Flush the BEGINs early on, because the stats event writing
+ may trigger more events recursively (e.g. data_stack_grow),
+ which may use the BEGIN events as parents. */
+ flush_output = !update;
+ } else {
+ str_printfa(str, "EVENT\t%"PRIu64"\t",
+ global_event == NULL ? 0 : global_event->id);
+ }
+ str_printfa(str, "%"PRIu64"\t",
+ parent_event == NULL ? 0 : parent_event->id);
+ if (!update)
+ str_printfa(str, "%u\t", ctx->type);
+ event_export(merged_event, str);
+ str_append_c(str, '\n');
+ event_unref(&merged_event);
+ if (flush_output) {
+ o_stream_nsend(client->conn.output, str_data(str), str_len(str));
+ str_truncate(str, 0);
+ }
+}
+
+static void
+stats_client_send_event(struct stats_client *client, struct event *event,
+ const struct failure_context *ctx)
+{
+ static int recursion = 0;
+
+ if (!client->handshaked)
+ return;
+
+ if (!event_filter_match(client->filter, event, ctx))
+ return;
+
+ /* Need to send the event for stats and/or export */
+ string_t *str = t_str_new(256);
+
+ if (++recursion == 0)
+ o_stream_cork(client->conn.output);
+ struct event *global_event = event_get_global();
+ if (global_event != NULL)
+ stats_event_write(client, global_event, NULL, ctx, str, TRUE);
+
+ stats_event_write(client, event, global_event, ctx, str, FALSE);
+ o_stream_nsend(client->conn.output, str_data(str), str_len(str));
+
+ i_assert(recursion > 0);
+ if (--recursion == 0)
+ o_stream_uncork(client->conn.output);
+}
+
+static void
+stats_client_free_event(struct stats_client *client, struct event *event)
+{
+ if (event->sent_to_stats_id == 0)
+ return;
+ o_stream_nsend_str(client->conn.output,
+ t_strdup_printf("END\t%"PRIu64"\n", event->id));
+}
+
+static bool
+stats_event_callback(struct event *event, enum event_callback_type type,
+ struct failure_context *ctx,
+ const char *fmt ATTR_UNUSED, va_list args ATTR_UNUSED)
+{
+ if (stats_clients->connections == NULL)
+ return TRUE;
+ struct stats_client *client =
+ (struct stats_client *)stats_clients->connections;
+ if (client->conn.output == NULL)
+ return TRUE;
+
+ switch (type) {
+ case EVENT_CALLBACK_TYPE_CREATE:
+ break;
+ case EVENT_CALLBACK_TYPE_SEND:
+ stats_client_send_event(client, event, ctx);
+ break;
+ case EVENT_CALLBACK_TYPE_FREE:
+ stats_client_free_event(client, event);
+ break;
+ }
+ return TRUE;
+}
+
+static void
+stats_category_append(string_t *str, const struct event_category *category)
+{
+ str_append(str, "CATEGORY\t");
+ str_append_tabescaped(str, category->name);
+ if (category->parent != NULL) {
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, category->parent->name);
+ }
+ str_append_c(str, '\n');
+}
+
+static void stats_category_registered(struct event_category *category)
+{
+ if (stats_clients->connections == NULL)
+ return;
+ struct stats_client *client =
+ (struct stats_client *)stats_clients->connections;
+ if (client->conn.output == NULL)
+ return;
+
+ string_t *str = t_str_new(64);
+ stats_category_append(str, category);
+ o_stream_nsend(client->conn.output, str_data(str), str_len(str));
+}
+
+static void stats_global_init(void)
+{
+ stats_clients = connection_list_init(&stats_client_set,
+ &stats_client_vfuncs);
+ event_register_callback(stats_event_callback);
+ event_category_register_callback(stats_category_registered);
+}
+
+static void stats_global_deinit(void)
+{
+ event_unregister_callback(stats_event_callback);
+ event_category_unregister_callback(stats_category_registered);
+ connection_list_deinit(&stats_clients);
+}
+
+static void stats_client_timeout(struct stats_client *client)
+{
+ e_error(client->conn.event, "Timeout waiting for handshake response");
+ io_loop_stop(client->ioloop);
+}
+
+static void stats_client_wait_handshake(struct stats_client *client)
+{
+ struct ioloop *prev_ioloop = current_ioloop;
+ struct timeout *to;
+
+ i_assert(client->to_reconnect == NULL);
+
+ client->ioloop = io_loop_create();
+ to = timeout_add(STATS_CLIENT_TIMEOUT_MSECS, stats_client_timeout, client);
+ connection_switch_ioloop(&client->conn);
+ io_loop_run(client->ioloop);
+ io_loop_set_current(prev_ioloop);
+ connection_switch_ioloop(&client->conn);
+ if (client->to_reconnect != NULL)
+ client->to_reconnect = io_loop_move_timeout(&client->to_reconnect);
+ io_loop_set_current(client->ioloop);
+ timeout_remove(&to);
+ io_loop_destroy(&client->ioloop);
+}
+
+static void stats_client_send_registered_categories(struct stats_client *client)
+{
+ struct event_category *const *categories;
+ unsigned int i, count;
+
+ string_t *str = t_str_new(64);
+ categories = event_get_registered_categories(&count);
+ for (i = 0; i < count; i++)
+ stats_category_append(str, categories[i]);
+ o_stream_nsend(client->conn.output, str_data(str), str_len(str));
+}
+
+static void stats_client_connect(struct stats_client *client)
+{
+ if (connection_client_connect(&client->conn) == 0) {
+ /* read the handshake so the global debug filter is updated */
+ stats_client_send_registered_categories(client);
+ if (!client->handshake_received_at_least_once)
+ stats_client_wait_handshake(client);
+ } else if (!client->silent_notfound_errors ||
+ (errno != ENOENT && errno != ECONNREFUSED)) {
+ i_error("net_connect_unix(%s) failed: %m", client->conn.name);
+ }
+}
+
+struct stats_client *
+stats_client_init(const char *path, bool silent_notfound_errors)
+{
+ struct stats_client *client;
+
+ if (stats_clients == NULL)
+ stats_global_init();
+
+ client = i_new(struct stats_client, 1);
+ client->silent_notfound_errors = silent_notfound_errors;
+ connection_init_client_unix(stats_clients, &client->conn, path);
+ stats_client_connect(client);
+ return client;
+}
+
+void stats_client_deinit(struct stats_client **_client)
+{
+ struct stats_client *client = *_client;
+
+ *_client = NULL;
+
+ event_filter_unref(&client->filter);
+ connection_deinit(&client->conn);
+ timeout_remove(&client->to_reconnect);
+ i_free(client);
+
+ if (stats_clients->connections == NULL)
+ stats_global_deinit();
+}
diff --git a/src/lib-master/stats-client.h b/src/lib-master/stats-client.h
new file mode 100644
index 0000000..c20bf5d
--- /dev/null
+++ b/src/lib-master/stats-client.h
@@ -0,0 +1,8 @@
+#ifndef STATS_CLIENT_H
+#define STATS_CLIENT_H
+
+struct stats_client *
+stats_client_init(const char *path, bool silent_notfound_errors);
+void stats_client_deinit(struct stats_client **client);
+
+#endif
diff --git a/src/lib-master/syslog-util.c b/src/lib-master/syslog-util.c
new file mode 100644
index 0000000..58827a8
--- /dev/null
+++ b/src/lib-master/syslog-util.c
@@ -0,0 +1,65 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "syslog-util.h"
+#include <syslog.h>
+
+struct syslog_facility_list syslog_facilities[] = {
+#ifdef LOG_AUTH
+ { "auth", LOG_AUTH },
+#endif
+#ifdef LOG_AUTHPRIV
+ { "authpriv", LOG_AUTHPRIV },
+#endif
+#ifdef LOG_CRON
+ { "cron", LOG_CRON },
+#endif
+#ifdef LOG_DAEMON
+ { "daemon", LOG_DAEMON },
+#endif
+#ifdef LOG_FTP
+ { "ftp", LOG_FTP },
+#endif
+#ifdef LOG_KERN
+ { "kern", LOG_KERN },
+#endif
+#ifdef LOG_LPR
+ { "lpr", LOG_LPR },
+#endif
+#ifdef LOG_MAIL
+ { "mail", LOG_MAIL },
+#endif
+#ifdef LOG_NEWS
+ { "news", LOG_NEWS },
+#endif
+#ifdef LOG_SYSLOG
+ { "syslog", LOG_SYSLOG },
+#endif
+#ifdef LOG_UUCP
+ { "uucp", LOG_UUCP },
+#endif
+ { "user", LOG_USER },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+
+ { NULL, 0 }
+};
+
+bool syslog_facility_find(const char *name, int *facility_r)
+{
+ int i;
+
+ for (i = 0; syslog_facilities[i].name != NULL; i++) {
+ if (strcmp(syslog_facilities[i].name, name) == 0) {
+ *facility_r = syslog_facilities[i].facility;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
diff --git a/src/lib-master/syslog-util.h b/src/lib-master/syslog-util.h
new file mode 100644
index 0000000..68e1250
--- /dev/null
+++ b/src/lib-master/syslog-util.h
@@ -0,0 +1,14 @@
+#ifndef SYSLOG_UTIL_H
+#define SYSLOG_UTIL_H
+
+struct syslog_facility_list {
+ const char *name;
+ int facility;
+};
+
+extern struct syslog_facility_list syslog_facilities[];
+
+/* Returns TRUE if found. */
+bool syslog_facility_find(const char *name, int *facility_r);
+
+#endif
diff --git a/src/lib-master/test-event-stats.c b/src/lib-master/test-event-stats.c
new file mode 100644
index 0000000..565118b
--- /dev/null
+++ b/src/lib-master/test-event-stats.c
@@ -0,0 +1,784 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "lib.h"
+#include "time-util.h"
+#include "lib-event-private.h"
+#include "str.h"
+#include "sleep.h"
+#include "ioloop.h"
+#include "connection.h"
+#include "ostream.h"
+#include "istream.h"
+#include "stats-client.h"
+#include "test-common.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#define TST_BEGIN(test_name) \
+ test_begin(test_name); \
+ ioloop_timeval.tv_sec = 0; \
+ ioloop_timeval.tv_usec = 0;
+
+#define BASE_DIR "."
+#define SOCK_PATH ".test-temp-stats-event-sock"
+
+#define SOCK_FULL BASE_DIR "/" SOCK_PATH
+
+static struct event_category test_cats[5] = {
+ {.name = "test1"},
+ {.name = "test2"},
+ {.name = "test3"},
+ {.name = "test4"},
+ {.name = "test5"},
+};
+
+static struct event_field test_fields[5] = {
+ {.key = "key1",
+ .value_type = EVENT_FIELD_VALUE_TYPE_STR,
+ .value = {.str = "str1"}},
+
+ {.key = "key2",
+ .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX,
+ .value = {.intmax = 20}},
+
+ {.key = "key3",
+ .value_type = EVENT_FIELD_VALUE_TYPE_TIMEVAL,
+ .value = {.timeval = {.tv_sec = 10}}},
+
+ {.key = "key4",
+ .value = {.str = "str4"}},
+
+ {.key = "key5",
+ .value = {.intmax = 50}},
+};
+
+static void stats_conn_accept(void *context ATTR_UNUSED);
+static void stats_conn_destroy(struct connection *_conn);
+static void stats_conn_input(struct connection *_conn);
+
+static bool compare_test_stats_to(const char *format, ...) ATTR_FORMAT(1, 2);
+
+static struct connection_settings stats_conn_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs stats_conn_vfuncs = {
+ .destroy = stats_conn_destroy,
+ .input = stats_conn_input
+};
+
+struct server_connection {
+ struct connection conn;
+
+ pool_t pool;
+ bool handshake_sent:1;
+};
+
+static int stats_sock_fd;
+static struct connection_list *stats_conn_list;
+static struct ioloop *ioloop;
+
+static pid_t stats_pid;
+
+static int run_tests(void);
+static void signal_process(const char *signal_file);
+static void wait_for_signal(const char *signal_file);
+static void kill_stats_child(void);
+
+static const char *stats_ready = ".test-temp-stats-event-stats-ready";
+static const char *test_done = ".test-temp-stats-event-test-done";
+static const char *exit_stats = ".test-temp-stats-event-exit-stats";
+static const char *stats_data_file = ".test-temp-stats-event-test_stats";
+
+static void kill_stats_child(void)
+{
+ i_assert(stats_pid != 0);
+ (void)kill(stats_pid, SIGKILL);
+ (void)waitpid(stats_pid, NULL, 0);
+}
+
+static void stats_proc(void)
+{
+ struct io *io_listen;
+ /* Make sure socket file not existing */
+ i_unlink_if_exists(SOCK_FULL);
+ stats_sock_fd = net_listen_unix(SOCK_FULL, 128);
+ if (stats_sock_fd == -1)
+ i_fatal("listen(%s) failed: %m", SOCK_FULL);
+ ioloop = io_loop_create();
+ io_listen = io_add(stats_sock_fd, IO_READ, stats_conn_accept, NULL);
+ stats_conn_list = connection_list_init(&stats_conn_set,
+ &stats_conn_vfuncs);
+ signal_process(stats_ready);
+ io_loop_run(ioloop);
+ io_remove(&io_listen);
+ connection_list_deinit(&stats_conn_list);
+ io_loop_destroy(&ioloop);
+ i_close_fd(&stats_sock_fd);
+ i_unlink(SOCK_FULL);
+}
+
+static void stats_conn_accept(void *context ATTR_UNUSED)
+{
+ int fd;
+ struct server_connection *conn;
+ pool_t pool;
+ fd = net_accept(stats_sock_fd, NULL, NULL);
+ if (stats_sock_fd == -1)
+ return;
+ if (stats_sock_fd == -2)
+ i_fatal("test stats: accept() failed: %m");
+ net_set_nonblock(fd, TRUE);
+ pool = pool_alloconly_create("stats connection", 512);
+ conn = p_new(pool, struct server_connection, 1);
+ conn->pool = pool;
+ connection_init_server(stats_conn_list,
+ &conn->conn,
+ "stats connection", fd, fd);
+}
+
+static void stats_conn_destroy(struct connection *_conn)
+{
+ struct server_connection *conn =
+ (struct server_connection *)_conn;
+ connection_deinit(&conn->conn);
+ pool_unref(&conn->pool);
+}
+
+static void stats_conn_input(struct connection *_conn)
+{
+ int fd;
+ struct ostream *stats_data_out;
+ struct server_connection *conn = (struct server_connection *)_conn;
+ const char *handshake = "VERSION\tstats-server\t4\t0\n"
+ "FILTER\tcategory=test1 OR category=test2 OR category=test3 OR "
+ "category=test4 OR category=test5\n";
+ const char *line = NULL;
+ if (!conn->handshake_sent) {
+ conn->handshake_sent = TRUE;
+ o_stream_nsend_str(conn->conn.output, handshake);
+ }
+ while (access(exit_stats, F_OK) < 0) {
+ /* Test process haven't signal yet about end of the tests */
+ while (access(test_done, F_OK) < 0 ||
+ ((line=i_stream_read_next_line(conn->conn.input)) != NULL)) {
+ if (line != NULL) {
+ if (str_begins(line, "VERSION"))
+ continue;
+
+ if ((fd=open(stats_data_file, O_WRONLY | O_CREAT | O_APPEND, 0600)) < 0) {
+ i_fatal("failed create stats data file %m");
+ }
+
+ stats_data_out = o_stream_create_fd_autoclose(&fd, SIZE_MAX);
+ o_stream_nsend_str(stats_data_out, line);
+ o_stream_nsend_str(stats_data_out, "\n");
+
+ o_stream_set_no_error_handling(stats_data_out, TRUE);
+ o_stream_unref(&stats_data_out);
+ }
+ i_sleep_msecs(100);
+ }
+ i_unlink(test_done);
+ signal_process(stats_ready);
+ }
+ i_unlink(exit_stats);
+ i_unlink_if_exists(test_done);
+ io_loop_stop(ioloop);
+}
+
+static void wait_for_signal(const char *signal_file)
+{
+ struct timeval start, now;
+ i_gettimeofday(&start);
+ while (access(signal_file, F_OK) < 0) {
+ i_sleep_msecs(10);
+ i_gettimeofday(&now);
+ if (timeval_diff_usecs(&now, &start) > 10000000) {
+ kill_stats_child();
+ i_fatal("wait_for_signal has timed out");
+ }
+ }
+ i_unlink(signal_file);
+}
+
+static void signal_process(const char *signal_file)
+{
+ int fd;
+ if ((fd = open(signal_file, O_CREAT, 0666)) < 0) {
+ if (stats_pid != 0) {
+ kill_stats_child();
+ }
+ i_fatal("Failed to create signal file %s", signal_file);
+ }
+ i_close_fd(&fd);
+}
+
+static bool compare_test_stats_data_line(const char *reference, const char *actual)
+{
+ const char *const *ref_args = t_strsplit(reference, "\t");
+ const char *const *act_args = t_strsplit(actual, "\t");
+ unsigned int max = str_array_length(ref_args);
+
+ /* different lengths imply not equal */
+ if (str_array_length(ref_args) != str_array_length(act_args))
+ return FALSE;
+
+ for (size_t i = 0; i < max; i++) {
+ if (i > 1 && i < 6) continue;
+ if (*(ref_args[i]) == 'l') {
+ i++;
+ continue;
+ }
+ if (strcmp(ref_args[i], act_args[i]) != 0) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static bool compare_test_stats_data_lines(const char *actual, const char *reference)
+{
+ const char *const *lines_ref = t_strsplit(reference, "\n");
+ const char *const *lines_act = t_strsplit(actual, "\n");
+ for(size_t i = 0; *lines_ref != NULL && *lines_act != NULL; i++, lines_ref++, lines_act++) {
+ if (!compare_test_stats_data_line(*lines_ref, *lines_act))
+ return FALSE;
+ }
+ return *lines_ref == *lines_act;
+}
+
+static bool compare_test_stats_to(const char *format, ...)
+{
+ bool res;
+ string_t *reference = t_str_new(1024);
+ struct istream *input;
+ va_list args;
+ va_start (args, format);
+ str_vprintfa (reference, format, args);
+ va_end (args);
+ /* signal stats process to receive and record stats data */
+ signal_process(test_done);
+ /* Wait stats data to be recorded by stats process */
+ wait_for_signal(stats_ready);
+
+ input = i_stream_create_file(stats_data_file, SIZE_MAX);
+ while (i_stream_read(input) > 0) ;
+ if (input->stream_errno != 0) {
+ i_fatal("stats data file read failed: %s",
+ i_stream_get_error(input));
+ res = FALSE;
+ } else {
+ size_t size;
+ const unsigned char *data = i_stream_get_data(input, &size);
+ res = compare_test_stats_data_lines(t_strdup_until(data, data+size), str_c(reference));
+ }
+ i_stream_unref(&input);
+ i_unlink(stats_data_file);
+ return res;
+}
+
+static void test_fail_callback(const struct failure_context *ctx ATTR_UNUSED,
+ const char *format ATTR_UNUSED,
+ va_list args ATTR_UNUSED)
+{
+ /* ignore message, all we need is stats */
+}
+
+static void register_all_categories(void)
+{
+ /* Run this before all the tests,
+ so stats client doesn't send CATEGORY\ttestx anymore,
+ so test will produce stats records independent of test order */
+ struct event *ev;
+ int i;
+ for (i = 0; i < 5; i++) {
+ ev = event_create(NULL);
+ event_add_category(ev, &test_cats[i]);
+ e_info(ev, "message");
+ event_unref(&ev);
+ }
+ signal_process(test_done);
+}
+
+static void test_no_merging1(void)
+{
+ /* NULL parent */
+ int l;
+ TST_BEGIN("no merging parent is NULL");
+ struct event *single_ev = event_create(NULL);
+ event_add_category(single_ev, &test_cats[0]);
+ event_add_str(single_ev, test_fields[0].key, test_fields[0].value.str);
+ e_info(single_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&single_ev);
+ test_assert(
+ compare_test_stats_to(
+ "EVENT 0 0 1 0 0"
+ " s"__FILE__" %d"
+ " l0 0 ctest1 Skey1 str1\n", l));
+ test_end();
+}
+
+static void test_no_merging2(void)
+{
+ /* Parent sent to stats */
+ int l;
+ uint64_t id;
+ TST_BEGIN("no merging parent sent to stats");
+ struct event *parent_ev = event_create(NULL);
+ event_add_category(parent_ev, &test_cats[0]);
+ parent_ev->sent_to_stats_id = parent_ev->change_id;
+ id = parent_ev->id;
+ struct event *child_ev = event_create(parent_ev);
+ event_add_category(child_ev, &test_cats[1]);
+ e_info(child_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&parent_ev);
+ event_unref(&child_ev);
+ test_assert(
+ compare_test_stats_to(
+ "EVENT 0 %"PRIu64" 1 0 0"
+ " s"__FILE__" %d"
+ " l0 0 ctest2\n"
+ "END 9\n", id, l));
+ test_end();
+}
+
+static void test_no_merging3(void)
+{
+ /* Parent have different timestamp */
+ int l, lp;
+ uint64_t idp;
+ TST_BEGIN("no merging parent timestamp differs");
+ struct event *parent_ev = event_create(NULL);
+ lp = __LINE__ - 1;
+ idp = parent_ev->id;
+ event_add_category(parent_ev, &test_cats[0]);
+ parent_ev->sent_to_stats_id = 0;
+ ioloop_timeval.tv_sec++;
+ struct event *child_ev = event_create(parent_ev);
+ event_add_category(child_ev, &test_cats[1]);
+ e_info(child_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&parent_ev);
+ event_unref(&child_ev);
+ test_assert(
+ compare_test_stats_to(
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d ctest1\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest2\n"
+ "END\t%"PRIu64"\n", idp, lp, idp, l, idp));
+ test_end();
+}
+
+static void test_merge_events1(void)
+{
+ int l;
+ TST_BEGIN("merge events parent NULL");
+ struct event *merge_ev1 = event_create(NULL);
+ event_add_category(merge_ev1, &test_cats[0]);
+ event_add_category(merge_ev1, &test_cats[1]);
+ event_add_str(merge_ev1,test_fields[0].key, test_fields[0].value.str);
+ event_add_int(merge_ev1,test_fields[1].key, test_fields[1].value.intmax);
+ struct event *merge_ev2 = event_create(merge_ev1);
+ event_add_category(merge_ev2, &test_cats[2]);
+ event_add_category(merge_ev2, &test_cats[1]);
+ event_add_timeval(merge_ev2,test_fields[2].key,
+ &test_fields[2].value.timeval);
+ event_add_int(merge_ev2,test_fields[1].key, test_fields[1].value.intmax);
+ e_info(merge_ev2, "info message");
+ l = __LINE__ - 1;
+ event_unref(&merge_ev1);
+ event_unref(&merge_ev2);
+ test_assert(
+ compare_test_stats_to(
+ "EVENT 0 0 1 0 0"
+ " s"__FILE__" %d l0 0"
+ " ctest3 ctest2 ctest1 Tkey3"
+ " 10 0 Ikey2 20"
+ " Skey1 str1\n", l));
+ test_end();
+}
+
+static void test_merge_events2(void)
+{
+ int l;
+ uint64_t id;
+ TST_BEGIN("merge events parent sent to stats");
+ struct event *parent_ev = event_create(NULL);
+ event_add_category(parent_ev, &test_cats[3]);
+ parent_ev->sent_to_stats_id = parent_ev->change_id;
+ struct event *merge_ev1 = event_create(parent_ev);
+ event_add_category(merge_ev1, &test_cats[0]);
+ event_add_category(merge_ev1, &test_cats[1]);
+ event_add_str(merge_ev1,test_fields[0].key, test_fields[0].value.str);
+ event_add_int(merge_ev1,test_fields[1].key, test_fields[1].value.intmax);
+ struct event *merge_ev2 = event_create(merge_ev1);
+ event_add_category(merge_ev2, &test_cats[2]);
+ event_add_category(merge_ev2, &test_cats[1]);
+ event_add_timeval(merge_ev2,test_fields[2].key,
+ &test_fields[2].value.timeval);
+ event_add_int(merge_ev2,test_fields[1].key, test_fields[1].value.intmax);
+ e_info(merge_ev2, "info message");
+ l = __LINE__ - 1;
+ id = parent_ev->id;
+ event_unref(&parent_ev);
+ event_unref(&merge_ev1);
+ event_unref(&merge_ev2);
+ test_assert(
+ compare_test_stats_to(
+ "EVENT 0 %"PRIu64" 1 0 0"
+ " s"__FILE__" %d l0 0"
+ " ctest3 ctest2 ctest1 Tkey3"
+ " 10 0 Ikey2 20"
+ " Skey1 str1\n"
+ "END 16\n", id, l));
+ test_end();
+}
+
+static void test_skip_parents(void)
+{
+ int l, lp;
+ uint64_t id;
+ TST_BEGIN("skip empty parents");
+ struct event *parent_to_log = event_create(NULL);
+ lp = __LINE__ - 1;
+ id = parent_to_log->id;
+ event_add_category(parent_to_log, &test_cats[0]);
+ ioloop_timeval.tv_sec++;
+ struct event *empty_parent1 = event_create(parent_to_log);
+ ioloop_timeval.tv_sec++;
+ struct event *empty_parent2 = event_create(empty_parent1);
+ ioloop_timeval.tv_sec++;
+ struct event *child_ev = event_create(empty_parent2);
+ event_add_category(child_ev, &test_cats[1]);
+ e_info(child_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&parent_to_log);
+ event_unref(&empty_parent1);
+ event_unref(&empty_parent2);
+ event_unref(&child_ev);
+ test_assert(
+ compare_test_stats_to(
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d ctest1\n"
+ "EVENT 0 %"PRIu64" 1 3 0 "
+ "s"__FILE__" %d l3 0"
+ " ctest2\nEND\t%"PRIu64"\n", id, lp, id, l, id));
+ test_end();
+}
+
+static void test_merge_events_skip_parents(void)
+{
+ int lp, l;
+ uint64_t id;
+ TST_BEGIN("merge events and skip empty parents");
+ struct event *parent_to_log = event_create(NULL);
+ lp = __LINE__ - 1;
+ id = parent_to_log->id;
+ event_add_category(parent_to_log, &test_cats[0]);
+ ioloop_timeval.tv_sec++;
+ struct event *empty_parent1 = event_create(parent_to_log);
+ ioloop_timeval.tv_sec++;
+ struct event *empty_parent2 = event_create(empty_parent1);
+ ioloop_timeval.tv_sec++;
+ struct event *child1_ev = event_create(empty_parent2);
+ event_add_category(child1_ev, &test_cats[1]);
+ event_add_category(child1_ev, &test_cats[2]);
+ event_add_int(child1_ev,test_fields[1].key, test_fields[1].value.intmax);
+ event_add_str(child1_ev,test_fields[0].key, test_fields[0].value.str);
+ struct event *child2_ev = event_create(empty_parent2);
+ event_add_category(child2_ev, &test_cats[3]);
+ event_add_category(child2_ev, &test_cats[4]);
+ event_add_timeval(child2_ev,test_fields[2].key,
+ &test_fields[2].value.timeval);
+ event_add_str(child2_ev,test_fields[3].key, test_fields[3].value.str);
+ e_info(child2_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&parent_to_log);
+ event_unref(&empty_parent1);
+ event_unref(&empty_parent2);
+ event_unref(&child1_ev);
+ event_unref(&child2_ev);
+ test_assert(
+ compare_test_stats_to(
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d ctest1\n"
+ "EVENT 0 %"PRIu64" 1 3 0 "
+ "s"__FILE__" %d l3 0 "
+ "ctest4 ctest5 Tkey3 10 0 Skey4"
+ " str4\nEND\t%"PRIu64"\n", id, lp, id, l, id));
+ test_end();
+}
+
+static struct event *make_event(struct event *parent,
+ struct event_category *cat,
+ int *line_r, uint64_t *id_r)
+{
+ struct event *event;
+ int line;
+
+ event = event_create(parent);
+ line = __LINE__ -1;
+
+ if (line_r != NULL)
+ *line_r = line;
+ if (id_r != NULL)
+ *id_r = event->id;
+
+ /* something in the test infrastructure assumes that at least one
+ category is always present - make it happy */
+ event_add_category(event, cat);
+
+ /* advance the clock to avoid event sending optimizations */
+ ioloop_timeval.tv_sec++;
+
+ return event;
+}
+
+static void test_parent_update_post_send(void)
+{
+ struct event *a, *b, *c;
+ uint64_t id;
+ int line, line_log1, line_log2;
+
+ TST_BEGIN("parent updated after send");
+
+ a = make_event(NULL, &test_cats[0], &line, &id);
+ b = make_event(a, &test_cats[1], NULL, NULL);
+ c = make_event(b, &test_cats[2], NULL, NULL);
+
+ /* set initial field values */
+ event_add_int(a, "a", 1);
+ event_add_int(b, "b", 2);
+ event_add_int(c, "c", 3);
+
+ /* force 'a' event to be sent */
+ e_info(b, "field 'a' should be 1");
+ line_log1 = __LINE__ - 1;
+
+ event_add_int(a, "a", 1000); /* update parent */
+
+ /* log child, which should re-sent parent */
+ e_info(c, "field 'a' should be 1000");
+ line_log2 = __LINE__ - 1;
+
+ event_unref(&a);
+ event_unref(&b);
+ event_unref(&c);
+
+ /* EVENT <parent> <type> ... */
+ /* BEGIN <id> <parent> <type> ... */
+ /* END <id> */
+ test_assert(
+ compare_test_stats_to(
+ /* first e_info() */
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d ctest1"
+ " Ia 1\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest2" " Ib 2\n"
+ /* second e_info() */
+ "UPDATE %"PRIu64" 0 0 0"
+ " s"__FILE__" %d ctest1"
+ " Ia 1000\n"
+ "BEGIN %"PRIu64" %"PRIu64" 1 0 0"
+ " s"__FILE__" %d"
+ " l0 0 ctest2 Ib 2\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest3"
+ " Ic 3\n"
+ "END\t%"PRIu64"\n"
+ "END\t%"PRIu64"\n",
+ id, line, /* BEGIN */
+ id, line_log1, /* EVENT */
+ id, line, /* UPDATE */
+ id + 1, id, line, /* BEGIN */
+ id + 1, line_log2, /* EVENT */
+ id + 1 /* END */,
+ id /* END */));
+
+ test_end();
+}
+
+static void test_large_event_id(void)
+{
+ TST_BEGIN("large event id");
+ int line, line_log1, line_log2, line_log3;
+ struct event *a, *b;
+ uint64_t id;
+
+ a = make_event(NULL, &test_cats[0], &line, &id);
+ a->id += 1000000;
+ id = a->id;
+ a->change_id++;
+ b = make_event(a, &test_cats[1], NULL, NULL);
+
+ ioloop_timeval.tv_sec++;
+ e_info(a, "emit");
+ line_log1 = __LINE__-1;
+ ioloop_timeval.tv_sec++;
+ e_info(b, "emit");
+ line_log2 = __LINE__-1;
+ event_add_int(a, "test1", 1);
+ e_info(b, "emit");
+ line_log3 = __LINE__-1;
+
+ event_unref(&b);
+ event_unref(&a);
+
+ test_assert(
+ compare_test_stats_to(
+ /* first e_info() */
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest1\n"
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d"
+ " l0 0 ctest1\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest2\n"
+ "UPDATE %"PRIu64" 0 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest1 Itest1 1\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest2\n"
+ "END %"PRIu64"\n",
+ (uint64_t)0, line_log1,
+ id, line,
+ id, line_log2,
+ id, line,
+ id, line_log3,
+ id
+ )
+ );
+
+ test_end();
+}
+
+static void test_global_event(void)
+{
+ TST_BEGIN("merge events global");
+ struct event *merge_ev1 = event_create(NULL);
+ event_add_category(merge_ev1, &test_cats[0]);
+ event_add_str(merge_ev1,test_fields[0].key, test_fields[0].value.str);
+ struct event *merge_ev2 = event_create(merge_ev1);
+ event_add_int(merge_ev2,test_fields[1].key, test_fields[1].value.intmax);
+
+ struct event *global_event = event_create(NULL);
+ int global_event_line = __LINE__ - 1;
+ uint64_t global_event_id = global_event->id;
+ event_add_str(global_event, "global", "value");
+ event_push_global(global_event);
+
+ struct timeval tv;
+ event_get_create_time(merge_ev1, &tv);
+
+ e_info(merge_ev2, "info message");
+ int log_line = __LINE__ - 1;
+
+ event_pop_global(global_event);
+ event_unref(&merge_ev1);
+ event_unref(&merge_ev2);
+ event_unref(&global_event);
+
+ test_assert(
+ compare_test_stats_to(
+ "BEGIN\t%"PRIu64"\t0\t1\t0\t0"
+ "\ts"__FILE__"\t%d"
+ "\tSglobal\tvalue\n"
+ "EVENT\t%"PRIu64"\t0\t1\t0\t0"
+ "\ts"__FILE__"\t%d\tl0\t0"
+ "\tctest1\tIkey2\t20\tSkey1\tstr1\n"
+ "END\t%"PRIu64"\n",
+ global_event_id, global_event_line,
+ global_event_id, log_line,
+ global_event_id));
+ test_end();
+}
+
+static int run_tests(void)
+{
+ int ret;
+ void (*const tests[])(void) = {
+ test_no_merging1,
+ test_no_merging2,
+ test_no_merging3,
+ test_merge_events1,
+ test_merge_events2,
+ test_skip_parents,
+ test_merge_events_skip_parents,
+ test_parent_update_post_send,
+ test_large_event_id,
+ test_global_event,
+ NULL
+ };
+ struct ioloop *ioloop = io_loop_create();
+ struct stats_client *stats_client = stats_client_init(SOCK_FULL, FALSE);
+ register_all_categories();
+ wait_for_signal(stats_ready);
+ /* Remove stats data file containing register categories related stuff */
+ i_unlink(stats_data_file);
+ ret = test_run(tests);
+ stats_client_deinit(&stats_client);
+ signal_process(exit_stats);
+ signal_process(test_done);
+ (void)waitpid(stats_pid, NULL, 0);
+ io_loop_destroy(&ioloop);
+ return ret;
+}
+
+static void cleanup_test_stats(void)
+{
+ i_unlink_if_exists(SOCK_FULL);
+ i_unlink_if_exists(stats_data_file);
+ i_unlink_if_exists(test_done);
+ i_unlink_if_exists(exit_stats);
+ i_unlink_if_exists(stats_ready);
+}
+
+static int launch_test_stats(void)
+{
+ int ret;
+
+ /* Make sure files are not existing */
+ cleanup_test_stats();
+
+ if ((stats_pid = fork()) == (pid_t)-1)
+ i_fatal("fork() failed: %m");
+ if (stats_pid == 0) {
+ stats_proc();
+ return 0;
+ }
+ wait_for_signal(stats_ready);
+ ret = run_tests();
+
+ /* Make sure we don't leave anything behind */
+ cleanup_test_stats();
+
+ return ret;
+}
+
+int main(void)
+{
+ int ret;
+ i_set_info_handler(test_fail_callback);
+ lib_init();
+ ret = launch_test_stats();
+ lib_deinit();
+ return ret;
+}
diff --git a/src/lib-master/test-master-service-settings-cache.c b/src/lib-master/test-master-service-settings-cache.c
new file mode 100644
index 0000000..1765839
--- /dev/null
+++ b/src/lib-master/test-master-service-settings-cache.c
@@ -0,0 +1,125 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "settings-parser.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+#include "master-service-settings-cache.h"
+
+
+struct master_service *master_service;
+
+static struct master_service test_master_service;
+static struct master_service_settings set;
+static struct master_service_settings_input input;
+static struct master_service_settings_output output;
+static struct master_service_settings_cache *cache;
+
+struct test_service_settings {
+ const char *foo;
+};
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct test_service_settings)
+
+static const struct setting_define test_setting_defines[] = {
+ DEF(STR, foo),
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct test_service_settings test_default_settings = {
+ .foo = ""
+};
+
+static const struct setting_parser_info test_setting_parser_info = {
+ .module_name = "module",
+ .defines = test_setting_defines,
+ .defaults = &test_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct test_service_settings),
+
+ .parent_offset = SIZE_MAX
+};
+
+int master_service_settings_read(struct master_service *service ATTR_UNUSED,
+ const struct master_service_settings_input *input ATTR_UNUSED,
+ struct master_service_settings_output *output_r,
+ const char **error_r ATTR_UNUSED)
+{
+ *output_r = output;
+ return 0;
+}
+
+int master_service_settings_get_filters(struct master_service *service ATTR_UNUSED,
+ const char *const **filters ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ return -1;
+}
+
+
+const struct master_service_settings *
+master_service_settings_get(struct master_service *service ATTR_UNUSED)
+{
+ return &set;
+}
+
+static void test_master_service_settings_cache_once(void)
+{
+ const struct setting_parser_context *parser;
+ const char *error;
+
+ output.used_local = output.service_uses_local && i_rand_limit(2) != 0;
+ if (output.used_local) {
+ input.local_ip.family = AF_INET;
+ input.local_ip.u.ip4.s_addr = i_rand_minmax(100, 199);
+ }
+ output.used_remote = output.service_uses_remote && i_rand_limit(2) != 0;
+ if (output.used_remote) {
+ input.remote_ip.family = AF_INET;
+ input.remote_ip.u.ip4.s_addr = i_rand_minmax(100, 199);
+ }
+ test_assert(master_service_settings_cache_read(cache, &input, NULL, &parser, &error) == 0);
+}
+
+static void test_master_service_settings_cache(void)
+{
+ int i, j;
+
+ for (i = 1; i < 4; i++) {
+ cache = master_service_settings_cache_init(master_service,
+ "module", "service_name");
+ output.service_uses_local = (i & 1) != 0;
+ output.service_uses_remote = (i & 2) != 0;
+ for (j = 0; j < 1000; j++)
+ test_master_service_settings_cache_once();
+ master_service_settings_cache_deinit(&cache);
+ }
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_master_service_settings_cache,
+ NULL
+ };
+ pool_t pool;
+ int ret;
+
+ i_zero(&input);
+ input.module = "module";
+ input.service = "service_name";
+
+ set.config_cache_size = 1024*4;
+ pool = pool_alloconly_create("set pool", 1024);
+ test_master_service.set_parser =
+ settings_parser_init(pool, &test_setting_parser_info, 0);
+ master_service = &test_master_service;
+ ret = test_run(test_functions);
+ settings_parser_deinit(&test_master_service.set_parser);
+ pool_unref(&pool);
+ return ret;
+}