summaryrefslogtreecommitdiffstats
path: root/src/log
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/log/Makefile.am26
-rw-r--r--src/log/Makefile.in825
-rw-r--r--src/log/doveadm-connection.c86
-rw-r--r--src/log/doveadm-connection.h6
-rw-r--r--src/log/log-connection.c533
-rw-r--r--src/log/log-connection.h15
-rw-r--r--src/log/log-error-buffer.c122
-rw-r--r--src/log/log-error-buffer.h24
-rw-r--r--src/log/log-settings.c49
-rw-r--r--src/log/main.c97
-rw-r--r--src/login-common/Makefile.am40
-rw-r--r--src/login-common/Makefile.in908
-rw-r--r--src/login-common/access-lookup.c118
-rw-r--r--src/login-common/access-lookup.h11
-rw-r--r--src/login-common/client-common-auth.c940
-rw-r--r--src/login-common/client-common.c1208
-rw-r--r--src/login-common/client-common.h368
-rw-r--r--src/login-common/login-common.h79
-rw-r--r--src/login-common/login-proxy-state.c165
-rw-r--r--src/login-common/login-proxy-state.h40
-rw-r--r--src/login-common/login-proxy.c1173
-rw-r--r--src/login-common/login-proxy.h120
-rw-r--r--src/login-common/login-settings.c227
-rw-r--r--src/login-common/login-settings.h50
-rw-r--r--src/login-common/main.c571
-rw-r--r--src/login-common/sasl-server.c568
-rw-r--r--src/login-common/sasl-server.h40
27 files changed, 8409 insertions, 0 deletions
diff --git a/src/log/Makefile.am b/src/log/Makefile.am
new file mode 100644
index 0000000..9888fcd
--- /dev/null
+++ b/src/log/Makefile.am
@@ -0,0 +1,26 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = log
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-master \
+ $(BINARY_CFLAGS)
+
+log_LDADD = $(LIBDOVECOT) \
+ $(BINARY_LDFLAGS)
+
+log_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+
+log_SOURCES = \
+ doveadm-connection.c \
+ log-connection.c \
+ log-error-buffer.c \
+ log-settings.c \
+ main.c
+
+noinst_HEADERS = \
+ doveadm-connection.h \
+ log-connection.h \
+ log-error-buffer.h
diff --git a/src/log/Makefile.in b/src/log/Makefile.in
new file mode 100644
index 0000000..33a2595
--- /dev/null
+++ b/src/log/Makefile.in
@@ -0,0 +1,825 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+pkglibexec_PROGRAMS = log$(EXEEXT)
+subdir = src/log
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(pkglibexecdir)"
+PROGRAMS = $(pkglibexec_PROGRAMS)
+am_log_OBJECTS = doveadm-connection.$(OBJEXT) log-connection.$(OBJEXT) \
+ log-error-buffer.$(OBJEXT) log-settings.$(OBJEXT) \
+ main.$(OBJEXT)
+log_OBJECTS = $(am_log_OBJECTS)
+am__DEPENDENCIES_1 =
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/doveadm-connection.Po \
+ ./$(DEPDIR)/log-connection.Po ./$(DEPDIR)/log-error-buffer.Po \
+ ./$(DEPDIR)/log-settings.Po ./$(DEPDIR)/main.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(log_SOURCES)
+DIST_SOURCES = $(log_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+pkglibexecdir = $(libexecdir)/dovecot
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-master \
+ $(BINARY_CFLAGS)
+
+log_LDADD = $(LIBDOVECOT) \
+ $(BINARY_LDFLAGS)
+
+log_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+log_SOURCES = \
+ doveadm-connection.c \
+ log-connection.c \
+ log-error-buffer.c \
+ log-settings.c \
+ main.c
+
+noinst_HEADERS = \
+ doveadm-connection.h \
+ log-connection.h \
+ log-error-buffer.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/log/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/log/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files
+
+clean-pkglibexecPROGRAMS:
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+log$(EXEEXT): $(log_OBJECTS) $(log_DEPENDENCIES) $(EXTRA_log_DEPENDENCIES)
+ @rm -f log$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(log_OBJECTS) $(log_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-connection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log-connection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log-error-buffer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibexecdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/doveadm-connection.Po
+ -rm -f ./$(DEPDIR)/log-connection.Po
+ -rm -f ./$(DEPDIR)/log-error-buffer.Po
+ -rm -f ./$(DEPDIR)/log-settings.Po
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibexecPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/doveadm-connection.Po
+ -rm -f ./$(DEPDIR)/log-connection.Po
+ -rm -f ./$(DEPDIR)/log-error-buffer.Po
+ -rm -f ./$(DEPDIR)/log-settings.Po
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkglibexecPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-pkglibexecPROGRAMS \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkglibexecPROGRAMS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkglibexecPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/log/doveadm-connection.c b/src/log/doveadm-connection.c
new file mode 100644
index 0000000..77872ac
--- /dev/null
+++ b/src/log/doveadm-connection.c
@@ -0,0 +1,86 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ostream.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "log-error-buffer.h"
+#include "doveadm-connection.h"
+
+#include <unistd.h>
+
+struct doveadm_connection {
+ struct log_error_buffer *errorbuf;
+
+ int fd;
+ struct ostream *output;
+};
+
+static void doveadm_connection_destroy(struct doveadm_connection **_conn);
+
+static int doveadm_connection_send_errors(struct doveadm_connection *conn)
+{
+ struct log_error_buffer_iter *iter;
+ const struct log_error *error;
+ string_t *str = t_str_new(256);
+ int ret = 0;
+
+ iter = log_error_buffer_iter_init(conn->errorbuf);
+ while ((error = log_error_buffer_iter_next(iter)) != NULL) {
+ str_truncate(str, 0);
+ str_printfa(str, "%s\t%ld\t",
+ failure_log_type_names[error->type],
+ (long)error->timestamp);
+ str_append_tabescaped(str, error->prefix);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, error->text);
+ str_append_c(str, '\n');
+ if (o_stream_send(conn->output,
+ str_data(str), str_len(str)) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+ log_error_buffer_iter_deinit(&iter);
+ return ret;
+}
+
+static int doveadm_output(struct doveadm_connection *conn)
+{
+ if (o_stream_flush(conn->output) != 0) {
+ /* error / finished */
+ doveadm_connection_destroy(&conn);
+ }
+ return 1;
+}
+
+void doveadm_connection_create(struct log_error_buffer *errorbuf, int fd)
+{
+ struct doveadm_connection *conn;
+
+ conn = i_new(struct doveadm_connection, 1);
+ conn->errorbuf = errorbuf;
+ conn->fd = fd;
+ conn->output = o_stream_create_fd(conn->fd, SIZE_MAX);
+ if (doveadm_connection_send_errors(conn) < 0)
+ doveadm_connection_destroy(&conn);
+ else {
+ o_stream_set_flush_callback(conn->output, doveadm_output, conn);
+ o_stream_set_flush_pending(conn->output, TRUE);
+ }
+}
+
+static void doveadm_connection_destroy(struct doveadm_connection **_conn)
+{
+ struct doveadm_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ o_stream_destroy(&conn->output);
+ if (close(conn->fd) < 0)
+ i_error("close(doveadm connection) failed: %m");
+ i_free(conn);
+
+ master_service_client_connection_destroyed(master_service);
+}
diff --git a/src/log/doveadm-connection.h b/src/log/doveadm-connection.h
new file mode 100644
index 0000000..c37eee4
--- /dev/null
+++ b/src/log/doveadm-connection.h
@@ -0,0 +1,6 @@
+#ifndef DOVEADM_CONNECTION_H
+#define DOVEADM_CONNECTION_H
+
+void doveadm_connection_create(struct log_error_buffer *errorbuf, int fd);
+
+#endif
diff --git a/src/log/log-connection.c b/src/log/log-connection.c
new file mode 100644
index 0000000..82c9374
--- /dev/null
+++ b/src/log/log-connection.c
@@ -0,0 +1,533 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "llist.h"
+#include "hash.h"
+#include "time-util.h"
+#include "process-title.h"
+#include "master-interface.h"
+#include "master-service.h"
+#include "log-error-buffer.h"
+#include "log-connection.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define FATAL_QUEUE_TIMEOUT_MSECS 500
+#define MAX_MSECS_PER_CONNECTION 100
+
+/* Log a warning after 1 secs when we've been all the time busy writing the
+ log connection. */
+#define LOG_WARN_PENDING_COUNT (1000 / MAX_MSECS_PER_CONNECTION)
+/* If we keep being busy, log a warning every 60 seconds. */
+#define LOG_WARN_PENDING_INTERVAL (60 * LOG_WARN_PENDING_COUNT)
+
+struct log_client {
+ struct ip_addr ip;
+ char *prefix;
+ bool fatal_logged:1;
+};
+
+struct log_line_metadata {
+ bool continues;
+ enum log_type log_type;
+ pid_t pid;
+ char *log_prefix;
+};
+
+struct log_connection {
+ struct log_connection *prev, *next;
+
+ struct log_error_buffer *errorbuf;
+ int fd;
+ int listen_fd;
+ struct io *io;
+ struct istream *input;
+
+ struct log_line_metadata partial_line;
+
+ char *default_prefix;
+ HASH_TABLE(void *, struct log_client *) clients;
+
+ unsigned int pending_count;
+
+ bool master:1;
+ bool handshaked:1;
+};
+
+static struct log_connection *log_connections = NULL;
+static ARRAY(struct log_connection *) logs_by_fd;
+static unsigned int global_pending_count;
+static struct log_connection *last_pending_log;
+
+static void
+log_connection_destroy(struct log_connection *log, bool shutting_down);
+
+static void log_refresh_proctitle(void)
+{
+ if (!verbose_proctitle)
+ return;
+
+ if (global_pending_count == 0)
+ process_title_set("");
+ else if (last_pending_log == NULL) {
+ process_title_set(t_strdup_printf(
+ "[%u services too fast]", global_pending_count));
+ } else if (global_pending_count > 1) {
+ process_title_set(t_strdup_printf(
+ "[%u services too fast, last: %d/%d/%s]",
+ global_pending_count,
+ last_pending_log->fd,
+ last_pending_log->listen_fd,
+ last_pending_log->default_prefix));
+ } else {
+ process_title_set(t_strdup_printf(
+ "[service too fast: %d/%d/%s]",
+ last_pending_log->fd,
+ last_pending_log->listen_fd,
+ last_pending_log->default_prefix));
+ }
+}
+
+static struct log_client *log_client_get(struct log_connection *log, pid_t pid)
+{
+ struct log_client *client;
+
+ client = hash_table_lookup(log->clients, POINTER_CAST(pid));
+ if (client == NULL) {
+ client = i_new(struct log_client, 1);
+ hash_table_insert(log->clients, POINTER_CAST(pid), client);
+ }
+ return client;
+}
+
+static void log_client_free(struct log_connection *log,
+ struct log_client *client, pid_t pid)
+{
+ hash_table_remove(log->clients, POINTER_CAST(pid));
+
+ i_free(client->prefix);
+ i_free(client);
+}
+
+static void log_parse_option(struct log_connection *log,
+ const struct failure_line *failure)
+{
+ struct log_client *client;
+
+ client = log_client_get(log, failure->pid);
+ if (str_begins(failure->text, "ip="))
+ (void)net_addr2ip(failure->text + 3, &client->ip);
+ else if (str_begins(failure->text, "prefix=")) {
+ i_free(client->prefix);
+ client->prefix = i_strdup(failure->text + 7);
+ }
+}
+
+static void
+client_log_ctx(struct log_connection *log,
+ const struct failure_context *ctx,
+ const struct timeval *log_time,
+ const char *prefix, const char *text)
+{
+ struct log_error err;
+
+ switch (ctx->type) {
+ case LOG_TYPE_DEBUG:
+ case LOG_TYPE_INFO:
+ break;
+ case LOG_TYPE_WARNING:
+ case LOG_TYPE_ERROR:
+ case LOG_TYPE_FATAL:
+ case LOG_TYPE_PANIC:
+ i_zero(&err);
+ err.type = ctx->type;
+ err.timestamp = log_time->tv_sec;
+ err.prefix = ctx->log_prefix != NULL ? ctx->log_prefix : prefix;
+ err.text = text;
+ log_error_buffer_add(log->errorbuf, &err);
+ break;
+ case LOG_TYPE_OPTION:
+ case LOG_TYPE_COUNT:
+ i_unreached();
+ }
+ /* log_prefix overrides the global prefix. Don't bother changing the
+ global prefix in that case. */
+ if (ctx->log_prefix == NULL)
+ i_set_failure_prefix("%s", prefix);
+ i_log_type(ctx, "%s", text);
+ if (ctx->log_prefix == NULL)
+ i_set_failure_prefix("%s", global_log_prefix);
+}
+
+static void
+client_log_fatal(struct log_connection *log, struct log_client *client,
+ const char *line, const struct timeval *log_time,
+ const struct tm *tm)
+{
+ struct failure_context failure_ctx;
+ const char *prefix = log->default_prefix;
+
+ i_zero(&failure_ctx);
+ failure_ctx.type = LOG_TYPE_FATAL;
+ failure_ctx.timestamp = tm;
+ failure_ctx.timestamp_usecs = log_time->tv_usec;
+
+ if (client != NULL) {
+ if (client->prefix != NULL)
+ prefix = client->prefix;
+ else if (client->ip.family != 0) {
+ line = t_strdup_printf("%s [last ip=%s]",
+ line, net_ip2addr(&client->ip));
+ }
+ }
+ client_log_ctx(log, &failure_ctx, log_time, prefix,
+ t_strconcat("master: ", line, NULL));
+}
+
+static void
+log_parse_master_line(const char *line, const struct timeval *log_time,
+ const struct tm *tm)
+{
+ struct log_connection *const *logs, *log;
+ struct log_client *client;
+ const char *p, *p2, *cmd, *pidstr;
+ unsigned int count;
+ unsigned int service_fd;
+ pid_t pid;
+
+ p = strchr(line, ' ');
+ if (p == NULL || (p2 = strchr(++p, ' ')) == NULL ||
+ str_to_uint(t_strcut(line, ' '), &service_fd) < 0) {
+ i_error("Received invalid input from master: %s", line);
+ return;
+ }
+ pidstr = t_strcut(p, ' ');
+ if (str_to_pid(pidstr, &pid) < 0) {
+ i_error("Received invalid pid from master: %s", pidstr);
+ return;
+ }
+ cmd = p2 + 1;
+
+ logs = array_get(&logs_by_fd, &count);
+ if (service_fd >= count || logs[service_fd] == NULL) {
+ if (strcmp(cmd, "BYE") == 0 && service_fd < count) {
+ /* master is probably shutting down and we already
+ noticed the log fd closing */
+ return;
+ }
+ i_error("Received master input for invalid service_fd %u: %s",
+ service_fd, line);
+ return;
+ }
+ log = logs[service_fd];
+ client = hash_table_lookup(log->clients, POINTER_CAST(pid));
+
+ if (strcmp(cmd, "BYE") == 0) {
+ if (client == NULL) {
+ /* we haven't seen anything important from this client.
+ it's not an error. */
+ return;
+ }
+ log_client_free(log, client, pid);
+ } else if (str_begins(cmd, "FATAL ")) {
+ client_log_fatal(log, client, cmd + 6, log_time, tm);
+ } else if (str_begins(cmd, "DEFAULT-FATAL ")) {
+ /* If the client has logged a fatal/panic, don't log this
+ message. */
+ if (client == NULL || !client->fatal_logged)
+ client_log_fatal(log, client, cmd + 14, log_time, tm);
+ } else {
+ i_error("Received unknown command from master: %s", cmd);
+ }
+}
+
+static void log_partial_line_free(struct log_connection *log)
+{
+ if (!log->partial_line.continues)
+ return;
+ i_free(log->partial_line.log_prefix);
+ i_zero(&log->partial_line);
+}
+
+static void
+log_it(struct log_connection *log, const char *line,
+ const struct timeval *log_time, const struct tm *tm, bool partial_line)
+{
+ struct failure_line failure;
+ struct failure_context failure_ctx;
+ struct log_client *client = NULL;
+ const char *prefix = "";
+
+ if (log->master) {
+ if (partial_line) {
+ /* really not expected */
+ i_error("Received partial line from master: %s", line);
+ }
+ log_parse_master_line(line, log_time, tm);
+ return;
+ }
+
+ if (log->partial_line.continues && line[0] != '\001') {
+ /* The previous line didn't have LF, and it's now continued
+ here. We have the extra \001 check in here, because the
+ write was larger than PIPE_BUF and there's no guarantee
+ that the full write is sequential. The second line could
+ be an unrelated line, and the continuation could happen
+ sometimes after it. */
+ i_zero(&failure);
+ failure.log_type = log->partial_line.log_type;
+ failure.pid = log->partial_line.pid;
+ failure.text = t_strconcat("<line continued> ", line, NULL);
+ if (log->partial_line.log_prefix != NULL)
+ prefix = t_strdup(log->partial_line.log_prefix);
+ if (!partial_line)
+ log_partial_line_free(log);
+ } else {
+ i_failure_parse_line(line, &failure);
+ if (partial_line) {
+ log->partial_line.continues = TRUE;
+ log->partial_line.log_type = failure.log_type;
+ log->partial_line.pid = failure.pid;
+ } else {
+ log_partial_line_free(log);
+ }
+ }
+ switch (failure.log_type) {
+ case LOG_TYPE_FATAL:
+ case LOG_TYPE_PANIC:
+ if (failure.pid != 0) {
+ client = log_client_get(log, failure.pid);
+ client->fatal_logged = TRUE;
+ }
+ break;
+ case LOG_TYPE_OPTION:
+ log_parse_option(log, &failure);
+ return;
+ default:
+ client = failure.pid == 0 ? NULL :
+ hash_table_lookup(log->clients,
+ POINTER_CAST(failure.pid));
+ break;
+ }
+ i_assert(failure.log_type < LOG_TYPE_COUNT);
+
+ i_zero(&failure_ctx);
+ failure_ctx.type = failure.log_type;
+ failure_ctx.timestamp = tm;
+ failure_ctx.timestamp_usecs = log_time->tv_usec;
+ if (failure.log_prefix_len != 0) {
+ failure_ctx.log_prefix =
+ t_strndup(failure.text, failure.log_prefix_len);
+ failure.text += failure.log_prefix_len;
+ if (partial_line && log->partial_line.log_prefix == NULL) {
+ log->partial_line.log_prefix =
+ i_strdup(failure_ctx.log_prefix);
+ }
+ } else if (failure.disable_log_prefix) {
+ failure_ctx.log_prefix = "";
+ } else {
+ prefix = client != NULL && client->prefix != NULL ?
+ client->prefix : log->default_prefix;
+ }
+ client_log_ctx(log, &failure_ctx, log_time, prefix, failure.text);
+}
+
+static int log_connection_handshake(struct log_connection *log)
+{
+ struct log_service_handshake handshake;
+ const unsigned char *data;
+ size_t size;
+ ssize_t ret;
+
+ /* we're reading from a FIFO, so we're assuming that we're getting a
+ full handshake packet immediately. if not, treat it as an error
+ message that we want to log. */
+ ret = i_stream_read(log->input);
+ if (ret == -1) {
+ i_error("read(log %s) failed: %s", log->default_prefix,
+ i_stream_get_error(log->input));
+ return -1;
+ }
+ data = i_stream_get_data(log->input, &size);
+ if (size < sizeof(handshake)) {
+ /* this isn't a handshake */
+ return 0;
+ }
+
+ i_assert(size >= sizeof(handshake));
+ memcpy(&handshake, data, sizeof(handshake));
+
+ if (handshake.log_magic != MASTER_LOG_MAGIC) {
+ /* this isn't a handshake */
+ return 0;
+ }
+
+ if (handshake.prefix_len > size - sizeof(handshake)) {
+ i_error("Missing prefix data in handshake");
+ return -1;
+ }
+ i_free(log->default_prefix);
+ log->default_prefix = i_strndup(data + sizeof(handshake),
+ handshake.prefix_len);
+ i_stream_skip(log->input, sizeof(handshake) + handshake.prefix_len);
+
+ if (strcmp(log->default_prefix, MASTER_LOG_PREFIX_NAME) == 0) {
+ if (log->listen_fd != MASTER_LISTEN_FD_FIRST) {
+ i_error("Received master prefix in handshake "
+ "from non-master fd %d", log->fd);
+ return -1;
+ }
+ log->master = TRUE;
+ }
+ log->handshaked = TRUE;
+ return 0;
+}
+
+static void log_connection_input(struct log_connection *log)
+{
+ const char *line;
+ ssize_t ret;
+ struct timeval now, start_timeval;
+ struct tm tm;
+ bool too_much = FALSE;
+
+ if (!log->handshaked) {
+ if (log_connection_handshake(log) < 0) {
+ log_connection_destroy(log, FALSE);
+ return;
+ }
+ /* come back here even if we read something else besides a
+ handshake. the first few lines could be coming from e.g.
+ libc before the proper handshake line is sent. */
+ }
+
+ io_loop_time_refresh();
+ start_timeval = ioloop_timeval;
+ while ((ret = i_stream_read(log->input)) > 0 || ret == -2) {
+ /* get new timestamps for every read() */
+ now = ioloop_timeval;
+ tm = *localtime(&now.tv_sec);
+
+ if (ret != -2) {
+ while ((line = i_stream_next_line(log->input)) != NULL) T_BEGIN {
+ log_it(log, line, &now, &tm, FALSE);
+ } T_END;
+ } else T_BEGIN {
+ /* LF not found, but buffer is full. Just log what we
+ have for now. */
+ size_t size;
+ const unsigned char *data =
+ i_stream_get_data(log->input, &size);
+ line = t_strndup(data, size);
+ log_it(log, line, &now, &tm, TRUE);
+ i_stream_skip(log->input, size);
+ } T_END;
+ io_loop_time_refresh();
+ if (timeval_diff_msecs(&ioloop_timeval, &start_timeval) > MAX_MSECS_PER_CONNECTION) {
+ too_much = TRUE;
+ break;
+ }
+ }
+
+ if (log->input->eof) {
+ if (log->input->stream_errno != 0)
+ i_error("read(log %s) failed: %m", log->default_prefix);
+ log_connection_destroy(log, FALSE);
+ } else {
+ i_assert(!log->input->closed);
+ if (!too_much) {
+ if (log->pending_count > 0) {
+ log->pending_count = 0;
+ i_assert(global_pending_count > 0);
+ global_pending_count--;
+ if (log == last_pending_log)
+ last_pending_log = NULL;
+ log_refresh_proctitle();
+ }
+ return;
+ }
+ last_pending_log = log;
+ if (log->pending_count++ == 0) {
+ global_pending_count++;
+ log_refresh_proctitle();
+ }
+ if (log->pending_count == LOG_WARN_PENDING_COUNT ||
+ (log->pending_count % LOG_WARN_PENDING_INTERVAL) == 0) {
+ i_warning("Log connection fd %d listen_fd %d prefix '%s' is sending input faster than we can write",
+ log->fd, log->listen_fd, log->default_prefix);
+ }
+ }
+}
+
+void log_connection_create(struct log_error_buffer *errorbuf,
+ int fd, int listen_fd)
+{
+ struct log_connection *log;
+
+ log = i_new(struct log_connection, 1);
+ log->errorbuf = errorbuf;
+ log->fd = fd;
+ log->listen_fd = listen_fd;
+ log->io = io_add(fd, IO_READ, log_connection_input, log);
+ log->input = i_stream_create_fd(fd, PIPE_BUF);
+ log->default_prefix = i_strdup_printf("listen_fd(%d): ", listen_fd);
+ hash_table_create_direct(&log->clients, default_pool, 0);
+ array_idx_set(&logs_by_fd, listen_fd, &log);
+
+ DLLIST_PREPEND(&log_connections, log);
+ log_connection_input(log);
+}
+
+static void
+log_connection_destroy(struct log_connection *log, bool shutting_down)
+{
+ struct hash_iterate_context *iter;
+ void *key;
+ struct log_client *client;
+ unsigned int client_count = 0;
+
+ array_idx_clear(&logs_by_fd, log->listen_fd);
+
+ DLLIST_REMOVE(&log_connections, log);
+
+ iter = hash_table_iterate_init(log->clients);
+ while (hash_table_iterate(iter, log->clients, &key, &client)) {
+ i_free(client);
+ client_count++;
+ }
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&log->clients);
+
+ if (client_count > 0 && shutting_down) {
+ i_warning("Shutting down logging for '%s' with %u clients",
+ log->default_prefix, client_count);
+ }
+
+ i_stream_unref(&log->input);
+ io_remove(&log->io);
+ if (close(log->fd) < 0)
+ i_error("close(log connection fd) failed: %m");
+ log_partial_line_free(log);
+ i_free(log->default_prefix);
+ i_free(log);
+
+ master_service_client_connection_destroyed(master_service);
+}
+
+void log_connections_init(void)
+{
+ i_array_init(&logs_by_fd, 64);
+}
+
+void log_connections_deinit(void)
+{
+ /* normally we don't exit until all log connections are gone,
+ but we could get here when we're being killed by a signal */
+ while (log_connections != NULL)
+ log_connection_destroy(log_connections, TRUE);
+ array_free(&logs_by_fd);
+}
diff --git a/src/log/log-connection.h b/src/log/log-connection.h
new file mode 100644
index 0000000..2885399
--- /dev/null
+++ b/src/log/log-connection.h
@@ -0,0 +1,15 @@
+#ifndef LOG_CONNECTION_H
+#define LOG_CONNECTION_H
+
+struct log_connection;
+
+extern bool verbose_proctitle;
+extern char *global_log_prefix;
+
+void log_connection_create(struct log_error_buffer *errorbuf,
+ int fd, int listen_fd);
+
+void log_connections_init(void);
+void log_connections_deinit(void);
+
+#endif
diff --git a/src/log/log-error-buffer.c b/src/log/log-error-buffer.c
new file mode 100644
index 0000000..608d1ad
--- /dev/null
+++ b/src/log/log-error-buffer.c
@@ -0,0 +1,122 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "log-error-buffer.h"
+
+#define LOG_ERROR_BUFFER_MAX_LINES 1000
+
+struct log_error_data {
+ struct log_error_data *next;
+
+ enum log_type type;
+ time_t timestamp;
+ unsigned char prefix_text[FLEXIBLE_ARRAY_MEMBER];
+};
+
+struct log_error_buffer {
+ struct log_error_data *head, *tail;
+ unsigned int count;
+};
+
+struct log_error_buffer_iter {
+ struct log_error_buffer *buf;
+ struct log_error_data *cur;
+ struct log_error error;
+};
+
+struct log_error_buffer *log_error_buffer_init(void)
+{
+ struct log_error_buffer *buf;
+
+ buf = i_new(struct log_error_buffer, 1);
+ return buf;
+}
+
+static void log_error_buffer_delete_head(struct log_error_buffer *buf)
+{
+ struct log_error_data *data;
+
+ i_assert(buf->head != NULL);
+
+ buf->count--;
+ data = buf->head;
+ buf->head = data->next;
+ if (buf->tail == data) {
+ /* last one */
+ buf->tail = NULL;
+ }
+ i_free(data);
+}
+
+void log_error_buffer_add(struct log_error_buffer *buf,
+ const struct log_error *error)
+{
+ size_t prefix_size = strlen(error->prefix)+1;
+ size_t text_size = strlen(error->text)+1;
+ struct log_error_data *data;
+
+ if (buf->count == LOG_ERROR_BUFFER_MAX_LINES)
+ log_error_buffer_delete_head(buf);
+
+ /* @UNSAFE */
+ data = i_malloc(MALLOC_ADD(sizeof(*data),
+ MALLOC_ADD(prefix_size, text_size)));
+ data->type = error->type;
+ data->timestamp = error->timestamp;
+ memcpy(data->prefix_text, error->prefix, prefix_size);
+ memcpy(data->prefix_text + prefix_size, error->text, text_size);
+
+ if (buf->tail != NULL)
+ buf->tail->next = data;
+ else
+ buf->head = data;
+ buf->tail = data;
+ buf->count++;
+}
+
+void log_error_buffer_deinit(struct log_error_buffer **_buf)
+{
+ struct log_error_buffer *buf = *_buf;
+
+ *_buf = NULL;
+ while (buf->count > 0)
+ log_error_buffer_delete_head(buf);
+ i_free(buf);
+}
+
+struct log_error_buffer_iter *
+log_error_buffer_iter_init(struct log_error_buffer *buf)
+{
+ struct log_error_buffer_iter *iter;
+
+ iter = i_new(struct log_error_buffer_iter, 1);
+ iter->buf = buf;
+ iter->cur = buf->head;
+ return iter;
+}
+
+struct log_error *
+log_error_buffer_iter_next(struct log_error_buffer_iter *iter)
+{
+ struct log_error_data *data = iter->cur;
+
+ if (data == NULL)
+ return NULL;
+ iter->cur = iter->cur->next;
+
+ iter->error.type = data->type;
+ iter->error.timestamp = data->timestamp;
+ iter->error.prefix = (const void *)data->prefix_text;
+ iter->error.text = (const void *)(data->prefix_text +
+ strlen(iter->error.prefix) + 1);
+ return &iter->error;
+}
+
+void log_error_buffer_iter_deinit(struct log_error_buffer_iter **_iter)
+{
+ struct log_error_buffer_iter *iter = *_iter;
+
+ *_iter = NULL;
+
+ i_free(iter);
+}
diff --git a/src/log/log-error-buffer.h b/src/log/log-error-buffer.h
new file mode 100644
index 0000000..e863c35
--- /dev/null
+++ b/src/log/log-error-buffer.h
@@ -0,0 +1,24 @@
+#ifndef LOG_ERROR_BUFFER_H
+#define LOG_ERROR_BUFFER_H
+
+struct log_error_buffer;
+
+struct log_error {
+ enum log_type type;
+ time_t timestamp;
+ const char *prefix;
+ const char *text;
+};
+
+struct log_error_buffer *log_error_buffer_init(void);
+void log_error_buffer_add(struct log_error_buffer *buf,
+ const struct log_error *error);
+void log_error_buffer_deinit(struct log_error_buffer **buf);
+
+struct log_error_buffer_iter *
+log_error_buffer_iter_init(struct log_error_buffer *buf);
+struct log_error *
+log_error_buffer_iter_next(struct log_error_buffer_iter *iter);
+void log_error_buffer_iter_deinit(struct log_error_buffer_iter **iter);
+
+#endif
diff --git a/src/log/log-settings.c b/src/log/log-settings.c
new file mode 100644
index 0000000..7c55544
--- /dev/null
+++ b/src/log/log-settings.c
@@ -0,0 +1,49 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+
+#include <stddef.h>
+
+/* <settings checks> */
+static struct file_listener_settings log_unix_listeners_array[] = {
+ { "log-errors", 0600, "", "" }
+};
+static struct file_listener_settings *log_unix_listeners[] = {
+ &log_unix_listeners_array[0]
+};
+static buffer_t log_unix_listeners_buf = {
+ { { log_unix_listeners, sizeof(log_unix_listeners) } }
+};
+/* </settings checks> */
+
+struct service_settings log_service_settings = {
+ .name = "log",
+ .protocol = "",
+ .type = "log",
+ .executable = "log",
+ .user = "",
+ .group = "",
+ .privileged_group = "",
+ .extra_groups = "",
+ .chroot = "",
+
+ .drop_priv_before_exec = FALSE,
+
+ .process_min_avail = 0,
+ .process_limit = 1,
+ .client_limit = 0,
+ .service_count = 0,
+ .idle_kill = UINT_MAX,
+ .vsz_limit = UOFF_T_MAX,
+
+ .unix_listeners = { { &log_unix_listeners_buf,
+ sizeof(log_unix_listeners[0]) } },
+ .fifo_listeners = ARRAY_INIT,
+ .inet_listeners = ARRAY_INIT,
+
+ .process_limit_1 = TRUE
+};
+
diff --git a/src/log/main.c b/src/log/main.c
new file mode 100644
index 0000000..a6ac950
--- /dev/null
+++ b/src/log/main.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "hostpid.h"
+#include "restrict-access.h"
+#include "master-interface.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+#include "log-error-buffer.h"
+#include "log-connection.h"
+#include "doveadm-connection.h"
+
+#include <unistd.h>
+
+bool verbose_proctitle;
+char *global_log_prefix;
+static struct log_error_buffer *errorbuf;
+
+static void
+sig_reopen_logs(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
+{
+ master_service->log_initialized = FALSE;
+ master_service_init_log_with_prefix(master_service, global_log_prefix);
+}
+
+static void main_init(void)
+{
+ lib_signals_set_handler(SIGUSR1, LIBSIG_FLAGS_SAFE,
+ sig_reopen_logs, NULL);
+
+ errorbuf = log_error_buffer_init();
+ log_connections_init();
+}
+
+static void main_deinit(void)
+{
+ log_connections_deinit();
+ log_error_buffer_deinit(&errorbuf);
+ i_free(global_log_prefix);
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+ if (conn->fifo) {
+ log_connection_create(errorbuf, conn->fd, conn->listen_fd);
+ /* kludge: normally FIFOs aren't counted as connections,
+ but here we want log process to stay open until all writers
+ have closed */
+ conn->fifo = FALSE;
+ } else if (strcmp(conn->name, "log-errors") == 0)
+ doveadm_connection_create(errorbuf, conn->fd);
+ else {
+ i_error("Unknown listener name: %s", conn->name);
+ return;
+ }
+
+ master_service_client_connection_accept(conn);
+}
+
+int main(int argc, char *argv[])
+{
+ const enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS;
+ const char *error;
+
+ master_service = master_service_init("log", service_flags,
+ &argc, &argv, "");
+
+ /* use log prefix and log to stderr until we've configured the real
+ logging */
+ global_log_prefix = i_strdup_printf("log(%s): ", my_pid);
+ i_set_failure_file("/dev/stderr", global_log_prefix);
+
+ if (master_getopt(master_service) > 0)
+ return FATAL_DEFAULT;
+
+ if (master_service_settings_read_simple(master_service,
+ NULL, &error) < 0)
+ i_fatal("Error reading configuration: %s", error);
+ master_service_init_log_with_prefix(master_service, global_log_prefix);
+
+ verbose_proctitle = master_service_settings_get(master_service)->verbose_proctitle;
+
+ restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL);
+ restrict_access_allow_coredumps(TRUE);
+
+ /* logging should never die if there are some clients */
+ master_service_set_die_with_master(master_service, FALSE);
+
+ main_init();
+ master_service_init_finish(master_service);
+ master_service_run(master_service, client_connected);
+ main_deinit();
+ master_service_deinit(&master_service);
+ return 0;
+}
diff --git a/src/login-common/Makefile.am b/src/login-common/Makefile.am
new file mode 100644
index 0000000..9b99e0f
--- /dev/null
+++ b/src/login-common/Makefile.am
@@ -0,0 +1,40 @@
+noinst_LTLIBRARIES = liblogin.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-auth \
+ -I$(top_srcdir)/src/lib-sasl \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-mail \
+ -DPKG_STATEDIR=\""$(statedir)"\" \
+ -DMODULEDIR=\""$(moduledir)"\"
+
+liblogin_la_SOURCES = \
+ access-lookup.c \
+ client-common.c \
+ client-common-auth.c \
+ login-proxy.c \
+ login-proxy-state.c \
+ login-settings.c \
+ main.c \
+ sasl-server.c
+
+headers = \
+ access-lookup.h \
+ client-common.h \
+ login-common.h \
+ login-proxy.h \
+ login-proxy-state.h \
+ login-settings.h \
+ sasl-server.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+pkglib_LTLIBRARIES = libdovecot-login.la
+libdovecot_login_la_SOURCES =
+libdovecot_login_la_LIBADD = liblogin.la ../lib-dovecot/libdovecot.la $(SSL_LIBS)
+libdovecot_login_la_DEPENDENCIES = liblogin.la
+libdovecot_login_la_LDFLAGS = -export-dynamic
diff --git a/src/login-common/Makefile.in b/src/login-common/Makefile.in
new file mode 100644
index 0000000..f70d87c
--- /dev/null
+++ b/src/login-common/Makefile.in
@@ -0,0 +1,908 @@
+# 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@
+subdir = src/login-common
+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__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)$(pkglibdir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkglib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+am_libdovecot_login_la_OBJECTS =
+libdovecot_login_la_OBJECTS = $(am_libdovecot_login_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 =
+libdovecot_login_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libdovecot_login_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+liblogin_la_LIBADD =
+am_liblogin_la_OBJECTS = access-lookup.lo client-common.lo \
+ client-common-auth.lo login-proxy.lo login-proxy-state.lo \
+ login-settings.lo main.lo sasl-server.lo
+liblogin_la_OBJECTS = $(am_liblogin_la_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)/access-lookup.Plo \
+ ./$(DEPDIR)/client-common-auth.Plo \
+ ./$(DEPDIR)/client-common.Plo \
+ ./$(DEPDIR)/login-proxy-state.Plo ./$(DEPDIR)/login-proxy.Plo \
+ ./$(DEPDIR)/login-settings.Plo ./$(DEPDIR)/main.Plo \
+ ./$(DEPDIR)/sasl-server.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libdovecot_login_la_SOURCES) $(liblogin_la_SOURCES)
+DIST_SOURCES = $(libdovecot_login_la_SOURCES) $(liblogin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = liblogin.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-auth \
+ -I$(top_srcdir)/src/lib-sasl \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-mail \
+ -DPKG_STATEDIR=\""$(statedir)"\" \
+ -DMODULEDIR=\""$(moduledir)"\"
+
+liblogin_la_SOURCES = \
+ access-lookup.c \
+ client-common.c \
+ client-common-auth.c \
+ login-proxy.c \
+ login-proxy-state.c \
+ login-settings.c \
+ main.c \
+ sasl-server.c
+
+headers = \
+ access-lookup.h \
+ client-common.h \
+ login-common.h \
+ login-proxy.h \
+ login-proxy-state.h \
+ login-settings.h \
+ sasl-server.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+pkglib_LTLIBRARIES = libdovecot-login.la
+libdovecot_login_la_SOURCES =
+libdovecot_login_la_LIBADD = liblogin.la ../lib-dovecot/libdovecot.la $(SSL_LIBS)
+libdovecot_login_la_DEPENDENCIES = liblogin.la
+libdovecot_login_la_LDFLAGS = -export-dynamic
+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/login-common/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/login-common/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \
+ }
+
+uninstall-pkglibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \
+ done
+
+clean-pkglibLTLIBRARIES:
+ -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES)
+ @list='$(pkglib_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}; \
+ }
+
+libdovecot-login.la: $(libdovecot_login_la_OBJECTS) $(libdovecot_login_la_DEPENDENCIES) $(EXTRA_libdovecot_login_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libdovecot_login_la_LINK) -rpath $(pkglibdir) $(libdovecot_login_la_OBJECTS) $(libdovecot_login_la_LIBADD) $(LIBS)
+
+liblogin.la: $(liblogin_la_OBJECTS) $(liblogin_la_DEPENDENCIES) $(EXTRA_liblogin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(liblogin_la_OBJECTS) $(liblogin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/access-lookup.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-common-auth.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/login-proxy-state.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/login-proxy.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/login-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sasl-server.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibdir)" "$(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-pkglibLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/access-lookup.Plo
+ -rm -f ./$(DEPDIR)/client-common-auth.Plo
+ -rm -f ./$(DEPDIR)/client-common.Plo
+ -rm -f ./$(DEPDIR)/login-proxy-state.Plo
+ -rm -f ./$(DEPDIR)/login-proxy.Plo
+ -rm -f ./$(DEPDIR)/login-settings.Plo
+ -rm -f ./$(DEPDIR)/main.Plo
+ -rm -f ./$(DEPDIR)/sasl-server.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibLTLIBRARIES
+
+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)/access-lookup.Plo
+ -rm -f ./$(DEPDIR)/client-common-auth.Plo
+ -rm -f ./$(DEPDIR)/client-common.Plo
+ -rm -f ./$(DEPDIR)/login-proxy-state.Plo
+ -rm -f ./$(DEPDIR)/login-proxy.Plo
+ -rm -f ./$(DEPDIR)/login-settings.Plo
+ -rm -f ./$(DEPDIR)/main.Plo
+ -rm -f ./$(DEPDIR)/sasl-server.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-pkglibLTLIBRARIES 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-pkglibLTLIBRARIES install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/login-common/access-lookup.c b/src/login-common/access-lookup.c
new file mode 100644
index 0000000..692b43c
--- /dev/null
+++ b/src/login-common/access-lookup.c
@@ -0,0 +1,118 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "net.h"
+#include "fdpass.h"
+#include "access-lookup.h"
+
+#include <unistd.h>
+
+#define ACCESS_LOOKUP_TIMEOUT_MSECS (1000*60)
+
+struct access_lookup {
+ int refcount;
+
+ int fd;
+ char *path;
+
+ struct io *io;
+ struct timeout *to;
+
+ access_lookup_callback_t *callback;
+ void *context;
+};
+
+static void access_lookup_input(struct access_lookup *lookup)
+{
+ unsigned char buf[3];
+ ssize_t ret;
+ bool success = FALSE;
+
+ ret = read(lookup->fd, buf, sizeof(buf));
+ if (ret < 0) {
+ i_error("read(%s) failed: %m", lookup->path);
+ } else if (ret == 0) {
+ /* connection close -> no success */
+ } else if (ret == 2 && buf[0] == '0' && buf[1] == '\n') {
+ /* no success */
+ } else if (ret == 2 && buf[0] == '1' && buf[1] == '\n') {
+ success = TRUE;
+ } else {
+ i_error("access(%s): Invalid input", lookup->path);
+ }
+
+ lookup->refcount++;
+ lookup->callback(success, lookup->context);
+ if (lookup->refcount > 1)
+ access_lookup_destroy(&lookup);
+ access_lookup_destroy(&lookup);
+}
+
+static void access_lookup_timeout(struct access_lookup *lookup)
+{
+ i_error("access(%s): Timed out while waiting for reply", lookup->path);
+
+ lookup->refcount++;
+ lookup->callback(FALSE, lookup->context);
+ if (lookup->refcount > 1)
+ access_lookup_destroy(&lookup);
+ access_lookup_destroy(&lookup);
+}
+
+struct access_lookup *
+access_lookup(const char *path, int client_fd, const char *daemon_name,
+ access_lookup_callback_t *callback, void *context)
+{
+ struct access_lookup *lookup;
+ const char *cmd;
+ ssize_t ret;
+ int fd;
+
+ fd = net_connect_unix(path);
+ if (fd == -1) {
+ i_error("connect(%s) failed: %m", path);
+ return NULL;
+ }
+
+ cmd = t_strconcat(daemon_name, "\n", NULL);
+ ret = fd_send(fd, client_fd, cmd, strlen(cmd));
+ if (ret != (ssize_t)strlen(cmd)) {
+ if (ret < 0)
+ i_error("fd_send(%s) failed: %m", path);
+ else
+ i_error("fd_send(%s) didn't write enough bytes", path);
+ i_close_fd(&fd);
+ return NULL;
+ }
+
+ lookup = i_new(struct access_lookup, 1);
+ lookup->refcount = 1;
+ lookup->fd = fd;
+ lookup->path = i_strdup(path);
+ lookup->io = io_add(fd, IO_READ, access_lookup_input, lookup);
+ lookup->to = timeout_add(ACCESS_LOOKUP_TIMEOUT_MSECS,
+ access_lookup_timeout, lookup);
+ lookup->callback = callback;
+ lookup->context = context;
+ return lookup;
+}
+
+void access_lookup_destroy(struct access_lookup **_lookup)
+{
+ struct access_lookup *lookup = *_lookup;
+
+ i_assert(lookup->refcount > 0);
+ if (--lookup->refcount > 0)
+ return;
+
+ *_lookup = NULL;
+
+ timeout_remove(&lookup->to);
+ io_remove(&lookup->io);
+ if (close(lookup->fd) < 0)
+ i_error("close(%s) failed: %m", lookup->path);
+
+ i_free(lookup->path);
+ i_free(lookup);
+}
diff --git a/src/login-common/access-lookup.h b/src/login-common/access-lookup.h
new file mode 100644
index 0000000..6ebb582
--- /dev/null
+++ b/src/login-common/access-lookup.h
@@ -0,0 +1,11 @@
+#ifndef ACCESS_LOOKUP_H
+#define ACCESS_LOOKUP_H
+
+typedef void access_lookup_callback_t(bool success, void *context);
+
+struct access_lookup *
+access_lookup(const char *path, int client_fd, const char *daemon_name,
+ access_lookup_callback_t *callback, void *context);
+void access_lookup_destroy(struct access_lookup **lookup);
+
+#endif
diff --git a/src/login-common/client-common-auth.c b/src/login-common/client-common-auth.c
new file mode 100644
index 0000000..9a9a710
--- /dev/null
+++ b/src/login-common/client-common-auth.c
@@ -0,0 +1,940 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "hostpid.h"
+#include "login-common.h"
+#include "array.h"
+#include "iostream.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "safe-memset.h"
+#include "time-util.h"
+#include "settings-parser.h"
+#include "login-proxy.h"
+#include "auth-client.h"
+#include "dsasl-client.h"
+#include "master-service-ssl-settings.h"
+#include "client-common.h"
+
+/* If we've been waiting auth server to respond for over this many milliseconds,
+ send a "waiting" message. */
+#define AUTH_WAITING_TIMEOUT_MSECS (30*1000)
+#define AUTH_WAITING_WARNING_TIMEOUT_MSECS (10*1000)
+
+struct client_auth_fail_code_id {
+ const char *id;
+ enum client_auth_fail_code code;
+};
+
+static const struct client_auth_fail_code_id client_auth_fail_codes[] = {
+ { AUTH_CLIENT_FAIL_CODE_AUTHZFAILED,
+ CLIENT_AUTH_FAIL_CODE_AUTHZFAILED },
+ { AUTH_CLIENT_FAIL_CODE_TEMPFAIL,
+ CLIENT_AUTH_FAIL_CODE_TEMPFAIL },
+ { AUTH_CLIENT_FAIL_CODE_USER_DISABLED,
+ CLIENT_AUTH_FAIL_CODE_USER_DISABLED },
+ { AUTH_CLIENT_FAIL_CODE_PASS_EXPIRED,
+ CLIENT_AUTH_FAIL_CODE_PASS_EXPIRED },
+ { AUTH_CLIENT_FAIL_CODE_INVALID_BASE64,
+ CLIENT_AUTH_FAIL_CODE_INVALID_BASE64 },
+ { AUTH_CLIENT_FAIL_CODE_MECH_INVALID,
+ CLIENT_AUTH_FAIL_CODE_MECH_INVALID },
+ { AUTH_CLIENT_FAIL_CODE_MECH_SSL_REQUIRED,
+ CLIENT_AUTH_FAIL_CODE_MECH_SSL_REQUIRED },
+ { AUTH_CLIENT_FAIL_CODE_ANONYMOUS_DENIED,
+ CLIENT_AUTH_FAIL_CODE_ANONYMOUS_DENIED },
+ { NULL, CLIENT_AUTH_FAIL_CODE_NONE }
+};
+
+static enum client_auth_fail_code
+client_auth_fail_code_lookup(const char *fail_code)
+{
+ const struct client_auth_fail_code_id *fail = client_auth_fail_codes;
+
+ while (fail->id != NULL) {
+ if (strcmp(fail->id, fail_code) == 0)
+ return fail->code;
+ fail++;
+ }
+
+ return CLIENT_AUTH_FAIL_CODE_NONE;
+}
+
+static void client_auth_failed(struct client *client)
+{
+ i_free_and_null(client->master_data_prefix);
+ if (client->auth_response != NULL)
+ str_truncate(client->auth_response, 0);
+
+ if (client->auth_initializing || client->destroyed)
+ return;
+
+ io_remove(&client->io);
+
+ if (!client_does_custom_io(client)) {
+ client->io = io_add_istream(client->input, client_input, client);
+ io_set_pending(client->io);
+ }
+}
+
+static void client_auth_waiting_timeout(struct client *client)
+{
+ if (!client->notified_auth_ready) {
+ e_warning(client->event, "Auth process not responding, "
+ "delayed sending initial response (greeting)");
+ }
+ client_notify_status(client, FALSE, client->master_tag == 0 ?
+ AUTH_SERVER_WAITING_MSG : AUTH_MASTER_WAITING_MSG);
+ timeout_remove(&client->to_auth_waiting);
+}
+
+void client_set_auth_waiting(struct client *client)
+{
+ i_assert(client->to_auth_waiting == NULL);
+ client->to_auth_waiting =
+ timeout_add(!client->notified_auth_ready ?
+ AUTH_WAITING_WARNING_TIMEOUT_MSECS :
+ AUTH_WAITING_TIMEOUT_MSECS,
+ client_auth_waiting_timeout, client);
+}
+
+static void alt_username_set(ARRAY_TYPE(const_string) *alt_usernames, pool_t pool,
+ const char *key, const char *value)
+{
+ char *const *fields;
+ unsigned int i, count;
+
+ fields = array_get(&global_alt_usernames, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(fields[i], key) == 0)
+ break;
+ }
+ if (i == count) {
+ char *new_key = i_strdup(key);
+ array_push_back(&global_alt_usernames, &new_key);
+ }
+
+ value = p_strdup(pool, value);
+ if (i < array_count(alt_usernames)) {
+ array_idx_set(alt_usernames, i, &value);
+ return;
+ }
+
+ /* array is NULL-terminated, so if there are unused fields in
+ the middle set them as "" */
+ while (array_count(alt_usernames) < i) {
+ const char *empty_str = "";
+ array_push_back(alt_usernames, &empty_str);
+ }
+ array_push_back(alt_usernames, &value);
+}
+
+static void client_auth_parse_args(struct client *client, bool success,
+ const char *const *args,
+ struct client_auth_reply *reply_r)
+{
+ const char *key, *value, *p, *error;
+ ARRAY_TYPE(const_string) alt_usernames;
+
+ t_array_init(&alt_usernames, 4);
+ i_zero(reply_r);
+ reply_r->proxy_host_immediate_failure_after_secs =
+ LOGIN_PROXY_DEFAULT_HOST_IMMEDIATE_FAILURE_AFTER_SECS;
+
+ for (; *args != NULL; args++) {
+ p = strchr(*args, '=');
+ if (p == NULL) {
+ key = *args;
+ value = "";
+ } else {
+ key = t_strdup_until(*args, p);
+ value = p + 1;
+ }
+ if (strcmp(key, "nologin") == 0) {
+ reply_r->nologin = TRUE;
+ reply_r->fail_code = CLIENT_AUTH_FAIL_CODE_LOGIN_DISABLED;
+ } else if (strcmp(key, "proxy") == 0)
+ reply_r->proxy = TRUE;
+ else if (strcmp(key, "reason") == 0)
+ reply_r->reason = value;
+ else if (strcmp(key, "host") == 0)
+ reply_r->host = value;
+ else if (strcmp(key, "hostip") == 0)
+ reply_r->hostip = value;
+ else if (strcmp(key, "source_ip") == 0)
+ reply_r->source_ip = value;
+ else if (strcmp(key, "port") == 0) {
+ if (net_str2port(value, &reply_r->port) < 0) {
+ e_error(client->event,
+ "Auth service returned invalid "
+ "port number: %s", value);
+ }
+ } else if (strcmp(key, "destuser") == 0)
+ reply_r->destuser = value;
+ else if (strcmp(key, "pass") == 0)
+ reply_r->password = value;
+ else if (strcmp(key, "proxy_timeout") == 0) {
+ /* backwards compatibility: plain number is seconds */
+ if (str_to_uint(value, &reply_r->proxy_timeout_msecs) == 0)
+ reply_r->proxy_timeout_msecs *= 1000;
+ else if (settings_get_time_msecs(value,
+ &reply_r->proxy_timeout_msecs, &error) < 0) {
+ e_error(client->event,
+ "BUG: Auth service returned invalid "
+ "proxy_timeout value '%s': %s",
+ value, error);
+ }
+ } else if (strcmp(key, "proxy_host_immediate_failure_after") == 0) {
+ if (settings_get_time(value,
+ &reply_r->proxy_host_immediate_failure_after_secs,
+ &error) < 0) {
+ e_error(client->event,
+ "BUG: Auth service returned invalid "
+ "proxy_host_immediate_failure_after value '%s': %s",
+ value, error);
+ }
+ } else if (strcmp(key, "proxy_refresh") == 0) {
+ if (str_to_uint(value, &reply_r->proxy_refresh_secs) < 0) {
+ e_error(client->event,
+ "BUG: Auth service returned invalid "
+ "proxy_refresh value: %s", value);
+ }
+ } else if (strcmp(key, "proxy_mech") == 0)
+ reply_r->proxy_mech = value;
+ else if (strcmp(key, "proxy_noauth") == 0)
+ reply_r->proxy_noauth = TRUE;
+ else if (strcmp(key, "proxy_nopipelining") == 0)
+ reply_r->proxy_nopipelining = TRUE;
+ else if (strcmp(key, "proxy_not_trusted") == 0)
+ reply_r->proxy_not_trusted = TRUE;
+ else if (strcmp(key, "master") == 0) {
+ /* ignore empty master field */
+ if (*value != '\0')
+ reply_r->master_user = value;
+ } else if (strcmp(key, "ssl") == 0) {
+ reply_r->ssl_flags |= PROXY_SSL_FLAG_YES;
+ if (strcmp(value, "any-cert") == 0)
+ reply_r->ssl_flags |= PROXY_SSL_FLAG_ANY_CERT;
+ if (reply_r->port == 0)
+ reply_r->port = login_binary->default_ssl_port;
+ } else if (strcmp(key, "starttls") == 0) {
+ reply_r->ssl_flags |= PROXY_SSL_FLAG_YES |
+ PROXY_SSL_FLAG_STARTTLS;
+ if (strcmp(value, "any-cert") == 0)
+ reply_r->ssl_flags |= PROXY_SSL_FLAG_ANY_CERT;
+ } else if (strcmp(key, "code") == 0) {
+ if (reply_r->fail_code != CLIENT_AUTH_FAIL_CODE_NONE) {
+ /* code already assigned */
+ } else {
+ reply_r->fail_code = client_auth_fail_code_lookup(value);
+ }
+ } else if (strcmp(key, "user") == 0 ||
+ strcmp(key, "postlogin_socket") == 0) {
+ /* already handled in sasl-server.c */
+ } else if (str_begins(key, "user_")) {
+ if (success) {
+ alt_username_set(&alt_usernames, client->pool,
+ key, value);
+ }
+ } else if (str_begins(key, "forward_")) {
+ /* these are passed to upstream */
+ } else
+ e_debug(event_auth, "Ignoring unknown passdb extra field: %s", key);
+ }
+ if (array_count(&alt_usernames) > 0) {
+ const char **alt;
+
+ alt = p_new(client->pool, const char *,
+ array_count(&alt_usernames) + 1);
+ memcpy(alt, array_front(&alt_usernames),
+ sizeof(*alt) * array_count(&alt_usernames));
+ client->alt_usernames = alt;
+ }
+ if (reply_r->port == 0)
+ reply_r->port = login_binary->default_port;
+
+ if (reply_r->destuser == NULL)
+ reply_r->destuser = client->virtual_user;
+}
+
+static void proxy_free_password(struct client *client)
+{
+ if (client->proxy_password == NULL)
+ return;
+
+ safe_memset(client->proxy_password, 0, strlen(client->proxy_password));
+ i_free_and_null(client->proxy_password);
+}
+
+static void client_proxy_append_conn_info(string_t *str, struct client *client)
+{
+ const char *source_host;
+
+ source_host = login_proxy_get_source_host(client->login_proxy);
+ if (source_host[0] != '\0')
+ str_printfa(str, " from %s", source_host);
+ if (strcmp(client->virtual_user, client->proxy_user) != 0) {
+ /* remote username is different, log it */
+ str_printfa(str, " as user %s", client->proxy_user);
+ }
+ if (client->proxy_master_user != NULL)
+ str_printfa(str, " (master %s)", client->proxy_master_user);
+}
+
+void client_proxy_finish_destroy_client(struct client *client)
+{
+ string_t *str = t_str_new(128);
+
+ if (client->input->closed) {
+ /* input stream got closed in client_send_raw_data().
+ In most places we don't have to check for this explicitly,
+ but login_proxy_detach() attempts to get and use the
+ istream's fd, which is now -1. */
+ client_destroy_iostream_error(client);
+ return;
+ }
+
+ /* Include hostname in the log message in case it's different from the
+ IP address in the prefix. */
+ const char *ip_str = login_proxy_get_ip_str(client->login_proxy);
+ const char *host = login_proxy_get_host(client->login_proxy);
+ str_printfa(str, "Started proxying to <%s>",
+ login_proxy_get_ip_str(client->login_proxy));
+ if (strcmp(ip_str, host) != 0)
+ str_printfa(str, " (<%s>)", host);
+
+ client_proxy_append_conn_info(str, client);
+
+ struct event *proxy_event = login_proxy_get_event(client->login_proxy);
+ login_proxy_append_success_log_info(client->login_proxy, str);
+ struct event_passthrough *e = event_create_passthrough(proxy_event)->
+ set_name("proxy_session_established");
+ e_info(e->event(), "%s", str_c(str));
+ login_proxy_detach(client->login_proxy);
+ client_destroy_success(client, NULL);
+}
+
+const char *client_proxy_get_state(struct client *client)
+{
+ return client->v.proxy_get_state(client);
+}
+
+void client_proxy_log_failure(struct client *client, const char *line)
+{
+ string_t *str = t_str_new(128);
+
+ str_printfa(str, "Login failed");
+ client_proxy_append_conn_info(str, client);
+ str_append(str, ": ");
+ str_append(str, line);
+ e_info(login_proxy_get_event(client->login_proxy), "%s", str_c(str));
+}
+
+static void client_proxy_failed(struct client *client)
+{
+ login_proxy_free(&client->login_proxy);
+ proxy_free_password(client);
+ i_free_and_null(client->proxy_user);
+ i_free_and_null(client->proxy_master_user);
+
+ client_auth_failed(client);
+}
+
+static void proxy_input(struct client *client)
+{
+ struct istream *input;
+ struct ostream *output;
+ const char *line;
+ unsigned int duration;
+
+ input = login_proxy_get_istream(client->login_proxy);
+ switch (i_stream_read(input)) {
+ case -2:
+ login_proxy_failed(client->login_proxy,
+ login_proxy_get_event(client->login_proxy),
+ LOGIN_PROXY_FAILURE_TYPE_PROTOCOL,
+ "Too long input line");
+ return;
+ case -1:
+ line = i_stream_next_line(input);
+ duration = ioloop_time - client->created.tv_sec;
+ const char *reason = t_strdup_printf(
+ "Disconnected by server: %s "
+ "(state=%s, duration=%us)%s",
+ io_stream_get_disconnect_reason(input, NULL),
+ client_proxy_get_state(client), duration,
+ line == NULL ? "" : t_strdup_printf(
+ " - BUG: line not read: %s", line));
+ login_proxy_failed(client->login_proxy,
+ login_proxy_get_event(client->login_proxy),
+ LOGIN_PROXY_FAILURE_TYPE_CONNECT, reason);
+ return;
+ }
+
+ output = client->output;
+ /* The "line" variable is allocated from the istream, but the istream
+ may be freed by proxy_parse_line(). Keep the istream referenced to
+ make sure the line isn't freed too early. */
+ i_stream_ref(input);
+ o_stream_ref(output);
+ o_stream_cork(output);
+ while ((line = i_stream_next_line(input)) != NULL) {
+ if (client->v.proxy_parse_line(client, line) != 0)
+ break;
+ }
+ o_stream_uncork(output);
+ o_stream_unref(&output);
+ i_stream_unref(&input);
+}
+
+void client_common_proxy_failed(struct client *client,
+ enum login_proxy_failure_type type,
+ const char *reason ATTR_UNUSED,
+ bool reconnecting)
+{
+ dsasl_client_free(&client->proxy_sasl_client);
+ if (reconnecting) {
+ client->v.proxy_reset(client);
+ return;
+ }
+
+ switch (type) {
+ case LOGIN_PROXY_FAILURE_TYPE_CONNECT:
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL:
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG:
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE:
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG:
+ case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL:
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH:
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL:
+ client->proxy_auth_failed = TRUE;
+ break;
+ }
+ client_proxy_failed(client);
+}
+
+static bool
+proxy_check_start(struct client *client, struct event *event,
+ const struct client_auth_reply *reply,
+ const struct dsasl_client_mech **sasl_mech_r,
+ struct ip_addr *ip_r)
+{
+ if (reply->password == NULL) {
+ e_error(event, "password not given");
+ return FALSE;
+ }
+ if (reply->host == NULL || *reply->host == '\0') {
+ e_error(event, "host not given");
+ return FALSE;
+ }
+
+ if (reply->hostip != NULL && reply->hostip[0] != '\0') {
+ if (net_addr2ip(reply->hostip, ip_r) < 0) {
+ e_error(event, "Invalid hostip %s", reply->hostip);
+ return FALSE;
+ }
+ } else if (net_addr2ip(reply->host, ip_r) < 0) {
+ e_error(event,
+ "BUG: host %s is not an IP (auth should have changed it)",
+ reply->host);
+ return FALSE;
+ }
+
+ if (reply->proxy_mech != NULL) {
+ *sasl_mech_r = dsasl_client_mech_find(reply->proxy_mech);
+ if (*sasl_mech_r == NULL) {
+ e_error(event, "Unsupported SASL mechanism %s",
+ reply->proxy_mech);
+ return FALSE;
+ }
+ } else if (reply->master_user != NULL) {
+ /* have to use PLAIN authentication with master user logins */
+ *sasl_mech_r = &dsasl_client_mech_plain;
+ }
+
+ if (login_proxy_is_ourself(client, reply->host, reply->port,
+ reply->destuser)) {
+ e_error(event, "Proxying loops to itself");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int proxy_start(struct client *client,
+ const struct client_auth_reply *reply)
+{
+ struct login_proxy_settings proxy_set;
+ const struct dsasl_client_mech *sasl_mech = NULL;
+ struct ip_addr ip;
+ struct event *event;
+
+ i_assert(reply->destuser != NULL);
+ i_assert(client->refcount > 1);
+ i_assert(!client->destroyed);
+ i_assert(client->proxy_sasl_client == NULL);
+
+ client->proxy_mech = NULL;
+ client->v.proxy_reset(client);
+ event = event_create(client->event);
+ event_set_append_log_prefix(event, t_strdup_printf(
+ "proxy(%s): ", client->virtual_user));
+
+ if (!proxy_check_start(client, event, reply, &sasl_mech, &ip)) {
+ client->v.proxy_failed(client,
+ LOGIN_PROXY_FAILURE_TYPE_INTERNAL,
+ LOGIN_PROXY_FAILURE_MSG, FALSE);
+ event_unref(&event);
+ return -1;
+ }
+
+ i_zero(&proxy_set);
+ proxy_set.host = reply->host;
+ proxy_set.ip = ip;
+ if (reply->source_ip != NULL) {
+ if (net_addr2ip(reply->source_ip, &proxy_set.source_ip) < 0)
+ proxy_set.source_ip.family = 0;
+ } else if (login_source_ips_count > 0) {
+ /* select the next source IP with round robin. */
+ proxy_set.source_ip = login_source_ips[login_source_ips_idx];
+ login_source_ips_idx =
+ (login_source_ips_idx + 1) % login_source_ips_count;
+ }
+ proxy_set.port = reply->port;
+ proxy_set.connect_timeout_msecs = reply->proxy_timeout_msecs;
+ if (proxy_set.connect_timeout_msecs == 0)
+ proxy_set.connect_timeout_msecs = client->set->login_proxy_timeout;
+ proxy_set.notify_refresh_secs = reply->proxy_refresh_secs;
+ proxy_set.ssl_flags = reply->ssl_flags;
+ proxy_set.host_immediate_failure_after_secs =
+ reply->proxy_host_immediate_failure_after_secs;
+ proxy_set.rawlog_dir = client->set->login_proxy_rawlog_dir;
+
+ /* Include destination ip:port also in the log prefix */
+ event_set_append_log_prefix(event, t_strdup_printf(
+ "proxy(%s,%s:%u): ", client->virtual_user,
+ net_ip2addr(&proxy_set.ip), proxy_set.port));
+
+ client->proxy_mech = sasl_mech;
+ client->proxy_user = i_strdup(reply->destuser);
+ client->proxy_master_user = i_strdup(reply->master_user);
+ client->proxy_password = i_strdup(reply->password);
+ client->proxy_noauth = reply->proxy_noauth;
+ client->proxy_nopipelining = reply->proxy_nopipelining;
+ client->proxy_not_trusted = reply->proxy_not_trusted;
+
+ if (login_proxy_new(client, event, &proxy_set, proxy_input,
+ client->v.proxy_failed) < 0) {
+ event_unref(&event);
+ return -1;
+ }
+ event_unref(&event);
+
+ /* disable input until authentication is finished */
+ io_remove(&client->io);
+ return 0;
+}
+
+static void ATTR_NULL(3, 4)
+client_auth_result(struct client *client, enum client_auth_result result,
+ const struct client_auth_reply *reply, const char *text)
+{
+ o_stream_cork(client->output);
+ client->v.auth_result(client, result, reply, text);
+ o_stream_uncork(client->output);
+}
+
+static bool
+client_auth_handle_reply(struct client *client,
+ const struct client_auth_reply *reply, bool success)
+{
+ if (reply->proxy) {
+ /* we want to proxy the connection to another server.
+ don't do this unless authentication succeeded. with
+ master user proxying we can get FAIL with proxy still set.
+
+ proxy host=.. [port=..] [destuser=..] pass=.. */
+ if (!success)
+ return FALSE;
+ if (proxy_start(client, reply) < 0)
+ client_auth_failed(client);
+ else {
+ /* this for plugins being able th hook into auth reply
+ when proxying is used */
+ client_auth_result(client, CLIENT_AUTH_RESULT_SUCCESS,
+ reply, NULL);
+ }
+ return TRUE;
+ }
+
+ if (reply->host != NULL) {
+ const char *reason;
+
+ if (reply->reason != NULL)
+ reason = reply->reason;
+ else if (reply->nologin)
+ reason = "Try this server instead.";
+ else
+ reason = "Logged in, but you should use this server instead.";
+
+ if (reply->nologin) {
+ client_auth_result(client,
+ CLIENT_AUTH_RESULT_REFERRAL_NOLOGIN,
+ reply, reason);
+ } else {
+ client_auth_result(client,
+ CLIENT_AUTH_RESULT_REFERRAL_SUCCESS,
+ reply, reason);
+ return TRUE;
+ }
+ } else if (reply->nologin) {
+ enum client_auth_result result = CLIENT_AUTH_RESULT_AUTHFAILED;
+ const char *timestamp, *reason = reply->reason;
+
+ /* Either failed or user login is disabled */
+ switch (reply->fail_code) {
+ case CLIENT_AUTH_FAIL_CODE_AUTHZFAILED:
+ result = CLIENT_AUTH_RESULT_AUTHZFAILED;
+ if (reason == NULL)
+ reason = "Authorization failed";
+ break;
+ case CLIENT_AUTH_FAIL_CODE_TEMPFAIL:
+ result = CLIENT_AUTH_RESULT_TEMPFAIL;
+ timestamp = t_strflocaltime("%Y-%m-%d %H:%M:%S", ioloop_time);
+ reason = t_strdup_printf(AUTH_TEMP_FAILED_MSG" [%s:%s]",
+ my_hostname, timestamp);
+ break;
+ case CLIENT_AUTH_FAIL_CODE_PASS_EXPIRED:
+ result = CLIENT_AUTH_RESULT_PASS_EXPIRED;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_INVALID_BASE64:
+ result = CLIENT_AUTH_RESULT_INVALID_BASE64;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_MECH_INVALID:
+ result = CLIENT_AUTH_RESULT_MECH_INVALID;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_MECH_SSL_REQUIRED:
+ result = CLIENT_AUTH_RESULT_MECH_SSL_REQUIRED;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_ANONYMOUS_DENIED:
+ result = CLIENT_AUTH_RESULT_ANONYMOUS_DENIED;
+ break;
+ case CLIENT_AUTH_FAIL_CODE_LOGIN_DISABLED:
+ result = CLIENT_AUTH_RESULT_LOGIN_DISABLED;
+ if (reason == NULL)
+ reason = "Login disabled for this user";
+ break;
+ case CLIENT_AUTH_FAIL_CODE_USER_DISABLED:
+ default:
+ if (reason != NULL)
+ result = CLIENT_AUTH_RESULT_AUTHFAILED_REASON;
+ else
+ result = CLIENT_AUTH_RESULT_AUTHFAILED;
+ }
+
+ if (reason == NULL)
+ reason = AUTH_FAILED_MSG;
+ client_auth_result(client, result, reply, reason);
+ } else {
+ /* normal login/failure */
+ return FALSE;
+ }
+
+ i_assert(reply->nologin);
+
+ if (!client->destroyed)
+ client_auth_failed(client);
+ return TRUE;
+}
+
+void client_auth_respond(struct client *client, const char *response)
+{
+ client->auth_waiting = FALSE;
+ client_set_auth_waiting(client);
+ auth_client_request_continue(client->auth_request, response);
+ if (!client_does_custom_io(client))
+ io_remove(&client->io);
+}
+
+void client_auth_abort(struct client *client)
+{
+ sasl_server_auth_abort(client);
+}
+
+void client_auth_fail(struct client *client, const char *text)
+{
+ sasl_server_auth_failed(client, text, NULL);
+}
+
+int client_auth_read_line(struct client *client)
+{
+ const unsigned char *data;
+ size_t i, size, len;
+
+ if (i_stream_read_more(client->input, &data, &size) == -1) {
+ client_destroy_iostream_error(client);
+ return -1;
+ }
+
+ /* see if we have a full line */
+ for (i = 0; i < size; i++) {
+ if (data[i] == '\n')
+ break;
+ }
+ if (client->auth_response == NULL)
+ client->auth_response = str_new(default_pool, I_MAX(i+1, 256));
+ if (str_len(client->auth_response) + i > LOGIN_MAX_AUTH_BUF_SIZE) {
+ client_destroy(client, "Authentication response too large");
+ return -1;
+ }
+ str_append_data(client->auth_response, data, i);
+ i_stream_skip(client->input, i == size ? size : i+1);
+
+ /* drop trailing \r */
+ len = str_len(client->auth_response);
+ if (len > 0 && str_c(client->auth_response)[len-1] == '\r')
+ str_truncate(client->auth_response, len-1);
+
+ return i < size ? 1 : 0;
+}
+
+void client_auth_parse_response(struct client *client)
+{
+ if (client_auth_read_line(client) <= 0)
+ return;
+
+ if (strcmp(str_c(client->auth_response), "*") == 0) {
+ sasl_server_auth_abort(client);
+ return;
+ }
+
+ client_auth_respond(client, str_c(client->auth_response));
+ memset(str_c_modifiable(client->auth_response), 0,
+ str_len(client->auth_response));
+}
+
+static void client_auth_input(struct client *client)
+{
+ i_assert(client->v.auth_parse_response != NULL);
+ client->v.auth_parse_response(client);
+}
+
+void client_auth_send_challenge(struct client *client, const char *data)
+{
+ struct const_iovec iov[3];
+
+ iov[0].iov_base = "+ ";
+ iov[0].iov_len = 2;
+ iov[1].iov_base = data;
+ iov[1].iov_len = strlen(data);
+ iov[2].iov_base = "\r\n";
+ iov[2].iov_len = 2;
+
+ o_stream_nsendv(client->output, iov, 3);
+}
+
+static void
+sasl_callback(struct client *client, enum sasl_server_reply sasl_reply,
+ const char *data, const char *const *args)
+{
+ struct client_auth_reply reply;
+
+ i_assert(!client->destroyed ||
+ sasl_reply == SASL_SERVER_REPLY_AUTH_ABORTED ||
+ sasl_reply == SASL_SERVER_REPLY_MASTER_FAILED);
+
+ client->last_auth_fail = CLIENT_AUTH_FAIL_CODE_NONE;
+ i_zero(&reply);
+ switch (sasl_reply) {
+ case SASL_SERVER_REPLY_SUCCESS:
+ timeout_remove(&client->to_auth_waiting);
+ if (args != NULL) {
+ client_auth_parse_args(client, TRUE, args, &reply);
+ reply.all_fields = args;
+ client->last_auth_fail = reply.fail_code;
+ if (client_auth_handle_reply(client, &reply, TRUE))
+ break;
+ }
+ client_auth_result(client, CLIENT_AUTH_RESULT_SUCCESS,
+ &reply, NULL);
+ client_destroy_success(client, "Login");
+ break;
+ case SASL_SERVER_REPLY_AUTH_FAILED:
+ case SASL_SERVER_REPLY_AUTH_ABORTED:
+ timeout_remove(&client->to_auth_waiting);
+ if (args != NULL) {
+ client_auth_parse_args(client, FALSE, args, &reply);
+ if (reply.reason == NULL)
+ reply.reason = data;
+ client->last_auth_fail = reply.fail_code;
+ reply.nologin = TRUE;
+ reply.all_fields = args;
+ if (client_auth_handle_reply(client, &reply, FALSE))
+ break;
+ }
+
+ if (sasl_reply == SASL_SERVER_REPLY_AUTH_ABORTED) {
+ client_auth_result(client, CLIENT_AUTH_RESULT_ABORTED,
+ &reply, "Authentication aborted by client.");
+ } else if (data == NULL) {
+ client_auth_result(client,
+ CLIENT_AUTH_RESULT_AUTHFAILED, &reply,
+ AUTH_FAILED_MSG);
+ } else {
+ client_auth_result(client,
+ CLIENT_AUTH_RESULT_AUTHFAILED_REASON, &reply,
+ data);
+ }
+
+ if (!client->destroyed)
+ client_auth_failed(client);
+ break;
+ case SASL_SERVER_REPLY_MASTER_FAILED:
+ if (data != NULL) {
+ /* authentication itself succeeded, we just hit some
+ internal failure. */
+ client_auth_result(client, CLIENT_AUTH_RESULT_TEMPFAIL,
+ &reply, data);
+ }
+
+ /* the fd may still be hanging somewhere in kernel or another
+ process. make sure the client gets disconnected. */
+ if (shutdown(client->fd, SHUT_RDWR) < 0 && errno != ENOTCONN)
+ e_error(client->event, "shutdown() failed: %m");
+
+ if (data != NULL) {
+ /* e.g. mail_max_userip_connections is reached */
+ } else {
+ /* The error should have been logged already.
+ The client will only see a generic internal error. */
+ client_notify_disconnect(client, CLIENT_DISCONNECT_INTERNAL_ERROR,
+ "Internal login failure. "
+ "Refer to server log for more information.");
+ data = t_strdup_printf("Internal login failure (pid=%s id=%u)",
+ my_pid, client->master_auth_id);
+ }
+ client->no_extra_disconnect_reason = TRUE;
+ client_destroy(client, data);
+ break;
+ case SASL_SERVER_REPLY_CONTINUE:
+ i_assert(client->v.auth_send_challenge != NULL);
+ client->v.auth_send_challenge(client, data);
+
+ timeout_remove(&client->to_auth_waiting);
+
+ if (client->auth_response != NULL)
+ str_truncate(client->auth_response, 0);
+
+ i_assert(client->io == NULL);
+ client->auth_waiting = TRUE;
+ if (!client_does_custom_io(client)) {
+ client->io = io_add_istream(client->input,
+ client_auth_input, client);
+ client_auth_input(client);
+ }
+ return;
+ }
+
+ client_unref(&client);
+}
+
+static int
+client_auth_begin_common(struct client *client, const char *mech_name,
+ enum sasl_server_auth_flags auth_flags,
+ const char *init_resp)
+{
+ if (!client->secured && strcmp(client->ssl_set->ssl, "required") == 0) {
+ if (client->set->auth_verbose) {
+ e_info(client->event, "Login failed: "
+ "SSL required for authentication");
+ }
+ client->auth_attempts++;
+ client_auth_result(client, CLIENT_AUTH_RESULT_SSL_REQUIRED, NULL,
+ "Authentication not allowed until SSL/TLS is enabled.");
+ return 1;
+ }
+
+
+ client_ref(client);
+ client->auth_initializing = TRUE;
+ sasl_server_auth_begin(client, login_binary->protocol, mech_name,
+ auth_flags, init_resp, sasl_callback);
+ client->auth_initializing = FALSE;
+ if (!client->authenticating)
+ return 1;
+
+ /* don't handle input until we get the initial auth reply */
+ io_remove(&client->io);
+ client_set_auth_waiting(client);
+ return 0;
+}
+
+int client_auth_begin(struct client *client, const char *mech_name,
+ const char *init_resp)
+{
+ return client_auth_begin_common(client, mech_name, 0, init_resp);
+}
+
+int client_auth_begin_private(struct client *client, const char *mech_name,
+ const char *init_resp)
+{
+ return client_auth_begin_common(client, mech_name,
+ SASL_SERVER_AUTH_FLAG_PRIVATE,
+ init_resp);
+}
+
+int client_auth_begin_implicit(struct client *client, const char *mech_name,
+ const char *init_resp)
+{
+ return client_auth_begin_common(client, mech_name,
+ SASL_SERVER_AUTH_FLAG_IMPLICIT,
+ init_resp);
+}
+
+bool client_check_plaintext_auth(struct client *client, bool pass_sent)
+{
+ bool ssl_required = (strcmp(client->ssl_set->ssl, "required") == 0);
+
+ if (client->secured || (!client->set->disable_plaintext_auth &&
+ !ssl_required))
+ return TRUE;
+
+ if (client->set->auth_verbose) {
+ e_info(client->event, "Login failed: "
+ "Plaintext authentication disabled");
+ }
+ if (pass_sent) {
+ client_notify_status(client, TRUE,
+ "Plaintext authentication not allowed "
+ "without SSL/TLS, but your client did it anyway. "
+ "If anyone was listening, the password was exposed.");
+ }
+
+ if (ssl_required) {
+ client_auth_result(client, CLIENT_AUTH_RESULT_SSL_REQUIRED, NULL,
+ AUTH_PLAINTEXT_DISABLED_MSG);
+ } else {
+ client_auth_result(client, CLIENT_AUTH_RESULT_MECH_SSL_REQUIRED, NULL,
+ AUTH_PLAINTEXT_DISABLED_MSG);
+ }
+ client->auth_attempts++;
+ return FALSE;
+}
+
+void clients_notify_auth_connected(void)
+{
+ struct client *client, *next;
+
+ for (client = clients; client != NULL; client = next) {
+ next = client->next;
+
+ timeout_remove(&client->to_auth_waiting);
+
+ client_notify_auth_ready(client);
+
+ if (!client_does_custom_io(client) && client->input_blocked) {
+ client->input_blocked = FALSE;
+ io_set_pending(client->io);
+ }
+ }
+}
diff --git a/src/login-common/client-common.c b/src/login-common/client-common.c
new file mode 100644
index 0000000..c6f6a16
--- /dev/null
+++ b/src/login-common/client-common.c
@@ -0,0 +1,1208 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "array.h"
+#include "hostpid.h"
+#include "llist.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream.h"
+#include "iostream-ssl.h"
+#include "iostream-proxy.h"
+#include "iostream-rawlog.h"
+#include "process-title.h"
+#include "hook-build.h"
+#include "buffer.h"
+#include "str.h"
+#include "strescape.h"
+#include "base64.h"
+#include "str-sanitize.h"
+#include "safe-memset.h"
+#include "time-util.h"
+#include "var-expand.h"
+#include "master-interface.h"
+#include "master-service.h"
+#include "master-service-ssl-settings.h"
+#include "master-auth.h"
+#include "anvil-client.h"
+#include "auth-client.h"
+#include "dsasl-client.h"
+#include "login-proxy.h"
+#include "client-common.h"
+
+struct client *clients = NULL;
+struct client *destroyed_clients = NULL;
+static struct client *last_client = NULL;
+static unsigned int clients_count = 0;
+
+static struct client *client_fd_proxies = NULL;
+static unsigned int client_fd_proxies_count = 0;
+
+struct login_client_module_hooks {
+ struct module *module;
+ const struct login_client_hooks *hooks;
+};
+
+static ARRAY(struct login_client_module_hooks) module_hooks = ARRAY_INIT;
+
+static const char *client_get_log_str(struct client *client, const char *msg);
+
+void login_client_hooks_add(struct module *module,
+ const struct login_client_hooks *hooks)
+{
+ struct login_client_module_hooks *hook;
+
+ hook = array_append_space(&module_hooks);
+ hook->module = module;
+ hook->hooks = hooks;
+}
+
+void login_client_hooks_remove(const struct login_client_hooks *hooks)
+{
+ const struct login_client_module_hooks *module_hook;
+ unsigned int idx = UINT_MAX;
+
+ array_foreach(&module_hooks, module_hook) {
+ if (module_hook->hooks == hooks) {
+ idx = array_foreach_idx(&module_hooks, module_hook);
+ break;
+ }
+ }
+ i_assert(idx != UINT_MAX);
+
+ array_delete(&module_hooks, idx, 1);
+}
+
+static void hook_login_client_allocated(struct client *client)
+{
+ const struct login_client_module_hooks *module_hook;
+ struct hook_build_context *ctx;
+
+ ctx = hook_build_init((void *)&client->v, sizeof(client->v));
+ client->vlast = &client->v;
+ array_foreach(&module_hooks, module_hook) {
+ if (module_hook->hooks->client_allocated != NULL) T_BEGIN {
+ module_hook->hooks->client_allocated(client);
+ hook_build_update(ctx, client->vlast);
+ } T_END;
+ }
+ client->vlast = NULL;
+ hook_build_deinit(&ctx);
+}
+
+static void client_idle_disconnect_timeout(struct client *client)
+{
+ const char *user_reason, *destroy_reason;
+ unsigned int secs;
+
+ if (client->master_tag != 0) {
+ secs = ioloop_time - client->auth_finished;
+ user_reason = "Timeout while finishing login.";
+ destroy_reason = t_strdup_printf(
+ "Timeout while finishing login (waited %u secs)", secs);
+ e_error(client->event, "%s", destroy_reason);
+ } else if (client->auth_request != NULL) {
+ user_reason =
+ "Disconnected for inactivity during authentication.";
+ destroy_reason = "Inactivity during authentication";
+ } else if (client->login_proxy != NULL) {
+ secs = ioloop_time - client->created.tv_sec;
+ user_reason = "Timeout while finishing login.";
+ destroy_reason = t_strdup_printf(
+ "Logging in timed out "
+ "(state=%s, duration=%us)",
+ client_proxy_get_state(client), secs);
+ e_error(login_proxy_get_event(client->login_proxy),
+ "%s", destroy_reason);
+ } else {
+ user_reason = "Disconnected for inactivity.";
+ destroy_reason = "Inactivity";
+ }
+ client_notify_disconnect(client, CLIENT_DISCONNECT_TIMEOUT, user_reason);
+ client_destroy(client, destroy_reason);
+}
+
+static void client_open_streams(struct client *client)
+{
+ client->input = i_stream_create_fd(client->fd, LOGIN_MAX_INBUF_SIZE);
+ client->output = o_stream_create_fd(client->fd, LOGIN_MAX_OUTBUF_SIZE);
+ o_stream_set_no_error_handling(client->output, TRUE);
+
+ if (login_rawlog_dir != NULL) {
+ if (iostream_rawlog_create(login_rawlog_dir, &client->input,
+ &client->output) < 0)
+ login_rawlog_dir = NULL;
+ }
+}
+
+static const char *
+client_log_msg_callback(struct client *client,
+ enum log_type log_type ATTR_UNUSED,
+ const char *message)
+{
+ return client_get_log_str(client, message);
+}
+
+static bool client_is_trusted(struct client *client)
+{
+ const char *const *net;
+ struct ip_addr net_ip;
+ unsigned int bits;
+
+ if (client->set->login_trusted_networks == NULL)
+ return FALSE;
+
+ net = t_strsplit_spaces(client->set->login_trusted_networks, ", ");
+ for (; *net != NULL; net++) {
+ if (net_parse_range(*net, &net_ip, &bits) < 0) {
+ e_error(client->event, "login_trusted_networks: "
+ "Invalid network '%s'", *net);
+ break;
+ }
+
+ if (net_is_in_network(&client->ip, &net_ip, bits))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+struct client *
+client_alloc(int fd, pool_t pool,
+ const struct master_service_connection *conn,
+ const struct login_settings *set,
+ const struct master_service_ssl_settings *ssl_set,
+ const struct master_service_ssl_server_settings *ssl_server_set)
+{
+ struct client *client;
+
+ i_assert(fd != -1);
+
+ client = login_binary->client_vfuncs->alloc(pool);
+ client->v = *login_binary->client_vfuncs;
+ if (client->v.auth_send_challenge == NULL)
+ client->v.auth_send_challenge = client_auth_send_challenge;
+ if (client->v.auth_parse_response == NULL)
+ client->v.auth_parse_response = client_auth_parse_response;
+
+ client->created = ioloop_timeval;
+ client->refcount = 1;
+
+ client->pool = pool;
+ client->preproxy_pool = pool_alloconly_create(MEMPOOL_GROWING"preproxy pool", 256);
+ client->set = set;
+ client->ssl_set = ssl_set;
+ client->ssl_server_set = ssl_server_set;
+ p_array_init(&client->module_contexts, client->pool, 5);
+
+ client->fd = fd;
+ client->local_ip = conn->local_ip;
+ client->local_port = conn->local_port;
+ client->ip = conn->remote_ip;
+ client->remote_port = conn->remote_port;
+ client->real_local_ip = conn->real_local_ip;
+ client->real_local_port = conn->real_local_port;
+ client->real_remote_ip = conn->real_remote_ip;
+ client->real_remote_port = conn->real_remote_port;
+ client->listener_name = p_strdup(client->pool, conn->name);
+ client->trusted = client_is_trusted(client);
+
+ if (conn->proxied) {
+ client->proxied_ssl = conn->proxy.ssl;
+ client->secured = conn->proxy.ssl || client->trusted;
+ client->ssl_secured = conn->proxy.ssl;
+ client->local_name = conn->proxy.hostname;
+ client->client_cert_common_name = conn->proxy.cert_common_name;
+ } else {
+ client->secured = client->trusted ||
+ net_ip_compare(&conn->real_remote_ip, &conn->real_local_ip);
+ }
+ client->proxy_ttl = LOGIN_PROXY_TTL;
+
+ client->event = event_create(NULL);
+ event_add_category(client->event, &login_binary->event_category);
+ event_add_str(client->event, "local_ip", net_ip2addr(&conn->local_ip));
+ event_add_int(client->event, "local_port", conn->local_port);
+ event_add_str(client->event, "remote_ip", net_ip2addr(&conn->remote_ip));
+ event_add_int(client->event, "remote_port", conn->remote_port);
+ event_add_str(client->event, "service", login_binary->protocol);
+ event_set_log_message_callback(client->event, client_log_msg_callback,
+ client);
+
+ client_open_streams(client);
+ return client;
+}
+
+void client_init(struct client *client, void **other_sets)
+{
+ if (last_client == NULL)
+ last_client = client;
+ client->list_type = CLIENT_LIST_TYPE_ACTIVE;
+ DLLIST_PREPEND(&clients, client);
+ clients_count++;
+
+ client->to_disconnect =
+ timeout_add(CLIENT_LOGIN_TIMEOUT_MSECS,
+ client_idle_disconnect_timeout, client);
+
+ hook_login_client_allocated(client);
+ client->v.create(client, other_sets);
+ client->create_finished = TRUE;
+
+ if (auth_client_is_connected(auth_client))
+ client_notify_auth_ready(client);
+ else
+ client_set_auth_waiting(client);
+
+ login_refresh_proctitle();
+}
+
+void client_disconnect(struct client *client, const char *reason,
+ bool add_disconnected_prefix)
+{
+ if (client->disconnected)
+ return;
+ client->disconnected = TRUE;
+
+ if (!client->login_success &&
+ !client->no_extra_disconnect_reason && reason != NULL) {
+ const char *extra_reason =
+ client_get_extra_disconnect_reason(client);
+ if (extra_reason[0] != '\0')
+ reason = t_strconcat(reason, " ", extra_reason, NULL);
+ }
+ if (reason != NULL) {
+ struct event *event = client->login_proxy == NULL ?
+ client->event :
+ login_proxy_get_event(client->login_proxy);
+ if (add_disconnected_prefix)
+ e_info(event, "Disconnected: %s", reason);
+ else
+ e_info(event, "%s", reason);
+ }
+
+ if (client->output != NULL)
+ o_stream_uncork(client->output);
+ if (!client->login_success) {
+ bool unref = FALSE;
+
+ io_remove(&client->io);
+ ssl_iostream_destroy(&client->ssl_iostream);
+ if (client->iostream_fd_proxy != NULL) {
+ iostream_proxy_unref(&client->iostream_fd_proxy);
+ unref = TRUE;
+ }
+ i_stream_close(client->input);
+ o_stream_close(client->output);
+ i_close_fd(&client->fd);
+ if (unref) {
+ i_assert(client->refcount > 1);
+ client_unref(&client);
+ }
+ } else {
+ /* Login was successful. We may now be proxying the connection,
+ so don't disconnect the client until client_unref(). */
+ if (client->iostream_fd_proxy != NULL) {
+ i_assert(!client->fd_proxying);
+ client->fd_proxying = TRUE;
+ i_assert(client->list_type == CLIENT_LIST_TYPE_DESTROYED);
+ DLLIST_REMOVE(&destroyed_clients, client);
+ client->list_type = CLIENT_LIST_TYPE_FD_PROXY;
+ DLLIST_PREPEND(&client_fd_proxies, client);
+ client_fd_proxies_count++;
+ }
+ }
+}
+
+void client_destroy(struct client *client, const char *reason)
+{
+ i_assert(client->create_finished);
+
+ if (client->destroyed)
+ return;
+ client->destroyed = TRUE;
+
+ if (last_client == client)
+ last_client = client->prev;
+ /* move to destroyed_clients linked list before it's potentially
+ added to client_fd_proxies. */
+ i_assert(!client->fd_proxying);
+ i_assert(client->list_type == CLIENT_LIST_TYPE_ACTIVE);
+ DLLIST_REMOVE(&clients, client);
+ client->list_type = CLIENT_LIST_TYPE_DESTROYED;
+ DLLIST_PREPEND(&destroyed_clients, client);
+
+ client_disconnect(client, reason, !client->login_success);
+
+ pool_unref(&client->preproxy_pool);
+ client->forward_fields = NULL;
+ client->client_id = NULL;
+
+ if (client->master_tag != 0) {
+ i_assert(client->auth_request == NULL);
+ i_assert(client->authenticating);
+ i_assert(client->refcount > 1);
+ client->authenticating = FALSE;
+ master_auth_request_abort(master_auth, client->master_tag);
+ client->refcount--;
+ } else if (client->auth_request != NULL ||
+ client->anvil_query != NULL) {
+ i_assert(client->authenticating);
+ sasl_server_auth_abort(client);
+ }
+ i_assert(!client->authenticating);
+ i_assert(client->auth_request == NULL);
+ i_assert(client->anvil_query == NULL);
+
+ timeout_remove(&client->to_disconnect);
+ timeout_remove(&client->to_auth_waiting);
+ str_free(&client->auth_response);
+
+ if (client->proxy_password != NULL) {
+ safe_memset(client->proxy_password, 0,
+ strlen(client->proxy_password));
+ i_free_and_null(client->proxy_password);
+ }
+
+ dsasl_client_free(&client->proxy_sasl_client);
+ if (client->login_proxy != NULL)
+ login_proxy_free(&client->login_proxy);
+ if (client->v.destroy != NULL)
+ client->v.destroy(client);
+ if (client_unref(&client) && initial_service_count == 1) {
+ /* as soon as this connection is done with proxying
+ (or whatever), the process will die. there's no need for
+ authentication anymore, so close the connection.
+ do this only with initial service_count=1, in case there
+ are other clients with pending authentications */
+ auth_client_disconnect(auth_client, "unnecessary connection");
+ }
+ login_client_destroyed();
+ login_refresh_proctitle();
+}
+
+void client_destroy_iostream_error(struct client *client)
+{
+ const char *reason =
+ io_stream_get_disconnect_reason(client->input, client->output);
+ client_destroy(client, reason);
+}
+
+void client_destroy_success(struct client *client, const char *reason)
+{
+ client->login_success = TRUE;
+ client_destroy(client, reason);
+}
+
+void client_ref(struct client *client)
+{
+ client->refcount++;
+}
+
+bool client_unref(struct client **_client)
+{
+ struct client *client = *_client;
+
+ *_client = NULL;
+
+ i_assert(client->refcount > 0);
+ if (--client->refcount > 0)
+ return TRUE;
+
+ if (!client->create_finished) {
+ i_stream_unref(&client->input);
+ o_stream_unref(&client->output);
+ pool_unref(&client->preproxy_pool);
+ event_unref(&client->event);
+ pool_unref(&client->pool);
+ return FALSE;
+ }
+
+ i_assert(client->destroyed);
+ i_assert(client->login_proxy == NULL);
+
+ if (client->v.free != NULL)
+ client->v.free(client);
+
+ ssl_iostream_destroy(&client->ssl_iostream);
+ iostream_proxy_unref(&client->iostream_fd_proxy);
+ if (client->fd_proxying) {
+ i_assert(client->list_type == CLIENT_LIST_TYPE_FD_PROXY);
+ DLLIST_REMOVE(&client_fd_proxies, client);
+ i_assert(client_fd_proxies_count > 0);
+ client_fd_proxies_count--;
+ } else {
+ i_assert(client->list_type == CLIENT_LIST_TYPE_DESTROYED);
+ DLLIST_REMOVE(&destroyed_clients, client);
+ }
+ client->list_type = CLIENT_LIST_TYPE_NONE;
+ i_stream_unref(&client->input);
+ o_stream_unref(&client->output);
+ i_close_fd(&client->fd);
+ event_unref(&client->event);
+
+ i_free(client->proxy_user);
+ i_free(client->proxy_master_user);
+ i_free(client->virtual_user);
+ i_free(client->virtual_user_orig);
+ i_free(client->virtual_auth_user);
+ i_free(client->auth_mech_name);
+ i_free(client->master_data_prefix);
+ pool_unref(&client->pool);
+
+ i_assert(clients_count > 0);
+ clients_count--;
+
+ master_service_client_connection_destroyed(master_service);
+ login_refresh_proctitle();
+ return FALSE;
+}
+
+void client_common_default_free(struct client *client ATTR_UNUSED)
+{
+}
+
+bool client_destroy_oldest(bool kill, struct timeval *created_r)
+{
+ struct client *client;
+
+ if (last_client == NULL) {
+ /* we have no clients */
+ return FALSE;
+ }
+
+ /* destroy the last client that hasn't successfully authenticated yet.
+ this is usually the last client, but don't kill it if it's just
+ waiting for master to finish its job. Also prefer to kill clients
+ that can immediately be killed (i.e. refcount=1) */
+ for (client = last_client; client != NULL; client = client->prev) {
+ if (client->master_tag == 0 && client->refcount == 1)
+ break;
+ }
+ if (client == NULL)
+ client = last_client;
+
+ *created_r = client->created;
+ if (!kill)
+ return TRUE;
+
+ client_notify_disconnect(client, CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
+ "Connection queue full");
+ client_ref(client);
+ client_destroy(client, "Connection queue full");
+ /* return TRUE only if the client was actually freed */
+ i_assert(client->create_finished);
+ return !client_unref(&client);
+}
+
+void clients_destroy_all_reason(const char *reason)
+{
+ struct client *client, *next;
+
+ for (client = clients; client != NULL; client = next) {
+ next = client->next;
+ client_notify_disconnect(client,
+ CLIENT_DISCONNECT_SYSTEM_SHUTDOWN, reason);
+ client_destroy(client, reason);
+ }
+}
+
+void clients_destroy_all(void)
+{
+ clients_destroy_all_reason("Shutting down");
+}
+
+static int client_sni_callback(const char *name, const char **error_r,
+ void *context)
+{
+ struct client *client = context;
+ struct ssl_iostream_context *ssl_ctx;
+ struct ssl_iostream_settings ssl_set;
+ void **other_sets;
+ const char *error;
+
+ if (client->ssl_servername_settings_read)
+ return 0;
+ client->ssl_servername_settings_read = TRUE;
+
+ client->local_name = p_strdup(client->pool, name);
+ client->set = login_settings_read(client->pool, &client->local_ip,
+ &client->ip, name,
+ &client->ssl_set,
+ &client->ssl_server_set, &other_sets);
+
+ master_service_ssl_server_settings_to_iostream_set(client->ssl_set,
+ client->ssl_server_set, pool_datastack_create(), &ssl_set);
+ if (ssl_iostream_server_context_cache_get(&ssl_set, &ssl_ctx, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Failed to initialize SSL server context: %s", error);
+ return -1;
+ }
+ ssl_iostream_change_context(client->ssl_iostream, ssl_ctx);
+ ssl_iostream_context_unref(&ssl_ctx);
+ return 0;
+}
+
+int client_init_ssl(struct client *client)
+{
+ struct ssl_iostream_context *ssl_ctx;
+ struct ssl_iostream_settings ssl_set;
+ const char *error;
+
+ i_assert(client->fd != -1);
+
+ if (strcmp(client->ssl_set->ssl, "no") == 0) {
+ e_info(client->event, "SSL is disabled (ssl=no)");
+ return -1;
+ }
+
+ master_service_ssl_server_settings_to_iostream_set(client->ssl_set,
+ client->ssl_server_set, pool_datastack_create(), &ssl_set);
+ /* If the client cert is invalid, we'll reply NO to the login
+ command. */
+ ssl_set.allow_invalid_cert = TRUE;
+ if (ssl_iostream_server_context_cache_get(&ssl_set, &ssl_ctx, &error) < 0) {
+ e_error(client->event,
+ "Failed to initialize SSL server context: %s", error);
+ return -1;
+ }
+ if (io_stream_create_ssl_server(ssl_ctx, &ssl_set,
+ &client->input, &client->output,
+ &client->ssl_iostream, &error) < 0) {
+ e_error(client->event,
+ "Failed to initialize SSL connection: %s", error);
+ ssl_iostream_context_unref(&ssl_ctx);
+ return -1;
+ }
+ ssl_iostream_context_unref(&ssl_ctx);
+ ssl_iostream_set_sni_callback(client->ssl_iostream,
+ client_sni_callback, client);
+
+ client->tls = TRUE;
+ client->secured = TRUE;
+ client->ssl_secured = TRUE;
+
+ if (client->starttls) {
+ io_remove(&client->io);
+ if (!client_does_custom_io(client)) {
+ client->io = io_add_istream(client->input,
+ client_input, client);
+ }
+ }
+ return 0;
+}
+
+static void client_start_tls(struct client *client)
+{
+ client->starttls = TRUE;
+ if (client_init_ssl(client) < 0) {
+ client_notify_disconnect(client,
+ CLIENT_DISCONNECT_INTERNAL_ERROR,
+ "TLS initialization failed.");
+ client_destroy(client, "TLS initialization failed.");
+ return;
+ }
+ login_refresh_proctitle();
+
+ client->v.starttls(client);
+}
+
+static int client_output_starttls(struct client *client)
+{
+ int ret;
+
+ if ((ret = o_stream_flush(client->output)) < 0) {
+ client_destroy_iostream_error(client);
+ return 1;
+ }
+
+ if (ret > 0) {
+ o_stream_unset_flush_callback(client->output);
+ client_start_tls(client);
+ }
+ return 1;
+}
+
+void client_cmd_starttls(struct client *client)
+{
+ if (client->tls) {
+ client->v.notify_starttls(client, FALSE, "TLS is already active.");
+ return;
+ }
+
+ if (!client_is_tls_enabled(client)) {
+ client->v.notify_starttls(client, FALSE, "TLS support isn't enabled.");
+ return;
+ }
+
+ /* remove input handler, SSL proxy gives us a new fd. we also have to
+ remove it in case we have to wait for buffer to be flushed */
+ io_remove(&client->io);
+
+ client->v.notify_starttls(client, TRUE, "Begin TLS negotiation now.");
+
+ /* uncork the old fd */
+ o_stream_uncork(client->output);
+
+ if (o_stream_flush(client->output) <= 0) {
+ /* the buffer has to be flushed */
+ o_stream_set_flush_pending(client->output, TRUE);
+ o_stream_set_flush_callback(client->output,
+ client_output_starttls, client);
+ } else {
+ client_start_tls(client);
+ }
+}
+
+static void
+iostream_fd_proxy_finished(enum iostream_proxy_side side ATTR_UNUSED,
+ enum iostream_proxy_status status ATTR_UNUSED,
+ struct client *client)
+{
+ /* Destroy the proxy now. The other side of the proxy is still
+ unfinished and we don't want to get back here and unreference
+ the client twice. */
+ iostream_proxy_unref(&client->iostream_fd_proxy);
+ client_unref(&client);
+}
+
+int client_get_plaintext_fd(struct client *client, int *fd_r, bool *close_fd_r)
+{
+ int fds[2];
+
+ if (!client->tls) {
+ /* Plaintext connection - We can send the fd directly to
+ the post-login process without any proxying. */
+ *fd_r = client->fd;
+ *close_fd_r = FALSE;
+ return 0;
+ }
+
+ /* We'll have to start proxying from now on until either side
+ disconnects. Create a socketpair where login process is proxying on
+ one side and the other side is sent to the post-login process. */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
+ e_error(client->event, "socketpair() failed: %m");
+ return -1;
+ }
+ fd_set_nonblock(fds[0], TRUE);
+ fd_set_nonblock(fds[1], TRUE);
+
+ struct ostream *output = o_stream_create_fd(fds[0], IO_BLOCK_SIZE);
+ struct istream *input =
+ i_stream_create_fd_autoclose(&fds[0], IO_BLOCK_SIZE);
+ o_stream_set_no_error_handling(output, TRUE);
+
+ i_assert(client->io == NULL);
+
+ client_ref(client);
+ client->iostream_fd_proxy =
+ iostream_proxy_create(input, output,
+ client->input, client->output);
+ i_stream_unref(&input);
+ o_stream_unref(&output);
+
+ iostream_proxy_set_completion_callback(client->iostream_fd_proxy,
+ iostream_fd_proxy_finished,
+ client);
+ iostream_proxy_start(client->iostream_fd_proxy);
+
+ *fd_r = fds[1];
+ *close_fd_r = TRUE;
+ return 0;
+}
+
+unsigned int clients_get_count(void)
+{
+ return clients_count;
+}
+
+unsigned int clients_get_fd_proxies_count(void)
+{
+ return client_fd_proxies_count;
+}
+
+struct client *clients_get_first_fd_proxy(void)
+{
+ return client_fd_proxies;
+}
+
+void client_add_forward_field(struct client *client, const char *key,
+ const char *value)
+{
+ if (client->forward_fields == NULL)
+ client->forward_fields = str_new(client->preproxy_pool, 32);
+ else
+ str_append_c(client->forward_fields, '\t');
+ /* prefixing is done by auth process */
+ str_append_tabescaped(client->forward_fields, key);
+ str_append_c(client->forward_fields, '=');
+ str_append_tabescaped(client->forward_fields, value);
+}
+
+const char *client_get_session_id(struct client *client)
+{
+ buffer_t *buf, *base64_buf;
+ struct timeval tv;
+ uint64_t timestamp;
+ unsigned int i;
+
+ if (client->session_id != NULL)
+ return client->session_id;
+
+ buf = t_buffer_create(24);
+ base64_buf = t_buffer_create(24*2);
+
+ i_gettimeofday(&tv);
+ timestamp = tv.tv_usec + (long long)tv.tv_sec * 1000ULL*1000ULL;
+
+ /* add lowest 48 bits of the timestamp. this gives us a bit less than
+ 9 years until it wraps */
+ for (i = 0; i < 48; i += 8)
+ buffer_append_c(buf, (timestamp >> i) & 0xff);
+
+ buffer_append_c(buf, client->remote_port & 0xff);
+ buffer_append_c(buf, (client->remote_port >> 8) & 0xff);
+ if (IPADDR_IS_V6(&client->ip))
+ buffer_append(buf, &client->ip.u.ip6, sizeof(client->ip.u.ip6));
+ else
+ buffer_append(buf, &client->ip.u.ip4, sizeof(client->ip.u.ip4));
+ base64_encode(buf->data, buf->used, base64_buf);
+ client->session_id = p_strdup(client->pool, str_c(base64_buf));
+ return client->session_id;
+}
+
+/* increment index if new proper login variables are added
+ * make sure the aliases stay in the current order */
+#define VAR_EXPAND_ALIAS_INDEX_START 27
+
+static struct var_expand_table login_var_expand_empty_tab[] = {
+ { 'u', NULL, "user" },
+ { 'n', NULL, "username" },
+ { 'd', NULL, "domain" },
+
+ { 's', NULL, "service" },
+ { 'h', NULL, "home" },
+ { 'l', NULL, "lip" },
+ { 'r', NULL, "rip" },
+ { 'p', NULL, "pid" },
+ { 'm', NULL, "mech" },
+ { 'a', NULL, "lport" },
+ { 'b', NULL, "rport" },
+ { 'c', NULL, "secured" },
+ { 'k', NULL, "ssl_security" },
+ { 'e', NULL, "mail_pid" },
+ { '\0', NULL, "session" },
+ { '\0', NULL, "real_lip" },
+ { '\0', NULL, "real_rip" },
+ { '\0', NULL, "real_lport" },
+ { '\0', NULL, "real_rport" },
+ { '\0', NULL, "orig_user" },
+ { '\0', NULL, "orig_username" },
+ { '\0', NULL, "orig_domain" },
+ { '\0', NULL, "auth_user" },
+ { '\0', NULL, "auth_username" },
+ { '\0', NULL, "auth_domain" },
+ { '\0', NULL, "listener" },
+ { '\0', NULL, "local_name" },
+
+ /* aliases: */
+ { '\0', NULL, "local_ip" },
+ { '\0', NULL, "remote_ip" },
+ { '\0', NULL, "local_port" },
+ { '\0', NULL, "remote_port" },
+ { '\0', NULL, "real_local_ip" },
+ { '\0', NULL, "real_remote_ip" },
+ { '\0', NULL, "real_local_port" },
+ { '\0', NULL, "real_remote_port" },
+ { '\0', NULL, "mechanism" },
+ { '\0', NULL, "original_user" },
+ { '\0', NULL, "original_username" },
+ { '\0', NULL, "original_domain" },
+
+ { '\0', NULL, NULL }
+};
+
+static void
+get_var_expand_users(struct var_expand_table *tab, const char *user)
+{
+ unsigned int i;
+
+ tab[0].value = user;
+ tab[1].value = t_strcut(user, '@');
+ tab[2].value = i_strchr_to_next(user, '@');
+
+ for (i = 0; i < 3; i++)
+ tab[i].value = str_sanitize(tab[i].value, 80);
+}
+
+static const struct var_expand_table *
+get_var_expand_table(struct client *client)
+{
+ struct var_expand_table *tab;
+
+ tab = t_malloc_no0(sizeof(login_var_expand_empty_tab));
+ memcpy(tab, login_var_expand_empty_tab,
+ sizeof(login_var_expand_empty_tab));
+
+ if (client->virtual_user != NULL)
+ get_var_expand_users(tab, client->virtual_user);
+ tab[3].value = login_binary->protocol;
+ tab[4].value = getenv("HOME");
+ tab[VAR_EXPAND_ALIAS_INDEX_START].value = tab[5].value =
+ net_ip2addr(&client->local_ip);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 1].value = tab[6].value =
+ net_ip2addr(&client->ip);
+ tab[7].value = my_pid;
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 8].value = tab[8].value =
+ client->auth_mech_name == NULL ? NULL :
+ str_sanitize(client->auth_mech_name, MAX_MECH_NAME);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 2].value = tab[9].value =
+ dec2str(client->local_port);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 3].value = tab[10].value =
+ dec2str(client->remote_port);
+ if (!client->tls) {
+ tab[11].value = client->secured ? "secured" : NULL;
+ tab[12].value = "";
+ } else if (client->proxied_ssl) {
+ tab[11].value = "TLS";
+ tab[12].value = "(proxied)";
+ } else {
+ const char *ssl_state =
+ ssl_iostream_is_handshaked(client->ssl_iostream) ?
+ "TLS" : "TLS handshaking";
+ const char *ssl_error =
+ ssl_iostream_get_last_error(client->ssl_iostream);
+
+ tab[11].value = ssl_error == NULL ? ssl_state :
+ t_strdup_printf("%s: %s", ssl_state, ssl_error);
+ tab[12].value =
+ ssl_iostream_get_security_string(client->ssl_iostream);
+ }
+ tab[13].value = client->mail_pid == 0 ? "" :
+ dec2str(client->mail_pid);
+ tab[14].value = client_get_session_id(client);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 4].value = tab[15].value =
+ net_ip2addr(&client->real_local_ip);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 5].value = tab[16].value =
+ net_ip2addr(&client->real_remote_ip);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 6].value = tab[17].value =
+ dec2str(client->real_local_port);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 7].value = tab[18].value =
+ dec2str(client->real_remote_port);
+ if (client->virtual_user_orig != NULL)
+ get_var_expand_users(tab+19, client->virtual_user_orig);
+ else {
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 9].value = tab[19].value = tab[0].value;
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 10].value = tab[20].value = tab[1].value;
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 11].value = tab[21].value = tab[2].value;
+ }
+ if (client->virtual_auth_user != NULL)
+ get_var_expand_users(tab+22, client->virtual_auth_user);
+ else {
+ tab[22].value = tab[19].value;
+ tab[23].value = tab[20].value;
+ tab[24].value = tab[21].value;
+ }
+ tab[25].value = client->listener_name;
+ tab[26].value = str_sanitize(client->local_name, 256);
+ return tab;
+}
+
+static bool have_username_key(const char *str)
+{
+ char key;
+
+ for (; *str != '\0'; str++) {
+ if (str[0] == '%' && str[1] != '\0') {
+ str++;
+ key = var_get_key(str);
+ if (key == 'u' || key == 'n')
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static int
+client_var_expand_func_passdb(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct client *client = context;
+ const char *field_name = data;
+ unsigned int i;
+ size_t field_name_len;
+
+ *value_r = NULL;
+
+ if (client->auth_passdb_args == NULL)
+ return 1;
+
+ field_name_len = strlen(field_name);
+ for (i = 0; client->auth_passdb_args[i] != NULL; i++) {
+ if (strncmp(client->auth_passdb_args[i], field_name,
+ field_name_len) == 0 &&
+ client->auth_passdb_args[i][field_name_len] == '=') {
+ *value_r = client->auth_passdb_args[i] + field_name_len+1;
+ return 1;
+ }
+ }
+ return 1;
+}
+
+static const char *
+client_get_log_str(struct client *client, const char *msg)
+{
+ static const struct var_expand_func_table func_table[] = {
+ { "passdb", client_var_expand_func_passdb },
+ { NULL, NULL }
+ };
+ static bool expand_error_logged = FALSE;
+ const struct var_expand_table *var_expand_table;
+ char *const *e;
+ const char *error;
+ string_t *str, *str2;
+ unsigned int pos;
+
+ var_expand_table = get_var_expand_table(client);
+
+ str = t_str_new(256);
+ str2 = t_str_new(128);
+ for (e = client->set->log_format_elements_split; *e != NULL; e++) {
+ pos = str_len(str);
+ if (var_expand_with_funcs(str, *e, var_expand_table,
+ func_table, client, &error) <= 0 &&
+ !expand_error_logged) {
+ /* NOTE: Don't log via client->event - it would cause
+ recursion */
+ i_error("Failed to expand log_format_elements=%s: %s",
+ *e, error);
+ expand_error_logged = TRUE;
+ }
+ if (have_username_key(*e)) {
+ /* username is added even if it's empty */
+ } else {
+ str_truncate(str2, 0);
+ if (var_expand(str2, *e, login_var_expand_empty_tab,
+ &error) <= 0) {
+ /* we just logged this error above. no need
+ to do it again. */
+ }
+ if (strcmp(str_c(str)+pos, str_c(str2)) == 0) {
+ /* empty %variables, don't add */
+ str_truncate(str, pos);
+ continue;
+ }
+ }
+
+ if (str_len(str) > 0)
+ str_append(str, ", ");
+ }
+
+ if (str_len(str) > 0)
+ str_truncate(str, str_len(str)-2);
+
+ const struct var_expand_table tab[3] = {
+ { 's', t_strdup(str_c(str)), NULL },
+ { '$', msg, NULL },
+ { '\0', NULL, NULL }
+ };
+
+ str_truncate(str, 0);
+ if (var_expand(str, client->set->login_log_format, tab, &error) <= 0) {
+ /* NOTE: Don't log via client->event - it would cause
+ recursion */
+ i_error("Failed to expand login_log_format=%s: %s",
+ client->set->login_log_format, error);
+ expand_error_logged = TRUE;
+ }
+ return str_c(str);
+}
+
+bool client_is_tls_enabled(struct client *client)
+{
+ return login_ssl_initialized && strcmp(client->ssl_set->ssl, "no") != 0;
+}
+
+const char *client_get_extra_disconnect_reason(struct client *client)
+{
+ unsigned int auth_secs = client->auth_first_started == 0 ? 0 :
+ ioloop_time - client->auth_first_started;
+
+ if (client->set->auth_ssl_require_client_cert &&
+ client->ssl_iostream != NULL) {
+ if (ssl_iostream_has_broken_client_cert(client->ssl_iostream))
+ return "(client sent an invalid cert)";
+ if (!ssl_iostream_has_valid_client_cert(client->ssl_iostream))
+ return "(client didn't send a cert)";
+ }
+
+ if (!client->notified_auth_ready)
+ return t_strdup_printf(
+ "(disconnected before auth was ready, waited %u secs)",
+ (unsigned int)(ioloop_time - client->created.tv_sec));
+
+ if (client->auth_attempts == 0) {
+ if (!client->banner_sent) {
+ /* disconnected by a plugin */
+ return "";
+ }
+ return t_strdup_printf("(no auth attempts in %u secs)",
+ (unsigned int)(ioloop_time - client->created.tv_sec));
+ }
+
+ /* some auth attempts without SSL/TLS */
+ if (client->set->auth_ssl_require_client_cert &&
+ client->ssl_iostream == NULL)
+ return "(cert required, client didn't start TLS)";
+
+ if (client->auth_waiting && client->auth_attempts == 1) {
+ return t_strdup_printf("(client didn't finish SASL auth, "
+ "waited %u secs)", auth_secs);
+ }
+ if (client->auth_request != NULL && client->auth_attempts == 1) {
+ return t_strdup_printf("(disconnected while authenticating, "
+ "waited %u secs)", auth_secs);
+ }
+ if (client->authenticating && client->auth_attempts == 1) {
+ return t_strdup_printf("(disconnected while finishing login, "
+ "waited %u secs)", auth_secs);
+ }
+ if (client->auth_try_aborted && client->auth_attempts == 1)
+ return "(aborted authentication)";
+ if (client->auth_process_comm_fail)
+ return "(auth process communication failure)";
+
+ if (client->proxy_auth_failed)
+ return "(proxy dest auth failed)";
+ if (client->auth_successes > 0) {
+ return t_strdup_printf("(internal failure, %u successful auths)",
+ client->auth_successes);
+ }
+
+ switch (client->last_auth_fail) {
+ case CLIENT_AUTH_FAIL_CODE_AUTHZFAILED:
+ return t_strdup_printf(
+ "(authorization failed, %u attempts in %u secs)",
+ client->auth_attempts, auth_secs);
+ case CLIENT_AUTH_FAIL_CODE_TEMPFAIL:
+ return "(auth service reported temporary failure)";
+ case CLIENT_AUTH_FAIL_CODE_USER_DISABLED:
+ return "(user disabled)";
+ case CLIENT_AUTH_FAIL_CODE_PASS_EXPIRED:
+ return "(password expired)";
+ case CLIENT_AUTH_FAIL_CODE_INVALID_BASE64:
+ return "(sent invalid base64 in response)";
+ case CLIENT_AUTH_FAIL_CODE_LOGIN_DISABLED:
+ return "(login disabled)";
+ case CLIENT_AUTH_FAIL_CODE_MECH_INVALID:
+ return "(tried to use unsupported auth mechanism)";
+ case CLIENT_AUTH_FAIL_CODE_MECH_SSL_REQUIRED:
+ return "(tried to use disallowed plaintext auth)";
+ default:
+ break;
+ }
+
+ return t_strdup_printf("(auth failed, %u attempts in %u secs)",
+ client->auth_attempts, auth_secs);
+}
+
+void client_notify_disconnect(struct client *client,
+ enum client_disconnect_reason reason,
+ const char *text)
+{
+ if (!client->notified_disconnect) {
+ if (client->v.notify_disconnect != NULL)
+ client->v.notify_disconnect(client, reason, text);
+ client->notified_disconnect = TRUE;
+ }
+}
+
+void client_notify_auth_ready(struct client *client)
+{
+ if (!client->notified_auth_ready) {
+ if (client->v.notify_auth_ready != NULL)
+ client->v.notify_auth_ready(client);
+ client->notified_auth_ready = TRUE;
+ }
+}
+
+void client_notify_status(struct client *client, bool bad, const char *text)
+{
+ if (client->v.notify_status != NULL)
+ client->v.notify_status(client, bad, text);
+}
+
+void client_common_send_raw_data(struct client *client,
+ const void *data, size_t size)
+{
+ ssize_t ret;
+
+ ret = o_stream_send(client->output, data, size);
+ if (ret < 0 || (size_t)ret != size) {
+ /* either disconnection or buffer full. in either case we want
+ this connection destroyed. however destroying it here might
+ break things if client is still tried to be accessed without
+ being referenced.. */
+ i_stream_close(client->input);
+ }
+}
+
+void client_send_raw_data(struct client *client, const void *data, size_t size)
+{
+ client->v.send_raw_data(client, data, size);
+}
+
+void client_send_raw(struct client *client, const char *data)
+{
+ client_send_raw_data(client, data, strlen(data));
+}
+
+bool client_read(struct client *client)
+{
+ switch (i_stream_read(client->input)) {
+ case -2:
+ /* buffer full */
+ client_notify_disconnect(client,
+ CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
+ "Input buffer full, aborting");
+ client_destroy(client, "Input buffer full");
+ return FALSE;
+ case -1:
+ /* disconnected */
+ client_destroy_iostream_error(client);
+ return FALSE;
+ case 0:
+ /* nothing new read */
+ return i_stream_get_data_size(client->input) > 0;
+ default:
+ /* something was read */
+ return TRUE;
+ }
+}
+
+void client_input(struct client *client)
+{
+ i_assert(client->v.input != NULL);
+ client->v.input(client);
+}
+
+void client_common_init(void)
+{
+ i_array_init(&module_hooks, 32);
+}
+
+void client_destroy_fd_proxies(void)
+{
+ while (client_fd_proxies != NULL) {
+ struct client *client = client_fd_proxies;
+ client_unref(&client);
+ }
+ i_assert(client_fd_proxies_count == 0);
+}
+
+void client_common_deinit(void)
+{
+ i_assert(destroyed_clients == NULL);
+ array_free(&module_hooks);
+}
diff --git a/src/login-common/client-common.h b/src/login-common/client-common.h
new file mode 100644
index 0000000..064cba6
--- /dev/null
+++ b/src/login-common/client-common.h
@@ -0,0 +1,368 @@
+#ifndef CLIENT_COMMON_H
+#define CLIENT_COMMON_H
+
+struct module;
+
+#include "net.h"
+#include "login-proxy.h"
+#include "sasl-server.h"
+#include "master-login.h" /* for LOGIN_MAX_SESSION_ID_LEN */
+
+#define LOGIN_MAX_SESSION_ID_LEN 64
+#define LOGIN_MAX_MASTER_PREFIX_LEN 128
+#define LOGIN_MAX_CLIENT_ID_LEN 256
+
+/* max. size of input buffer. this means:
+
+ IMAP: Max. length of command's all parameters. SASL-IR is read into
+ a separate larger buffer.
+ POP3: Max. length of a command line (spec says 512 would be enough)
+*/
+#define LOGIN_MAX_INBUF_SIZE \
+ (MASTER_AUTH_MAX_DATA_SIZE - LOGIN_MAX_MASTER_PREFIX_LEN - \
+ LOGIN_MAX_SESSION_ID_LEN)
+/* max. size of output buffer. if it gets full, the client is disconnected.
+ SASL authentication gives the largest output. */
+#define LOGIN_MAX_OUTBUF_SIZE 4096
+
+/* Max. length of SASL authentication buffer. */
+#define LOGIN_MAX_AUTH_BUF_SIZE 8192
+
+/* Disconnect client after this many milliseconds if it hasn't managed
+ to log in yet. */
+#define CLIENT_LOGIN_TIMEOUT_MSECS (MASTER_LOGIN_TIMEOUT_SECS*1000)
+
+#define AUTH_SERVER_WAITING_MSG \
+ "Waiting for authentication process to respond.."
+#define AUTH_MASTER_WAITING_MSG \
+ "Waiting for authentication master process to respond.."
+
+/* Client logged out without having successfully authenticated. */
+#define CLIENT_UNAUTHENTICATED_LOGOUT_MSG \
+ "Aborted login by logging out"
+
+struct master_service_connection;
+
+enum client_disconnect_reason {
+ CLIENT_DISCONNECT_TIMEOUT,
+ CLIENT_DISCONNECT_SYSTEM_SHUTDOWN,
+ CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
+ CLIENT_DISCONNECT_INTERNAL_ERROR
+};
+
+enum client_auth_fail_code {
+ CLIENT_AUTH_FAIL_CODE_NONE = 0,
+ CLIENT_AUTH_FAIL_CODE_AUTHZFAILED,
+ CLIENT_AUTH_FAIL_CODE_TEMPFAIL,
+ CLIENT_AUTH_FAIL_CODE_USER_DISABLED,
+ CLIENT_AUTH_FAIL_CODE_PASS_EXPIRED,
+ CLIENT_AUTH_FAIL_CODE_INVALID_BASE64,
+ CLIENT_AUTH_FAIL_CODE_LOGIN_DISABLED,
+ CLIENT_AUTH_FAIL_CODE_MECH_INVALID,
+ CLIENT_AUTH_FAIL_CODE_MECH_SSL_REQUIRED,
+ CLIENT_AUTH_FAIL_CODE_ANONYMOUS_DENIED,
+};
+
+enum client_auth_result {
+ CLIENT_AUTH_RESULT_SUCCESS,
+ CLIENT_AUTH_RESULT_REFERRAL_SUCCESS,
+ CLIENT_AUTH_RESULT_REFERRAL_NOLOGIN,
+ CLIENT_AUTH_RESULT_ABORTED,
+ CLIENT_AUTH_RESULT_AUTHFAILED,
+ CLIENT_AUTH_RESULT_AUTHFAILED_REASON,
+ CLIENT_AUTH_RESULT_AUTHZFAILED,
+ CLIENT_AUTH_RESULT_TEMPFAIL,
+ CLIENT_AUTH_RESULT_PASS_EXPIRED,
+ CLIENT_AUTH_RESULT_SSL_REQUIRED,
+ CLIENT_AUTH_RESULT_INVALID_BASE64,
+ CLIENT_AUTH_RESULT_LOGIN_DISABLED,
+ CLIENT_AUTH_RESULT_MECH_INVALID,
+ CLIENT_AUTH_RESULT_MECH_SSL_REQUIRED,
+ CLIENT_AUTH_RESULT_ANONYMOUS_DENIED
+};
+
+enum client_list_type {
+ CLIENT_LIST_TYPE_NONE = 0,
+ /* clients (disconnected=FALSE, fd_proxying=FALSE, destroyed=FALSE) */
+ CLIENT_LIST_TYPE_ACTIVE,
+ /* destroyed_clients (destroyed=TRUE, fd_proxying=FALSE). Either the
+ client will soon be freed or it's only referenced via
+ "login_proxies". */
+ CLIENT_LIST_TYPE_DESTROYED,
+ /* client_fd_proxies (fd_proxying=TRUE) */
+ CLIENT_LIST_TYPE_FD_PROXY,
+};
+
+struct client_auth_reply {
+ const char *master_user, *reason;
+ enum client_auth_fail_code fail_code;
+
+ /* for proxying */
+ const char *host, *hostip, *source_ip;
+ const char *destuser, *password, *proxy_mech;
+ in_port_t port;
+ unsigned int proxy_timeout_msecs;
+ unsigned int proxy_refresh_secs;
+ unsigned int proxy_host_immediate_failure_after_secs;
+ enum login_proxy_ssl_flags ssl_flags;
+
+ /* all the key=value fields returned by passdb */
+ const char *const *all_fields;
+
+ bool proxy:1;
+ bool proxy_noauth:1;
+ bool proxy_nopipelining:1;
+ bool proxy_not_trusted:1;
+ bool nologin:1;
+};
+
+struct client_vfuncs {
+ struct client *(*alloc)(pool_t pool);
+ void (*create)(struct client *client, void **other_sets);
+ void (*destroy)(struct client *client);
+ void (*notify_auth_ready)(struct client *client);
+ void (*notify_disconnect)(struct client *client,
+ enum client_disconnect_reason reason,
+ const char *text);
+ void (*notify_status)(struct client *client,
+ bool bad, const char *text);
+ void (*notify_starttls)(struct client *client,
+ bool success, const char *text);
+ void (*starttls)(struct client *client);
+ void (*input)(struct client *client);
+ bool (*sasl_filter_mech)(struct client *client,
+ struct auth_mech_desc *mech);
+ bool (*sasl_check_login)(struct client *client);
+ void (*auth_send_challenge)(struct client *client, const char *data);
+ void (*auth_parse_response)(struct client *client);
+ void (*auth_result)(struct client *client,
+ enum client_auth_result result,
+ const struct client_auth_reply *reply,
+ const char *text);
+ void (*proxy_reset)(struct client *client);
+ int (*proxy_parse_line)(struct client *client, const char *line);
+ void (*proxy_failed)(struct client *client,
+ enum login_proxy_failure_type type,
+ const char *reason, bool reconnecting);
+ const char *(*proxy_get_state)(struct client *client);
+ void (*send_raw_data)(struct client *client,
+ const void *data, size_t size);
+ bool (*input_next_cmd)(struct client *client);
+ void (*free)(struct client *client);
+};
+
+struct client {
+ struct client *prev, *next;
+ /* Specifies which linked list the client is in */
+ enum client_list_type list_type;
+
+ pool_t pool;
+ /* this pool gets free'd once proxying starts */
+ pool_t preproxy_pool;
+ struct client_vfuncs v;
+ struct client_vfuncs *vlast;
+
+ struct timeval created;
+ int refcount;
+ struct event *event;
+
+ struct ip_addr local_ip;
+ struct ip_addr ip;
+ struct ip_addr real_remote_ip, real_local_ip;
+ in_port_t local_port, remote_port;
+ in_port_t real_local_port, real_remote_port;
+ struct ssl_iostream *ssl_iostream;
+ const struct login_settings *set;
+ const struct master_service_ssl_settings *ssl_set;
+ const struct master_service_ssl_server_settings *ssl_server_set;
+ const char *session_id, *listener_name, *postlogin_socket_path;
+ const char *local_name;
+ const char *client_cert_common_name;
+
+ string_t *client_id;
+ string_t *forward_fields;
+
+ int fd;
+ struct istream *input;
+ struct ostream *output;
+ struct io *io;
+ struct iostream_proxy *iostream_fd_proxy;
+ struct timeout *to_auth_waiting;
+ struct timeout *to_disconnect;
+
+ unsigned char *master_data_prefix;
+ unsigned int master_data_prefix_len;
+
+ struct login_proxy *login_proxy;
+ char *proxy_user, *proxy_master_user, *proxy_password;
+ const struct dsasl_client_mech *proxy_mech;
+ struct dsasl_client *proxy_sasl_client;
+ unsigned int proxy_ttl;
+
+ char *auth_mech_name;
+ enum sasl_server_auth_flags auth_flags;
+ struct auth_client_request *auth_request;
+ string_t *auth_response;
+ time_t auth_first_started, auth_finished;
+ const char *sasl_final_resp;
+ const char *const *auth_passdb_args;
+ struct anvil_query *anvil_query;
+ struct anvil_request *anvil_request;
+
+ unsigned int master_auth_id;
+ unsigned int master_tag;
+ sasl_server_callback_t *sasl_callback;
+
+ unsigned int bad_counter;
+ unsigned int auth_attempts, auth_successes;
+ enum client_auth_fail_code last_auth_fail;
+ pid_t mail_pid;
+
+ /* Module-specific contexts. */
+ ARRAY(union login_client_module_context *) module_contexts;
+
+ char *virtual_user, *virtual_user_orig, *virtual_auth_user;
+ /* passdb user_* fields are set here after a successful auth.
+ This is a NULL-terminated array where fields are in the same order
+ as in global_alt_usernames. If some field doesn't exist, it's "".
+ Can also be NULL if there are no user_* fields. */
+ const char **alt_usernames;
+ /* director_username_hash cached, if non-zero */
+ unsigned int director_username_hash_cache;
+
+ bool create_finished:1;
+ bool disconnected:1;
+ bool destroyed:1;
+ bool input_blocked:1;
+ bool login_success:1;
+ bool no_extra_disconnect_reason:1;
+ bool starttls:1;
+ bool tls:1;
+ bool proxied_ssl:1;
+ bool secured:1;
+ bool ssl_secured:1;
+ bool trusted:1;
+ bool ssl_servername_settings_read:1;
+ bool banner_sent:1;
+ bool authenticating:1;
+ bool auth_try_aborted:1;
+ bool auth_initializing:1;
+ bool auth_process_comm_fail:1;
+ bool auth_anonymous:1;
+ bool proxy_auth_failed:1;
+ bool proxy_noauth:1;
+ bool proxy_nopipelining:1;
+ bool proxy_not_trusted:1;
+ bool auth_waiting:1;
+ bool notified_auth_ready:1;
+ bool notified_disconnect:1;
+ bool fd_proxying:1;
+ /* ... */
+};
+
+union login_client_module_context {
+ struct client_vfuncs super;
+ struct login_module_register *reg;
+};
+
+struct login_client_hooks {
+ void (*client_allocated)(struct client *client);
+};
+
+extern struct client *clients;
+
+typedef void login_client_allocated_func_t(struct client *client);
+
+void login_client_hooks_add(struct module *module,
+ const struct login_client_hooks *hooks);
+void login_client_hooks_remove(const struct login_client_hooks *hooks);
+
+struct client *
+client_alloc(int fd, pool_t pool,
+ const struct master_service_connection *conn,
+ const struct login_settings *set,
+ const struct master_service_ssl_settings *ssl_set,
+ const struct master_service_ssl_server_settings *ssl_server_set);
+void client_init(struct client *client, void **other_sets);
+void client_disconnect(struct client *client, const char *reason,
+ bool add_disconnected_prefix);
+void client_destroy(struct client *client, const char *reason);
+void client_destroy_iostream_error(struct client *client);
+/* Destroy the client after a successful login. Either the client fd was
+ sent to the post-login process, or the connection will be proxied. */
+void client_destroy_success(struct client *client, const char *reason);
+
+void client_ref(struct client *client);
+bool client_unref(struct client **client) ATTR_NOWARN_UNUSED_RESULT;
+
+int client_init_ssl(struct client *client);
+void client_cmd_starttls(struct client *client);
+
+int client_get_plaintext_fd(struct client *client, int *fd_r, bool *close_fd_r);
+
+unsigned int clients_get_count(void) ATTR_PURE;
+unsigned int clients_get_fd_proxies_count(void);
+struct client *clients_get_first_fd_proxy(void);
+
+void client_add_forward_field(struct client *client, const char *key,
+ const char *value);
+void client_set_title(struct client *client);
+const char *client_get_extra_disconnect_reason(struct client *client);
+
+void client_auth_respond(struct client *client, const char *response);
+void client_auth_abort(struct client *client);
+bool client_is_tls_enabled(struct client *client);
+void client_auth_fail(struct client *client, const char *text);
+const char *client_get_session_id(struct client *client);
+
+bool client_read(struct client *client);
+
+void client_input(struct client *client);
+
+static inline bool
+client_does_custom_io(struct client *client)
+{
+ return (client->v.input == NULL);
+}
+
+void client_notify_auth_ready(struct client *client);
+void client_notify_status(struct client *client, bool bad, const char *text);
+void client_notify_disconnect(struct client *client,
+ enum client_disconnect_reason reason,
+ const char *text);
+
+void client_send_raw_data(struct client *client, const void *data, size_t size);
+void client_send_raw(struct client *client, const char *data);
+void client_common_send_raw_data(struct client *client,
+ const void *data, size_t size);
+void client_common_default_free(struct client *client);
+void client_common_proxy_failed(struct client *client,
+ enum login_proxy_failure_type type,
+ const char *reason, bool reconnecting);
+
+void client_set_auth_waiting(struct client *client);
+void client_auth_send_challenge(struct client *client, const char *data);
+void client_auth_parse_response(struct client *client);
+int client_auth_begin(struct client *client, const char *mech_name,
+ const char *init_resp);
+int client_auth_begin_private(struct client *client, const char *mech_name,
+ const char *init_resp);
+int client_auth_begin_implicit(struct client *client, const char *mech_name,
+ const char *init_resp);
+bool client_check_plaintext_auth(struct client *client, bool pass_sent);
+int client_auth_read_line(struct client *client);
+
+void client_proxy_finish_destroy_client(struct client *client);
+void client_proxy_log_failure(struct client *client, const char *line);
+const char *client_proxy_get_state(struct client *client);
+
+void clients_notify_auth_connected(void);
+bool client_destroy_oldest(bool kill, struct timeval *created_r);
+void clients_destroy_all(void);
+void clients_destroy_all_reason(const char *reason);
+
+void client_destroy_fd_proxies(void);
+void client_common_init(void);
+void client_common_deinit(void);
+
+#endif
diff --git a/src/login-common/login-common.h b/src/login-common/login-common.h
new file mode 100644
index 0000000..cd6f5fb
--- /dev/null
+++ b/src/login-common/login-common.h
@@ -0,0 +1,79 @@
+#ifndef LOGIN_COMMON_H
+#define LOGIN_COMMON_H
+
+#include "lib.h"
+#include "net.h"
+#include "login-settings.h"
+
+/* Used only for string sanitization */
+#define MAX_MECH_NAME 64
+
+#define AUTH_FAILED_MSG "Authentication failed."
+#define AUTH_TEMP_FAILED_MSG "Temporary authentication failure."
+#define AUTH_PLAINTEXT_DISABLED_MSG \
+ "Plaintext authentication disallowed on non-secure (SSL/TLS) connections."
+
+#define LOGIN_DEFAULT_SOCKET "login"
+#define LOGIN_TOKEN_DEFAULT_SOCKET "tokenlogin"
+
+struct login_binary {
+ /* e.g. imap, pop3 */
+ const char *protocol;
+ /* e.g. imap-login, pop3-login */
+ const char *process_name;
+
+ /* e.g. 143, 110 */
+ in_port_t default_port;
+ /* e.g. 993, 995. if there is no ssl port, use 0. */
+ in_port_t default_ssl_port;
+
+ /* if value is NULL, LOGIN_DEFAULT_SOCKET is used as the default */
+ const char *default_login_socket;
+
+ struct event_category event_category;
+
+ const struct client_vfuncs *client_vfuncs;
+ void (*preinit)(void);
+ void (*init)(void);
+ void (*deinit)(void);
+
+ bool sasl_support_final_reply:1;
+ bool anonymous_login_acceptable:1;
+};
+
+struct login_module_register {
+ unsigned int id;
+};
+extern struct login_module_register login_module_register;
+
+extern struct login_binary *login_binary;
+extern struct auth_client *auth_client;
+extern struct master_auth *master_auth;
+extern bool closing_down, login_debug;
+extern struct anvil_client *anvil;
+extern const char *login_rawlog_dir;
+extern unsigned int initial_service_count;
+/* NULL-terminated array of all alt_usernames seen so far. Existing fields are
+ never removed. */
+extern ARRAY_TYPE(string) global_alt_usernames;
+extern bool login_ssl_initialized;
+
+extern const struct login_settings *global_login_settings;
+extern const struct master_service_ssl_settings *global_ssl_settings;
+extern void **global_other_settings;
+
+extern const struct ip_addr *login_source_ips;
+extern unsigned int login_source_ips_idx, login_source_ips_count;
+extern struct event *event_auth;
+
+
+void login_refresh_proctitle(void);
+void login_client_destroyed(void);
+
+/* Call to guarantee that the "anvil" global variable is initialized. */
+void login_anvil_init(void);
+
+int login_binary_run(struct login_binary *binary,
+ int argc, char *argv[]);
+
+#endif
diff --git a/src/login-common/login-proxy-state.c b/src/login-common/login-proxy-state.c
new file mode 100644
index 0000000..e97d809
--- /dev/null
+++ b/src/login-common/login-proxy-state.c
@@ -0,0 +1,165 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "ioloop.h"
+#include "hash.h"
+#include "strescape.h"
+#include "login-proxy-state.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#define NOTIFY_RETRY_REOPEN_MSECS (60*1000)
+
+struct login_proxy_state {
+ HASH_TABLE(struct login_proxy_record *,
+ struct login_proxy_record *) hash;
+ pool_t pool;
+
+ const char *notify_path;
+ int notify_fd;
+
+ struct timeout *to_reopen;
+};
+
+static int login_proxy_state_notify_open(struct login_proxy_state *state);
+
+static unsigned int
+login_proxy_record_hash(const struct login_proxy_record *rec)
+{
+ return net_ip_hash(&rec->ip) ^ rec->port;
+}
+
+static int login_proxy_record_cmp(struct login_proxy_record *rec1,
+ struct login_proxy_record *rec2)
+{
+ if (!net_ip_compare(&rec1->ip, &rec2->ip))
+ return 1;
+
+ return (int)rec1->port - (int)rec2->port;
+}
+
+struct login_proxy_state *login_proxy_state_init(const char *notify_path)
+{
+ struct login_proxy_state *state;
+
+ state = i_new(struct login_proxy_state, 1);
+ state->pool = pool_alloconly_create("login proxy state", 1024);
+ hash_table_create(&state->hash, state->pool, 0,
+ login_proxy_record_hash, login_proxy_record_cmp);
+ state->notify_path = p_strdup(state->pool, notify_path);
+ state->notify_fd = -1;
+ return state;
+}
+
+static void login_proxy_state_close(struct login_proxy_state *state)
+{
+ i_close_fd_path(&state->notify_fd, state->notify_path);
+}
+
+void login_proxy_state_deinit(struct login_proxy_state **_state)
+{
+ struct login_proxy_state *state = *_state;
+ struct hash_iterate_context *iter;
+ struct login_proxy_record *rec;
+
+ *_state = NULL;
+
+ /* sanity check: */
+ iter = hash_table_iterate_init(state->hash);
+ while (hash_table_iterate(iter, state->hash, &rec, &rec))
+ i_assert(rec->num_waiting_connections == 0);
+ hash_table_iterate_deinit(&iter);
+
+ timeout_remove(&state->to_reopen);
+ login_proxy_state_close(state);
+ hash_table_destroy(&state->hash);
+ pool_unref(&state->pool);
+ i_free(state);
+}
+
+struct login_proxy_record *
+login_proxy_state_get(struct login_proxy_state *state,
+ const struct ip_addr *ip, in_port_t port)
+{
+ struct login_proxy_record *rec, key;
+
+ i_zero(&key);
+ key.ip = *ip;
+ key.port = port;
+
+ rec = hash_table_lookup(state->hash, &key);
+ if (rec == NULL) {
+ rec = p_new(state->pool, struct login_proxy_record, 1);
+ rec->ip = *ip;
+ rec->port = port;
+ hash_table_insert(state->hash, rec, rec);
+ }
+ return rec;
+}
+
+static void login_proxy_state_reopen(struct login_proxy_state *state)
+{
+ timeout_remove(&state->to_reopen);
+ (void)login_proxy_state_notify_open(state);
+}
+
+static int login_proxy_state_notify_open(struct login_proxy_state *state)
+{
+ if (state->to_reopen != NULL) {
+ /* reopen later */
+ return -1;
+ }
+
+ state->notify_fd = open(state->notify_path, O_WRONLY);
+ if (state->notify_fd == -1) {
+ i_error("open(%s) failed: %m", state->notify_path);
+ state->to_reopen = timeout_add(NOTIFY_RETRY_REOPEN_MSECS,
+ login_proxy_state_reopen, state);
+ return -1;
+ }
+ fd_set_nonblock(state->notify_fd, TRUE);
+ return 0;
+}
+
+static bool login_proxy_state_try_notify(struct login_proxy_state *state,
+ const char *user)
+{
+ size_t len;
+ ssize_t ret;
+
+ if (state->notify_fd == -1) {
+ if (login_proxy_state_notify_open(state) < 0)
+ return TRUE;
+ i_assert(state->notify_fd != -1);
+ }
+
+ T_BEGIN {
+ const char *cmd;
+
+ cmd = t_strconcat(str_tabescape(user), "\n", NULL);
+ len = strlen(cmd);
+ ret = write(state->notify_fd, cmd, len);
+ } T_END;
+
+ if (ret != (ssize_t)len) {
+ if (ret < 0)
+ i_error("write(%s) failed: %m", state->notify_path);
+ else {
+ i_error("write(%s) wrote partial update",
+ state->notify_path);
+ }
+ login_proxy_state_close(state);
+ /* retry sending */
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void login_proxy_state_notify(struct login_proxy_state *state,
+ const char *user)
+{
+ if (!login_proxy_state_try_notify(state, user))
+ (void)login_proxy_state_try_notify(state, user);
+}
diff --git a/src/login-common/login-proxy-state.h b/src/login-common/login-proxy-state.h
new file mode 100644
index 0000000..405cd0d
--- /dev/null
+++ b/src/login-common/login-proxy-state.h
@@ -0,0 +1,40 @@
+#ifndef LOGIN_PROXY_STATE_H
+#define LOGIN_PROXY_STATE_H
+
+#include <sys/time.h>
+
+struct login_proxy_record {
+ struct ip_addr ip;
+ in_port_t port;
+
+ /* These are used to spread client-visible disconnects over longer
+ periods of time to avoid reconnect spikes when a server dies.
+
+ If num_disconnects_since_ts=0 when server disconnects us, it's
+ increased and disconnect_timestamp is updated. Afterwards it's
+ increased for each new disconnection. num_disconnects_since_ts gets
+ reset back to zero whenever a) last_success gets updated or b)
+ num_delayed_client_disconnects drops to 0. */
+ struct timeval disconnect_timestamp;
+ unsigned int num_disconnects_since_ts;
+ unsigned int num_delayed_client_disconnects;
+
+ /* these are tracking connect()s, not necessarily logins: */
+ unsigned int num_waiting_connections;
+ /* number of connections we're proxying now (post-login) */
+ unsigned int num_proxying_connections;
+ struct timeval last_failure;
+ struct timeval last_success;
+};
+
+struct login_proxy_state *login_proxy_state_init(const char *notify_path);
+void login_proxy_state_deinit(struct login_proxy_state **state);
+
+struct login_proxy_record *
+login_proxy_state_get(struct login_proxy_state *state,
+ const struct ip_addr *ip, in_port_t port);
+
+void login_proxy_state_notify(struct login_proxy_state *state,
+ const char *user);
+
+#endif
diff --git a/src/login-common/login-proxy.c b/src/login-common/login-proxy.c
new file mode 100644
index 0000000..1fdb3f0
--- /dev/null
+++ b/src/login-common/login-proxy.c
@@ -0,0 +1,1173 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream.h"
+#include "iostream-proxy.h"
+#include "iostream-rawlog.h"
+#include "iostream-ssl.h"
+#include "llist.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "time-util.h"
+#include "master-service.h"
+#include "master-service-ssl-settings.h"
+#include "ipc-server.h"
+#include "mail-user-hash.h"
+#include "client-common.h"
+#include "login-proxy-state.h"
+#include "login-proxy.h"
+
+
+#define MAX_PROXY_INPUT_SIZE 4096
+#define PROXY_MAX_OUTBUF_SIZE 1024
+#define LOGIN_PROXY_DIE_IDLE_SECS 2
+#define LOGIN_PROXY_IPC_PATH "ipc-proxy"
+#define LOGIN_PROXY_IPC_NAME "proxy"
+#define LOGIN_PROXY_KILL_PREFIX "Disconnected by proxy: "
+#define KILLED_BY_ADMIN_REASON "Kicked by admin"
+#define KILLED_BY_DIRECTOR_REASON "Kicked via director"
+#define KILLED_BY_SHUTDOWN_REASON "Process shutting down"
+#define LOGIN_PROXY_SIDE_SELF "proxy"
+/* Wait this long before retrying on reconnect */
+#define PROXY_CONNECT_RETRY_MSECS 1000
+/* Don't even try to reconnect if proxying will timeout in less than this. */
+#define PROXY_CONNECT_RETRY_MIN_MSECS (PROXY_CONNECT_RETRY_MSECS + 100)
+#define PROXY_DISCONNECT_INTERVAL_MSECS 100
+
+#define LOGIN_PROXY_SIDE_CLIENT IOSTREAM_PROXY_SIDE_LEFT
+#define LOGIN_PROXY_SIDE_SERVER IOSTREAM_PROXY_SIDE_RIGHT
+
+enum login_proxy_free_flags {
+ LOGIN_PROXY_FREE_FLAG_DELAYED = BIT(0)
+};
+
+struct login_proxy {
+ struct login_proxy *prev, *next;
+
+ struct client *client;
+ struct event *event;
+ int server_fd;
+ struct io *client_wait_io, *server_io;
+ struct istream *client_input, *server_input;
+ struct ostream *client_output, *server_output;
+ struct iostream_proxy *iostream_proxy;
+ struct ssl_iostream *server_ssl_iostream;
+
+ struct timeval created;
+ struct timeout *to, *to_notify;
+ struct login_proxy_record *state_rec;
+
+ struct ip_addr ip, source_ip;
+ char *host;
+ in_port_t port;
+ unsigned int connect_timeout_msecs;
+ unsigned int notify_refresh_secs;
+ unsigned int host_immediate_failure_after_secs;
+ unsigned int reconnect_count;
+ enum login_proxy_ssl_flags ssl_flags;
+ char *rawlog_dir;
+
+ login_proxy_input_callback_t *input_callback;
+ login_proxy_failure_callback_t *failure_callback;
+
+ bool connected:1;
+ bool detached:1;
+ bool destroying:1;
+ bool delayed_disconnect:1;
+ bool disable_reconnect:1;
+ bool num_waiting_connections_updated:1;
+};
+
+static struct login_proxy_state *proxy_state;
+static struct login_proxy *login_proxies = NULL;
+static struct login_proxy *login_proxies_pending = NULL;
+static struct login_proxy *login_proxies_disconnecting = NULL;
+static struct ipc_server *login_proxy_ipc_server;
+static unsigned int detached_login_proxies_count = 0;
+
+static int login_proxy_connect(struct login_proxy *proxy);
+static void login_proxy_disconnect(struct login_proxy *proxy);
+static void login_proxy_ipc_cmd(struct ipc_cmd *cmd, const char *line);
+static void login_proxy_free_final(struct login_proxy *proxy);
+
+static void ATTR_NULL(2)
+login_proxy_free_full(struct login_proxy **_proxy, const char *log_msg,
+ const char *disconnect_side,
+ const char *disconnect_reason,
+ enum login_proxy_free_flags flags);
+
+static time_t proxy_last_io(struct login_proxy *proxy)
+{
+ struct timeval tv1, tv2, tv3, tv4;
+
+ i_stream_get_last_read_time(proxy->client_input, &tv1);
+ i_stream_get_last_read_time(proxy->server_input, &tv2);
+ o_stream_get_last_write_time(proxy->client_output, &tv3);
+ o_stream_get_last_write_time(proxy->server_output, &tv4);
+ return I_MAX(tv1.tv_sec, I_MAX(tv2.tv_sec, I_MAX(tv3.tv_sec, tv4.tv_sec)));
+}
+
+static void login_proxy_free_errstr(struct login_proxy **_proxy,
+ const char *errstr, bool server)
+{
+ struct login_proxy *proxy = *_proxy;
+ string_t *log_msg = t_str_new(128);
+ const char *disconnect_side = server ? "server" : "client";
+
+ str_printfa(log_msg, "Disconnected by %s", disconnect_side);
+ if (errstr[0] != '\0')
+ str_printfa(log_msg, ": %s", errstr);
+
+ str_printfa(log_msg, " (%ds idle, in=%"PRIuUOFF_T", out=%"PRIuUOFF_T,
+ (int)(ioloop_time - proxy_last_io(proxy)),
+ proxy->server_output->offset, proxy->client_output->offset);
+ if (o_stream_get_buffer_used_size(proxy->client_output) > 0) {
+ str_printfa(log_msg, "+%zu",
+ o_stream_get_buffer_used_size(proxy->client_output));
+ }
+ if (iostream_proxy_is_waiting_output(proxy->iostream_proxy,
+ LOGIN_PROXY_SIDE_SERVER))
+ str_append(log_msg, ", client output blocked");
+ if (iostream_proxy_is_waiting_output(proxy->iostream_proxy,
+ LOGIN_PROXY_SIDE_CLIENT))
+ str_append(log_msg, ", server output blocked");
+
+ str_append_c(log_msg, ')');
+ login_proxy_free_full(_proxy, str_c(log_msg), errstr, disconnect_side,
+ server ? LOGIN_PROXY_FREE_FLAG_DELAYED : 0);
+}
+
+static void proxy_client_disconnected_input(struct login_proxy *proxy)
+{
+ /* we're already disconnected from server. either wait for
+ disconnection timeout or for client to disconnect itself. */
+ if (i_stream_read(proxy->client_input) < 0)
+ login_proxy_free_final(proxy);
+ else {
+ i_stream_skip(proxy->client_input,
+ i_stream_get_data_size(proxy->client_input));
+ }
+}
+
+static void proxy_prelogin_input(struct login_proxy *proxy)
+{
+ proxy->input_callback(proxy->client);
+}
+
+static void proxy_plain_connected(struct login_proxy *proxy)
+{
+ proxy->server_input =
+ i_stream_create_fd(proxy->server_fd, MAX_PROXY_INPUT_SIZE);
+ proxy->server_output =
+ o_stream_create_fd(proxy->server_fd, SIZE_MAX);
+ o_stream_set_no_error_handling(proxy->server_output, TRUE);
+
+ proxy->server_io =
+ io_add(proxy->server_fd, IO_READ, proxy_prelogin_input, proxy);
+
+ if (proxy->rawlog_dir != NULL) {
+ if (iostream_rawlog_create(proxy->rawlog_dir,
+ &proxy->server_input,
+ &proxy->server_output) < 0)
+ i_free(proxy->rawlog_dir);
+ }
+}
+
+static void proxy_fail_connect(struct login_proxy *proxy)
+{
+ i_assert(!proxy->num_waiting_connections_updated);
+
+ if (timeval_cmp(&proxy->created, &proxy->state_rec->last_success) < 0) {
+ /* there was a successful connection done since we started
+ connecting. perhaps this is just a temporary one-off
+ failure. */
+ } else {
+ proxy->state_rec->last_failure = ioloop_timeval;
+ }
+ i_assert(proxy->state_rec->num_waiting_connections > 0);
+ proxy->state_rec->num_waiting_connections--;
+ proxy->num_waiting_connections_updated = TRUE;
+}
+
+void login_proxy_append_success_log_info(struct login_proxy *proxy,
+ string_t *str)
+{
+ int msecs = timeval_diff_msecs(&ioloop_timeval, &proxy->created);
+ str_printfa(str, " (%d.%03d secs", msecs/1000, msecs%1000);
+ if (proxy->reconnect_count > 0)
+ str_printfa(str, ", %u reconnects", proxy->reconnect_count);
+ str_append_c(str, ')');
+}
+
+static void
+proxy_connect_error_append(struct login_proxy *proxy, string_t *str)
+{
+ struct ip_addr local_ip;
+ in_port_t local_port;
+
+ if (!proxy->connected) {
+ str_printfa(str, "connect(%s, %u) failed: %m",
+ net_ip2addr(&proxy->ip), proxy->port);
+ } else {
+ str_printfa(str, "Login timed out in state=%s",
+ client_proxy_get_state(proxy->client));
+ }
+ str_printfa(str, " (after %u secs",
+ (unsigned int)(ioloop_time - proxy->created.tv_sec));
+ if (proxy->reconnect_count > 0)
+ str_printfa(str, ", %u reconnects", proxy->reconnect_count);
+
+ if (proxy->server_fd != -1 &&
+ net_getsockname(proxy->server_fd, &local_ip, &local_port) == 0) {
+ str_printfa(str, ", local=%s:%u",
+ net_ip2addr(&local_ip), local_port);
+ } else if (proxy->source_ip.family != 0) {
+ str_printfa(str, ", local=%s",
+ net_ip2addr(&proxy->source_ip));
+ }
+
+ str_append_c(str, ')');
+}
+
+static void proxy_reconnect_timeout(struct login_proxy *proxy)
+{
+ timeout_remove(&proxy->to);
+ (void)login_proxy_connect(proxy);
+}
+
+static bool proxy_try_reconnect(struct login_proxy *proxy)
+{
+ int since_started_msecs, left_msecs;
+
+ if (proxy->reconnect_count >= proxy->client->set->login_proxy_max_reconnects)
+ return FALSE;
+ if (proxy->disable_reconnect)
+ return FALSE;
+
+ since_started_msecs =
+ timeval_diff_msecs(&ioloop_timeval, &proxy->created);
+ if (since_started_msecs < 0)
+ return FALSE; /* time moved backwards */
+ left_msecs = (int)proxy->connect_timeout_msecs - since_started_msecs;
+ if (left_msecs <= PROXY_CONNECT_RETRY_MIN_MSECS)
+ return FALSE;
+
+ login_proxy_disconnect(proxy);
+ proxy->to = timeout_add(PROXY_CONNECT_RETRY_MSECS,
+ proxy_reconnect_timeout, proxy);
+ proxy->reconnect_count++;
+ return TRUE;
+}
+
+static bool proxy_connect_failed(struct login_proxy *proxy)
+{
+ string_t *str = t_str_new(128);
+
+ if (!proxy->connected)
+ proxy_fail_connect(proxy);
+ proxy_connect_error_append(proxy, str);
+ return login_proxy_failed(proxy, proxy->event,
+ LOGIN_PROXY_FAILURE_TYPE_CONNECT,
+ str_c(str));
+}
+
+static void proxy_wait_connect(struct login_proxy *proxy)
+{
+ errno = net_geterror(proxy->server_fd);
+ if (errno != 0) {
+ (void)proxy_connect_failed(proxy);
+ return;
+ }
+ proxy->connected = TRUE;
+ proxy->num_waiting_connections_updated = TRUE;
+ proxy->state_rec->last_success = ioloop_timeval;
+ i_assert(proxy->state_rec->num_waiting_connections > 0);
+ proxy->state_rec->num_waiting_connections--;
+ proxy->state_rec->num_proxying_connections++;
+ proxy->state_rec->num_disconnects_since_ts = 0;
+
+ io_remove(&proxy->server_io);
+ proxy_plain_connected(proxy);
+
+ if ((proxy->ssl_flags & PROXY_SSL_FLAG_YES) != 0 &&
+ (proxy->ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) {
+ if (login_proxy_starttls(proxy) < 0) {
+ /* proxy is already destroyed */
+ }
+ }
+}
+
+static void proxy_connect_timeout(struct login_proxy *proxy)
+{
+ errno = ETIMEDOUT;
+ (void)proxy_connect_failed(proxy);
+}
+
+static int login_proxy_connect(struct login_proxy *proxy)
+{
+ struct login_proxy_record *rec = proxy->state_rec;
+
+ e_debug(proxy->event, "Connecting to <%s>",
+ login_proxy_get_ip_str(proxy->client->login_proxy));
+
+ /* this needs to be done early, since login_proxy_free() shrinks
+ num_waiting_connections. */
+ proxy->num_waiting_connections_updated = FALSE;
+ rec->num_waiting_connections++;
+
+ if (proxy->client->proxy_ttl <= 1) {
+ login_proxy_failed(proxy, proxy->event,
+ LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG,
+ "TTL reached zero - proxies appear to be looping?");
+ return -1;
+ }
+
+ if (rec->last_success.tv_sec == 0) {
+ /* first connect to this IP. don't start immediately failing
+ the check below. */
+ rec->last_success.tv_sec = ioloop_timeval.tv_sec - 1;
+ }
+ if (proxy->host_immediate_failure_after_secs != 0 &&
+ timeval_cmp(&rec->last_failure, &rec->last_success) > 0 &&
+ (unsigned int)(rec->last_failure.tv_sec - rec->last_success.tv_sec) >
+ proxy->host_immediate_failure_after_secs &&
+ rec->num_waiting_connections > 1) {
+ /* the server is down. fail immediately */
+ proxy->disable_reconnect = TRUE;
+ login_proxy_failed(proxy, proxy->event,
+ LOGIN_PROXY_FAILURE_TYPE_CONNECT,
+ "Host is down");
+ return -1;
+ }
+
+ proxy->server_fd = net_connect_ip(&proxy->ip, proxy->port,
+ proxy->source_ip.family == 0 ? NULL :
+ &proxy->source_ip);
+ if (proxy->server_fd == -1) {
+ if (!proxy_connect_failed(proxy))
+ return -1;
+ /* trying to reconnect later */
+ return 0;
+ }
+
+ in_port_t source_port;
+ if (net_getsockname(proxy->server_fd, NULL, &source_port) == 0)
+ event_add_int(proxy->event, "source_port", source_port);
+
+ proxy->server_io = io_add(proxy->server_fd, IO_WRITE,
+ proxy_wait_connect, proxy);
+ if (proxy->connect_timeout_msecs != 0) {
+ proxy->to = timeout_add(proxy->connect_timeout_msecs,
+ proxy_connect_timeout, proxy);
+ }
+ return 0;
+}
+
+int login_proxy_new(struct client *client, struct event *event,
+ const struct login_proxy_settings *set,
+ login_proxy_input_callback_t *input_callback,
+ login_proxy_failure_callback_t *failure_callback)
+{
+ struct login_proxy *proxy;
+
+ i_assert(set->host != NULL && set->host[0] != '\0');
+ i_assert(client->login_proxy == NULL);
+
+ proxy = i_new(struct login_proxy, 1);
+ proxy->client = client;
+ proxy->event = event;
+ proxy->server_fd = -1;
+ proxy->created = ioloop_timeval;
+ proxy->ip = set->ip;
+ proxy->source_ip = set->source_ip;
+ proxy->host = i_strdup(set->host);
+ proxy->port = set->port;
+ proxy->connect_timeout_msecs = set->connect_timeout_msecs;
+ proxy->notify_refresh_secs = set->notify_refresh_secs;
+ proxy->host_immediate_failure_after_secs =
+ set->host_immediate_failure_after_secs;
+ proxy->ssl_flags = set->ssl_flags;
+ proxy->state_rec = login_proxy_state_get(proxy_state, &proxy->ip,
+ proxy->port);
+ proxy->rawlog_dir = i_strdup_empty(set->rawlog_dir);
+
+ /* add event fields */
+ event_add_str(proxy->event, "source_ip",
+ login_proxy_get_source_host(proxy));
+ event_add_str(proxy->event, "dest_ip", net_ip2addr(&proxy->ip));
+ event_add_int(proxy->event, "dest_port",
+ login_proxy_get_port(proxy));
+ event_add_str(event, "dest_host", set->host);
+ event_add_str(event, "master_user", client->proxy_master_user);
+
+ client_ref(client);
+ event_ref(proxy->event);
+
+ DLLIST_PREPEND(&login_proxies_pending, proxy);
+
+ proxy->input_callback = input_callback;
+ proxy->failure_callback = failure_callback;
+ client->login_proxy = proxy;
+
+ struct event_passthrough *e = event_create_passthrough(proxy->event)->
+ set_name("proxy_session_started");
+ e_debug(e->event(), "Created proxy session to <%s>",
+ login_proxy_get_ip_str(proxy->client->login_proxy));
+
+ return login_proxy_connect(proxy);
+}
+
+static void login_proxy_disconnect(struct login_proxy *proxy)
+{
+ timeout_remove(&proxy->to);
+ timeout_remove(&proxy->to_notify);
+
+ if (!proxy->num_waiting_connections_updated) {
+ i_assert(proxy->state_rec->num_waiting_connections > 0);
+ proxy->state_rec->num_waiting_connections--;
+ proxy->num_waiting_connections_updated = TRUE;
+ }
+ if (proxy->connected) {
+ i_assert(proxy->state_rec->num_proxying_connections > 0);
+ proxy->state_rec->num_proxying_connections--;
+ }
+
+ iostream_proxy_unref(&proxy->iostream_proxy);
+ ssl_iostream_destroy(&proxy->server_ssl_iostream);
+
+ io_remove(&proxy->server_io);
+ i_stream_destroy(&proxy->server_input);
+ o_stream_destroy(&proxy->server_output);
+ if (proxy->server_fd != -1) {
+ net_disconnect(proxy->server_fd);
+ proxy->server_fd = -1;
+ }
+ proxy->connected = FALSE;
+}
+
+static void login_proxy_free_final(struct login_proxy *proxy)
+{
+ i_assert(proxy->server_ssl_iostream == NULL);
+
+ if (proxy->delayed_disconnect) {
+ DLLIST_REMOVE(&login_proxies_disconnecting, proxy);
+
+ i_assert(proxy->state_rec->num_delayed_client_disconnects > 0);
+ if (--proxy->state_rec->num_delayed_client_disconnects == 0)
+ proxy->state_rec->num_disconnects_since_ts = 0;
+ timeout_remove(&proxy->to);
+ }
+
+ io_remove(&proxy->client_wait_io);
+ i_stream_destroy(&proxy->client_input);
+ o_stream_destroy(&proxy->client_output);
+ client_unref(&proxy->client);
+ event_unref(&proxy->event);
+ i_free(proxy->host);
+ i_free(proxy->rawlog_dir);
+ i_free(proxy);
+}
+
+static unsigned int login_proxy_delay_disconnect(struct login_proxy *proxy)
+{
+ struct login_proxy_record *rec = proxy->state_rec;
+ const unsigned int max_delay =
+ proxy->client->set->login_proxy_max_disconnect_delay;
+ struct timeval disconnect_time_offset;
+ unsigned int max_disconnects_per_sec, delay_msecs_since_ts, max_conns;
+ int delay_msecs;
+
+ if (rec->num_disconnects_since_ts == 0) {
+ rec->disconnect_timestamp = ioloop_timeval;
+ /* start from a slightly random timestamp. this way all proxy
+ processes will disconnect at slightly different times to
+ spread the load. */
+ timeval_add_msecs(&rec->disconnect_timestamp,
+ i_rand_limit(PROXY_DISCONNECT_INTERVAL_MSECS));
+ }
+ rec->num_disconnects_since_ts++;
+ if (proxy->to != NULL) {
+ /* we were already lazily disconnecting this */
+ return 0;
+ }
+ if (max_delay == 0) {
+ /* delaying is disabled */
+ return 0;
+ }
+ max_conns = rec->num_proxying_connections + rec->num_disconnects_since_ts;
+ max_disconnects_per_sec = (max_conns + max_delay-1) / max_delay;
+ if (rec->num_disconnects_since_ts <= max_disconnects_per_sec &&
+ rec->num_delayed_client_disconnects == 0) {
+ /* wait delaying until we have 1 second's worth of clients
+ disconnected */
+ return 0;
+ }
+
+ /* see at which time we should be disconnecting the client.
+ do it in 100ms intervals so the timeouts are triggered together. */
+ disconnect_time_offset = rec->disconnect_timestamp;
+ delay_msecs_since_ts = PROXY_DISCONNECT_INTERVAL_MSECS *
+ (max_delay * rec->num_disconnects_since_ts *
+ (1000/PROXY_DISCONNECT_INTERVAL_MSECS) / max_conns);
+ timeval_add_msecs(&disconnect_time_offset, delay_msecs_since_ts);
+ delay_msecs = timeval_diff_msecs(&disconnect_time_offset, &ioloop_timeval);
+ if (delay_msecs <= 0) {
+ /* we already reached the time */
+ return 0;
+ }
+
+ rec->num_delayed_client_disconnects++;
+ proxy->delayed_disconnect = TRUE;
+ proxy->to = timeout_add(delay_msecs, login_proxy_free_final, proxy);
+ DLLIST_PREPEND(&login_proxies_disconnecting, proxy);
+ return delay_msecs;
+}
+
+static void ATTR_NULL(2)
+login_proxy_free_full(struct login_proxy **_proxy, const char *log_msg,
+ const char *disconnect_reason,
+ const char *disconnect_side,
+ enum login_proxy_free_flags flags)
+{
+ struct login_proxy *proxy = *_proxy;
+ struct client *client = proxy->client;
+ unsigned int delay_ms = 0;
+
+ *_proxy = NULL;
+
+ if (proxy->destroying)
+ return;
+ proxy->destroying = TRUE;
+
+ struct event_passthrough *e = event_create_passthrough(proxy->event)->
+ add_str("disconnect_reason", disconnect_reason)->
+ add_str("disconnect_side", disconnect_side)->
+ set_name("proxy_session_finished");
+
+ if (proxy->detached) {
+ i_assert(proxy->connected);
+ e->add_int("idle_secs", ioloop_time - proxy_last_io(proxy));
+ e->add_int("bytes_in", proxy->server_output->offset);
+ e->add_int("bytes_out", proxy->client_output->offset);
+ }
+
+ /* we'll disconnect server side in any case. */
+ login_proxy_disconnect(proxy);
+
+ if (proxy->detached) {
+ /* detached proxy */
+ i_assert(log_msg != NULL || proxy->client->destroyed);
+ DLLIST_REMOVE(&login_proxies, proxy);
+
+ if ((flags & LOGIN_PROXY_FREE_FLAG_DELAYED) != 0)
+ delay_ms = login_proxy_delay_disconnect(proxy);
+
+ if (delay_ms == 0)
+ e_info(e->event(), "%s", log_msg);
+ else {
+ e_info(e->add_int("delay_ms", delay_ms)->event(),
+ "%s - disconnecting client in %ums",
+ log_msg, delay_ms);
+ }
+ i_assert(detached_login_proxies_count > 0);
+ detached_login_proxies_count--;
+ } else {
+ i_assert(proxy->client_input == NULL);
+ i_assert(proxy->client_output == NULL);
+ if (log_msg != NULL)
+ e_debug(e->event(), "%s", log_msg);
+ else
+ e_debug(e->event(), "Failed to connect to %s",
+ login_proxy_get_ip_str(proxy));
+
+ DLLIST_REMOVE(&login_proxies_pending, proxy);
+ }
+ client->login_proxy = NULL;
+
+ if (delay_ms == 0)
+ login_proxy_free_final(proxy);
+ else {
+ i_assert(proxy->client_wait_io == NULL);
+ proxy->client_wait_io = io_add_istream(proxy->client_input,
+ proxy_client_disconnected_input, proxy);
+ }
+}
+
+void login_proxy_free(struct login_proxy **_proxy)
+{
+ struct login_proxy *proxy = *_proxy;
+
+ i_assert(!proxy->detached || proxy->client->destroyed);
+ /* Note: The NULL error is never even attempted to be used here. */
+ login_proxy_free_full(_proxy, NULL, "", LOGIN_PROXY_SIDE_SELF, 0);
+}
+
+bool login_proxy_failed(struct login_proxy *proxy, struct event *event,
+ enum login_proxy_failure_type type, const char *reason)
+{
+ const char *log_prefix;
+ bool try_reconnect = TRUE;
+ event_add_str(event, "error", reason);
+
+ switch (type) {
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL:
+ log_prefix = "Aborting due to internal error: ";
+ try_reconnect = FALSE;
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG:
+ log_prefix = "";
+ try_reconnect = FALSE;
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_CONNECT:
+ log_prefix = "";
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG:
+ try_reconnect = FALSE;
+ /* fall through */
+ case LOGIN_PROXY_FAILURE_TYPE_REMOTE:
+ log_prefix = "Aborting due to remote server: ";
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL:
+ log_prefix = "Remote server sent invalid input: ";
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH:
+ log_prefix = "";
+ try_reconnect = FALSE;
+ break;
+ case LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL:
+ log_prefix = "";
+ break;
+ default:
+ i_unreached();
+ }
+
+ if (try_reconnect && proxy_try_reconnect(proxy)) {
+ event_add_int(event, "reconnect_attempts", proxy->reconnect_count);
+ e_debug(event, "%s%s - reconnecting (attempt #%d)",
+ log_prefix, reason, proxy->reconnect_count);
+ proxy->failure_callback(proxy->client, type, reason, TRUE);
+ return TRUE;
+ }
+
+ if (type != LOGIN_PROXY_FAILURE_TYPE_AUTH &&
+ type != LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL)
+ e_error(event, "%s%s", log_prefix, reason);
+ else if (proxy->client->set->auth_verbose)
+ client_proxy_log_failure(proxy->client, reason);
+ proxy->failure_callback(proxy->client, type, reason, FALSE);
+ return FALSE;
+}
+
+bool login_proxy_is_ourself(const struct client *client, const char *host,
+ in_port_t port, const char *destuser)
+{
+ struct ip_addr ip;
+
+ if (port != client->local_port)
+ return FALSE;
+
+ if (net_addr2ip(host, &ip) < 0)
+ return FALSE;
+ if (!net_ip_compare(&ip, &client->local_ip))
+ return FALSE;
+
+ return strcmp(client->virtual_user, destuser) == 0;
+}
+
+struct istream *login_proxy_get_istream(struct login_proxy *proxy)
+{
+ return proxy->server_input;
+}
+
+struct ostream *login_proxy_get_ostream(struct login_proxy *proxy)
+{
+ return proxy->server_output;
+}
+
+struct event *login_proxy_get_event(struct login_proxy *proxy)
+{
+ return proxy->event;
+}
+
+const char *login_proxy_get_source_host(const struct login_proxy *proxy)
+{
+ return net_ip2addr(&proxy->source_ip);
+}
+
+const char *login_proxy_get_host(const struct login_proxy *proxy)
+{
+ return proxy->host;
+}
+
+const char *login_proxy_get_ip_str(const struct login_proxy *proxy)
+{
+ return net_ip2addr(&proxy->ip);
+}
+
+in_port_t login_proxy_get_port(const struct login_proxy *proxy)
+{
+ return proxy->port;
+}
+
+enum login_proxy_ssl_flags
+login_proxy_get_ssl_flags(const struct login_proxy *proxy)
+{
+ return proxy->ssl_flags;
+}
+
+static void
+login_proxy_finished(enum iostream_proxy_side side,
+ enum iostream_proxy_status status,
+ struct login_proxy *proxy)
+{
+ const char *errstr;
+ bool server_side;
+
+ server_side = side == LOGIN_PROXY_SIDE_SERVER;
+ switch (status) {
+ case IOSTREAM_PROXY_STATUS_INPUT_EOF:
+ /* success */
+ errstr = "";
+ break;
+ case IOSTREAM_PROXY_STATUS_INPUT_ERROR:
+ errstr = side == LOGIN_PROXY_SIDE_CLIENT ?
+ i_stream_get_error(proxy->client_input) :
+ i_stream_get_error(proxy->server_input);
+ break;
+ case IOSTREAM_PROXY_STATUS_OTHER_SIDE_OUTPUT_ERROR:
+ server_side = !server_side;
+ errstr = side == LOGIN_PROXY_SIDE_CLIENT ?
+ o_stream_get_error(proxy->server_output) :
+ o_stream_get_error(proxy->client_output);
+ break;
+ default:
+ i_unreached();
+ }
+ login_proxy_free_errstr(&proxy, errstr, server_side);
+}
+
+static void login_proxy_notify(struct login_proxy *proxy)
+{
+ login_proxy_state_notify(proxy_state, proxy->client->proxy_user);
+}
+
+void login_proxy_detach(struct login_proxy *proxy)
+{
+ struct client *client = proxy->client;
+
+ pool_unref(&proxy->client->preproxy_pool);
+
+ i_assert(!proxy->detached);
+ i_assert(proxy->server_input != NULL);
+ i_assert(proxy->server_output != NULL);
+
+ timeout_remove(&proxy->to);
+ io_remove(&proxy->server_io);
+
+ proxy->detached = TRUE;
+ proxy->client_input = client->input;
+ proxy->client_output = client->output;
+
+ o_stream_set_max_buffer_size(client->output, PROXY_MAX_OUTBUF_SIZE);
+ client->input = NULL;
+ client->output = NULL;
+
+ /* from now on, just do dummy proxying */
+ proxy->iostream_proxy =
+ iostream_proxy_create(proxy->client_input, proxy->client_output,
+ proxy->server_input, proxy->server_output);
+ iostream_proxy_set_completion_callback(proxy->iostream_proxy,
+ login_proxy_finished, proxy);
+ iostream_proxy_start(proxy->iostream_proxy);
+
+ if (proxy->notify_refresh_secs != 0) {
+ proxy->to_notify =
+ timeout_add(proxy->notify_refresh_secs * 1000,
+ login_proxy_notify, proxy);
+ }
+
+ proxy->input_callback = NULL;
+ proxy->failure_callback = NULL;
+
+ if (login_proxy_ipc_server == NULL) {
+ login_proxy_ipc_server =
+ ipc_server_init(LOGIN_PROXY_IPC_PATH,
+ LOGIN_PROXY_IPC_NAME,
+ login_proxy_ipc_cmd);
+ }
+
+ DLLIST_REMOVE(&login_proxies_pending, proxy);
+ DLLIST_PREPEND(&login_proxies, proxy);
+ detached_login_proxies_count++;
+
+ client->login_proxy = NULL;
+}
+
+int login_proxy_starttls(struct login_proxy *proxy)
+{
+ struct ssl_iostream_context *ssl_ctx;
+ struct ssl_iostream_settings ssl_set;
+ const char *error;
+
+ master_service_ssl_client_settings_to_iostream_set(
+ proxy->client->ssl_set, pool_datastack_create(), &ssl_set);
+ if ((proxy->ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0)
+ ssl_set.allow_invalid_cert = TRUE;
+ /* NOTE: We're explicitly disabling ssl_client_ca_* settings for now
+ at least. The main problem is that we're chrooted, so we can't read
+ them at this point anyway. The second problem is that especially
+ ssl_client_ca_dir does blocking disk I/O, which could cause
+ unexpected hangs when login process handles multiple clients. */
+ ssl_set.ca_file = ssl_set.ca_dir = NULL;
+
+ io_remove(&proxy->server_io);
+ if (ssl_iostream_client_context_cache_get(&ssl_set, &ssl_ctx, &error) < 0) {
+ const char *reason = t_strdup_printf(
+ "Failed to create SSL client context: %s", error);
+ login_proxy_failed(proxy, proxy->event,
+ LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason);
+ return -1;
+ }
+
+ if (io_stream_create_ssl_client(ssl_ctx, proxy->host, &ssl_set,
+ &proxy->server_input,
+ &proxy->server_output,
+ &proxy->server_ssl_iostream,
+ &error) < 0) {
+ const char *reason = t_strdup_printf(
+ "Failed to create SSL client: %s", error);
+ login_proxy_failed(proxy, proxy->event,
+ LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason);
+ ssl_iostream_context_unref(&ssl_ctx);
+ return -1;
+ }
+ ssl_iostream_context_unref(&ssl_ctx);
+ if (ssl_iostream_handshake(proxy->server_ssl_iostream) < 0) {
+ error = ssl_iostream_get_last_error(proxy->server_ssl_iostream);
+ const char *reason = t_strdup_printf(
+ "Failed to start SSL handshake: %s",
+ ssl_iostream_get_last_error(proxy->server_ssl_iostream));
+ login_proxy_failed(proxy, proxy->event,
+ LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason);
+ return -1;
+ }
+
+ proxy->server_io = io_add_istream(proxy->server_input,
+ proxy_prelogin_input, proxy);
+ return 0;
+}
+
+static void proxy_kill_idle(struct login_proxy *proxy)
+{
+ login_proxy_free_full(&proxy,
+ LOGIN_PROXY_KILL_PREFIX KILLED_BY_SHUTDOWN_REASON,
+ KILLED_BY_SHUTDOWN_REASON,
+ LOGIN_PROXY_SIDE_SELF, 0);
+}
+
+void login_proxy_kill_idle(void)
+{
+ struct login_proxy *proxy, *next;
+ time_t now = time(NULL);
+ time_t stop_timestamp = now - LOGIN_PROXY_DIE_IDLE_SECS;
+ unsigned int stop_msecs;
+
+ for (proxy = login_proxies; proxy != NULL; proxy = next) {
+ next = proxy->next;
+ time_t last_io = proxy_last_io(proxy);
+
+ if (last_io <= stop_timestamp)
+ proxy_kill_idle(proxy);
+ else {
+ i_assert(proxy->to == NULL);
+ stop_msecs = (last_io - stop_timestamp) * 1000;
+ proxy->to = timeout_add(stop_msecs,
+ proxy_kill_idle, proxy);
+ }
+ }
+}
+
+static bool
+want_kick_host(struct login_proxy *proxy, const char *const *args,
+ unsigned int key_idx ATTR_UNUSED)
+{
+ return str_array_find(args, proxy->host);
+}
+
+static bool
+want_kick_virtual_user(struct login_proxy *proxy, const char *const *args,
+ unsigned int key_idx ATTR_UNUSED)
+{
+ return str_array_find(args, proxy->client->virtual_user);
+}
+
+static bool
+want_kick_alt_username(struct login_proxy *proxy, const char *const *args,
+ unsigned int key_idx)
+{
+ unsigned int i;
+ struct client *client = proxy->client;
+
+ if (client->alt_usernames == NULL)
+ return FALSE;
+ for (i = 0; i < key_idx; i++) {
+ if (client->alt_usernames[i] == NULL)
+ return FALSE;
+ }
+ if (client->alt_usernames[i] == NULL)
+ return FALSE;
+ return str_array_find(args, client->alt_usernames[i]);
+}
+
+static void
+login_proxy_cmd_kick_full(struct ipc_cmd *cmd, const char *const *args,
+ bool (*want_kick)(struct login_proxy *, const char *const *,
+ unsigned int), unsigned int key_idx)
+{
+ struct login_proxy *proxy, *next;
+ unsigned int count = 0;
+
+ if (args[0] == NULL) {
+ ipc_cmd_fail(&cmd, "Missing parameter");
+ return;
+ }
+
+ for (proxy = login_proxies; proxy != NULL; proxy = next) T_BEGIN {
+ next = proxy->next;
+
+ if (want_kick(proxy, args, key_idx)) {
+ login_proxy_free_full(&proxy,
+ LOGIN_PROXY_KILL_PREFIX KILLED_BY_ADMIN_REASON,
+ KILLED_BY_ADMIN_REASON,
+ LOGIN_PROXY_SIDE_SELF,
+ LOGIN_PROXY_FREE_FLAG_DELAYED);
+ count++;
+ }
+ } T_END;
+ for (proxy = login_proxies_pending; proxy != NULL; proxy = next) T_BEGIN {
+ next = proxy->next;
+
+ if (want_kick(proxy, args, key_idx)) {
+ client_disconnect(proxy->client,
+ LOGIN_PROXY_KILL_PREFIX KILLED_BY_ADMIN_REASON,
+ FALSE);
+ client_destroy(proxy->client, NULL);
+ count++;
+ }
+ } T_END;
+ ipc_cmd_success_reply(&cmd, t_strdup_printf("%u", count));
+}
+
+static void
+login_proxy_cmd_kick(struct ipc_cmd *cmd, const char *const *args)
+{
+ login_proxy_cmd_kick_full(cmd, args, want_kick_virtual_user, 0);
+}
+
+static void
+login_proxy_cmd_kick_host(struct ipc_cmd *cmd, const char *const *args)
+{
+ login_proxy_cmd_kick_full(cmd, args, want_kick_host, 0);
+}
+
+static void
+login_proxy_cmd_kick_alt(struct ipc_cmd *cmd, const char *const *args)
+{
+ char *const *fields;
+ unsigned int i, count;
+
+ if (args[0] == NULL) {
+ ipc_cmd_fail(&cmd, "Missing parameter");
+ return;
+ }
+ fields = array_get(&global_alt_usernames, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(fields[i], args[0]) == 0)
+ break;
+ }
+ if (i == count) {
+ /* field doesn't exist, but it's not an error necessarily */
+ ipc_cmd_success_reply(&cmd, "0");
+ return;
+ }
+
+ login_proxy_cmd_kick_full(cmd, args+1, want_kick_alt_username, i);
+}
+
+static bool director_username_hash(struct client *client, unsigned int *hash_r)
+{
+ const char *error;
+
+ if (client->director_username_hash_cache != 0) {
+ /* already set */
+ } else if (!mail_user_hash(client->virtual_user,
+ client->set->director_username_hash,
+ &client->director_username_hash_cache,
+ &error)) {
+ e_error(client->event,
+ "Failed to expand director_username_hash=%s: %s",
+ client->set->director_username_hash, error);
+ return FALSE;
+ }
+
+ *hash_r = client->director_username_hash_cache;
+ return TRUE;
+}
+
+static void
+login_proxy_cmd_kick_director_hash(struct ipc_cmd *cmd, const char *const *args)
+{
+ struct login_proxy *proxy, *next;
+ struct ip_addr except_ip;
+ unsigned int hash, proxy_hash, count = 0;
+
+ if (args[0] == NULL || str_to_uint(args[0], &hash) < 0) {
+ ipc_cmd_fail(&cmd, "Invalid parameters");
+ return;
+ }
+ /* optional except_ip parameter specifies that we're not killing the
+ connections that are proxying to the except_ip backend */
+ except_ip.family = 0;
+ if (args[1] != NULL && args[1][0] != '\0' &&
+ net_addr2ip(args[1], &except_ip) < 0) {
+ ipc_cmd_fail(&cmd, "Invalid except_ip parameter");
+ return;
+ }
+
+ for (proxy = login_proxies; proxy != NULL; proxy = next) {
+ next = proxy->next;
+
+ if (director_username_hash(proxy->client, &proxy_hash) &&
+ proxy_hash == hash &&
+ !net_ip_compare(&proxy->ip, &except_ip)) {
+ login_proxy_free_full(&proxy,
+ LOGIN_PROXY_KILL_PREFIX KILLED_BY_DIRECTOR_REASON,
+ KILLED_BY_DIRECTOR_REASON,
+ LOGIN_PROXY_SIDE_SELF,
+ LOGIN_PROXY_FREE_FLAG_DELAYED);
+ count++;
+ }
+ }
+ for (proxy = login_proxies_pending; proxy != NULL; proxy = next) {
+ next = proxy->next;
+
+ if (director_username_hash(proxy->client, &proxy_hash) &&
+ proxy_hash == hash &&
+ !net_ip_compare(&proxy->ip, &except_ip)) {
+ client_disconnect(proxy->client,
+ LOGIN_PROXY_KILL_PREFIX KILLED_BY_DIRECTOR_REASON,
+ FALSE);
+ client_destroy(proxy->client, NULL);
+ count++;
+ }
+ }
+ ipc_cmd_success_reply(&cmd, t_strdup_printf("%u", count));
+}
+
+static void
+login_proxy_cmd_list_reply(struct ipc_cmd *cmd, string_t *str,
+ struct login_proxy *proxy)
+{
+ unsigned int i, alt_count = array_count(&global_alt_usernames);
+
+ str_truncate(str, 0);
+ str_append_tabescaped(str, proxy->client->virtual_user);
+ str_append_c(str, '\t');
+ i = 0;
+ if (proxy->client->alt_usernames != NULL) {
+ for (; proxy->client->alt_usernames[i] != NULL; i++) {
+ str_append_tabescaped(str, proxy->client->alt_usernames[i]);
+ str_append_c(str, '\t');
+ }
+ i_assert(i <= alt_count);
+ }
+ for (; i < alt_count; i++)
+ str_append_c(str, '\t');
+
+ str_printfa(str, "%s\t%s\t%s\t%u", login_binary->protocol,
+ net_ip2addr(&proxy->client->ip),
+ net_ip2addr(&proxy->ip), proxy->port);
+ ipc_cmd_send(cmd, str_c(str));
+}
+
+static void
+login_proxy_cmd_list(struct ipc_cmd *cmd, const char *const *args ATTR_UNUSED)
+{
+ struct login_proxy *proxy;
+ char *field;
+ string_t *str = t_str_new(64);
+
+ str_append(str, "username\t");
+ array_foreach_elem(&global_alt_usernames, field) {
+ str_append_tabescaped(str, field);
+ str_append_c(str, '\t');
+ }
+ str_append(str, "service\tsrc-ip\tdest-ip\tdest-port");
+
+ ipc_cmd_send(cmd, str_c(str));
+
+ for (proxy = login_proxies; proxy != NULL; proxy = proxy->next)
+ login_proxy_cmd_list_reply(cmd, str, proxy);
+ for (proxy = login_proxies_pending; proxy != NULL; proxy = proxy->next)
+ login_proxy_cmd_list_reply(cmd, str, proxy);
+ ipc_cmd_success(&cmd);
+}
+
+static void login_proxy_ipc_cmd(struct ipc_cmd *cmd, const char *line)
+{
+ const char *const *args = t_strsplit_tabescaped(line);
+ const char *name = args[0];
+
+ args++;
+ if (strcmp(name, "KICK") == 0)
+ login_proxy_cmd_kick(cmd, args);
+ else if (strcmp(name, "KICK-ALT") == 0)
+ login_proxy_cmd_kick_alt(cmd, args);
+ else if (strcmp(name, "KICK-DIRECTOR-HASH") == 0)
+ login_proxy_cmd_kick_director_hash(cmd, args);
+ else if (strcmp(name, "LIST-FULL") == 0)
+ login_proxy_cmd_list(cmd, args);
+ else if (strcmp(name, "KICK-HOST") == 0)
+ login_proxy_cmd_kick_host(cmd, args);
+ else
+ ipc_cmd_fail(&cmd, "Unknown command");
+}
+
+unsigned int login_proxies_get_detached_count(void)
+{
+ return detached_login_proxies_count;
+}
+
+struct client *login_proxies_get_first_detached_client(void)
+{
+ return login_proxies == NULL ? NULL : login_proxies->client;
+}
+
+void login_proxy_init(const char *proxy_notify_pipe_path)
+{
+ proxy_state = login_proxy_state_init(proxy_notify_pipe_path);
+}
+
+void login_proxy_deinit(void)
+{
+ struct login_proxy *proxy;
+
+ while (login_proxies != NULL) {
+ proxy = login_proxies;
+ login_proxy_free_full(&proxy,
+ LOGIN_PROXY_KILL_PREFIX KILLED_BY_SHUTDOWN_REASON,
+ KILLED_BY_SHUTDOWN_REASON,
+ LOGIN_PROXY_SIDE_SELF, 0);
+ }
+ i_assert(detached_login_proxies_count == 0);
+
+ while (login_proxies_disconnecting != NULL)
+ login_proxy_free_final(login_proxies_disconnecting);
+ if (login_proxy_ipc_server != NULL)
+ ipc_server_deinit(&login_proxy_ipc_server);
+ login_proxy_state_deinit(&proxy_state);
+}
diff --git a/src/login-common/login-proxy.h b/src/login-common/login-proxy.h
new file mode 100644
index 0000000..428c3d5
--- /dev/null
+++ b/src/login-common/login-proxy.h
@@ -0,0 +1,120 @@
+#ifndef LOGIN_PROXY_H
+#define LOGIN_PROXY_H
+
+#include "net.h"
+
+/* Max. number of embedded proxying connections until proxying fails.
+ This is intended to avoid an accidental configuration where two proxies
+ keep connecting to each others, both thinking the other one is supposed to
+ handle the user. This only works if both proxies support the Dovecot
+ TTL extension feature. */
+#define LOGIN_PROXY_TTL 5
+#define LOGIN_PROXY_DEFAULT_HOST_IMMEDIATE_FAILURE_AFTER_SECS 30
+
+#define LOGIN_PROXY_FAILURE_MSG "Account is temporarily unavailable."
+
+struct client;
+struct login_proxy;
+
+enum login_proxy_ssl_flags {
+ /* Use SSL/TLS enabled */
+ PROXY_SSL_FLAG_YES = 0x01,
+ /* Don't do SSL handshake immediately after connected */
+ PROXY_SSL_FLAG_STARTTLS = 0x02,
+ /* Don't require that the received certificate is valid */
+ PROXY_SSL_FLAG_ANY_CERT = 0x04
+};
+
+enum login_proxy_failure_type {
+ /* connect() failed or remote disconnected us. */
+ LOGIN_PROXY_FAILURE_TYPE_CONNECT,
+ /* Internal error. */
+ LOGIN_PROXY_FAILURE_TYPE_INTERNAL,
+ /* Internal configuration error. */
+ LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG,
+ /* Remote command failed unexpectedly. */
+ LOGIN_PROXY_FAILURE_TYPE_REMOTE,
+ /* Remote isn't configured as expected (e.g. STARTTLS required, but
+ no such capability). */
+ LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG,
+ /* Remote server is unexpectedly violating the protocol standard. */
+ LOGIN_PROXY_FAILURE_TYPE_PROTOCOL,
+ /* Authentication failed to backend. The LOGIN/AUTH command reply was
+ already sent to the client. */
+ LOGIN_PROXY_FAILURE_TYPE_AUTH,
+ /* Authentication failed with a temporary failure code. Attempting it
+ again might work. */
+ LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL,
+};
+
+struct login_proxy_settings {
+ const char *host;
+ struct ip_addr ip, source_ip;
+ in_port_t port;
+ unsigned int connect_timeout_msecs;
+ /* send a notification about proxy connection to proxy-notify pipe
+ every n seconds */
+ unsigned int notify_refresh_secs;
+ unsigned int host_immediate_failure_after_secs;
+ enum login_proxy_ssl_flags ssl_flags;
+ const char *rawlog_dir;
+};
+
+/* Called when new input comes from proxy. */
+typedef void login_proxy_input_callback_t(struct client *client);
+/* Called when proxying fails. If reconnecting=TRUE, this is just an
+ intermediate notification that the proxying will attempt to reconnect soon
+ before failing. */
+typedef void login_proxy_failure_callback_t(struct client *client,
+ enum login_proxy_failure_type type,
+ const char *reason,
+ bool reconnecting);
+
+/* Create a proxy to given host. Returns NULL if failed. Given callback is
+ called when new input is available from proxy. */
+int login_proxy_new(struct client *client, struct event *event,
+ const struct login_proxy_settings *set,
+ login_proxy_input_callback_t *input_callback,
+ login_proxy_failure_callback_t *failure_callback);
+/* Free the proxy. This should be called if authentication fails. */
+void login_proxy_free(struct login_proxy **proxy);
+
+/* Login proxying session has failed. Returns TRUE if the reconnection is
+ attempted. */
+bool login_proxy_failed(struct login_proxy *proxy, struct event *event,
+ enum login_proxy_failure_type type, const char *reason);
+
+/* Return TRUE if host/port/destuser combination points to same as current
+ connection. */
+bool login_proxy_is_ourself(const struct client *client, const char *host,
+ in_port_t port, const char *destuser);
+
+/* Detach proxy from client. This is done after the authentication is
+ successful and all that is left is the dummy proxying. */
+void login_proxy_detach(struct login_proxy *proxy);
+
+/* STARTTLS command was issued. */
+int login_proxy_starttls(struct login_proxy *proxy);
+
+struct istream *login_proxy_get_istream(struct login_proxy *proxy);
+struct ostream *login_proxy_get_ostream(struct login_proxy *proxy);
+
+void login_proxy_append_success_log_info(struct login_proxy *proxy,
+ string_t *str);
+struct event *login_proxy_get_event(struct login_proxy *proxy);
+const char *login_proxy_get_source_host(const struct login_proxy *proxy) ATTR_PURE;
+const char *login_proxy_get_host(const struct login_proxy *proxy) ATTR_PURE;
+const char *login_proxy_get_ip_str(const struct login_proxy *proxy) ATTR_PURE;
+in_port_t login_proxy_get_port(const struct login_proxy *proxy) ATTR_PURE;
+enum login_proxy_ssl_flags
+login_proxy_get_ssl_flags(const struct login_proxy *proxy) ATTR_PURE;
+
+void login_proxy_kill_idle(void);
+
+unsigned int login_proxies_get_detached_count(void);
+struct client *login_proxies_get_first_detached_client(void);
+
+void login_proxy_init(const char *proxy_notify_pipe_path);
+void login_proxy_deinit(void);
+
+#endif
diff --git a/src/login-common/login-settings.c b/src/login-common/login-settings.c
new file mode 100644
index 0000000..e680677
--- /dev/null
+++ b/src/login-common/login-settings.c
@@ -0,0 +1,227 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "hostpid.h"
+#include "var-expand.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "master-service-ssl-settings.h"
+#include "master-service-settings-cache.h"
+#include "login-settings.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+static bool login_settings_check(void *_set, pool_t pool, const char **error_r);
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct login_settings)
+
+static const struct setting_define login_setting_defines[] = {
+ DEF(STR, login_trusted_networks),
+ DEF(STR, login_source_ips),
+ DEF(STR_VARS, login_greeting),
+ DEF(STR, login_log_format_elements),
+ DEF(STR, login_log_format),
+ DEF(STR, login_access_sockets),
+ DEF(STR_VARS, login_proxy_notify_path),
+ DEF(STR, login_plugin_dir),
+ DEF(STR, login_plugins),
+ DEF(TIME_MSECS, login_proxy_timeout),
+ DEF(UINT, login_proxy_max_reconnects),
+ DEF(TIME, login_proxy_max_disconnect_delay),
+ DEF(STR, login_proxy_rawlog_dir),
+ DEF(STR, director_username_hash),
+
+ DEF(BOOL, auth_ssl_require_client_cert),
+ DEF(BOOL, auth_ssl_username_from_cert),
+
+ DEF(BOOL, disable_plaintext_auth),
+ DEF(BOOL, auth_verbose),
+ DEF(BOOL, auth_debug),
+ DEF(BOOL, verbose_proctitle),
+
+ DEF(UINT, mail_max_userip_connections),
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct login_settings login_default_settings = {
+ .login_trusted_networks = "",
+ .login_source_ips = "",
+ .login_greeting = PACKAGE_NAME" ready.",
+ .login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c session=<%{session}>",
+ .login_log_format = "%$: %s",
+ .login_access_sockets = "",
+ .login_proxy_notify_path = "proxy-notify",
+ .login_plugin_dir = MODULEDIR"/login",
+ .login_plugins = "",
+ .login_proxy_timeout = 30*1000,
+ .login_proxy_max_reconnects = 3,
+ .login_proxy_max_disconnect_delay = 0,
+ .login_proxy_rawlog_dir = "",
+ .director_username_hash = "%Lu",
+
+ .auth_ssl_require_client_cert = FALSE,
+ .auth_ssl_username_from_cert = FALSE,
+
+ .disable_plaintext_auth = TRUE,
+ .auth_verbose = FALSE,
+ .auth_debug = FALSE,
+ .verbose_proctitle = FALSE,
+
+ .mail_max_userip_connections = 10
+};
+
+const struct setting_parser_info login_setting_parser_info = {
+ .module_name = "login",
+ .defines = login_setting_defines,
+ .defaults = &login_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct login_settings),
+
+ .parent_offset = SIZE_MAX,
+
+ .check_func = login_settings_check
+};
+
+static const struct setting_parser_info *default_login_set_roots[] = {
+ &login_setting_parser_info,
+ NULL
+};
+
+const struct setting_parser_info **login_set_roots = default_login_set_roots;
+
+static struct master_service_settings_cache *set_cache;
+
+/* <settings checks> */
+static bool login_settings_check(void *_set, pool_t pool,
+ const char **error_r ATTR_UNUSED)
+{
+ struct login_settings *set = _set;
+
+ set->log_format_elements_split =
+ p_strsplit(pool, set->login_log_format_elements, " ");
+
+ if (set->auth_debug_passwords)
+ set->auth_debug = TRUE;
+ if (set->auth_debug)
+ set->auth_verbose = TRUE;
+ return TRUE;
+}
+/* </settings checks> */
+
+static const struct var_expand_table *
+login_set_var_expand_table(const struct master_service_settings_input *input)
+{
+ const struct var_expand_table stack_tab[] = {
+ { 'l', net_ip2addr(&input->local_ip), "lip" },
+ { 'r', net_ip2addr(&input->remote_ip), "rip" },
+ { 'p', my_pid, "pid" },
+ { 's', input->service, "service" },
+ { '\0', input->local_name, "local_name" },
+ /* aliases */
+ { '\0', net_ip2addr(&input->local_ip), "local_ip" },
+ { '\0', net_ip2addr(&input->remote_ip), "remote_ip" },
+ /* NOTE: Make sure login_log_format_elements_split has all these
+ variables (in client-common.c:get_var_expand_table()). */
+ { '\0', NULL, NULL }
+ };
+ struct var_expand_table *tab;
+
+ tab = t_malloc_no0(sizeof(stack_tab));
+ memcpy(tab, stack_tab, sizeof(stack_tab));
+ return tab;
+}
+
+static void *
+login_setting_dup(pool_t pool, const struct setting_parser_info *info,
+ const void *src_set)
+{
+ const char *error;
+ void *dest;
+
+ dest = settings_dup(info, src_set, pool);
+ if (!settings_check(info, pool, dest, &error)) {
+ const char *name = info->module_name;
+
+ i_fatal("settings_check(%s) failed: %s",
+ name != NULL ? name : "unknown", error);
+ }
+ return dest;
+}
+
+struct login_settings *
+login_settings_read(pool_t pool,
+ const struct ip_addr *local_ip,
+ const struct ip_addr *remote_ip,
+ const char *local_name,
+ const struct master_service_ssl_settings **ssl_set_r,
+ const struct master_service_ssl_server_settings **ssl_server_set_r,
+ void ***other_settings_r)
+{
+ struct master_service_settings_input input;
+ const char *error;
+ const struct setting_parser_context *parser;
+ void *const *cache_sets;
+ void **sets;
+ unsigned int i, count;
+
+ i_zero(&input);
+ input.roots = login_set_roots;
+ input.module = login_binary->process_name;
+ input.service = login_binary->protocol;
+ input.local_name = local_name;
+
+ if (local_ip != NULL)
+ input.local_ip = *local_ip;
+ if (remote_ip != NULL)
+ input.remote_ip = *remote_ip;
+
+ if (set_cache == NULL) {
+ set_cache = master_service_settings_cache_init(master_service,
+ input.module,
+ input.service);
+ /* lookup filters
+
+ this is only enabled if service_count > 1 because otherwise
+ login process will process only one request and this is only
+ useful when more than one request is processed.
+ */
+ if (master_service_get_service_count(master_service) > 1)
+ master_service_settings_cache_init_filter(set_cache);
+ }
+
+ if (master_service_settings_cache_read(set_cache, &input, NULL,
+ &parser, &error) < 0)
+ i_fatal("Error reading configuration: %s", error);
+
+ cache_sets = master_service_settings_parser_get_others(master_service, parser);
+ for (count = 0; input.roots[count] != NULL; count++) ;
+ i_assert(cache_sets[count] == NULL);
+ sets = p_new(pool, void *, count + 1);
+ for (i = 0; i < count; i++)
+ sets[i] = login_setting_dup(pool, input.roots[i], cache_sets[i]);
+
+ if (settings_var_expand(&login_setting_parser_info, sets[0], pool,
+ login_set_var_expand_table(&input), &error) <= 0)
+ i_fatal("Failed to expand settings: %s", error);
+
+ *ssl_set_r =
+ login_setting_dup(pool, &master_service_ssl_setting_parser_info,
+ settings_parser_get_list(parser)[1]);
+ *ssl_server_set_r =
+ login_setting_dup(pool, &master_service_ssl_server_setting_parser_info,
+ settings_parser_get_list(parser)[2]);
+ *other_settings_r = sets + 1;
+ return sets[0];
+}
+
+void login_settings_deinit(void)
+{
+ if (set_cache != NULL)
+ master_service_settings_cache_deinit(&set_cache);
+}
diff --git a/src/login-common/login-settings.h b/src/login-common/login-settings.h
new file mode 100644
index 0000000..5bff837
--- /dev/null
+++ b/src/login-common/login-settings.h
@@ -0,0 +1,50 @@
+#ifndef LOGIN_SETTINGS_H
+#define LOGIN_SETTINGS_H
+
+struct master_service_ssl_settings;
+struct master_service_ssl_server_settings;
+
+struct login_settings {
+ const char *login_trusted_networks;
+ const char *login_source_ips;
+ const char *login_greeting;
+ const char *login_log_format_elements, *login_log_format;
+ const char *login_access_sockets;
+ const char *login_proxy_notify_path;
+ const char *login_plugin_dir;
+ const char *login_plugins;
+ unsigned int login_proxy_timeout;
+ unsigned int login_proxy_max_reconnects;
+ unsigned int login_proxy_max_disconnect_delay;
+ const char *login_proxy_rawlog_dir;
+ const char *director_username_hash;
+
+ bool auth_ssl_require_client_cert;
+ bool auth_ssl_username_from_cert;
+
+ bool disable_plaintext_auth;
+ bool auth_verbose;
+ bool auth_debug;
+ bool auth_debug_passwords;
+ bool verbose_proctitle;
+
+ unsigned int mail_max_userip_connections;
+
+ /* generated: */
+ char *const *log_format_elements_split;
+};
+
+extern const struct setting_parser_info **login_set_roots;
+extern const struct setting_parser_info login_setting_parser_info;
+
+struct login_settings *
+login_settings_read(pool_t pool,
+ const struct ip_addr *local_ip,
+ const struct ip_addr *remote_ip,
+ const char *local_name,
+ const struct master_service_ssl_settings **ssl_set_r,
+ const struct master_service_ssl_server_settings **ssl_server_set_r,
+ void ***other_settings_r) ATTR_NULL(2, 3, 4);
+void login_settings_deinit(void);
+
+#endif
diff --git a/src/login-common/main.c b/src/login-common/main.c
new file mode 100644
index 0000000..d5622d9
--- /dev/null
+++ b/src/login-common/main.c
@@ -0,0 +1,571 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "randgen.h"
+#include "module-dir.h"
+#include "process-title.h"
+#include "restrict-access.h"
+#include "restrict-process-size.h"
+#include "master-auth.h"
+#include "master-service.h"
+#include "master-interface.h"
+#include "iostream-ssl.h"
+#include "client-common.h"
+#include "access-lookup.h"
+#include "anvil-client.h"
+#include "auth-client.h"
+#include "dsasl-client.h"
+#include "master-service-ssl-settings.h"
+#include "login-proxy.h"
+
+#include <unistd.h>
+#include <syslog.h>
+
+#define AUTH_CLIENT_IDLE_TIMEOUT_MSECS (1000*60)
+
+struct login_access_lookup {
+ struct master_service_connection conn;
+ struct io *io;
+
+ char **sockets, **next_socket;
+ struct access_lookup *access;
+};
+
+struct event *event_auth;
+static struct event_category event_category_auth = {
+ .name = "auth",
+};
+
+struct login_binary *login_binary;
+struct auth_client *auth_client;
+struct master_auth *master_auth;
+bool closing_down, login_debug;
+struct anvil_client *anvil;
+const char *login_rawlog_dir = NULL;
+unsigned int initial_service_count;
+struct login_module_register login_module_register;
+ARRAY_TYPE(string) global_alt_usernames;
+bool login_ssl_initialized;
+
+const struct login_settings *global_login_settings;
+const struct master_service_ssl_settings *global_ssl_settings;
+const struct master_service_ssl_server_settings *global_ssl_server_settings;
+void **global_other_settings;
+
+static ARRAY(struct ip_addr) login_source_ips_array;
+const struct ip_addr *login_source_ips;
+unsigned int login_source_ips_idx, login_source_ips_count;
+
+static struct module *modules;
+static struct timeout *auth_client_to;
+static const char *post_login_socket;
+static bool shutting_down = FALSE;
+static bool ssl_connections = FALSE;
+static bool auth_connected_once = FALSE;
+
+static void login_access_lookup_next(struct login_access_lookup *lookup);
+
+static bool get_first_client(struct client **client_r)
+{
+ struct client *client = clients;
+
+ if (client == NULL)
+ client = login_proxies_get_first_detached_client();
+ if (client == NULL)
+ client = clients_get_first_fd_proxy();
+ *client_r = client;
+ return client != NULL;
+}
+
+void login_refresh_proctitle(void)
+{
+ struct client *client;
+ const char *addr;
+
+ if (!global_login_settings->verbose_proctitle)
+ return;
+
+ /* clients_get_count() includes all the clients being served.
+ Inside that there are 3 groups:
+ 1. pre-login clients
+ 2. post-login clients being proxied to remote hosts
+ 3. post-login clients being proxied to post-login processes
+ Currently the post-login proxying is done only for SSL/TLS
+ connections, so we're assuming that they're the same. */
+ string_t *str = t_str_new(64);
+ if (clients_get_count() == 0) {
+ /* no clients */
+ } else if (clients_get_count() > 1 || !get_first_client(&client)) {
+ str_printfa(str, "[%u pre-login", clients_get_count() -
+ login_proxies_get_detached_count() -
+ clients_get_fd_proxies_count());
+ if (login_proxies_get_detached_count() > 0) {
+ /* show detached proxies only if they exist, so
+ non-proxy servers don't unnecessarily show them. */
+ str_printfa(str, " + %u proxies",
+ login_proxies_get_detached_count());
+ }
+ if (clients_get_fd_proxies_count() > 0) {
+ /* show post-login proxies only if they exist, so
+ proxy-only servers don't unnecessarily show them. */
+ str_printfa(str, " + %u TLS proxies",
+ clients_get_fd_proxies_count());
+ }
+ str_append_c(str, ']');
+ } else {
+ str_append_c(str, '[');
+ addr = net_ip2addr(&client->ip);
+ if (addr[0] != '\0')
+ str_printfa(str, "%s ", addr);
+ if (client->fd_proxying)
+ str_append(str, "TLS proxy");
+ else if (client->destroyed)
+ str_append(str, "proxy");
+ else
+ str_append(str, "pre-login");
+ str_append_c(str, ']');
+ }
+ process_title_set(str_c(str));
+}
+
+static void auth_client_idle_timeout(struct auth_client *auth_client)
+{
+ i_assert(clients == NULL);
+
+ auth_client_disconnect(auth_client, "idle disconnect");
+ timeout_remove(&auth_client_to);
+}
+
+void login_client_destroyed(void)
+{
+ if (clients == NULL && auth_client_to == NULL) {
+ auth_client_to = timeout_add(AUTH_CLIENT_IDLE_TIMEOUT_MSECS,
+ auth_client_idle_timeout,
+ auth_client);
+ }
+}
+
+static void login_die(void)
+{
+ shutting_down = TRUE;
+ login_proxy_kill_idle();
+
+ if (!auth_client_is_connected(auth_client)) {
+ /* we don't have auth client, and we might never get one */
+ clients_destroy_all();
+ }
+}
+
+static void
+client_connected_finish(const struct master_service_connection *conn)
+{
+ struct client *client;
+ const struct login_settings *set;
+ const struct master_service_ssl_settings *ssl_set;
+ const struct master_service_ssl_server_settings *ssl_server_set;
+ pool_t pool;
+ void **other_sets;
+
+ pool = pool_alloconly_create("login client", 8*1024);
+ set = login_settings_read(pool, &conn->local_ip,
+ &conn->remote_ip, NULL,
+ &ssl_set, &ssl_server_set, &other_sets);
+
+ client = client_alloc(conn->fd, pool, conn, set,
+ ssl_set, ssl_server_set);
+ if (ssl_connections || conn->ssl) {
+ if (client_init_ssl(client) < 0) {
+ client_unref(&client);
+ net_disconnect(conn->fd);
+ master_service_client_connection_destroyed(master_service);
+ return;
+ }
+ }
+ client_init(client, other_sets);
+
+ timeout_remove(&auth_client_to);
+}
+
+static void login_access_lookup_free(struct login_access_lookup *lookup)
+{
+ io_remove(&lookup->io);
+ if (lookup->access != NULL)
+ access_lookup_destroy(&lookup->access);
+ if (lookup->conn.fd != -1) {
+ if (close(lookup->conn.fd) < 0)
+ i_error("close(client) failed: %m");
+ master_service_client_connection_destroyed(master_service);
+ }
+
+ p_strsplit_free(default_pool, lookup->sockets);
+ i_free(lookup);
+}
+
+static void login_access_callback(bool success, void *context)
+{
+ struct login_access_lookup *lookup = context;
+
+ if (!success) {
+ i_info("access(%s): Client refused (rip=%s)",
+ *lookup->next_socket,
+ net_ip2addr(&lookup->conn.remote_ip));
+ login_access_lookup_free(lookup);
+ } else {
+ lookup->next_socket++;
+ login_access_lookup_next(lookup);
+ }
+}
+
+static void login_access_lookup_next(struct login_access_lookup *lookup)
+{
+ if (*lookup->next_socket == NULL) {
+ /* last one */
+ io_remove(&lookup->io);
+ client_connected_finish(&lookup->conn);
+ lookup->conn.fd = -1;
+ login_access_lookup_free(lookup);
+ return;
+ }
+ lookup->access = access_lookup(*lookup->next_socket, lookup->conn.fd,
+ login_binary->protocol,
+ login_access_callback, lookup);
+ if (lookup->access == NULL)
+ login_access_lookup_free(lookup);
+}
+
+static void client_input_error(struct login_access_lookup *lookup)
+{
+ char c;
+ int ret;
+
+ ret = recv(lookup->conn.fd, &c, 1, MSG_PEEK);
+ if (ret <= 0) {
+ i_info("access(%s): Client disconnected during lookup (rip=%s)",
+ *lookup->next_socket,
+ net_ip2addr(&lookup->conn.remote_ip));
+ login_access_lookup_free(lookup);
+ } else {
+ /* actual input. stop listening until lookup is done. */
+ io_remove(&lookup->io);
+ }
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+ const char *access_sockets =
+ global_login_settings->login_access_sockets;
+ struct login_access_lookup *lookup;
+
+ master_service_client_connection_accept(conn);
+ if (conn->remote_ip.family != 0) {
+ /* log the connection's IP address in case we crash. it's of
+ course possible that another earlier client causes the
+ crash, but this is better than nothing. */
+ i_set_failure_send_ip(&conn->remote_ip);
+ }
+
+ /* make sure we're connected (or attempting to connect) to auth */
+ auth_client_connect(auth_client);
+
+ if (*access_sockets == '\0') {
+ /* no access checks */
+ client_connected_finish(conn);
+ return;
+ }
+
+ lookup = i_new(struct login_access_lookup, 1);
+ lookup->conn = *conn;
+ lookup->io = io_add(conn->fd, IO_READ, client_input_error, lookup);
+ lookup->sockets = p_strsplit_spaces(default_pool, access_sockets, " ");
+ lookup->next_socket = lookup->sockets;
+
+ login_access_lookup_next(lookup);
+}
+
+static void auth_connect_notify(struct auth_client *client ATTR_UNUSED,
+ bool connected, void *context ATTR_UNUSED)
+{
+ if (connected) {
+ auth_connected_once = TRUE;
+ clients_notify_auth_connected();
+ } else if (shutting_down)
+ clients_destroy_all();
+ else if (!auth_connected_once) {
+ /* auth disconnected without having ever succeeded, so the
+ auth process is probably misconfigured. no point in
+ keeping the client connections hanging. */
+ clients_destroy_all_reason("Auth process broken");
+ }
+}
+
+static bool anvil_reconnect_callback(void)
+{
+ /* we got disconnected from anvil. we can't reconnect to it since we're
+ chrooted, so just die after we've finished handling the current
+ connections. */
+ master_service_stop_new_connections(master_service);
+ return FALSE;
+}
+
+void login_anvil_init(void)
+{
+ if (anvil != NULL)
+ return;
+
+ anvil = anvil_client_init("anvil", anvil_reconnect_callback, 0);
+ if (anvil_client_connect(anvil, TRUE) < 0)
+ i_fatal("Couldn't connect to anvil");
+}
+
+static void
+parse_login_source_ips(const char *ips_str)
+{
+ const char *const *tmp;
+ struct ip_addr *tmp_ips;
+ bool skip_nonworking = FALSE;
+ unsigned int i, tmp_ips_count;
+ int ret;
+
+ if (ips_str[0] == '?') {
+ /* try binding to the IP immediately. if it doesn't
+ work, skip it. (this allows using the same config file for
+ all the servers.) */
+ skip_nonworking = TRUE;
+ ips_str++;
+ }
+ i_array_init(&login_source_ips_array, 4);
+ for (tmp = t_strsplit_spaces(ips_str, ", "); *tmp != NULL; tmp++) {
+ ret = net_gethostbyname(*tmp, &tmp_ips, &tmp_ips_count);
+ if (ret != 0) {
+ i_error("login_source_ips: net_gethostbyname(%s) failed: %s",
+ *tmp, net_gethosterror(ret));
+ continue;
+ }
+ for (i = 0; i < tmp_ips_count; i++) {
+ if (skip_nonworking && net_try_bind(&tmp_ips[i]) < 0)
+ continue;
+ array_push_back(&login_source_ips_array, &tmp_ips[i]);
+ }
+ }
+
+ /* make the array contents easily accessible */
+ login_source_ips = array_get(&login_source_ips_array,
+ &login_source_ips_count);
+}
+
+static void login_load_modules(void)
+{
+ struct module_dir_load_settings mod_set;
+
+ if (global_login_settings->login_plugins[0] == '\0')
+ return;
+
+ i_zero(&mod_set);
+ mod_set.abi_version = DOVECOT_ABI_VERSION;
+ mod_set.binary_name = login_binary->process_name;
+ mod_set.setting_name = "login_plugins";
+ mod_set.require_init_funcs = TRUE;
+ mod_set.debug = login_debug;
+
+ modules = module_dir_load(global_login_settings->login_plugin_dir,
+ global_login_settings->login_plugins,
+ &mod_set);
+ module_dir_init(modules);
+}
+
+static void login_ssl_init(void)
+{
+ struct ssl_iostream_settings ssl_set;
+ const char *error;
+
+ if (strcmp(global_ssl_settings->ssl, "no") == 0)
+ return;
+
+ master_service_ssl_server_settings_to_iostream_set(global_ssl_settings,
+ global_ssl_server_settings, pool_datastack_create(), &ssl_set);
+ if (io_stream_ssl_global_init(&ssl_set, &error) < 0)
+ i_fatal("Failed to initialize SSL library: %s", error);
+ login_ssl_initialized = TRUE;
+}
+
+static void main_preinit(void)
+{
+ unsigned int max_fds;
+
+ /* Initialize SSL proxy so it can read certificate and private
+ key file. */
+ login_ssl_init();
+ dsasl_clients_init();
+ client_common_init();
+
+ /* set the number of fds we want to use. it may get increased or
+ decreased. leave a couple of extra fds for auth sockets and such.
+
+ worst case each connection can use:
+
+ - 1 for client
+ - 1 for login proxy
+ - 2 for client-side ssl proxy
+ - 2 for server-side ssl proxy (with login proxy)
+
+ However, login process nowadays supports plugins, there are rawlogs
+ and so on. Don't enforce the fd limit anymore, but use this value
+ for optimizing the ioloop's fd table size.
+ */
+ max_fds = MASTER_LISTEN_FD_FIRST + 16 +
+ master_service_get_socket_count(master_service) +
+ master_service_get_client_limit(master_service)*6;
+ io_loop_set_max_fd_count(current_ioloop, max_fds);
+
+ i_assert(strcmp(global_ssl_settings->ssl, "no") == 0 ||
+ login_ssl_initialized);
+
+ if (global_login_settings->mail_max_userip_connections > 0)
+ login_anvil_init();
+
+ /* read the login_source_ips before chrooting so it can access
+ /etc/hosts */
+ parse_login_source_ips(global_login_settings->login_source_ips);
+ if (login_source_ips_count > 0) {
+ /* randomize the initial index in case service_count=1
+ (although in that case it's unlikely this setting is
+ even used..) */
+ login_source_ips_idx = i_rand_limit(login_source_ips_count);
+ }
+
+ login_load_modules();
+
+ restrict_access_by_env(0, NULL);
+ if (login_debug)
+ restrict_access_allow_coredumps(TRUE);
+ initial_service_count = master_service_get_service_count(master_service);
+
+ if (restrict_access_get_current_chroot() == NULL) {
+ if (chdir("login") < 0)
+ i_fatal("chdir(login) failed: %m");
+ }
+
+ if (login_rawlog_dir != NULL &&
+ access(login_rawlog_dir, W_OK | X_OK) < 0) {
+ i_error("access(%s, wx) failed: %m - disabling rawlog",
+ login_rawlog_dir);
+ login_rawlog_dir = NULL;
+ }
+}
+
+static void main_init(const char *login_socket)
+{
+ /* make sure we can't fork() */
+ restrict_process_count(1);
+
+ event_auth = event_create(NULL);
+ event_set_forced_debug(event_auth, global_login_settings->auth_debug);
+ event_add_category(event_auth, &event_category_auth);
+
+ i_array_init(&global_alt_usernames, 4);
+ master_service_set_avail_overflow_callback(master_service,
+ client_destroy_oldest);
+ master_service_set_die_callback(master_service, login_die);
+
+ auth_client = auth_client_init(login_socket, (unsigned int)getpid(),
+ FALSE);
+ auth_client_connect(auth_client);
+ auth_client_set_connect_notify(auth_client, auth_connect_notify, NULL);
+ master_auth = master_auth_init(master_service, post_login_socket);
+
+ login_binary->init();
+
+ login_proxy_init(global_login_settings->login_proxy_notify_path);
+}
+
+static void main_deinit(void)
+{
+ client_destroy_fd_proxies();
+ ssl_iostream_context_cache_free();
+ login_proxy_deinit();
+
+ login_binary->deinit();
+ module_dir_unload(&modules);
+ auth_client_deinit(&auth_client);
+ master_auth_deinit(&master_auth);
+
+ char *str;
+ array_foreach_elem(&global_alt_usernames, str)
+ i_free(str);
+ array_free(&global_alt_usernames);
+
+ if (anvil != NULL)
+ anvil_client_deinit(&anvil);
+ timeout_remove(&auth_client_to);
+ client_common_deinit();
+ dsasl_clients_deinit();
+ login_settings_deinit();
+ event_unref(&event_auth);
+}
+
+int login_binary_run(struct login_binary *binary,
+ int argc, char *argv[])
+{
+ enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN |
+ MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE |
+ MASTER_SERVICE_FLAG_HAVE_STARTTLS |
+ MASTER_SERVICE_FLAG_NO_SSL_INIT;
+ pool_t set_pool;
+ const char *login_socket;
+ int c;
+
+ login_binary = binary;
+ login_socket = binary->default_login_socket != NULL ?
+ binary->default_login_socket : LOGIN_DEFAULT_SOCKET;
+ post_login_socket = binary->protocol;
+
+ master_service = master_service_init(login_binary->process_name,
+ service_flags, &argc, &argv,
+ "Dl:R:S");
+ master_service_init_log(master_service);
+
+ while ((c = master_getopt(master_service)) > 0) {
+ switch (c) {
+ case 'D':
+ login_debug = TRUE;
+ break;
+ case 'l':
+ post_login_socket = optarg;
+ break;
+ case 'R':
+ login_rawlog_dir = optarg;
+ break;
+ case 'S':
+ ssl_connections = TRUE;
+ break;
+ default:
+ return FATAL_DEFAULT;
+ }
+ }
+ if (argv[optind] != NULL)
+ login_socket = argv[optind];
+
+ login_binary->preinit();
+
+ set_pool = pool_alloconly_create("global login settings", 4096);
+ global_login_settings =
+ login_settings_read(set_pool, NULL, NULL, NULL,
+ &global_ssl_settings,
+ &global_ssl_server_settings,
+ &global_other_settings);
+
+ main_preinit();
+ main_init(login_socket);
+
+ master_service_init_finish(master_service);
+ master_service_run(master_service, client_connected);
+ main_deinit();
+ array_free(&login_source_ips_array);
+ pool_unref(&set_pool);
+ master_service_deinit(&master_service);
+ return 0;
+}
diff --git a/src/login-common/sasl-server.c b/src/login-common/sasl-server.c
new file mode 100644
index 0000000..3a03a37
--- /dev/null
+++ b/src/login-common/sasl-server.c
@@ -0,0 +1,568 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "str.h"
+#include "base64.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "write-full.h"
+#include "strescape.h"
+#include "str-sanitize.h"
+#include "anvil-client.h"
+#include "auth-client.h"
+#include "iostream-ssl.h"
+#include "master-service.h"
+#include "master-service-ssl-settings.h"
+#include "master-interface.h"
+#include "master-auth.h"
+#include "client-common.h"
+
+#include <unistd.h>
+
+#define ERR_TOO_MANY_USERIP_CONNECTIONS \
+ "Maximum number of connections from user+IP exceeded " \
+ "(mail_max_userip_connections=%u)"
+
+struct anvil_request {
+ struct client *client;
+ unsigned int auth_pid;
+ unsigned char cookie[MASTER_AUTH_COOKIE_SIZE];
+};
+
+static bool
+sasl_server_filter_mech(struct client *client, struct auth_mech_desc *mech)
+{
+ if (client->v.sasl_filter_mech != NULL &&
+ !client->v.sasl_filter_mech(client, mech))
+ return FALSE;
+ return ((mech->flags & MECH_SEC_ANONYMOUS) == 0 ||
+ login_binary->anonymous_login_acceptable);
+}
+
+const struct auth_mech_desc *
+sasl_server_get_advertised_mechs(struct client *client, unsigned int *count_r)
+{
+ const struct auth_mech_desc *mech;
+ struct auth_mech_desc *ret_mech;
+ unsigned int i, j, count;
+
+ mech = auth_client_get_available_mechs(auth_client, &count);
+ if (count == 0 || (!client->secured &&
+ strcmp(client->ssl_set->ssl, "required") == 0)) {
+ *count_r = 0;
+ return NULL;
+ }
+
+ ret_mech = t_new(struct auth_mech_desc, count);
+ for (i = j = 0; i < count; i++) {
+ struct auth_mech_desc fmech = mech[i];
+
+ if (!sasl_server_filter_mech(client, &fmech))
+ continue;
+
+ /* a) transport is secured
+ b) auth mechanism isn't plaintext
+ c) we allow insecure authentication
+ */
+ if ((fmech.flags & MECH_SEC_PRIVATE) == 0 &&
+ (client->secured || !client->set->disable_plaintext_auth ||
+ (fmech.flags & MECH_SEC_PLAINTEXT) == 0))
+ ret_mech[j++] = fmech;
+ }
+ *count_r = j;
+ return ret_mech;
+}
+
+const struct auth_mech_desc *
+sasl_server_find_available_mech(struct client *client, const char *name)
+{
+ const struct auth_mech_desc *mech;
+ struct auth_mech_desc fmech;
+
+ mech = auth_client_find_mech(auth_client, name);
+ if (mech == NULL)
+ return NULL;
+
+ fmech = *mech;
+ if (!sasl_server_filter_mech(client, &fmech))
+ return NULL;
+ if (memcmp(&fmech, mech, sizeof(fmech)) != 0) {
+ struct auth_mech_desc *nmech = t_new(struct auth_mech_desc, 1);
+
+ *nmech = fmech;
+ mech = nmech;
+ }
+ return mech;
+}
+
+static enum auth_request_flags
+client_get_auth_flags(struct client *client)
+{
+ enum auth_request_flags auth_flags = 0;
+
+ if (client->ssl_iostream != NULL &&
+ ssl_iostream_has_valid_client_cert(client->ssl_iostream))
+ auth_flags |= AUTH_REQUEST_FLAG_VALID_CLIENT_CERT;
+ if (client->tls || client->proxied_ssl)
+ auth_flags |= AUTH_REQUEST_FLAG_TRANSPORT_SECURITY_TLS;
+ if (client->secured)
+ auth_flags |= AUTH_REQUEST_FLAG_SECURED;
+ if (login_binary->sasl_support_final_reply)
+ auth_flags |= AUTH_REQUEST_FLAG_SUPPORT_FINAL_RESP;
+ return auth_flags;
+}
+
+static void ATTR_NULL(3, 4)
+call_client_callback(struct client *client, enum sasl_server_reply reply,
+ const char *data, const char *const *args)
+{
+ sasl_server_callback_t *sasl_callback;
+
+ i_assert(reply != SASL_SERVER_REPLY_CONTINUE);
+
+ sasl_callback = client->sasl_callback;
+ client->sasl_callback = NULL;
+
+ sasl_callback(client, reply, data, args);
+ /* NOTE: client may be destroyed now */
+}
+
+static void
+master_auth_callback(const struct master_auth_reply *reply, void *context)
+{
+ struct client *client = context;
+ enum sasl_server_reply sasl_reply = SASL_SERVER_REPLY_MASTER_FAILED;
+ const char *data = NULL;
+
+ client->master_tag = 0;
+ client->authenticating = FALSE;
+ if (reply != NULL) {
+ switch (reply->status) {
+ case MASTER_AUTH_STATUS_OK:
+ sasl_reply = SASL_SERVER_REPLY_SUCCESS;
+ break;
+ case MASTER_AUTH_STATUS_INTERNAL_ERROR:
+ sasl_reply = SASL_SERVER_REPLY_MASTER_FAILED;
+ break;
+ }
+ client->mail_pid = reply->mail_pid;
+ } else {
+ auth_client_send_cancel(auth_client, client->master_auth_id);
+ }
+ call_client_callback(client, sasl_reply, data, NULL);
+}
+
+static int master_send_request(struct anvil_request *anvil_request)
+{
+ struct client *client = anvil_request->client;
+ struct master_auth_request_params params;
+ struct master_auth_request req;
+ const unsigned char *data;
+ size_t size;
+ buffer_t *buf;
+ const char *session_id = client_get_session_id(client);
+ int fd;
+ bool close_fd;
+
+ if (client_get_plaintext_fd(client, &fd, &close_fd) < 0)
+ return -1;
+
+ i_zero(&req);
+ req.auth_pid = anvil_request->auth_pid;
+ req.auth_id = client->master_auth_id;
+ req.local_ip = client->local_ip;
+ req.remote_ip = client->ip;
+ req.local_port = client->local_port;
+ req.remote_port = client->remote_port;
+ req.client_pid = getpid();
+ if (client->ssl_iostream != NULL &&
+ ssl_iostream_get_compression(client->ssl_iostream) != NULL)
+ req.flags |= MAIL_AUTH_REQUEST_FLAG_TLS_COMPRESSION;
+ if (client->secured)
+ req.flags |= MAIL_AUTH_REQUEST_FLAG_CONN_SECURED;
+ if (client->ssl_secured)
+ req.flags |= MAIL_AUTH_REQUEST_FLAG_CONN_SSL_SECURED;
+ if (HAS_ALL_BITS(client->auth_flags, SASL_SERVER_AUTH_FLAG_IMPLICIT))
+ req.flags |= MAIL_AUTH_REQUEST_FLAG_IMPLICIT;
+ memcpy(req.cookie, anvil_request->cookie, sizeof(req.cookie));
+
+ buf = t_buffer_create(256);
+ /* session ID */
+ buffer_append(buf, session_id, strlen(session_id)+1);
+ /* protocol specific data (e.g. IMAP tag) */
+ buffer_append(buf, client->master_data_prefix,
+ client->master_data_prefix_len);
+ /* buffered client input */
+ data = i_stream_get_data(client->input, &size);
+ buffer_append(buf, data, size);
+ req.data_size = buf->used;
+ i_stream_skip(client->input, size);
+
+ client->auth_finished = ioloop_time;
+
+ i_zero(&params);
+ params.client_fd = fd;
+ params.socket_path = client->postlogin_socket_path;
+ params.request = req;
+ params.data = buf->data;
+ master_auth_request_full(master_auth, &params, master_auth_callback,
+ client, &client->master_tag);
+ if (close_fd)
+ i_close_fd(&fd);
+ return 0;
+}
+
+static void ATTR_NULL(1)
+anvil_lookup_callback(const char *reply, void *context)
+{
+ struct anvil_request *req = context;
+ struct client *client = req->client;
+ const struct login_settings *set = client->set;
+ const char *errmsg;
+ unsigned int conn_count;
+ int ret;
+
+ client->anvil_query = NULL;
+ client->anvil_request = NULL;
+
+ conn_count = 0;
+ if (reply != NULL && str_to_uint(reply, &conn_count) < 0)
+ i_fatal("Received invalid reply from anvil: %s", reply);
+
+ /* reply=NULL if we didn't need to do anvil lookup,
+ or if the anvil lookup failed. allow failed anvil lookups in. */
+ if (reply == NULL || conn_count < set->mail_max_userip_connections) {
+ ret = master_send_request(req);
+ errmsg = NULL; /* client will see internal error */
+ } else {
+ ret = -1;
+ errmsg = t_strdup_printf(ERR_TOO_MANY_USERIP_CONNECTIONS,
+ set->mail_max_userip_connections);
+ }
+ if (ret < 0) {
+ client->authenticating = FALSE;
+ auth_client_send_cancel(auth_client, client->master_auth_id);
+ call_client_callback(client, SASL_SERVER_REPLY_MASTER_FAILED,
+ errmsg, NULL);
+ }
+ i_free(req);
+}
+
+static void
+anvil_check_too_many_connections(struct client *client,
+ struct auth_client_request *request)
+{
+ struct anvil_request *req;
+ const char *query, *cookie;
+ buffer_t buf;
+
+ req = i_new(struct anvil_request, 1);
+ req->client = client;
+ req->auth_pid = auth_client_request_get_server_pid(request);
+
+ buffer_create_from_data(&buf, req->cookie, sizeof(req->cookie));
+ cookie = auth_client_request_get_cookie(request);
+ if (strlen(cookie) == MASTER_AUTH_COOKIE_SIZE*2)
+ (void)hex_to_binary(cookie, &buf);
+
+ if (client->virtual_user == NULL ||
+ client->set->mail_max_userip_connections == 0) {
+ anvil_lookup_callback(NULL, req);
+ return;
+ }
+
+ query = t_strconcat("LOOKUP\t", login_binary->protocol, "/",
+ net_ip2addr(&client->ip), "/",
+ str_tabescape(client->virtual_user), NULL);
+ client->anvil_request = req;
+ client->anvil_query =
+ anvil_client_query(anvil, query, anvil_lookup_callback, req);
+}
+
+static bool
+sasl_server_check_login(struct client *client)
+{
+ if (client->v.sasl_check_login != NULL &&
+ !client->v.sasl_check_login(client))
+ return FALSE;
+ if (client->auth_anonymous &&
+ !login_binary->anonymous_login_acceptable) {
+ sasl_server_auth_failed(client,
+ "Anonymous login denied",
+ AUTH_CLIENT_FAIL_CODE_ANONYMOUS_DENIED);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool args_parse_user(struct client *client, const char *arg)
+{
+ if (str_begins(arg, "user=")) {
+ i_free(client->virtual_user);
+ i_free_and_null(client->virtual_user_orig);
+ i_free_and_null(client->virtual_auth_user);
+ client->virtual_user = i_strdup(arg + 5);
+ event_add_str(client->event, "user", client->virtual_user);
+ } else if (str_begins(arg, "original_user=")) {
+ i_free(client->virtual_user_orig);
+ client->virtual_user_orig = i_strdup(arg + 14);
+ } else if (str_begins(arg, "auth_user=")) {
+ i_free(client->virtual_auth_user);
+ client->virtual_auth_user = i_strdup(arg + 10);
+ } else {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+authenticate_callback(struct auth_client_request *request,
+ enum auth_request_status status, const char *data_base64,
+ const char *const *args, void *context)
+{
+ struct client *client = context;
+ unsigned int i;
+ bool nologin;
+
+ if (!client->authenticating) {
+ /* client aborted */
+ i_assert(status < 0);
+ return;
+ }
+ client->auth_waiting = FALSE;
+
+ i_assert(client->auth_request == request);
+ switch (status) {
+ case AUTH_REQUEST_STATUS_CONTINUE:
+ /* continue */
+ client->sasl_callback(client, SASL_SERVER_REPLY_CONTINUE,
+ data_base64, NULL);
+ break;
+ case AUTH_REQUEST_STATUS_OK:
+ client->master_auth_id = auth_client_request_get_id(request);
+ client->auth_request = NULL;
+ client->auth_successes++;
+ client->auth_passdb_args = p_strarray_dup(client->pool, args);
+ client->postlogin_socket_path = NULL;
+
+ nologin = FALSE;
+ for (i = 0; args[i] != NULL; i++) {
+ if (args_parse_user(client, args[i]))
+ ;
+ else if (str_begins(args[i], "postlogin_socket=")) {
+ client->postlogin_socket_path =
+ p_strdup(client->pool, args[i] + 17);
+ } else if (strcmp(args[i], "nologin") == 0 ||
+ strcmp(args[i], "proxy") == 0) {
+ /* user can't login */
+ nologin = TRUE;
+ } else if (strcmp(args[i], "anonymous") == 0 ) {
+ client->auth_anonymous = TRUE;
+ } else if (str_begins(args[i], "resp=") &&
+ login_binary->sasl_support_final_reply) {
+ client->sasl_final_resp =
+ p_strdup(client->pool, args[i] + 5);
+ }
+ }
+
+ if (nologin) {
+ client->authenticating = FALSE;
+ call_client_callback(client, SASL_SERVER_REPLY_SUCCESS,
+ NULL, args);
+ } else if (!sasl_server_check_login(client)) {
+ i_assert(!client->authenticating);
+ } else {
+ anvil_check_too_many_connections(client, request);
+ }
+ break;
+ case AUTH_REQUEST_STATUS_INTERNAL_FAIL:
+ client->auth_process_comm_fail = TRUE;
+ /* fall through */
+ case AUTH_REQUEST_STATUS_FAIL:
+ case AUTH_REQUEST_STATUS_ABORT:
+ client->auth_request = NULL;
+
+ if (args != NULL) {
+ /* parse our username if it's there */
+ for (i = 0; args[i] != NULL; i++)
+ (void)args_parse_user(client, args[i]);
+ }
+
+ client->authenticating = FALSE;
+ call_client_callback(client, SASL_SERVER_REPLY_AUTH_FAILED,
+ NULL, args);
+ break;
+ }
+}
+
+static bool get_cert_username(struct client *client, const char **username_r,
+ const char **error_r)
+{
+ /* this was proxied connection, so we use the name here */
+ if (client->client_cert_common_name != NULL) {
+ *username_r = client->client_cert_common_name;
+ return TRUE;
+ }
+
+ /* no SSL */
+ if (client->ssl_iostream == NULL) {
+ *username_r = NULL;
+ return TRUE;
+ }
+
+ /* no client certificate */
+ if (!ssl_iostream_has_valid_client_cert(client->ssl_iostream)) {
+ *username_r = NULL;
+ return TRUE;
+ }
+
+ /* get peer name */
+ const char *username = ssl_iostream_get_peer_name(client->ssl_iostream);
+
+ /* if we wanted peer name, but it was not there, fail */
+ if (client->set->auth_ssl_username_from_cert &&
+ (username == NULL || *username == '\0')) {
+ if (client->set->auth_ssl_require_client_cert) {
+ *error_r = "Missing username in certificate";
+ return FALSE;
+ }
+ }
+
+ *username_r = username;
+ return TRUE;
+}
+
+void sasl_server_auth_begin(struct client *client,
+ const char *service, const char *mech_name,
+ enum sasl_server_auth_flags flags,
+ const char *initial_resp_base64,
+ sasl_server_callback_t *callback)
+{
+ struct auth_request_info info;
+ const struct auth_mech_desc *mech;
+ bool private = HAS_ALL_BITS(flags, SASL_SERVER_AUTH_FLAG_PRIVATE);
+ const char *error;
+
+ i_assert(auth_client_is_connected(auth_client));
+
+ client->auth_attempts++;
+ client->authenticating = TRUE;
+ client->master_auth_id = 0;
+ if (client->auth_first_started == 0)
+ client->auth_first_started = ioloop_time;
+ i_free(client->auth_mech_name);
+ client->auth_mech_name = str_ucase(i_strdup(mech_name));
+ client->auth_anonymous = FALSE;
+ client->auth_flags = flags;
+ client->sasl_callback = callback;
+
+ mech = sasl_server_find_available_mech(client, mech_name);
+ if (mech == NULL ||
+ ((mech->flags & MECH_SEC_PRIVATE) != 0 && !private)) {
+ sasl_server_auth_failed(client,
+ "Unsupported authentication mechanism.",
+ AUTH_CLIENT_FAIL_CODE_MECH_INVALID);
+ return;
+ }
+
+ i_assert(!private || (mech->flags & MECH_SEC_PRIVATE) != 0);
+
+ if (!client->secured && client->set->disable_plaintext_auth &&
+ (mech->flags & MECH_SEC_PLAINTEXT) != 0) {
+ sasl_server_auth_failed(client,
+ "Plaintext authentication disabled.",
+ AUTH_CLIENT_FAIL_CODE_MECH_SSL_REQUIRED);
+ return;
+ }
+
+ i_zero(&info);
+ info.mech = mech->name;
+ info.service = service;
+ info.session_id = client_get_session_id(client);
+
+ if (!get_cert_username(client, &info.cert_username, &error)) {
+ e_error(client->event,
+ "Cannot get username from certificate: %s", error);
+ sasl_server_auth_failed(client,
+ "Unable to validate certificate",
+ AUTH_CLIENT_FAIL_CODE_AUTHZFAILED);
+ return;
+ }
+
+ if (client->ssl_iostream != NULL) {
+ info.cert_username = ssl_iostream_get_peer_name(client->ssl_iostream);
+ info.ssl_cipher = ssl_iostream_get_cipher(client->ssl_iostream,
+ &info.ssl_cipher_bits);
+ info.ssl_pfs = ssl_iostream_get_pfs(client->ssl_iostream);
+ info.ssl_protocol =
+ ssl_iostream_get_protocol_name(client->ssl_iostream);
+ }
+ info.flags = client_get_auth_flags(client);
+ info.local_ip = client->local_ip;
+ info.remote_ip = client->ip;
+ info.local_port = client->local_port;
+ info.local_name = client->local_name;
+ info.remote_port = client->remote_port;
+ info.real_local_ip = client->real_local_ip;
+ info.real_remote_ip = client->real_remote_ip;
+ info.real_local_port = client->real_local_port;
+ info.real_remote_port = client->real_remote_port;
+ if (client->client_id != NULL)
+ info.client_id = str_c(client->client_id);
+ if (client->forward_fields != NULL)
+ info.forward_fields = str_c(client->forward_fields);
+ info.initial_resp_base64 = initial_resp_base64;
+
+ client->auth_request =
+ auth_client_request_new(auth_client, &info,
+ authenticate_callback, client);
+}
+
+static void ATTR_NULL(2, 3)
+sasl_server_auth_cancel(struct client *client, const char *reason,
+ const char *code, enum sasl_server_reply reply)
+{
+ i_assert(client->authenticating);
+
+ if (client->set->auth_verbose && reason != NULL) {
+ const char *auth_name =
+ str_sanitize(client->auth_mech_name, MAX_MECH_NAME);
+ e_info(client->event, "Authenticate %s failed: %s",
+ auth_name, reason);
+ }
+
+ client->authenticating = FALSE;
+ if (client->auth_request != NULL)
+ auth_client_request_abort(&client->auth_request, reason);
+ if (client->master_auth_id != 0)
+ auth_client_send_cancel(auth_client, client->master_auth_id);
+
+ if (code != NULL) {
+ const char *args[2];
+
+ args[0] = t_strconcat("code=", code, NULL);
+ args[1] = NULL;
+ call_client_callback(client, reply, reason, args);
+ return;
+ }
+
+ call_client_callback(client, reply, reason, NULL);
+}
+
+void sasl_server_auth_failed(struct client *client, const char *reason,
+ const char *code)
+{
+ sasl_server_auth_cancel(client, reason, code, SASL_SERVER_REPLY_AUTH_FAILED);
+}
+
+void sasl_server_auth_abort(struct client *client)
+{
+ client->auth_try_aborted = TRUE;
+ if (client->anvil_query != NULL) {
+ anvil_client_query_abort(anvil, &client->anvil_query);
+ i_free(client->anvil_request);
+ }
+ sasl_server_auth_cancel(client, NULL, NULL, SASL_SERVER_REPLY_AUTH_ABORTED);
+}
diff --git a/src/login-common/sasl-server.h b/src/login-common/sasl-server.h
new file mode 100644
index 0000000..62210b7
--- /dev/null
+++ b/src/login-common/sasl-server.h
@@ -0,0 +1,40 @@
+#ifndef SASL_SERVER_H
+#define SASL_SERVER_H
+
+struct client;
+
+enum sasl_server_reply {
+ SASL_SERVER_REPLY_SUCCESS,
+ SASL_SERVER_REPLY_AUTH_FAILED,
+ SASL_SERVER_REPLY_AUTH_ABORTED,
+ SASL_SERVER_REPLY_MASTER_FAILED,
+ SASL_SERVER_REPLY_CONTINUE
+};
+
+enum sasl_server_auth_flags {
+ /* Allow the use of private mechanism */
+ SASL_SERVER_AUTH_FLAG_PRIVATE = BIT(0),
+ /* Signal to the post-login service that this is an implicit login,
+ meaning that no command success reply is expected. */
+ SASL_SERVER_AUTH_FLAG_IMPLICIT = BIT(1),
+};
+
+typedef void sasl_server_callback_t(struct client *client,
+ enum sasl_server_reply reply,
+ const char *data, const char *const *args);
+
+const struct auth_mech_desc *
+sasl_server_get_advertised_mechs(struct client *client, unsigned int *count_r);
+const struct auth_mech_desc *
+sasl_server_find_available_mech(struct client *client, const char *name);
+
+void sasl_server_auth_begin(struct client *client,
+ const char *service, const char *mech_name,
+ enum sasl_server_auth_flags flags,
+ const char *initial_resp_base64,
+ sasl_server_callback_t *callback);
+void sasl_server_auth_failed(struct client *client, const char *reason,
+ const char *code) ATTR_NULL(3);
+void sasl_server_auth_abort(struct client *client);
+
+#endif