diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-master | |
parent | Initial commit. (diff) | |
download | dovecot-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')
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(¶ms); + params.client_fd = fd; + params.request = *request; + params.data = data; + + master_auth_request_full(auth, ¶ms, 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; +} |