diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
commit | 0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch) | |
tree | 3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/login-common | |
parent | Initial commit. (diff) | |
download | dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip |
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/login-common')
-rw-r--r-- | src/login-common/Makefile.am | 40 | ||||
-rw-r--r-- | src/login-common/Makefile.in | 908 | ||||
-rw-r--r-- | src/login-common/access-lookup.c | 118 | ||||
-rw-r--r-- | src/login-common/access-lookup.h | 11 | ||||
-rw-r--r-- | src/login-common/client-common-auth.c | 953 | ||||
-rw-r--r-- | src/login-common/client-common.c | 1212 | ||||
-rw-r--r-- | src/login-common/client-common.h | 373 | ||||
-rw-r--r-- | src/login-common/login-common.h | 79 | ||||
-rw-r--r-- | src/login-common/login-proxy-state.c | 165 | ||||
-rw-r--r-- | src/login-common/login-proxy-state.h | 40 | ||||
-rw-r--r-- | src/login-common/login-proxy.c | 1173 | ||||
-rw-r--r-- | src/login-common/login-proxy.h | 120 | ||||
-rw-r--r-- | src/login-common/login-settings.c | 227 | ||||
-rw-r--r-- | src/login-common/login-settings.h | 50 | ||||
-rw-r--r-- | src/login-common/main.c | 571 | ||||
-rw-r--r-- | src/login-common/sasl-server.c | 604 | ||||
-rw-r--r-- | src/login-common/sasl-server.h | 41 |
17 files changed, 6685 insertions, 0 deletions
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..4282d21 --- /dev/null +++ b/src/login-common/Makefile.in @@ -0,0 +1,908 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +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..ec5194b --- /dev/null +++ b/src/login-common/client-common-auth.c @@ -0,0 +1,953 @@ +/* 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 if (str_begins(key, "event_")) { + /* add name to event */ + event_add_str(client->event, key + 6, value); + } else if (strcmp(key, "resp") == 0) { + /* ignore final response */ + continue; + } 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; + + /* This has to happen before * handling, otherwise + client can abort failed request. */ + if (client->final_response) { + sasl_server_auth_delayed_final(client); + 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..fc44d2b --- /dev/null +++ b/src/login-common/client-common.c @@ -0,0 +1,1212 @@ +/* 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 || + client->final_response) { + 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 if (client->ssl_iostream != NULL) { + 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); + } else { + tab[11].value = "TLS"; + tab[12].value = ""; + } + 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..a21dea1 --- /dev/null +++ b/src/login-common/client-common.h @@ -0,0 +1,373 @@ +#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 *sasl_final_delayed_resp; + enum sasl_server_reply delayed_final_reply; + const char *const *final_args; + 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_client_continue_pending: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; + bool final_response: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..2552f1c --- /dev/null +++ b/src/login-common/sasl-server.c @@ -0,0 +1,604 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "login-common.h" +#include "array.h" +#include "md5.h" +#include "sasl-server.h" +#include "str.h" +#include "base64.h" +#include "buffer.h" +#include "hex-binary.h" +#include "ioloop.h" +#include "istream.h" +#include "strfuncs.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(¶ms); + params.client_fd = fd; + params.socket_path = client->postlogin_socket_path; + params.request = req; + params.data = buf->data; + master_auth_request_full(master_auth, ¶ms, 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], "event_")) { + const char *key = args[i] + 6; + const char *value = strchr(key, '='); + if (value != NULL) { + event_add_str(client->event, + t_strdup_until(key, value), + value+1); + } + } else if (str_begins(args[i], "resp=")) { + client->sasl_final_delayed_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++) { + if (args_parse_user(client, args[i])) + continue; + if (str_begins(args[i], "resp=")) { + client->sasl_final_delayed_resp = + p_strdup(client->preproxy_pool, + t_strdup(args[i]+5)); + } + } + } + + if (client->sasl_final_delayed_resp != NULL && + !login_binary->sasl_support_final_reply) { + client->final_response = TRUE; + client->final_args = p_strarray_dup(client->preproxy_pool, args); + client->delayed_final_reply = SASL_SERVER_REPLY_AUTH_FAILED; + client->sasl_callback(client, SASL_SERVER_REPLY_CONTINUE, + client->sasl_final_delayed_resp, NULL); + } else { + 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; + client->final_response = 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); +} + +void sasl_server_auth_delayed_final(struct client *client) +{ + client->final_response = FALSE; + client->authenticating = FALSE; + client->auth_client_continue_pending = FALSE; + call_client_callback(client, client->delayed_final_reply, NULL, client->final_args); +} diff --git a/src/login-common/sasl-server.h b/src/login-common/sasl-server.h new file mode 100644 index 0000000..66d0cb4 --- /dev/null +++ b/src/login-common/sasl-server.h @@ -0,0 +1,41 @@ +#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_delayed_final(struct client *client); +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 |