summaryrefslogtreecommitdiffstats
path: root/src/auth
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/auth
parentInitial commit. (diff)
downloaddovecot-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/auth')
-rw-r--r--src/auth/Makefile.am301
-rw-r--r--src/auth/Makefile.in1928
-rw-r--r--src/auth/auth-cache.c481
-rw-r--r--src/auth/auth-cache.h53
-rw-r--r--src/auth/auth-client-connection.c456
-rw-r--r--src/auth/auth-client-connection.h37
-rw-r--r--src/auth/auth-common.h17
-rw-r--r--src/auth/auth-fields.c226
-rw-r--r--src/auth/auth-fields.h48
-rw-r--r--src/auth/auth-master-connection.c855
-rw-r--r--src/auth/auth-master-connection.h44
-rw-r--r--src/auth/auth-penalty.c176
-rw-r--r--src/auth/auth-penalty.h28
-rw-r--r--src/auth/auth-policy.c620
-rw-r--r--src/auth/auth-policy.h11
-rw-r--r--src/auth/auth-request-fields.c525
-rw-r--r--src/auth/auth-request-handler-private.h27
-rw-r--r--src/auth/auth-request-handler.c991
-rw-r--r--src/auth/auth-request-handler.h69
-rw-r--r--src/auth/auth-request-stats.c77
-rw-r--r--src/auth/auth-request-stats.h15
-rw-r--r--src/auth/auth-request-var-expand.c303
-rw-r--r--src/auth/auth-request-var-expand.h44
-rw-r--r--src/auth/auth-request.c2595
-rw-r--r--src/auth/auth-request.h400
-rw-r--r--src/auth/auth-settings.c553
-rw-r--r--src/auth/auth-settings.h110
-rw-r--r--src/auth/auth-stats.c116
-rw-r--r--src/auth/auth-stats.h16
-rw-r--r--src/auth/auth-token.c177
-rw-r--r--src/auth/auth-token.h11
-rw-r--r--src/auth/auth-worker-client.c972
-rw-r--r--src/auth/auth-worker-client.h27
-rw-r--r--src/auth/auth-worker-server.c519
-rw-r--r--src/auth/auth-worker-server.h18
-rw-r--r--src/auth/auth.c450
-rw-r--r--src/auth/auth.h91
-rw-r--r--src/auth/checkpassword-reply.c110
-rw-r--r--src/auth/crypt-blowfish.c900
-rw-r--r--src/auth/crypt-blowfish.h29
-rw-r--r--src/auth/db-checkpassword.c563
-rw-r--r--src/auth/db-checkpassword.h29
-rw-r--r--src/auth/db-dict-cache-key.c64
-rw-r--r--src/auth/db-dict.c654
-rw-r--r--src/auth/db-dict.h82
-rw-r--r--src/auth/db-ldap.c2035
-rw-r--r--src/auth/db-ldap.h221
-rw-r--r--src/auth/db-lua.c814
-rw-r--r--src/auth/db-lua.h33
-rw-r--r--src/auth/db-oauth2.c915
-rw-r--r--src/auth/db-oauth2.h48
-rw-r--r--src/auth/db-passwd-file.c493
-rw-r--r--src/auth/db-passwd-file.h58
-rw-r--r--src/auth/db-sql.c178
-rw-r--r--src/auth/db-sql.h42
-rw-r--r--src/auth/main.c398
-rw-r--r--src/auth/mech-anonymous.c48
-rw-r--r--src/auth/mech-apop.c173
-rw-r--r--src/auth/mech-cram-md5.c193
-rw-r--r--src/auth/mech-digest-md5-private.h38
-rw-r--r--src/auth/mech-digest-md5.c592
-rw-r--r--src/auth/mech-dovecot-token.c92
-rw-r--r--src/auth/mech-external.c64
-rw-r--r--src/auth/mech-gssapi.c797
-rw-r--r--src/auth/mech-login.c76
-rw-r--r--src/auth/mech-oauth2.c339
-rw-r--r--src/auth/mech-otp-common.c71
-rw-r--r--src/auth/mech-otp-common.h23
-rw-r--r--src/auth/mech-otp.c240
-rw-r--r--src/auth/mech-plain-common.c22
-rw-r--r--src/auth/mech-plain-common.h7
-rw-r--r--src/auth/mech-plain.c88
-rw-r--r--src/auth/mech-scram.c550
-rw-r--r--src/auth/mech-scram.h10
-rw-r--r--src/auth/mech-winbind.c364
-rw-r--r--src/auth/mech.c245
-rw-r--r--src/auth/mech.h77
-rw-r--r--src/auth/mycrypt.c26
-rw-r--r--src/auth/mycrypt.h8
-rw-r--r--src/auth/passdb-blocking.c173
-rw-r--r--src/auth/passdb-blocking.h11
-rw-r--r--src/auth/passdb-bsdauth.c97
-rw-r--r--src/auth/passdb-cache.c214
-rw-r--r--src/auth/passdb-cache.h21
-rw-r--r--src/auth/passdb-checkpassword.c153
-rw-r--r--src/auth/passdb-dict.c184
-rw-r--r--src/auth/passdb-imap.c241
-rw-r--r--src/auth/passdb-ldap.c517
-rw-r--r--src/auth/passdb-lua.c193
-rw-r--r--src/auth/passdb-oauth2.c90
-rw-r--r--src/auth/passdb-pam.c401
-rw-r--r--src/auth/passdb-passwd-file.c210
-rw-r--r--src/auth/passdb-passwd.c127
-rw-r--r--src/auth/passdb-shadow.c125
-rw-r--r--src/auth/passdb-sql.c310
-rw-r--r--src/auth/passdb-static.c113
-rw-r--r--src/auth/passdb-template.c102
-rw-r--r--src/auth/passdb-template.h16
-rw-r--r--src/auth/passdb.c351
-rw-r--r--src/auth/passdb.h123
-rw-r--r--src/auth/password-scheme-crypt.c196
-rw-r--r--src/auth/password-scheme-md5crypt.c147
-rw-r--r--src/auth/password-scheme-otp.c40
-rw-r--r--src/auth/password-scheme-pbkdf2.c82
-rw-r--r--src/auth/password-scheme-scram.c224
-rw-r--r--src/auth/password-scheme-sodium.c92
-rw-r--r--src/auth/password-scheme.c822
-rw-r--r--src/auth/password-scheme.h147
-rw-r--r--src/auth/test-auth-cache.c82
-rw-r--r--src/auth/test-auth-request-fields.c147
-rw-r--r--src/auth/test-auth-request-var-expand.c259
-rw-r--r--src/auth/test-auth.h25
-rw-r--r--src/auth/test-db-dict.c43
-rw-r--r--src/auth/test-libpassword.c137
-rw-r--r--src/auth/test-lua.c182
-rw-r--r--src/auth/test-main.c39
-rw-r--r--src/auth/test-mech.c412
-rw-r--r--src/auth/test-mock.c109
-rw-r--r--src/auth/test-username-filter.c50
-rw-r--r--src/auth/userdb-blocking.c144
-rw-r--r--src/auth/userdb-blocking.h12
-rw-r--r--src/auth/userdb-checkpassword.c92
-rw-r--r--src/auth/userdb-dict.c205
-rw-r--r--src/auth/userdb-ldap.c343
-rw-r--r--src/auth/userdb-lua.c139
-rw-r--r--src/auth/userdb-passwd-file.c247
-rw-r--r--src/auth/userdb-passwd.c253
-rw-r--r--src/auth/userdb-prefetch.c59
-rw-r--r--src/auth/userdb-sql.c319
-rw-r--r--src/auth/userdb-static.c144
-rw-r--r--src/auth/userdb-template.c124
-rw-r--r--src/auth/userdb-template.h15
-rw-r--r--src/auth/userdb.c256
-rw-r--r--src/auth/userdb.h92
134 files changed, 34647 insertions, 0 deletions
diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am
new file mode 100644
index 0000000..9e6200b
--- /dev/null
+++ b/src/auth/Makefile.am
@@ -0,0 +1,301 @@
+noinst_LTLIBRARIES = libpassword.la libauth.la
+auth_moduledir = $(moduledir)/auth
+
+# automake seems to force making this unconditional..
+NOPLUGIN_LDFLAGS =
+
+if GSSAPI_PLUGIN
+GSSAPI_LIB = libmech_gssapi.la
+endif
+
+if LDAP_PLUGIN
+LDAP_LIB = libauthdb_ldap.la
+endif
+
+LUA_LIB =
+AUTH_LUA_LIBS =
+AUTH_LUA_LDADD =
+
+if HAVE_LUA
+if AUTH_LUA_PLUGIN
+LUA_LIB += libauthdb_lua.la
+else
+AUTH_LUA_LIBS += $(LIBDOVECOT_LUA)
+AUTH_LUA_LDADD += $(LUA_LIBS)
+endif
+endif
+
+auth_module_LTLIBRARIES = \
+ $(GSSAPI_LIB) \
+ $(LDAP_LIB) \
+ $(LUA_LIB) \
+ libauthdb_imap.la
+
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = auth checkpassword-reply
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-auth \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-dns \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-sql \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-old-stats \
+ -I$(top_srcdir)/src/lib-otp \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-oauth2 \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-lua \
+ -I$(top_srcdir)/src/lib-dcrypt \
+ -DAUTH_MODULE_DIR=\""$(auth_moduledir)"\" \
+ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \
+ -DPKG_RUNDIR=\""$(rundir)"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)/dovecot"\" \
+ $(LUA_CFLAGS) \
+ $(AUTH_CFLAGS)
+
+auth_LDFLAGS = -export-dynamic
+
+libpassword_la_SOURCES = \
+ crypt-blowfish.c \
+ mycrypt.c \
+ password-scheme.c \
+ password-scheme-crypt.c \
+ password-scheme-md5crypt.c \
+ password-scheme-scram.c \
+ password-scheme-otp.c \
+ password-scheme-pbkdf2.c \
+ password-scheme-sodium.c
+libpassword_la_CFLAGS = $(AM_CPPFLAGS) $(LIBSODIUM_CFLAGS)
+
+auth_libs = \
+ libauth.la \
+ libstats_auth.la \
+ libpassword.la \
+ ../lib-otp/libotp.la \
+ $(AUTH_LUA_LIBS) \
+ $(LIBDOVECOT_SQL)
+
+auth_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+auth_LDADD = $(auth_libs) $(LIBDOVECOT) $(AUTH_LIBS) $(BINARY_LDFLAGS) $(AUTH_LUA_LDADD)
+auth_DEPENDENCIES = $(auth_libs) $(LIBDOVECOT_DEPS)
+auth_SOURCES = main.c
+
+ldap_sources = db-ldap.c passdb-ldap.c userdb-ldap.c
+lua_sources = db-lua.c passdb-lua.c userdb-lua.c
+
+libauth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libauth_la_SOURCES = \
+ auth.c \
+ auth-cache.c \
+ auth-client-connection.c \
+ auth-master-connection.c \
+ auth-policy.c \
+ mech-otp-common.c \
+ mech-plain-common.c \
+ auth-penalty.c \
+ auth-request.c \
+ auth-request-fields.c \
+ auth-request-handler.c \
+ auth-request-stats.c \
+ auth-request-var-expand.c \
+ auth-settings.c \
+ auth-fields.c \
+ auth-token.c \
+ auth-worker-client.c \
+ auth-worker-server.c \
+ db-checkpassword.c \
+ db-dict.c \
+ db-dict-cache-key.c \
+ db-oauth2.c \
+ db-sql.c \
+ db-passwd-file.c \
+ mech.c \
+ mech-anonymous.c \
+ mech-plain.c \
+ mech-login.c \
+ mech-cram-md5.c \
+ mech-digest-md5.c \
+ mech-external.c \
+ mech-gssapi.c \
+ mech-otp.c \
+ mech-scram.c \
+ mech-apop.c \
+ mech-winbind.c \
+ mech-dovecot-token.c \
+ mech-oauth2.c \
+ passdb.c \
+ passdb-blocking.c \
+ passdb-bsdauth.c \
+ passdb-cache.c \
+ passdb-checkpassword.c \
+ passdb-dict.c \
+ passdb-oauth2.c \
+ passdb-passwd.c \
+ passdb-passwd-file.c \
+ passdb-pam.c \
+ passdb-shadow.c \
+ passdb-sql.c \
+ passdb-static.c \
+ passdb-template.c \
+ userdb.c \
+ userdb-blocking.c \
+ userdb-checkpassword.c \
+ userdb-dict.c \
+ userdb-passwd.c \
+ userdb-passwd-file.c \
+ userdb-prefetch.c \
+ userdb-static.c \
+ userdb-sql.c \
+ userdb-template.c \
+ $(ldap_sources) \
+ $(lua_sources)
+
+headers = \
+ auth.h \
+ auth-cache.h \
+ auth-client-connection.h \
+ auth-common.h \
+ auth-master-connection.h \
+ mech-otp-common.h \
+ mech-plain-common.h \
+ mech-digest-md5-private.h \
+ mech-scram.h \
+ auth-penalty.h \
+ auth-policy.h \
+ auth-request.h \
+ auth-request-handler.h \
+ auth-request-handler-private.h \
+ auth-request-stats.h \
+ auth-request-var-expand.h \
+ auth-settings.h \
+ auth-stats.h \
+ auth-fields.h \
+ auth-token.h \
+ auth-worker-client.h \
+ auth-worker-server.h \
+ db-dict.h \
+ db-ldap.h \
+ db-sql.h \
+ db-passwd-file.h \
+ db-checkpassword.h \
+ db-oauth2.h \
+ mech.h \
+ mycrypt.h \
+ passdb.h \
+ passdb-blocking.h \
+ passdb-cache.h \
+ passdb-template.h \
+ password-scheme.h \
+ userdb.h \
+ userdb-blocking.h \
+ userdb-template.h
+
+if GSSAPI_PLUGIN
+libmech_gssapi_la_LDFLAGS = -module -avoid-version
+libmech_gssapi_la_LIBADD = $(KRB5_LIBS)
+libmech_gssapi_la_CPPFLAGS = $(AM_CPPFLAGS) $(KRB5_CFLAGS) -DPLUGIN_BUILD
+libmech_gssapi_la_SOURCES = mech-gssapi.c
+endif
+
+if LDAP_PLUGIN
+libauthdb_ldap_la_LDFLAGS = -module -avoid-version
+libauthdb_ldap_la_LIBADD = $(LDAP_LIBS)
+libauthdb_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+libauthdb_ldap_la_SOURCES = $(ldap_sources)
+endif
+
+if AUTH_LUA_PLUGIN
+libauthdb_lua_la_LDFLAGS = -module -avoid-version
+libauthdb_lua_la_LIBADD = $(LIBDOVECOT_LUA)
+libauthdb_lua_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+libauthdb_lua_la_SOURCES = $(lua_sources)
+endif
+
+libauthdb_imap_la_LDFLAGS = -module -avoid-version
+libauthdb_imap_la_LIBADD = \
+ ../lib-imap-client/libimap_client.la \
+ $(LIBDOVECOT)
+libauthdb_imap_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-client
+libauthdb_imap_la_SOURCES = passdb-imap.c
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+checkpassword_reply_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+checkpassword_reply_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS)
+checkpassword_reply_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+
+checkpassword_reply_sources = \
+ checkpassword-reply.c
+
+stats_moduledir = $(moduledir)/old-stats
+stats_module_LTLIBRARIES = libstats_auth.la
+
+libstats_auth_la_LDFLAGS = -module -avoid-version
+libstats_auth_la_LIBADD = $(LIBDOVECOT)
+libstats_auth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libstats_auth_la_SOURCES = auth-stats.c
+
+test_programs = \
+ test-libpassword \
+ test-auth-cache \
+ test-auth \
+ test-mech
+
+noinst_PROGRAMS = $(test_programs)
+
+noinst_HEADERS = test-auth.h crypt-blowfish.h db-lua.h
+
+test_libs = \
+ ../lib-dovecot/libdovecot.la
+
+test_libpassword_SOURCES = test-libpassword.c
+test_libpassword_LDADD = \
+ libpassword.la \
+ ../lib-otp/libotp.la \
+ $(CRYPT_LIBS) \
+ $(LIBDOVECOT_SQL) \
+ $(LIBSODIUM_LIBS) \
+ $(test_libs) \
+ $(BINARY_LDFLAGS)
+
+test_libpassword_DEPENDENCIES = libpassword.la
+test_libpassword_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+
+test_auth_cache_SOURCES = auth-cache.c test-auth-cache.c
+test_auth_cache_LDADD = $(test_libs)
+test_auth_cache_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs)
+# this is needed to force auth-cache.c recompilation
+test_auth_cache_CPPFLAGS = $(AM_CPPFLAGS)
+
+test_auth_SOURCES = \
+ test-auth-request-var-expand.c \
+ test-auth-request-fields.c \
+ test-username-filter.c \
+ test-db-dict.c \
+ test-lua.c \
+ test-mock.c \
+ test-main.c
+
+test_auth_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS)
+test_auth_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs)
+
+test_mech_SOURCES = \
+ test-mock.c \
+ test-mech.c
+
+test_mech_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS)
+test_mech_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/auth/Makefile.in b/src/auth/Makefile.in
new file mode 100644
index 0000000..c2af057
--- /dev/null
+++ b/src/auth/Makefile.in
@@ -0,0 +1,1928 @@
+# 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@
+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@
+@AUTH_LUA_PLUGIN_TRUE@@HAVE_LUA_TRUE@am__append_1 = libauthdb_lua.la
+@AUTH_LUA_PLUGIN_FALSE@@HAVE_LUA_TRUE@am__append_2 = $(LIBDOVECOT_LUA)
+@AUTH_LUA_PLUGIN_FALSE@@HAVE_LUA_TRUE@am__append_3 = $(LUA_LIBS)
+pkglibexec_PROGRAMS = auth$(EXEEXT) checkpassword-reply$(EXEEXT)
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/auth
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-libpassword$(EXEEXT) test-auth-cache$(EXEEXT) \
+ test-auth$(EXEEXT) test-mech$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \
+ "$(DESTDIR)$(auth_moduledir)" "$(DESTDIR)$(stats_moduledir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS)
+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; }; \
+ }
+LTLIBRARIES = $(auth_module_LTLIBRARIES) $(noinst_LTLIBRARIES) \
+ $(stats_module_LTLIBRARIES)
+libauth_la_LIBADD =
+am__objects_1 = db-ldap.lo passdb-ldap.lo userdb-ldap.lo
+am__objects_2 = db-lua.lo passdb-lua.lo userdb-lua.lo
+am_libauth_la_OBJECTS = auth.lo auth-cache.lo \
+ auth-client-connection.lo auth-master-connection.lo \
+ auth-policy.lo mech-otp-common.lo mech-plain-common.lo \
+ auth-penalty.lo auth-request.lo auth-request-fields.lo \
+ auth-request-handler.lo auth-request-stats.lo \
+ auth-request-var-expand.lo auth-settings.lo auth-fields.lo \
+ auth-token.lo auth-worker-client.lo auth-worker-server.lo \
+ db-checkpassword.lo db-dict.lo db-dict-cache-key.lo \
+ db-oauth2.lo db-sql.lo db-passwd-file.lo mech.lo \
+ mech-anonymous.lo mech-plain.lo mech-login.lo mech-cram-md5.lo \
+ mech-digest-md5.lo mech-external.lo mech-gssapi.lo mech-otp.lo \
+ mech-scram.lo mech-apop.lo mech-winbind.lo \
+ mech-dovecot-token.lo mech-oauth2.lo passdb.lo \
+ passdb-blocking.lo passdb-bsdauth.lo passdb-cache.lo \
+ passdb-checkpassword.lo passdb-dict.lo passdb-oauth2.lo \
+ passdb-passwd.lo passdb-passwd-file.lo passdb-pam.lo \
+ passdb-shadow.lo passdb-sql.lo passdb-static.lo \
+ passdb-template.lo userdb.lo userdb-blocking.lo \
+ userdb-checkpassword.lo userdb-dict.lo userdb-passwd.lo \
+ userdb-passwd-file.lo userdb-prefetch.lo userdb-static.lo \
+ userdb-sql.lo userdb-template.lo $(am__objects_1) \
+ $(am__objects_2)
+libauth_la_OBJECTS = $(am_libauth_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am__DEPENDENCIES_1 =
+libauthdb_imap_la_DEPENDENCIES = ../lib-imap-client/libimap_client.la \
+ $(am__DEPENDENCIES_1)
+am_libauthdb_imap_la_OBJECTS = libauthdb_imap_la-passdb-imap.lo
+libauthdb_imap_la_OBJECTS = $(am_libauthdb_imap_la_OBJECTS)
+libauthdb_imap_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libauthdb_imap_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+@LDAP_PLUGIN_TRUE@libauthdb_ldap_la_DEPENDENCIES = \
+@LDAP_PLUGIN_TRUE@ $(am__DEPENDENCIES_1)
+am__libauthdb_ldap_la_SOURCES_DIST = db-ldap.c passdb-ldap.c \
+ userdb-ldap.c
+am__objects_3 = libauthdb_ldap_la-db-ldap.lo \
+ libauthdb_ldap_la-passdb-ldap.lo \
+ libauthdb_ldap_la-userdb-ldap.lo
+@LDAP_PLUGIN_TRUE@am_libauthdb_ldap_la_OBJECTS = $(am__objects_3)
+libauthdb_ldap_la_OBJECTS = $(am_libauthdb_ldap_la_OBJECTS)
+libauthdb_ldap_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libauthdb_ldap_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+@LDAP_PLUGIN_TRUE@am_libauthdb_ldap_la_rpath = -rpath \
+@LDAP_PLUGIN_TRUE@ $(auth_moduledir)
+@AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_DEPENDENCIES = \
+@AUTH_LUA_PLUGIN_TRUE@ $(am__DEPENDENCIES_1)
+am__libauthdb_lua_la_SOURCES_DIST = db-lua.c passdb-lua.c userdb-lua.c
+am__objects_4 = libauthdb_lua_la-db-lua.lo \
+ libauthdb_lua_la-passdb-lua.lo libauthdb_lua_la-userdb-lua.lo
+@AUTH_LUA_PLUGIN_TRUE@am_libauthdb_lua_la_OBJECTS = $(am__objects_4)
+libauthdb_lua_la_OBJECTS = $(am_libauthdb_lua_la_OBJECTS)
+libauthdb_lua_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libauthdb_lua_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+@AUTH_LUA_PLUGIN_TRUE@@HAVE_LUA_TRUE@am_libauthdb_lua_la_rpath = \
+@AUTH_LUA_PLUGIN_TRUE@@HAVE_LUA_TRUE@ -rpath $(auth_moduledir)
+@GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_DEPENDENCIES = \
+@GSSAPI_PLUGIN_TRUE@ $(am__DEPENDENCIES_1)
+am__libmech_gssapi_la_SOURCES_DIST = mech-gssapi.c
+@GSSAPI_PLUGIN_TRUE@am_libmech_gssapi_la_OBJECTS = \
+@GSSAPI_PLUGIN_TRUE@ libmech_gssapi_la-mech-gssapi.lo
+libmech_gssapi_la_OBJECTS = $(am_libmech_gssapi_la_OBJECTS)
+libmech_gssapi_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libmech_gssapi_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+@GSSAPI_PLUGIN_TRUE@am_libmech_gssapi_la_rpath = -rpath \
+@GSSAPI_PLUGIN_TRUE@ $(auth_moduledir)
+libpassword_la_LIBADD =
+am_libpassword_la_OBJECTS = libpassword_la-crypt-blowfish.lo \
+ libpassword_la-mycrypt.lo libpassword_la-password-scheme.lo \
+ libpassword_la-password-scheme-crypt.lo \
+ libpassword_la-password-scheme-md5crypt.lo \
+ libpassword_la-password-scheme-scram.lo \
+ libpassword_la-password-scheme-otp.lo \
+ libpassword_la-password-scheme-pbkdf2.lo \
+ libpassword_la-password-scheme-sodium.lo
+libpassword_la_OBJECTS = $(am_libpassword_la_OBJECTS)
+libpassword_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(libpassword_la_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o \
+ $@
+am_libstats_auth_la_OBJECTS = auth-stats.lo
+libstats_auth_la_OBJECTS = $(am_libstats_auth_la_OBJECTS)
+libstats_auth_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libstats_auth_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+am_auth_OBJECTS = auth-main.$(OBJEXT)
+auth_OBJECTS = $(am_auth_OBJECTS)
+@AUTH_LUA_PLUGIN_FALSE@@HAVE_LUA_TRUE@am__DEPENDENCIES_2 = \
+@AUTH_LUA_PLUGIN_FALSE@@HAVE_LUA_TRUE@ $(am__DEPENDENCIES_1)
+am__DEPENDENCIES_3 = $(am__DEPENDENCIES_2)
+am__DEPENDENCIES_4 = libauth.la libstats_auth.la libpassword.la \
+ ../lib-otp/libotp.la $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1)
+auth_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(auth_LDFLAGS) $(LDFLAGS) -o $@
+checkpassword_reply_SOURCES = checkpassword-reply.c
+checkpassword_reply_OBJECTS = \
+ checkpassword_reply-checkpassword-reply.$(OBJEXT)
+am_test_auth_OBJECTS = test-auth-request-var-expand.$(OBJEXT) \
+ test-auth-request-fields.$(OBJEXT) \
+ test-username-filter.$(OBJEXT) test-db-dict.$(OBJEXT) \
+ test-lua.$(OBJEXT) test-mock.$(OBJEXT) test-main.$(OBJEXT)
+test_auth_OBJECTS = $(am_test_auth_OBJECTS)
+am_test_auth_cache_OBJECTS = test_auth_cache-auth-cache.$(OBJEXT) \
+ test_auth_cache-test-auth-cache.$(OBJEXT)
+test_auth_cache_OBJECTS = $(am_test_auth_cache_OBJECTS)
+am_test_libpassword_OBJECTS = \
+ test_libpassword-test-libpassword.$(OBJEXT)
+test_libpassword_OBJECTS = $(am_test_libpassword_OBJECTS)
+am_test_mech_OBJECTS = test-mock.$(OBJEXT) test-mech.$(OBJEXT)
+test_mech_OBJECTS = $(am_test_mech_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)/auth-cache.Plo \
+ ./$(DEPDIR)/auth-client-connection.Plo \
+ ./$(DEPDIR)/auth-fields.Plo ./$(DEPDIR)/auth-main.Po \
+ ./$(DEPDIR)/auth-master-connection.Plo \
+ ./$(DEPDIR)/auth-penalty.Plo ./$(DEPDIR)/auth-policy.Plo \
+ ./$(DEPDIR)/auth-request-fields.Plo \
+ ./$(DEPDIR)/auth-request-handler.Plo \
+ ./$(DEPDIR)/auth-request-stats.Plo \
+ ./$(DEPDIR)/auth-request-var-expand.Plo \
+ ./$(DEPDIR)/auth-request.Plo ./$(DEPDIR)/auth-settings.Plo \
+ ./$(DEPDIR)/auth-stats.Plo ./$(DEPDIR)/auth-token.Plo \
+ ./$(DEPDIR)/auth-worker-client.Plo \
+ ./$(DEPDIR)/auth-worker-server.Plo ./$(DEPDIR)/auth.Plo \
+ ./$(DEPDIR)/checkpassword_reply-checkpassword-reply.Po \
+ ./$(DEPDIR)/db-checkpassword.Plo \
+ ./$(DEPDIR)/db-dict-cache-key.Plo ./$(DEPDIR)/db-dict.Plo \
+ ./$(DEPDIR)/db-ldap.Plo ./$(DEPDIR)/db-lua.Plo \
+ ./$(DEPDIR)/db-oauth2.Plo ./$(DEPDIR)/db-passwd-file.Plo \
+ ./$(DEPDIR)/db-sql.Plo \
+ ./$(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo \
+ ./$(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo \
+ ./$(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo \
+ ./$(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo \
+ ./$(DEPDIR)/libauthdb_lua_la-db-lua.Plo \
+ ./$(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo \
+ ./$(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo \
+ ./$(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo \
+ ./$(DEPDIR)/libpassword_la-crypt-blowfish.Plo \
+ ./$(DEPDIR)/libpassword_la-mycrypt.Plo \
+ ./$(DEPDIR)/libpassword_la-password-scheme-crypt.Plo \
+ ./$(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo \
+ ./$(DEPDIR)/libpassword_la-password-scheme-otp.Plo \
+ ./$(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo \
+ ./$(DEPDIR)/libpassword_la-password-scheme-scram.Plo \
+ ./$(DEPDIR)/libpassword_la-password-scheme-sodium.Plo \
+ ./$(DEPDIR)/libpassword_la-password-scheme.Plo \
+ ./$(DEPDIR)/mech-anonymous.Plo ./$(DEPDIR)/mech-apop.Plo \
+ ./$(DEPDIR)/mech-cram-md5.Plo ./$(DEPDIR)/mech-digest-md5.Plo \
+ ./$(DEPDIR)/mech-dovecot-token.Plo \
+ ./$(DEPDIR)/mech-external.Plo ./$(DEPDIR)/mech-gssapi.Plo \
+ ./$(DEPDIR)/mech-login.Plo ./$(DEPDIR)/mech-oauth2.Plo \
+ ./$(DEPDIR)/mech-otp-common.Plo ./$(DEPDIR)/mech-otp.Plo \
+ ./$(DEPDIR)/mech-plain-common.Plo ./$(DEPDIR)/mech-plain.Plo \
+ ./$(DEPDIR)/mech-scram.Plo ./$(DEPDIR)/mech-winbind.Plo \
+ ./$(DEPDIR)/mech.Plo ./$(DEPDIR)/passdb-blocking.Plo \
+ ./$(DEPDIR)/passdb-bsdauth.Plo ./$(DEPDIR)/passdb-cache.Plo \
+ ./$(DEPDIR)/passdb-checkpassword.Plo \
+ ./$(DEPDIR)/passdb-dict.Plo ./$(DEPDIR)/passdb-ldap.Plo \
+ ./$(DEPDIR)/passdb-lua.Plo ./$(DEPDIR)/passdb-oauth2.Plo \
+ ./$(DEPDIR)/passdb-pam.Plo ./$(DEPDIR)/passdb-passwd-file.Plo \
+ ./$(DEPDIR)/passdb-passwd.Plo ./$(DEPDIR)/passdb-shadow.Plo \
+ ./$(DEPDIR)/passdb-sql.Plo ./$(DEPDIR)/passdb-static.Plo \
+ ./$(DEPDIR)/passdb-template.Plo ./$(DEPDIR)/passdb.Plo \
+ ./$(DEPDIR)/test-auth-request-fields.Po \
+ ./$(DEPDIR)/test-auth-request-var-expand.Po \
+ ./$(DEPDIR)/test-db-dict.Po ./$(DEPDIR)/test-lua.Po \
+ ./$(DEPDIR)/test-main.Po ./$(DEPDIR)/test-mech.Po \
+ ./$(DEPDIR)/test-mock.Po ./$(DEPDIR)/test-username-filter.Po \
+ ./$(DEPDIR)/test_auth_cache-auth-cache.Po \
+ ./$(DEPDIR)/test_auth_cache-test-auth-cache.Po \
+ ./$(DEPDIR)/test_libpassword-test-libpassword.Po \
+ ./$(DEPDIR)/userdb-blocking.Plo \
+ ./$(DEPDIR)/userdb-checkpassword.Plo \
+ ./$(DEPDIR)/userdb-dict.Plo ./$(DEPDIR)/userdb-ldap.Plo \
+ ./$(DEPDIR)/userdb-lua.Plo ./$(DEPDIR)/userdb-passwd-file.Plo \
+ ./$(DEPDIR)/userdb-passwd.Plo ./$(DEPDIR)/userdb-prefetch.Plo \
+ ./$(DEPDIR)/userdb-sql.Plo ./$(DEPDIR)/userdb-static.Plo \
+ ./$(DEPDIR)/userdb-template.Plo ./$(DEPDIR)/userdb.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 = $(libauth_la_SOURCES) $(libauthdb_imap_la_SOURCES) \
+ $(libauthdb_ldap_la_SOURCES) $(libauthdb_lua_la_SOURCES) \
+ $(libmech_gssapi_la_SOURCES) $(libpassword_la_SOURCES) \
+ $(libstats_auth_la_SOURCES) $(auth_SOURCES) \
+ checkpassword-reply.c $(test_auth_SOURCES) \
+ $(test_auth_cache_SOURCES) $(test_libpassword_SOURCES) \
+ $(test_mech_SOURCES)
+DIST_SOURCES = $(libauth_la_SOURCES) $(libauthdb_imap_la_SOURCES) \
+ $(am__libauthdb_ldap_la_SOURCES_DIST) \
+ $(am__libauthdb_lua_la_SOURCES_DIST) \
+ $(am__libmech_gssapi_la_SOURCES_DIST) \
+ $(libpassword_la_SOURCES) $(libstats_auth_la_SOURCES) \
+ $(auth_SOURCES) checkpassword-reply.c $(test_auth_SOURCES) \
+ $(test_auth_cache_SOURCES) $(test_libpassword_SOURCES) \
+ $(test_mech_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS) $(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)
+pkglibexecdir = $(libexecdir)/dovecot
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+
+# automake seems to force making this unconditional..
+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 = libpassword.la libauth.la
+auth_moduledir = $(moduledir)/auth
+@GSSAPI_PLUGIN_TRUE@GSSAPI_LIB = libmech_gssapi.la
+@LDAP_PLUGIN_TRUE@LDAP_LIB = libauthdb_ldap.la
+LUA_LIB = $(am__append_1)
+AUTH_LUA_LIBS = $(am__append_2)
+AUTH_LUA_LDADD = $(am__append_3)
+auth_module_LTLIBRARIES = \
+ $(GSSAPI_LIB) \
+ $(LDAP_LIB) \
+ $(LUA_LIB) \
+ libauthdb_imap.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-auth \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-dns \
+ -I$(top_srcdir)/src/lib-http \
+ -I$(top_srcdir)/src/lib-sql \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-old-stats \
+ -I$(top_srcdir)/src/lib-otp \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-oauth2 \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-lua \
+ -I$(top_srcdir)/src/lib-dcrypt \
+ -DAUTH_MODULE_DIR=\""$(auth_moduledir)"\" \
+ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \
+ -DPKG_RUNDIR=\""$(rundir)"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)/dovecot"\" \
+ $(LUA_CFLAGS) \
+ $(AUTH_CFLAGS)
+
+auth_LDFLAGS = -export-dynamic
+libpassword_la_SOURCES = \
+ crypt-blowfish.c \
+ mycrypt.c \
+ password-scheme.c \
+ password-scheme-crypt.c \
+ password-scheme-md5crypt.c \
+ password-scheme-scram.c \
+ password-scheme-otp.c \
+ password-scheme-pbkdf2.c \
+ password-scheme-sodium.c
+
+libpassword_la_CFLAGS = $(AM_CPPFLAGS) $(LIBSODIUM_CFLAGS)
+auth_libs = \
+ libauth.la \
+ libstats_auth.la \
+ libpassword.la \
+ ../lib-otp/libotp.la \
+ $(AUTH_LUA_LIBS) \
+ $(LIBDOVECOT_SQL)
+
+auth_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+auth_LDADD = $(auth_libs) $(LIBDOVECOT) $(AUTH_LIBS) $(BINARY_LDFLAGS) $(AUTH_LUA_LDADD)
+auth_DEPENDENCIES = $(auth_libs) $(LIBDOVECOT_DEPS)
+auth_SOURCES = main.c
+ldap_sources = db-ldap.c passdb-ldap.c userdb-ldap.c
+lua_sources = db-lua.c passdb-lua.c userdb-lua.c
+libauth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libauth_la_SOURCES = \
+ auth.c \
+ auth-cache.c \
+ auth-client-connection.c \
+ auth-master-connection.c \
+ auth-policy.c \
+ mech-otp-common.c \
+ mech-plain-common.c \
+ auth-penalty.c \
+ auth-request.c \
+ auth-request-fields.c \
+ auth-request-handler.c \
+ auth-request-stats.c \
+ auth-request-var-expand.c \
+ auth-settings.c \
+ auth-fields.c \
+ auth-token.c \
+ auth-worker-client.c \
+ auth-worker-server.c \
+ db-checkpassword.c \
+ db-dict.c \
+ db-dict-cache-key.c \
+ db-oauth2.c \
+ db-sql.c \
+ db-passwd-file.c \
+ mech.c \
+ mech-anonymous.c \
+ mech-plain.c \
+ mech-login.c \
+ mech-cram-md5.c \
+ mech-digest-md5.c \
+ mech-external.c \
+ mech-gssapi.c \
+ mech-otp.c \
+ mech-scram.c \
+ mech-apop.c \
+ mech-winbind.c \
+ mech-dovecot-token.c \
+ mech-oauth2.c \
+ passdb.c \
+ passdb-blocking.c \
+ passdb-bsdauth.c \
+ passdb-cache.c \
+ passdb-checkpassword.c \
+ passdb-dict.c \
+ passdb-oauth2.c \
+ passdb-passwd.c \
+ passdb-passwd-file.c \
+ passdb-pam.c \
+ passdb-shadow.c \
+ passdb-sql.c \
+ passdb-static.c \
+ passdb-template.c \
+ userdb.c \
+ userdb-blocking.c \
+ userdb-checkpassword.c \
+ userdb-dict.c \
+ userdb-passwd.c \
+ userdb-passwd-file.c \
+ userdb-prefetch.c \
+ userdb-static.c \
+ userdb-sql.c \
+ userdb-template.c \
+ $(ldap_sources) \
+ $(lua_sources)
+
+headers = \
+ auth.h \
+ auth-cache.h \
+ auth-client-connection.h \
+ auth-common.h \
+ auth-master-connection.h \
+ mech-otp-common.h \
+ mech-plain-common.h \
+ mech-digest-md5-private.h \
+ mech-scram.h \
+ auth-penalty.h \
+ auth-policy.h \
+ auth-request.h \
+ auth-request-handler.h \
+ auth-request-handler-private.h \
+ auth-request-stats.h \
+ auth-request-var-expand.h \
+ auth-settings.h \
+ auth-stats.h \
+ auth-fields.h \
+ auth-token.h \
+ auth-worker-client.h \
+ auth-worker-server.h \
+ db-dict.h \
+ db-ldap.h \
+ db-sql.h \
+ db-passwd-file.h \
+ db-checkpassword.h \
+ db-oauth2.h \
+ mech.h \
+ mycrypt.h \
+ passdb.h \
+ passdb-blocking.h \
+ passdb-cache.h \
+ passdb-template.h \
+ password-scheme.h \
+ userdb.h \
+ userdb-blocking.h \
+ userdb-template.h
+
+@GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_LDFLAGS = -module -avoid-version
+@GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_LIBADD = $(KRB5_LIBS)
+@GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_CPPFLAGS = $(AM_CPPFLAGS) $(KRB5_CFLAGS) -DPLUGIN_BUILD
+@GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_SOURCES = mech-gssapi.c
+@LDAP_PLUGIN_TRUE@libauthdb_ldap_la_LDFLAGS = -module -avoid-version
+@LDAP_PLUGIN_TRUE@libauthdb_ldap_la_LIBADD = $(LDAP_LIBS)
+@LDAP_PLUGIN_TRUE@libauthdb_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+@LDAP_PLUGIN_TRUE@libauthdb_ldap_la_SOURCES = $(ldap_sources)
+@AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_LDFLAGS = -module -avoid-version
+@AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_LIBADD = $(LIBDOVECOT_LUA)
+@AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+@AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_SOURCES = $(lua_sources)
+libauthdb_imap_la_LDFLAGS = -module -avoid-version
+libauthdb_imap_la_LIBADD = \
+ ../lib-imap-client/libimap_client.la \
+ $(LIBDOVECOT)
+
+libauthdb_imap_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-client
+
+libauthdb_imap_la_SOURCES = passdb-imap.c
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+checkpassword_reply_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+checkpassword_reply_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS)
+checkpassword_reply_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+checkpassword_reply_sources = \
+ checkpassword-reply.c
+
+stats_moduledir = $(moduledir)/old-stats
+stats_module_LTLIBRARIES = libstats_auth.la
+libstats_auth_la_LDFLAGS = -module -avoid-version
+libstats_auth_la_LIBADD = $(LIBDOVECOT)
+libstats_auth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+libstats_auth_la_SOURCES = auth-stats.c
+test_programs = \
+ test-libpassword \
+ test-auth-cache \
+ test-auth \
+ test-mech
+
+noinst_HEADERS = test-auth.h crypt-blowfish.h db-lua.h
+test_libs = \
+ ../lib-dovecot/libdovecot.la
+
+test_libpassword_SOURCES = test-libpassword.c
+test_libpassword_LDADD = \
+ libpassword.la \
+ ../lib-otp/libotp.la \
+ $(CRYPT_LIBS) \
+ $(LIBDOVECOT_SQL) \
+ $(LIBSODIUM_LIBS) \
+ $(test_libs) \
+ $(BINARY_LDFLAGS)
+
+test_libpassword_DEPENDENCIES = libpassword.la
+test_libpassword_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+test_auth_cache_SOURCES = auth-cache.c test-auth-cache.c
+test_auth_cache_LDADD = $(test_libs)
+test_auth_cache_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs)
+# this is needed to force auth-cache.c recompilation
+test_auth_cache_CPPFLAGS = $(AM_CPPFLAGS)
+test_auth_SOURCES = \
+ test-auth-request-var-expand.c \
+ test-auth-request-fields.c \
+ test-username-filter.c \
+ test-db-dict.c \
+ test-lua.c \
+ test-mock.c \
+ test-main.c
+
+test_auth_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS)
+test_auth_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs)
+test_mech_SOURCES = \
+ test-mock.c \
+ test-mech.c
+
+test_mech_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS)
+test_mech_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs)
+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/auth/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/auth/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files
+
+clean-pkglibexecPROGRAMS:
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-auth_moduleLTLIBRARIES: $(auth_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(auth_module_LTLIBRARIES)'; test -n "$(auth_moduledir)" || 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)$(auth_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(auth_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(auth_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(auth_moduledir)"; \
+ }
+
+uninstall-auth_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(auth_module_LTLIBRARIES)'; test -n "$(auth_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(auth_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(auth_moduledir)/$$f"; \
+ done
+
+clean-auth_moduleLTLIBRARIES:
+ -test -z "$(auth_module_LTLIBRARIES)" || rm -f $(auth_module_LTLIBRARIES)
+ @list='$(auth_module_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}; \
+ }
+
+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-stats_moduleLTLIBRARIES: $(stats_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(stats_module_LTLIBRARIES)'; test -n "$(stats_moduledir)" || 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)$(stats_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(stats_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(stats_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(stats_moduledir)"; \
+ }
+
+uninstall-stats_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(stats_module_LTLIBRARIES)'; test -n "$(stats_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(stats_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(stats_moduledir)/$$f"; \
+ done
+
+clean-stats_moduleLTLIBRARIES:
+ -test -z "$(stats_module_LTLIBRARIES)" || rm -f $(stats_module_LTLIBRARIES)
+ @list='$(stats_module_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}; \
+ }
+
+libauth.la: $(libauth_la_OBJECTS) $(libauth_la_DEPENDENCIES) $(EXTRA_libauth_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libauth_la_OBJECTS) $(libauth_la_LIBADD) $(LIBS)
+
+libauthdb_imap.la: $(libauthdb_imap_la_OBJECTS) $(libauthdb_imap_la_DEPENDENCIES) $(EXTRA_libauthdb_imap_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libauthdb_imap_la_LINK) -rpath $(auth_moduledir) $(libauthdb_imap_la_OBJECTS) $(libauthdb_imap_la_LIBADD) $(LIBS)
+
+libauthdb_ldap.la: $(libauthdb_ldap_la_OBJECTS) $(libauthdb_ldap_la_DEPENDENCIES) $(EXTRA_libauthdb_ldap_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libauthdb_ldap_la_LINK) $(am_libauthdb_ldap_la_rpath) $(libauthdb_ldap_la_OBJECTS) $(libauthdb_ldap_la_LIBADD) $(LIBS)
+
+libauthdb_lua.la: $(libauthdb_lua_la_OBJECTS) $(libauthdb_lua_la_DEPENDENCIES) $(EXTRA_libauthdb_lua_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libauthdb_lua_la_LINK) $(am_libauthdb_lua_la_rpath) $(libauthdb_lua_la_OBJECTS) $(libauthdb_lua_la_LIBADD) $(LIBS)
+
+libmech_gssapi.la: $(libmech_gssapi_la_OBJECTS) $(libmech_gssapi_la_DEPENDENCIES) $(EXTRA_libmech_gssapi_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libmech_gssapi_la_LINK) $(am_libmech_gssapi_la_rpath) $(libmech_gssapi_la_OBJECTS) $(libmech_gssapi_la_LIBADD) $(LIBS)
+
+libpassword.la: $(libpassword_la_OBJECTS) $(libpassword_la_DEPENDENCIES) $(EXTRA_libpassword_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libpassword_la_LINK) $(libpassword_la_OBJECTS) $(libpassword_la_LIBADD) $(LIBS)
+
+libstats_auth.la: $(libstats_auth_la_OBJECTS) $(libstats_auth_la_DEPENDENCIES) $(EXTRA_libstats_auth_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libstats_auth_la_LINK) -rpath $(stats_moduledir) $(libstats_auth_la_OBJECTS) $(libstats_auth_la_LIBADD) $(LIBS)
+
+auth$(EXEEXT): $(auth_OBJECTS) $(auth_DEPENDENCIES) $(EXTRA_auth_DEPENDENCIES)
+ @rm -f auth$(EXEEXT)
+ $(AM_V_CCLD)$(auth_LINK) $(auth_OBJECTS) $(auth_LDADD) $(LIBS)
+
+checkpassword-reply$(EXEEXT): $(checkpassword_reply_OBJECTS) $(checkpassword_reply_DEPENDENCIES) $(EXTRA_checkpassword_reply_DEPENDENCIES)
+ @rm -f checkpassword-reply$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(checkpassword_reply_OBJECTS) $(checkpassword_reply_LDADD) $(LIBS)
+
+test-auth$(EXEEXT): $(test_auth_OBJECTS) $(test_auth_DEPENDENCIES) $(EXTRA_test_auth_DEPENDENCIES)
+ @rm -f test-auth$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_auth_OBJECTS) $(test_auth_LDADD) $(LIBS)
+
+test-auth-cache$(EXEEXT): $(test_auth_cache_OBJECTS) $(test_auth_cache_DEPENDENCIES) $(EXTRA_test_auth_cache_DEPENDENCIES)
+ @rm -f test-auth-cache$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_auth_cache_OBJECTS) $(test_auth_cache_LDADD) $(LIBS)
+
+test-libpassword$(EXEEXT): $(test_libpassword_OBJECTS) $(test_libpassword_DEPENDENCIES) $(EXTRA_test_libpassword_DEPENDENCIES)
+ @rm -f test-libpassword$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_libpassword_OBJECTS) $(test_libpassword_LDADD) $(LIBS)
+
+test-mech$(EXEEXT): $(test_mech_OBJECTS) $(test_mech_DEPENDENCIES) $(EXTRA_test_mech_DEPENDENCIES)
+ @rm -f test-mech$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_mech_OBJECTS) $(test_mech_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-cache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-client-connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-fields.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-main.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-master-connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-penalty.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-policy.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request-fields.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request-handler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request-stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request-var-expand.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-token.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-worker-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-worker-server.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/checkpassword_reply-checkpassword-reply.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-checkpassword.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-dict-cache-key.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-dict.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-ldap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-lua.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-oauth2.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-passwd-file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-sql.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_lua_la-db-lua.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-crypt-blowfish.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-mycrypt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-crypt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-otp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-scram.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-sodium.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-anonymous.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-apop.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-cram-md5.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-digest-md5.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-dovecot-token.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-external.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-gssapi.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-login.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-oauth2.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-otp-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-otp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-plain-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-plain.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-scram.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-winbind.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-blocking.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-bsdauth.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-cache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-checkpassword.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-dict.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-ldap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-lua.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-oauth2.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-pam.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-passwd-file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-passwd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-shadow.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-sql.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-static.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-template.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-auth-request-fields.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-auth-request-var-expand.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-db-dict.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-lua.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-main.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-mech.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-mock.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-username-filter.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_auth_cache-auth-cache.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_auth_cache-test-auth-cache.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_libpassword-test-libpassword.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-blocking.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-checkpassword.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-dict.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-ldap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-lua.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-passwd-file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-passwd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-prefetch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-sql.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-static.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-template.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb.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 $@ $<
+
+libauthdb_imap_la-passdb-imap.lo: passdb-imap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_imap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_imap_la-passdb-imap.lo -MD -MP -MF $(DEPDIR)/libauthdb_imap_la-passdb-imap.Tpo -c -o libauthdb_imap_la-passdb-imap.lo `test -f 'passdb-imap.c' || echo '$(srcdir)/'`passdb-imap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_imap_la-passdb-imap.Tpo $(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='passdb-imap.c' object='libauthdb_imap_la-passdb-imap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_imap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_imap_la-passdb-imap.lo `test -f 'passdb-imap.c' || echo '$(srcdir)/'`passdb-imap.c
+
+libauthdb_ldap_la-db-ldap.lo: db-ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_ldap_la-db-ldap.lo -MD -MP -MF $(DEPDIR)/libauthdb_ldap_la-db-ldap.Tpo -c -o libauthdb_ldap_la-db-ldap.lo `test -f 'db-ldap.c' || echo '$(srcdir)/'`db-ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_ldap_la-db-ldap.Tpo $(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='db-ldap.c' object='libauthdb_ldap_la-db-ldap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_ldap_la-db-ldap.lo `test -f 'db-ldap.c' || echo '$(srcdir)/'`db-ldap.c
+
+libauthdb_ldap_la-passdb-ldap.lo: passdb-ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_ldap_la-passdb-ldap.lo -MD -MP -MF $(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Tpo -c -o libauthdb_ldap_la-passdb-ldap.lo `test -f 'passdb-ldap.c' || echo '$(srcdir)/'`passdb-ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Tpo $(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='passdb-ldap.c' object='libauthdb_ldap_la-passdb-ldap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_ldap_la-passdb-ldap.lo `test -f 'passdb-ldap.c' || echo '$(srcdir)/'`passdb-ldap.c
+
+libauthdb_ldap_la-userdb-ldap.lo: userdb-ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_ldap_la-userdb-ldap.lo -MD -MP -MF $(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Tpo -c -o libauthdb_ldap_la-userdb-ldap.lo `test -f 'userdb-ldap.c' || echo '$(srcdir)/'`userdb-ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Tpo $(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='userdb-ldap.c' object='libauthdb_ldap_la-userdb-ldap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_ldap_la-userdb-ldap.lo `test -f 'userdb-ldap.c' || echo '$(srcdir)/'`userdb-ldap.c
+
+libauthdb_lua_la-db-lua.lo: db-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_lua_la-db-lua.lo -MD -MP -MF $(DEPDIR)/libauthdb_lua_la-db-lua.Tpo -c -o libauthdb_lua_la-db-lua.lo `test -f 'db-lua.c' || echo '$(srcdir)/'`db-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_lua_la-db-lua.Tpo $(DEPDIR)/libauthdb_lua_la-db-lua.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='db-lua.c' object='libauthdb_lua_la-db-lua.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_lua_la-db-lua.lo `test -f 'db-lua.c' || echo '$(srcdir)/'`db-lua.c
+
+libauthdb_lua_la-passdb-lua.lo: passdb-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_lua_la-passdb-lua.lo -MD -MP -MF $(DEPDIR)/libauthdb_lua_la-passdb-lua.Tpo -c -o libauthdb_lua_la-passdb-lua.lo `test -f 'passdb-lua.c' || echo '$(srcdir)/'`passdb-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_lua_la-passdb-lua.Tpo $(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='passdb-lua.c' object='libauthdb_lua_la-passdb-lua.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_lua_la-passdb-lua.lo `test -f 'passdb-lua.c' || echo '$(srcdir)/'`passdb-lua.c
+
+libauthdb_lua_la-userdb-lua.lo: userdb-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_lua_la-userdb-lua.lo -MD -MP -MF $(DEPDIR)/libauthdb_lua_la-userdb-lua.Tpo -c -o libauthdb_lua_la-userdb-lua.lo `test -f 'userdb-lua.c' || echo '$(srcdir)/'`userdb-lua.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_lua_la-userdb-lua.Tpo $(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='userdb-lua.c' object='libauthdb_lua_la-userdb-lua.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_lua_la-userdb-lua.lo `test -f 'userdb-lua.c' || echo '$(srcdir)/'`userdb-lua.c
+
+libmech_gssapi_la-mech-gssapi.lo: mech-gssapi.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmech_gssapi_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libmech_gssapi_la-mech-gssapi.lo -MD -MP -MF $(DEPDIR)/libmech_gssapi_la-mech-gssapi.Tpo -c -o libmech_gssapi_la-mech-gssapi.lo `test -f 'mech-gssapi.c' || echo '$(srcdir)/'`mech-gssapi.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmech_gssapi_la-mech-gssapi.Tpo $(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mech-gssapi.c' object='libmech_gssapi_la-mech-gssapi.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmech_gssapi_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libmech_gssapi_la-mech-gssapi.lo `test -f 'mech-gssapi.c' || echo '$(srcdir)/'`mech-gssapi.c
+
+libpassword_la-crypt-blowfish.lo: crypt-blowfish.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-crypt-blowfish.lo -MD -MP -MF $(DEPDIR)/libpassword_la-crypt-blowfish.Tpo -c -o libpassword_la-crypt-blowfish.lo `test -f 'crypt-blowfish.c' || echo '$(srcdir)/'`crypt-blowfish.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-crypt-blowfish.Tpo $(DEPDIR)/libpassword_la-crypt-blowfish.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='crypt-blowfish.c' object='libpassword_la-crypt-blowfish.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-crypt-blowfish.lo `test -f 'crypt-blowfish.c' || echo '$(srcdir)/'`crypt-blowfish.c
+
+libpassword_la-mycrypt.lo: mycrypt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-mycrypt.lo -MD -MP -MF $(DEPDIR)/libpassword_la-mycrypt.Tpo -c -o libpassword_la-mycrypt.lo `test -f 'mycrypt.c' || echo '$(srcdir)/'`mycrypt.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-mycrypt.Tpo $(DEPDIR)/libpassword_la-mycrypt.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mycrypt.c' object='libpassword_la-mycrypt.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-mycrypt.lo `test -f 'mycrypt.c' || echo '$(srcdir)/'`mycrypt.c
+
+libpassword_la-password-scheme.lo: password-scheme.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme.Tpo -c -o libpassword_la-password-scheme.lo `test -f 'password-scheme.c' || echo '$(srcdir)/'`password-scheme.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme.Tpo $(DEPDIR)/libpassword_la-password-scheme.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme.c' object='libpassword_la-password-scheme.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme.lo `test -f 'password-scheme.c' || echo '$(srcdir)/'`password-scheme.c
+
+libpassword_la-password-scheme-crypt.lo: password-scheme-crypt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-crypt.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-crypt.Tpo -c -o libpassword_la-password-scheme-crypt.lo `test -f 'password-scheme-crypt.c' || echo '$(srcdir)/'`password-scheme-crypt.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-crypt.Tpo $(DEPDIR)/libpassword_la-password-scheme-crypt.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-crypt.c' object='libpassword_la-password-scheme-crypt.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-crypt.lo `test -f 'password-scheme-crypt.c' || echo '$(srcdir)/'`password-scheme-crypt.c
+
+libpassword_la-password-scheme-md5crypt.lo: password-scheme-md5crypt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-md5crypt.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-md5crypt.Tpo -c -o libpassword_la-password-scheme-md5crypt.lo `test -f 'password-scheme-md5crypt.c' || echo '$(srcdir)/'`password-scheme-md5crypt.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-md5crypt.Tpo $(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-md5crypt.c' object='libpassword_la-password-scheme-md5crypt.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-md5crypt.lo `test -f 'password-scheme-md5crypt.c' || echo '$(srcdir)/'`password-scheme-md5crypt.c
+
+libpassword_la-password-scheme-scram.lo: password-scheme-scram.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-scram.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-scram.Tpo -c -o libpassword_la-password-scheme-scram.lo `test -f 'password-scheme-scram.c' || echo '$(srcdir)/'`password-scheme-scram.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-scram.Tpo $(DEPDIR)/libpassword_la-password-scheme-scram.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-scram.c' object='libpassword_la-password-scheme-scram.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-scram.lo `test -f 'password-scheme-scram.c' || echo '$(srcdir)/'`password-scheme-scram.c
+
+libpassword_la-password-scheme-otp.lo: password-scheme-otp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-otp.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-otp.Tpo -c -o libpassword_la-password-scheme-otp.lo `test -f 'password-scheme-otp.c' || echo '$(srcdir)/'`password-scheme-otp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-otp.Tpo $(DEPDIR)/libpassword_la-password-scheme-otp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-otp.c' object='libpassword_la-password-scheme-otp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-otp.lo `test -f 'password-scheme-otp.c' || echo '$(srcdir)/'`password-scheme-otp.c
+
+libpassword_la-password-scheme-pbkdf2.lo: password-scheme-pbkdf2.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-pbkdf2.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Tpo -c -o libpassword_la-password-scheme-pbkdf2.lo `test -f 'password-scheme-pbkdf2.c' || echo '$(srcdir)/'`password-scheme-pbkdf2.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Tpo $(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-pbkdf2.c' object='libpassword_la-password-scheme-pbkdf2.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-pbkdf2.lo `test -f 'password-scheme-pbkdf2.c' || echo '$(srcdir)/'`password-scheme-pbkdf2.c
+
+libpassword_la-password-scheme-sodium.lo: password-scheme-sodium.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-sodium.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-sodium.Tpo -c -o libpassword_la-password-scheme-sodium.lo `test -f 'password-scheme-sodium.c' || echo '$(srcdir)/'`password-scheme-sodium.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-sodium.Tpo $(DEPDIR)/libpassword_la-password-scheme-sodium.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-sodium.c' object='libpassword_la-password-scheme-sodium.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-sodium.lo `test -f 'password-scheme-sodium.c' || echo '$(srcdir)/'`password-scheme-sodium.c
+
+auth-main.o: main.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(auth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT auth-main.o -MD -MP -MF $(DEPDIR)/auth-main.Tpo -c -o auth-main.o `test -f 'main.c' || echo '$(srcdir)/'`main.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/auth-main.Tpo $(DEPDIR)/auth-main.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='main.c' object='auth-main.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(auth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o auth-main.o `test -f 'main.c' || echo '$(srcdir)/'`main.c
+
+auth-main.obj: main.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(auth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT auth-main.obj -MD -MP -MF $(DEPDIR)/auth-main.Tpo -c -o auth-main.obj `if test -f 'main.c'; then $(CYGPATH_W) 'main.c'; else $(CYGPATH_W) '$(srcdir)/main.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/auth-main.Tpo $(DEPDIR)/auth-main.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='main.c' object='auth-main.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(auth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o auth-main.obj `if test -f 'main.c'; then $(CYGPATH_W) 'main.c'; else $(CYGPATH_W) '$(srcdir)/main.c'; fi`
+
+checkpassword_reply-checkpassword-reply.o: checkpassword-reply.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(checkpassword_reply_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT checkpassword_reply-checkpassword-reply.o -MD -MP -MF $(DEPDIR)/checkpassword_reply-checkpassword-reply.Tpo -c -o checkpassword_reply-checkpassword-reply.o `test -f 'checkpassword-reply.c' || echo '$(srcdir)/'`checkpassword-reply.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/checkpassword_reply-checkpassword-reply.Tpo $(DEPDIR)/checkpassword_reply-checkpassword-reply.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='checkpassword-reply.c' object='checkpassword_reply-checkpassword-reply.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(checkpassword_reply_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o checkpassword_reply-checkpassword-reply.o `test -f 'checkpassword-reply.c' || echo '$(srcdir)/'`checkpassword-reply.c
+
+checkpassword_reply-checkpassword-reply.obj: checkpassword-reply.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(checkpassword_reply_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT checkpassword_reply-checkpassword-reply.obj -MD -MP -MF $(DEPDIR)/checkpassword_reply-checkpassword-reply.Tpo -c -o checkpassword_reply-checkpassword-reply.obj `if test -f 'checkpassword-reply.c'; then $(CYGPATH_W) 'checkpassword-reply.c'; else $(CYGPATH_W) '$(srcdir)/checkpassword-reply.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/checkpassword_reply-checkpassword-reply.Tpo $(DEPDIR)/checkpassword_reply-checkpassword-reply.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='checkpassword-reply.c' object='checkpassword_reply-checkpassword-reply.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(checkpassword_reply_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o checkpassword_reply-checkpassword-reply.obj `if test -f 'checkpassword-reply.c'; then $(CYGPATH_W) 'checkpassword-reply.c'; else $(CYGPATH_W) '$(srcdir)/checkpassword-reply.c'; fi`
+
+test_auth_cache-auth-cache.o: auth-cache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_auth_cache-auth-cache.o -MD -MP -MF $(DEPDIR)/test_auth_cache-auth-cache.Tpo -c -o test_auth_cache-auth-cache.o `test -f 'auth-cache.c' || echo '$(srcdir)/'`auth-cache.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_auth_cache-auth-cache.Tpo $(DEPDIR)/test_auth_cache-auth-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='auth-cache.c' object='test_auth_cache-auth-cache.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_auth_cache-auth-cache.o `test -f 'auth-cache.c' || echo '$(srcdir)/'`auth-cache.c
+
+test_auth_cache-auth-cache.obj: auth-cache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_auth_cache-auth-cache.obj -MD -MP -MF $(DEPDIR)/test_auth_cache-auth-cache.Tpo -c -o test_auth_cache-auth-cache.obj `if test -f 'auth-cache.c'; then $(CYGPATH_W) 'auth-cache.c'; else $(CYGPATH_W) '$(srcdir)/auth-cache.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_auth_cache-auth-cache.Tpo $(DEPDIR)/test_auth_cache-auth-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='auth-cache.c' object='test_auth_cache-auth-cache.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_auth_cache-auth-cache.obj `if test -f 'auth-cache.c'; then $(CYGPATH_W) 'auth-cache.c'; else $(CYGPATH_W) '$(srcdir)/auth-cache.c'; fi`
+
+test_auth_cache-test-auth-cache.o: test-auth-cache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_auth_cache-test-auth-cache.o -MD -MP -MF $(DEPDIR)/test_auth_cache-test-auth-cache.Tpo -c -o test_auth_cache-test-auth-cache.o `test -f 'test-auth-cache.c' || echo '$(srcdir)/'`test-auth-cache.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_auth_cache-test-auth-cache.Tpo $(DEPDIR)/test_auth_cache-test-auth-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-auth-cache.c' object='test_auth_cache-test-auth-cache.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_auth_cache-test-auth-cache.o `test -f 'test-auth-cache.c' || echo '$(srcdir)/'`test-auth-cache.c
+
+test_auth_cache-test-auth-cache.obj: test-auth-cache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_auth_cache-test-auth-cache.obj -MD -MP -MF $(DEPDIR)/test_auth_cache-test-auth-cache.Tpo -c -o test_auth_cache-test-auth-cache.obj `if test -f 'test-auth-cache.c'; then $(CYGPATH_W) 'test-auth-cache.c'; else $(CYGPATH_W) '$(srcdir)/test-auth-cache.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_auth_cache-test-auth-cache.Tpo $(DEPDIR)/test_auth_cache-test-auth-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-auth-cache.c' object='test_auth_cache-test-auth-cache.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_auth_cache-test-auth-cache.obj `if test -f 'test-auth-cache.c'; then $(CYGPATH_W) 'test-auth-cache.c'; else $(CYGPATH_W) '$(srcdir)/test-auth-cache.c'; fi`
+
+test_libpassword-test-libpassword.o: test-libpassword.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_libpassword_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_libpassword-test-libpassword.o -MD -MP -MF $(DEPDIR)/test_libpassword-test-libpassword.Tpo -c -o test_libpassword-test-libpassword.o `test -f 'test-libpassword.c' || echo '$(srcdir)/'`test-libpassword.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_libpassword-test-libpassword.Tpo $(DEPDIR)/test_libpassword-test-libpassword.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-libpassword.c' object='test_libpassword-test-libpassword.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_libpassword_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_libpassword-test-libpassword.o `test -f 'test-libpassword.c' || echo '$(srcdir)/'`test-libpassword.c
+
+test_libpassword-test-libpassword.obj: test-libpassword.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_libpassword_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_libpassword-test-libpassword.obj -MD -MP -MF $(DEPDIR)/test_libpassword-test-libpassword.Tpo -c -o test_libpassword-test-libpassword.obj `if test -f 'test-libpassword.c'; then $(CYGPATH_W) 'test-libpassword.c'; else $(CYGPATH_W) '$(srcdir)/test-libpassword.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_libpassword-test-libpassword.Tpo $(DEPDIR)/test_libpassword-test-libpassword.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-libpassword.c' object='test_libpassword-test-libpassword.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_libpassword_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_libpassword-test-libpassword.obj `if test -f 'test-libpassword.c'; then $(CYGPATH_W) 'test-libpassword.c'; else $(CYGPATH_W) '$(srcdir)/test-libpassword.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(auth_moduledir)" "$(DESTDIR)$(stats_moduledir)" "$(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-auth_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES clean-noinstPROGRAMS \
+ clean-pkglibexecPROGRAMS clean-stats_moduleLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/auth-cache.Plo
+ -rm -f ./$(DEPDIR)/auth-client-connection.Plo
+ -rm -f ./$(DEPDIR)/auth-fields.Plo
+ -rm -f ./$(DEPDIR)/auth-main.Po
+ -rm -f ./$(DEPDIR)/auth-master-connection.Plo
+ -rm -f ./$(DEPDIR)/auth-penalty.Plo
+ -rm -f ./$(DEPDIR)/auth-policy.Plo
+ -rm -f ./$(DEPDIR)/auth-request-fields.Plo
+ -rm -f ./$(DEPDIR)/auth-request-handler.Plo
+ -rm -f ./$(DEPDIR)/auth-request-stats.Plo
+ -rm -f ./$(DEPDIR)/auth-request-var-expand.Plo
+ -rm -f ./$(DEPDIR)/auth-request.Plo
+ -rm -f ./$(DEPDIR)/auth-settings.Plo
+ -rm -f ./$(DEPDIR)/auth-stats.Plo
+ -rm -f ./$(DEPDIR)/auth-token.Plo
+ -rm -f ./$(DEPDIR)/auth-worker-client.Plo
+ -rm -f ./$(DEPDIR)/auth-worker-server.Plo
+ -rm -f ./$(DEPDIR)/auth.Plo
+ -rm -f ./$(DEPDIR)/checkpassword_reply-checkpassword-reply.Po
+ -rm -f ./$(DEPDIR)/db-checkpassword.Plo
+ -rm -f ./$(DEPDIR)/db-dict-cache-key.Plo
+ -rm -f ./$(DEPDIR)/db-dict.Plo
+ -rm -f ./$(DEPDIR)/db-ldap.Plo
+ -rm -f ./$(DEPDIR)/db-lua.Plo
+ -rm -f ./$(DEPDIR)/db-oauth2.Plo
+ -rm -f ./$(DEPDIR)/db-passwd-file.Plo
+ -rm -f ./$(DEPDIR)/db-sql.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_lua_la-db-lua.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo
+ -rm -f ./$(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-crypt-blowfish.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-mycrypt.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-crypt.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-otp.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-scram.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-sodium.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme.Plo
+ -rm -f ./$(DEPDIR)/mech-anonymous.Plo
+ -rm -f ./$(DEPDIR)/mech-apop.Plo
+ -rm -f ./$(DEPDIR)/mech-cram-md5.Plo
+ -rm -f ./$(DEPDIR)/mech-digest-md5.Plo
+ -rm -f ./$(DEPDIR)/mech-dovecot-token.Plo
+ -rm -f ./$(DEPDIR)/mech-external.Plo
+ -rm -f ./$(DEPDIR)/mech-gssapi.Plo
+ -rm -f ./$(DEPDIR)/mech-login.Plo
+ -rm -f ./$(DEPDIR)/mech-oauth2.Plo
+ -rm -f ./$(DEPDIR)/mech-otp-common.Plo
+ -rm -f ./$(DEPDIR)/mech-otp.Plo
+ -rm -f ./$(DEPDIR)/mech-plain-common.Plo
+ -rm -f ./$(DEPDIR)/mech-plain.Plo
+ -rm -f ./$(DEPDIR)/mech-scram.Plo
+ -rm -f ./$(DEPDIR)/mech-winbind.Plo
+ -rm -f ./$(DEPDIR)/mech.Plo
+ -rm -f ./$(DEPDIR)/passdb-blocking.Plo
+ -rm -f ./$(DEPDIR)/passdb-bsdauth.Plo
+ -rm -f ./$(DEPDIR)/passdb-cache.Plo
+ -rm -f ./$(DEPDIR)/passdb-checkpassword.Plo
+ -rm -f ./$(DEPDIR)/passdb-dict.Plo
+ -rm -f ./$(DEPDIR)/passdb-ldap.Plo
+ -rm -f ./$(DEPDIR)/passdb-lua.Plo
+ -rm -f ./$(DEPDIR)/passdb-oauth2.Plo
+ -rm -f ./$(DEPDIR)/passdb-pam.Plo
+ -rm -f ./$(DEPDIR)/passdb-passwd-file.Plo
+ -rm -f ./$(DEPDIR)/passdb-passwd.Plo
+ -rm -f ./$(DEPDIR)/passdb-shadow.Plo
+ -rm -f ./$(DEPDIR)/passdb-sql.Plo
+ -rm -f ./$(DEPDIR)/passdb-static.Plo
+ -rm -f ./$(DEPDIR)/passdb-template.Plo
+ -rm -f ./$(DEPDIR)/passdb.Plo
+ -rm -f ./$(DEPDIR)/test-auth-request-fields.Po
+ -rm -f ./$(DEPDIR)/test-auth-request-var-expand.Po
+ -rm -f ./$(DEPDIR)/test-db-dict.Po
+ -rm -f ./$(DEPDIR)/test-lua.Po
+ -rm -f ./$(DEPDIR)/test-main.Po
+ -rm -f ./$(DEPDIR)/test-mech.Po
+ -rm -f ./$(DEPDIR)/test-mock.Po
+ -rm -f ./$(DEPDIR)/test-username-filter.Po
+ -rm -f ./$(DEPDIR)/test_auth_cache-auth-cache.Po
+ -rm -f ./$(DEPDIR)/test_auth_cache-test-auth-cache.Po
+ -rm -f ./$(DEPDIR)/test_libpassword-test-libpassword.Po
+ -rm -f ./$(DEPDIR)/userdb-blocking.Plo
+ -rm -f ./$(DEPDIR)/userdb-checkpassword.Plo
+ -rm -f ./$(DEPDIR)/userdb-dict.Plo
+ -rm -f ./$(DEPDIR)/userdb-ldap.Plo
+ -rm -f ./$(DEPDIR)/userdb-lua.Plo
+ -rm -f ./$(DEPDIR)/userdb-passwd-file.Plo
+ -rm -f ./$(DEPDIR)/userdb-passwd.Plo
+ -rm -f ./$(DEPDIR)/userdb-prefetch.Plo
+ -rm -f ./$(DEPDIR)/userdb-sql.Plo
+ -rm -f ./$(DEPDIR)/userdb-static.Plo
+ -rm -f ./$(DEPDIR)/userdb-template.Plo
+ -rm -f ./$(DEPDIR)/userdb.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-auth_moduleLTLIBRARIES \
+ install-pkginc_libHEADERS install-stats_moduleLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibexecPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/auth-cache.Plo
+ -rm -f ./$(DEPDIR)/auth-client-connection.Plo
+ -rm -f ./$(DEPDIR)/auth-fields.Plo
+ -rm -f ./$(DEPDIR)/auth-main.Po
+ -rm -f ./$(DEPDIR)/auth-master-connection.Plo
+ -rm -f ./$(DEPDIR)/auth-penalty.Plo
+ -rm -f ./$(DEPDIR)/auth-policy.Plo
+ -rm -f ./$(DEPDIR)/auth-request-fields.Plo
+ -rm -f ./$(DEPDIR)/auth-request-handler.Plo
+ -rm -f ./$(DEPDIR)/auth-request-stats.Plo
+ -rm -f ./$(DEPDIR)/auth-request-var-expand.Plo
+ -rm -f ./$(DEPDIR)/auth-request.Plo
+ -rm -f ./$(DEPDIR)/auth-settings.Plo
+ -rm -f ./$(DEPDIR)/auth-stats.Plo
+ -rm -f ./$(DEPDIR)/auth-token.Plo
+ -rm -f ./$(DEPDIR)/auth-worker-client.Plo
+ -rm -f ./$(DEPDIR)/auth-worker-server.Plo
+ -rm -f ./$(DEPDIR)/auth.Plo
+ -rm -f ./$(DEPDIR)/checkpassword_reply-checkpassword-reply.Po
+ -rm -f ./$(DEPDIR)/db-checkpassword.Plo
+ -rm -f ./$(DEPDIR)/db-dict-cache-key.Plo
+ -rm -f ./$(DEPDIR)/db-dict.Plo
+ -rm -f ./$(DEPDIR)/db-ldap.Plo
+ -rm -f ./$(DEPDIR)/db-lua.Plo
+ -rm -f ./$(DEPDIR)/db-oauth2.Plo
+ -rm -f ./$(DEPDIR)/db-passwd-file.Plo
+ -rm -f ./$(DEPDIR)/db-sql.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_lua_la-db-lua.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo
+ -rm -f ./$(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo
+ -rm -f ./$(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-crypt-blowfish.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-mycrypt.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-crypt.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-otp.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-scram.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-sodium.Plo
+ -rm -f ./$(DEPDIR)/libpassword_la-password-scheme.Plo
+ -rm -f ./$(DEPDIR)/mech-anonymous.Plo
+ -rm -f ./$(DEPDIR)/mech-apop.Plo
+ -rm -f ./$(DEPDIR)/mech-cram-md5.Plo
+ -rm -f ./$(DEPDIR)/mech-digest-md5.Plo
+ -rm -f ./$(DEPDIR)/mech-dovecot-token.Plo
+ -rm -f ./$(DEPDIR)/mech-external.Plo
+ -rm -f ./$(DEPDIR)/mech-gssapi.Plo
+ -rm -f ./$(DEPDIR)/mech-login.Plo
+ -rm -f ./$(DEPDIR)/mech-oauth2.Plo
+ -rm -f ./$(DEPDIR)/mech-otp-common.Plo
+ -rm -f ./$(DEPDIR)/mech-otp.Plo
+ -rm -f ./$(DEPDIR)/mech-plain-common.Plo
+ -rm -f ./$(DEPDIR)/mech-plain.Plo
+ -rm -f ./$(DEPDIR)/mech-scram.Plo
+ -rm -f ./$(DEPDIR)/mech-winbind.Plo
+ -rm -f ./$(DEPDIR)/mech.Plo
+ -rm -f ./$(DEPDIR)/passdb-blocking.Plo
+ -rm -f ./$(DEPDIR)/passdb-bsdauth.Plo
+ -rm -f ./$(DEPDIR)/passdb-cache.Plo
+ -rm -f ./$(DEPDIR)/passdb-checkpassword.Plo
+ -rm -f ./$(DEPDIR)/passdb-dict.Plo
+ -rm -f ./$(DEPDIR)/passdb-ldap.Plo
+ -rm -f ./$(DEPDIR)/passdb-lua.Plo
+ -rm -f ./$(DEPDIR)/passdb-oauth2.Plo
+ -rm -f ./$(DEPDIR)/passdb-pam.Plo
+ -rm -f ./$(DEPDIR)/passdb-passwd-file.Plo
+ -rm -f ./$(DEPDIR)/passdb-passwd.Plo
+ -rm -f ./$(DEPDIR)/passdb-shadow.Plo
+ -rm -f ./$(DEPDIR)/passdb-sql.Plo
+ -rm -f ./$(DEPDIR)/passdb-static.Plo
+ -rm -f ./$(DEPDIR)/passdb-template.Plo
+ -rm -f ./$(DEPDIR)/passdb.Plo
+ -rm -f ./$(DEPDIR)/test-auth-request-fields.Po
+ -rm -f ./$(DEPDIR)/test-auth-request-var-expand.Po
+ -rm -f ./$(DEPDIR)/test-db-dict.Po
+ -rm -f ./$(DEPDIR)/test-lua.Po
+ -rm -f ./$(DEPDIR)/test-main.Po
+ -rm -f ./$(DEPDIR)/test-mech.Po
+ -rm -f ./$(DEPDIR)/test-mock.Po
+ -rm -f ./$(DEPDIR)/test-username-filter.Po
+ -rm -f ./$(DEPDIR)/test_auth_cache-auth-cache.Po
+ -rm -f ./$(DEPDIR)/test_auth_cache-test-auth-cache.Po
+ -rm -f ./$(DEPDIR)/test_libpassword-test-libpassword.Po
+ -rm -f ./$(DEPDIR)/userdb-blocking.Plo
+ -rm -f ./$(DEPDIR)/userdb-checkpassword.Plo
+ -rm -f ./$(DEPDIR)/userdb-dict.Plo
+ -rm -f ./$(DEPDIR)/userdb-ldap.Plo
+ -rm -f ./$(DEPDIR)/userdb-lua.Plo
+ -rm -f ./$(DEPDIR)/userdb-passwd-file.Plo
+ -rm -f ./$(DEPDIR)/userdb-passwd.Plo
+ -rm -f ./$(DEPDIR)/userdb-prefetch.Plo
+ -rm -f ./$(DEPDIR)/userdb-sql.Plo
+ -rm -f ./$(DEPDIR)/userdb-static.Plo
+ -rm -f ./$(DEPDIR)/userdb-template.Plo
+ -rm -f ./$(DEPDIR)/userdb.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-auth_moduleLTLIBRARIES \
+ uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS \
+ uninstall-stats_moduleLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-auth_moduleLTLIBRARIES clean-generic \
+ clean-libtool clean-noinstLTLIBRARIES clean-noinstPROGRAMS \
+ clean-pkglibexecPROGRAMS clean-stats_moduleLTLIBRARIES \
+ 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-auth_moduleLTLIBRARIES 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-pkglibexecPROGRAMS \
+ install-ps install-ps-am install-stats_moduleLTLIBRARIES \
+ 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-auth_moduleLTLIBRARIES uninstall-pkginc_libHEADERS \
+ uninstall-pkglibexecPROGRAMS uninstall-stats_moduleLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/auth/auth-cache.c b/src/auth/auth-cache.c
new file mode 100644
index 0000000..e8aa105
--- /dev/null
+++ b/src/auth/auth-cache.c
@@ -0,0 +1,481 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "lib-signals.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "var-expand.h"
+#include "auth-request.h"
+#include "auth-cache.h"
+
+#include <time.h>
+
+struct auth_cache {
+ HASH_TABLE(char *, struct auth_cache_node *) hash;
+ struct auth_cache_node *head, *tail;
+
+ size_t max_size, size_left;
+ unsigned int ttl_secs, neg_ttl_secs;
+
+ unsigned int hit_count, miss_count;
+ unsigned int pos_entries, neg_entries;
+ unsigned long long pos_size, neg_size;
+};
+
+static bool
+auth_request_var_expand_tab_find(const char *key, unsigned int size,
+ unsigned int *idx_r)
+{
+ const struct var_expand_table *tab = auth_request_var_expand_static_tab;
+ unsigned int i;
+
+ for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) {
+ if (size == 1) {
+ if (key[0] == tab[i].key) {
+ *idx_r = i;
+ return TRUE;
+ }
+ } else if (tab[i].long_key != NULL) {
+ if (strncmp(key, tab[i].long_key, size) == 0 &&
+ tab[i].long_key[size] == '\0') {
+ *idx_r = i;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+static void
+auth_cache_key_add_var(string_t *str, const char *data, unsigned int len)
+{
+ if (str_len(str) > 0)
+ str_append_c(str, '\t');
+ str_append_c(str, '%');
+ if (len == 1)
+ str_append_c(str, data[0]);
+ else {
+ str_append_c(str, '{');
+ str_append_data(str, data, len);
+ str_append_c(str, '}');
+ }
+}
+
+static void auth_cache_key_add_tab_idx(string_t *str, unsigned int i)
+{
+ const struct var_expand_table *tab =
+ &auth_request_var_expand_static_tab[i];
+
+ if (str_len(str) > 0)
+ str_append_c(str, '\t');
+ str_append_c(str, '%');
+ if (tab->key != '\0')
+ str_append_c(str, tab->key);
+ else {
+ str_append_c(str, '{');
+ str_append(str, tab->long_key);
+ str_append_c(str, '}');
+ }
+}
+
+char *auth_cache_parse_key(pool_t pool, const char *query)
+{
+ string_t *str;
+ bool key_seen[AUTH_REQUEST_VAR_TAB_COUNT];
+ const char *extra_vars;
+ unsigned int i, idx, size, tab_idx;
+
+ memset(key_seen, 0, sizeof(key_seen));
+
+ str = t_str_new(32);
+ for (; *query != '\0'; ) {
+ if (*query != '%') {
+ query++;
+ continue;
+ }
+
+ var_get_key_range(++query, &idx, &size);
+ if (size == 0) {
+ /* broken %variable ending too early */
+ break;
+ }
+ query += idx;
+
+ if (!auth_request_var_expand_tab_find(query, size, &tab_idx)) {
+ /* just add the key. it would be nice to prevent
+ duplicates here as well, but that's just too
+ much trouble and probably very rare. */
+ auth_cache_key_add_var(str, query, size);
+ } else {
+ i_assert(tab_idx < N_ELEMENTS(key_seen));
+ key_seen[tab_idx] = TRUE;
+ }
+ query += size;
+ }
+
+ if (key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] &&
+ key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX]) {
+ /* %n and %d both used -> replace with %u */
+ key_seen[AUTH_REQUEST_VAR_TAB_USER_IDX] = TRUE;
+ key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] = FALSE;
+ key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX] = FALSE;
+ }
+
+ /* we rely on these being at the beginning */
+ i_assert(AUTH_REQUEST_VAR_TAB_USER_IDX == 0);
+ i_assert(AUTH_REQUEST_VAR_TAB_USERNAME_IDX == 1);
+ i_assert(AUTH_REQUEST_VAR_TAB_DOMAIN_IDX == 2);
+
+ extra_vars = t_strdup(str_c(str));
+ str_truncate(str, 0);
+ for (i = 0; i < N_ELEMENTS(key_seen); i++) {
+ if (key_seen[i])
+ auth_cache_key_add_tab_idx(str, i);
+ }
+
+ if (*extra_vars != '\0') {
+ if (str_len(str) > 0)
+ str_append_c(str, '\t');
+ str_append(str, extra_vars);
+ }
+
+ return p_strdup(pool, str_c(str));
+}
+
+static void
+auth_cache_node_unlink(struct auth_cache *cache, struct auth_cache_node *node)
+{
+ if (node->prev != NULL)
+ node->prev->next = node->next;
+ else {
+ /* unlinking tail */
+ cache->tail = node->next;
+ }
+
+ if (node->next != NULL)
+ node->next->prev = node->prev;
+ else {
+ /* unlinking head */
+ cache->head = node->prev;
+ }
+}
+
+static void
+auth_cache_node_link_head(struct auth_cache *cache,
+ struct auth_cache_node *node)
+{
+ node->prev = cache->head;
+ node->next = NULL;
+
+ cache->head = node;
+ if (node->prev != NULL)
+ node->prev->next = node;
+ else
+ cache->tail = node;
+}
+
+static void
+auth_cache_node_destroy(struct auth_cache *cache, struct auth_cache_node *node)
+{
+ char *key = node->data;
+
+ auth_cache_node_unlink(cache, node);
+
+ cache->size_left += node->alloc_size;
+ hash_table_remove(cache->hash, key);
+ i_free(node);
+}
+
+static void sig_auth_cache_clear(const siginfo_t *si ATTR_UNUSED, void *context)
+{
+ struct auth_cache *cache = context;
+
+ i_info("SIGHUP received, %u cache entries flushed",
+ auth_cache_clear(cache));
+}
+
+static void sig_auth_cache_stats(const siginfo_t *si ATTR_UNUSED, void *context)
+{
+ struct auth_cache *cache = context;
+ unsigned int total_count;
+ size_t cache_used;
+
+ total_count = cache->hit_count + cache->miss_count;
+ i_info("Authentication cache hits %u/%u (%u%%)",
+ cache->hit_count, total_count,
+ total_count == 0 ? 100 : (cache->hit_count * 100 / total_count));
+
+ i_info("Authentication cache inserts: "
+ "positive: %u entries %llu bytes, "
+ "negative: %u entries %llu bytes",
+ cache->pos_entries, cache->pos_size,
+ cache->neg_entries, cache->neg_size);
+
+ cache_used = cache->max_size - cache->size_left;
+ i_info("Authentication cache current size: "
+ "%zu bytes used of %zu bytes (%u%%)",
+ cache_used, cache->max_size,
+ (unsigned int)(cache_used * 100ULL / cache->max_size));
+
+ /* reset counters */
+ cache->hit_count = cache->miss_count = 0;
+ cache->pos_entries = cache->neg_entries = 0;
+ cache->pos_size = cache->neg_size = 0;
+}
+
+struct auth_cache *auth_cache_new(size_t max_size, unsigned int ttl_secs,
+ unsigned int neg_ttl_secs
+)
+{
+ struct auth_cache *cache;
+
+ cache = i_new(struct auth_cache, 1);
+ hash_table_create(&cache->hash, default_pool, 0, str_hash, strcmp);
+ cache->max_size = max_size;
+ cache->size_left = max_size;
+ cache->ttl_secs = ttl_secs;
+ cache->neg_ttl_secs = neg_ttl_secs;
+
+ lib_signals_set_handler(SIGHUP, LIBSIG_FLAGS_SAFE,
+ sig_auth_cache_clear, cache);
+ lib_signals_set_handler(SIGUSR2, LIBSIG_FLAGS_SAFE,
+ sig_auth_cache_stats, cache);
+ return cache;
+}
+
+void auth_cache_free(struct auth_cache **_cache)
+{
+ struct auth_cache *cache = *_cache;
+
+ *_cache = NULL;
+ lib_signals_unset_handler(SIGHUP, sig_auth_cache_clear, cache);
+ lib_signals_unset_handler(SIGUSR2, sig_auth_cache_stats, cache);
+
+ auth_cache_clear(cache);
+ hash_table_destroy(&cache->hash);
+ i_free(cache);
+}
+
+unsigned int auth_cache_clear(struct auth_cache *cache)
+{
+ unsigned int ret = hash_table_count(cache->hash);
+
+ while (cache->tail != NULL)
+ auth_cache_node_destroy(cache, cache->tail);
+ hash_table_clear(cache->hash, FALSE);
+ return ret;
+}
+
+static bool auth_cache_node_is_user(struct auth_cache_node *node,
+ const char *username)
+{
+ const char *data = node->data;
+ size_t username_len;
+
+ /* The cache nodes begin with "P"/"U", passdb/userdb ID, optional
+ "+" master user, "\t" and then usually followed by the username.
+ It's too much trouble to keep track of all the cache keys, so we'll
+ just match it as if it was the username. If e.g. '%n' is used in the
+ cache key instead of '%u', it means that cache entries can be
+ removed only when @domain isn't in the username parameter. */
+ if (*data != 'P' && *data != 'U')
+ return FALSE;
+ data++;
+
+ while (*data >= '0' && *data <= '9')
+ data++;
+ if (*data == '+') {
+ /* skip over +master_user */
+ while (*data != '\t' && *data != '\0')
+ data++;
+ }
+ if (*data != '\t')
+ return FALSE;
+ data++;
+
+ username_len = strlen(username);
+ return str_begins(data, username) &&
+ (data[username_len] == '\t' || data[username_len] == '\0');
+}
+
+static bool auth_cache_node_is_one_of_users(struct auth_cache_node *node,
+ const char *const *usernames)
+{
+ unsigned int i;
+
+ for (i = 0; usernames[i] != NULL; i++) {
+ if (auth_cache_node_is_user(node, usernames[i]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+unsigned int auth_cache_clear_users(struct auth_cache *cache,
+ const char *const *usernames)
+{
+ struct auth_cache_node *node, *next;
+ unsigned int ret = 0;
+
+ for (node = cache->tail; node != NULL; node = next) {
+ next = node->next;
+ if (auth_cache_node_is_one_of_users(node, usernames)) {
+ auth_cache_node_destroy(cache, node);
+ ret++;
+ }
+ }
+ return ret;
+}
+
+static const char *
+auth_cache_escape(const char *string,
+ const struct auth_request *auth_request ATTR_UNUSED)
+{
+ /* cache key %variables are separated by tabs, make sure that there
+ are no tabs in the string */
+ return str_tabescape(string);
+}
+
+static const char *
+auth_request_expand_cache_key(const struct auth_request *request,
+ const char *key, const char *username)
+{
+ static bool error_logged = FALSE;
+ const char *error;
+
+ /* Uniquely identify the request's passdb/userdb with the P/U prefix
+ and by "%!", which expands to the passdb/userdb ID number. */
+ key = t_strconcat(request->userdb_lookup ? "U" : "P", "%!",
+ request->fields.master_user == NULL ? "" : "+%{master_user}",
+ "\t", key, NULL);
+
+ /* It's fine to have unknown %variables in the cache key.
+ For example db-ldap can have pass_attrs containing
+ %{ldap:fields} which are used for output, not as part of
+ the input needed for cache_key. Those could in theory be
+ filtered out early in the cache_key, but that gets more
+ problematic when it needs to support also filtering out
+ e.g. %{sha256:ldap:fields}. */
+ string_t *value = t_str_new(128);
+ unsigned int count = 0;
+ const struct var_expand_table *table =
+ auth_request_get_var_expand_table_full(request,
+ username, auth_cache_escape, &count);
+ if (auth_request_var_expand_with_table(value, key, request, table,
+ auth_cache_escape, &error) < 0 &&
+ !error_logged) {
+ error_logged = TRUE;
+ e_error(authdb_event(request),
+ "Failed to expand auth cache key %s: %s", key, error);
+ }
+ return str_c(value);
+}
+
+const char *
+auth_cache_lookup(struct auth_cache *cache, const struct auth_request *request,
+ const char *key, struct auth_cache_node **node_r,
+ bool *expired_r, bool *neg_expired_r)
+{
+ struct auth_cache_node *node;
+ const char *value;
+ unsigned int ttl_secs;
+ time_t now;
+
+ *expired_r = FALSE;
+ *neg_expired_r = FALSE;
+
+ key = auth_request_expand_cache_key(request, key, request->fields.translated_username);
+ node = hash_table_lookup(cache->hash, key);
+ if (node == NULL) {
+ cache->miss_count++;
+ return NULL;
+ }
+
+ value = node->data + strlen(node->data) + 1;
+ ttl_secs = *value == '\0' ? cache->neg_ttl_secs : cache->ttl_secs;
+
+ now = time(NULL);
+ if (node->created < now - (time_t)ttl_secs) {
+ /* TTL expired */
+ cache->miss_count++;
+ *expired_r = TRUE;
+ } else {
+ /* move to head */
+ if (node != cache->head) {
+ auth_cache_node_unlink(cache, node);
+ auth_cache_node_link_head(cache, node);
+ }
+ cache->hit_count++;
+ }
+ if (node->created < now - (time_t)cache->neg_ttl_secs)
+ *neg_expired_r = TRUE;
+
+ if (node_r != NULL)
+ *node_r = node;
+
+ return value;
+}
+
+void auth_cache_insert(struct auth_cache *cache, struct auth_request *request,
+ const char *key, const char *value, bool last_success)
+{
+ struct auth_cache_node *node;
+ size_t data_size, alloc_size, key_len, value_len = strlen(value);
+ char *hash_key;
+
+ if (*value == '\0' && cache->neg_ttl_secs == 0) {
+ /* we're not caching negative entries */
+ return;
+ }
+
+ key = auth_request_expand_cache_key(request, key, request->fields.translated_username);
+ key_len = strlen(key);
+
+ data_size = key_len + 1 + value_len + 1;
+ alloc_size = sizeof(struct auth_cache_node) + data_size;
+
+ /* make sure we have enough space */
+ while (cache->size_left < alloc_size && cache->tail != NULL)
+ auth_cache_node_destroy(cache, cache->tail);
+
+ node = hash_table_lookup(cache->hash, key);
+ if (node != NULL) {
+ /* key is already in cache (probably expired), remove it */
+ auth_cache_node_destroy(cache, node);
+ }
+
+ /* @UNSAFE */
+ node = i_malloc(alloc_size);
+ node->created = time(NULL);
+ node->alloc_size = alloc_size;
+ node->last_success = last_success;
+ memcpy(node->data, key, key_len);
+ memcpy(node->data + key_len + 1, value, value_len);
+
+ auth_cache_node_link_head(cache, node);
+
+ cache->size_left -= alloc_size;
+ hash_key = node->data;
+ hash_table_insert(cache->hash, hash_key, node);
+
+ if (*value != '\0') {
+ cache->pos_entries++;
+ cache->pos_size += alloc_size;
+ } else {
+ cache->neg_entries++;
+ cache->neg_size += alloc_size;
+ }
+}
+
+void auth_cache_remove(struct auth_cache *cache,
+ const struct auth_request *request, const char *key)
+{
+ struct auth_cache_node *node;
+
+ key = auth_request_expand_cache_key(request, key, request->fields.user);
+ node = hash_table_lookup(cache->hash, key);
+ if (node == NULL)
+ return;
+
+ auth_cache_node_destroy(cache, node);
+}
diff --git a/src/auth/auth-cache.h b/src/auth/auth-cache.h
new file mode 100644
index 0000000..ab02fe5
--- /dev/null
+++ b/src/auth/auth-cache.h
@@ -0,0 +1,53 @@
+#ifndef AUTH_CACHE_H
+#define AUTH_CACHE_H
+
+struct auth_cache_node {
+ struct auth_cache_node *prev, *next;
+
+ time_t created;
+ /* Total number of bytes used by this node */
+ uint32_t alloc_size:31;
+ /* TRUE if the user gave the correct password the last time. */
+ bool last_success:1;
+
+ char data[]; /* key \0 value \0 */
+};
+
+struct auth_cache;
+struct auth_request;
+
+/* Parses all %x variables from query and compresses them into tab-separated
+ list, so it can be used as a cache key. */
+char *auth_cache_parse_key(pool_t pool, const char *query);
+
+/* Create a new cache. max_size specifies the maximum amount of memory in
+ bytes to use for cache (it's not fully exact). ttl_secs specifies time to
+ live for cache record, requests older than that are not used.
+ neg_ttl_secs specifies the TTL for negative entries. */
+struct auth_cache *auth_cache_new(size_t max_size, unsigned int ttl_secs,
+ unsigned int neg_ttl_secs);
+void auth_cache_free(struct auth_cache **cache);
+
+/* Clear the cache. Returns how many entries were removed. */
+unsigned int ATTR_NOWARN_UNUSED_RESULT
+auth_cache_clear(struct auth_cache *cache);
+unsigned int auth_cache_clear_users(struct auth_cache *cache,
+ const char *const *usernames);
+
+/* Look key from cache. key should be the same string as returned by
+ auth_cache_parse_key(). Returned node can't be used after any other
+ auth_cache_*() calls. */
+const char *
+auth_cache_lookup(struct auth_cache *cache, const struct auth_request *request,
+ const char *key, struct auth_cache_node **node_r,
+ bool *expired_r, bool *neg_expired_r);
+/* Insert key => value into cache. "" value means negative cache entry. */
+void auth_cache_insert(struct auth_cache *cache, struct auth_request *request,
+ const char *key, const char *value, bool last_success);
+
+/* Remove key from cache */
+void auth_cache_remove(struct auth_cache *cache,
+ const struct auth_request *request,
+ const char *key);
+
+#endif
diff --git a/src/auth/auth-client-connection.c b/src/auth/auth-client-connection.c
new file mode 100644
index 0000000..961480e
--- /dev/null
+++ b/src/auth/auth-client-connection.c
@@ -0,0 +1,456 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "net.h"
+#include "hex-binary.h"
+#include "hostpid.h"
+#include "llist.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "randgen.h"
+#include "safe-memset.h"
+#include "master-service.h"
+#include "mech.h"
+#include "auth-fields.h"
+#include "auth-request-handler.h"
+#include "auth-client-interface.h"
+#include "auth-client-connection.h"
+#include "auth-master-connection.h"
+
+
+#define OUTBUF_THROTTLE_SIZE (1024*50)
+
+#define AUTH_DEBUG_SENSITIVE_SUFFIX \
+ " (previous base64 data may contain sensitive data)"
+
+static void auth_client_disconnected(struct auth_client_connection **_conn);
+static void auth_client_connection_unref(struct auth_client_connection **_conn);
+static void auth_client_input(struct auth_client_connection *conn);
+
+static struct auth_client_connection *auth_client_connections;
+
+static const char *reply_line_hide_pass(const char *line)
+{
+ string_t *newline;
+ const char *p, *p2;
+
+ if (strstr(line, "pass") == NULL)
+ return line;
+
+ newline = t_str_new(strlen(line));
+
+ const char *const *fields = t_strsplit(line, "\t");
+
+ while(*fields != NULL) {
+ p = strstr(*fields, "pass");
+ p2 = strchr(*fields, '=');
+ if (p == NULL || p2 == NULL || p2 < p) {
+ str_append(newline, *fields);
+ } else {
+ /* include = */
+ str_append_data(newline, *fields, (p2 - *fields)+1);
+ str_append(newline, PASSWORD_HIDDEN_STR);
+ }
+ str_append_c(newline, '\t');
+ fields++;
+ }
+
+ return str_c(newline);
+}
+
+static void auth_client_send(struct auth_client_connection *conn,
+ const char *cmd)
+{
+ struct const_iovec iov[2];
+
+ iov[0].iov_base = cmd;
+ iov[0].iov_len = strlen(cmd);
+ iov[1].iov_base = "\n";
+ iov[1].iov_len = 1;
+ o_stream_nsendv(conn->output, iov, 2);
+
+ if (o_stream_get_buffer_used_size(conn->output) >=
+ OUTBUF_THROTTLE_SIZE) {
+ /* stop reading new requests until client has read the pending
+ replies. */
+ io_remove(&conn->io);
+ }
+
+ e_debug(conn->event, "client passdb out: %s",
+ conn->auth->set->debug_passwords ?
+ cmd : reply_line_hide_pass(cmd));
+}
+
+static void auth_callback(const char *reply,
+ struct auth_client_connection *conn)
+{
+ if (reply == NULL) {
+ /* handler destroyed */
+ auth_client_connection_unref(&conn);
+ } else {
+ auth_client_send(conn, reply);
+ }
+}
+
+static bool
+auth_client_input_cpid(struct auth_client_connection *conn, const char *args)
+{
+ struct auth_client_connection *old;
+ unsigned int pid;
+
+ i_assert(conn->pid == 0);
+
+ if (str_to_uint(args, &pid) < 0 || pid == 0) {
+ e_error(conn->event, "BUG: Authentication client said it's PID 0");
+ return FALSE;
+ }
+
+ if (conn->login_requests)
+ old = auth_client_connection_lookup(pid);
+ else {
+ /* the client is only authenticating, not logging in.
+ the PID isn't necessary, and since we allow authentication
+ via TCP sockets the PIDs may conflict, so ignore them. */
+ old = NULL;
+ pid = 0;
+ }
+
+ if (old != NULL) {
+ /* already exists. it's possible that it just reconnected,
+ see if the old connection is still there. */
+ i_assert(old != conn);
+ if (i_stream_read(old->input) == -1) {
+ auth_client_disconnected(&old);
+ old = NULL;
+ }
+ }
+
+ if (old != NULL) {
+ e_error(conn->event, "BUG: Authentication client gave a PID "
+ "%u of existing connection", pid);
+ return FALSE;
+ }
+
+ /* handshake complete, we can now actually start serving requests */
+ conn->refcount++;
+ conn->request_handler =
+ auth_request_handler_create(conn->token_auth, auth_callback, conn,
+ !conn->login_requests ? NULL :
+ auth_master_request_callback);
+ auth_request_handler_set(conn->request_handler, conn->connect_uid, pid);
+
+ conn->pid = pid;
+ e_debug(conn->event, "auth client connected (pid=%u)", conn->pid);
+ return TRUE;
+}
+
+static int auth_client_output(struct auth_client_connection *conn)
+{
+ if (o_stream_flush(conn->output) < 0) {
+ auth_client_disconnected(&conn);
+ return 1;
+ }
+
+ if (o_stream_get_buffer_used_size(conn->output) <=
+ OUTBUF_THROTTLE_SIZE/3 && conn->io == NULL) {
+ /* allow input again */
+ conn->io = io_add(conn->fd, IO_READ, auth_client_input, conn);
+ }
+ return 1;
+}
+
+static const char *
+auth_line_hide_pass(struct auth_client_connection *conn, const char *line)
+{
+ const char *p, *p2;
+
+ p = strstr(line, "\tresp=");
+ if (p == NULL)
+ return line;
+ p += 6;
+
+ if (conn->auth->set->debug_passwords)
+ return t_strconcat(line, AUTH_DEBUG_SENSITIVE_SUFFIX, NULL);
+
+ p2 = strchr(p, '\t');
+ return t_strconcat(t_strdup_until(line, p), PASSWORD_HIDDEN_STR,
+ p2, NULL);
+}
+
+static const char *
+cont_line_hide_pass(struct auth_client_connection *conn, const char *line)
+{
+ const char *p;
+
+ if (conn->auth->set->debug_passwords)
+ return t_strconcat(line, AUTH_DEBUG_SENSITIVE_SUFFIX, NULL);
+
+ p = strchr(line, '\t');
+ if (p == NULL)
+ return line;
+
+ return t_strconcat(t_strdup_until(line, p), PASSWORD_HIDDEN_STR, NULL);
+}
+
+static bool
+auth_client_cancel(struct auth_client_connection *conn, const char *line)
+{
+ unsigned int client_id;
+
+ if (str_to_uint(line, &client_id) < 0) {
+ e_error(conn->event, "BUG: Authentication client sent broken CANCEL");
+ return FALSE;
+ }
+
+ auth_request_handler_cancel_request(conn->request_handler, client_id);
+ return TRUE;
+}
+
+static bool
+auth_client_handle_line(struct auth_client_connection *conn, const char *line)
+{
+ if (str_begins(line, "AUTH\t")) {
+ if (conn->auth->set->debug) {
+ e_debug(conn->event, "client in: %s",
+ auth_line_hide_pass(conn, line));
+ }
+ return auth_request_handler_auth_begin(conn->request_handler,
+ line + 5);
+ }
+ if (str_begins(line, "CONT\t")) {
+ if (conn->auth->set->debug) {
+ e_debug(conn->event, "client in: %s",
+ cont_line_hide_pass(conn, line));
+ }
+ return auth_request_handler_auth_continue(conn->request_handler,
+ line + 5);
+ }
+ if (str_begins(line, "CANCEL\t")) {
+ if (conn->auth->set->debug)
+ e_debug(conn->event, "client in: %s", line);
+ return auth_client_cancel(conn, line + 7);
+ }
+
+ e_error(conn->event, "BUG: Authentication client sent unknown command: %s",
+ str_sanitize(line, 80));
+ return FALSE;
+}
+
+static void auth_client_input(struct auth_client_connection *conn)
+{
+ char *line;
+ bool ret;
+
+ switch (i_stream_read(conn->input)) {
+ case 0:
+ return;
+ case -1:
+ /* disconnected */
+ auth_client_disconnected(&conn);
+ return;
+ case -2:
+ /* buffer full */
+ e_error(conn->event, "BUG: Auth client %u sent us more than %d bytes",
+ conn->pid, (int)AUTH_CLIENT_MAX_LINE_LENGTH);
+ auth_client_connection_destroy(&conn);
+ return;
+ }
+
+ while (conn->request_handler == NULL) {
+ /* still handshaking */
+ line = i_stream_next_line(conn->input);
+ if (line == NULL)
+ return;
+
+ if (!conn->version_received) {
+ unsigned int vmajor, vminor;
+ const char *p;
+
+ /* split the version line */
+ if (!str_begins(line, "VERSION\t") ||
+ str_parse_uint(line + 8, &vmajor, &p) < 0 ||
+ *(p++) != '\t' || str_to_uint(p, &vminor) < 0) {
+ e_error(conn->event, "Authentication client "
+ "sent invalid VERSION line: %s", line);
+ auth_client_connection_destroy(&conn);
+ return;
+ }
+ /* make sure the major version matches */
+ if (vmajor != AUTH_MASTER_PROTOCOL_MAJOR_VERSION) {
+ e_error(conn->event, "Authentication client "
+ "not compatible with this server "
+ "(mixed old and new binaries?)");
+ auth_client_connection_destroy(&conn);
+ return;
+ }
+ conn->version_minor = vminor;
+ conn->version_received = TRUE;
+ continue;
+ }
+
+ if (str_begins(line, "CPID\t")) {
+ if (!auth_client_input_cpid(conn, line + 5)) {
+ auth_client_connection_destroy(&conn);
+ return;
+ }
+ } else {
+ e_error(conn->event, "BUG: Authentication client sent "
+ "unknown handshake command: %s",
+ str_sanitize(line, 80));
+ auth_client_connection_destroy(&conn);
+ return;
+ }
+ }
+
+ conn->refcount++;
+ while ((line = i_stream_next_line(conn->input)) != NULL) {
+ T_BEGIN {
+ ret = auth_client_handle_line(conn, line);
+ safe_memset(line, 0, strlen(line));
+ } T_END;
+
+ if (!ret) {
+ struct auth_client_connection *tmp_conn = conn;
+ auth_client_connection_destroy(&tmp_conn);
+ break;
+ }
+ }
+ auth_client_connection_unref(&conn);
+}
+
+void auth_client_connection_create(struct auth *auth, int fd,
+ bool login_requests, bool token_auth)
+{
+ static unsigned int connect_uid_counter = 0;
+ struct auth_client_connection *conn;
+ const char *mechanisms;
+ string_t *str;
+
+ conn = i_new(struct auth_client_connection, 1);
+ conn->auth = auth;
+ conn->refcount = 1;
+ conn->connect_uid = ++connect_uid_counter;
+ conn->login_requests = login_requests;
+ conn->token_auth = token_auth;
+ conn->event = event_create(auth_event);
+ event_set_forced_debug(conn->event, auth->set->debug);
+ random_fill(conn->cookie, sizeof(conn->cookie));
+
+ conn->fd = fd;
+ conn->input = i_stream_create_fd(fd, AUTH_CLIENT_MAX_LINE_LENGTH);
+ conn->output = o_stream_create_fd(fd, SIZE_MAX);
+ o_stream_set_no_error_handling(conn->output, TRUE);
+ o_stream_set_flush_callback(conn->output, auth_client_output, conn);
+ conn->io = io_add(fd, IO_READ, auth_client_input, conn);
+
+ DLLIST_PREPEND(&auth_client_connections, conn);
+
+ if (token_auth) {
+ mechanisms = t_strconcat("MECH\t",
+ mech_dovecot_token.mech_name, "\n", NULL);
+ } else {
+ mechanisms = str_c(auth->reg->handshake);
+ }
+
+ str = t_str_new(128);
+ str_printfa(str, "VERSION\t%u\t%u\n%sSPID\t%s\nCUID\t%u\nCOOKIE\t",
+ AUTH_CLIENT_PROTOCOL_MAJOR_VERSION,
+ AUTH_CLIENT_PROTOCOL_MINOR_VERSION,
+ mechanisms, my_pid, conn->connect_uid);
+ binary_to_hex_append(str, conn->cookie, sizeof(conn->cookie));
+ str_append(str, "\nDONE\n");
+
+ if (o_stream_send(conn->output, str_data(str), str_len(str)) < 0)
+ auth_client_disconnected(&conn);
+}
+
+void auth_client_connection_destroy(struct auth_client_connection **_conn)
+{
+ struct auth_client_connection *conn = *_conn;
+
+ *_conn = NULL;
+ if (conn->fd == -1)
+ return;
+
+ DLLIST_REMOVE(&auth_client_connections, conn);
+
+ i_stream_close(conn->input);
+ o_stream_close(conn->output);
+
+ io_remove(&conn->io);
+
+ net_disconnect(conn->fd);
+ conn->fd = -1;
+
+ if (conn->request_handler != NULL) {
+ auth_request_handler_abort_requests(conn->request_handler);
+ auth_request_handler_destroy(&conn->request_handler);
+ }
+
+ master_service_client_connection_destroyed(master_service);
+ auth_client_connection_unref(&conn);
+}
+
+static void auth_client_disconnected(struct auth_client_connection **_conn)
+{
+ struct auth_client_connection *conn = *_conn;
+ unsigned int request_count;
+ int err;
+
+ *_conn = NULL;
+
+ if (conn->input->stream_errno != 0)
+ err = conn->input->stream_errno;
+ else if (conn->output->stream_errno != 0)
+ err = conn->output->stream_errno;
+ else
+ err = 0;
+
+ request_count = conn->request_handler == NULL ? 0 :
+ auth_request_handler_get_request_count(conn->request_handler);
+ if (request_count > 0) {
+ e_error(conn->event, "auth client %u disconnected with %u "
+ "pending requests: %s", conn->pid, request_count,
+ err == 0 ? "EOF" : strerror(err));
+ }
+ auth_client_connection_destroy(&conn);
+}
+
+static void auth_client_connection_unref(struct auth_client_connection **_conn)
+{
+ struct auth_client_connection *conn = *_conn;
+
+ *_conn = NULL;
+ if (--conn->refcount > 0)
+ return;
+
+ event_unref(&conn->event);
+ i_stream_unref(&conn->input);
+ o_stream_unref(&conn->output);
+ i_free(conn);
+}
+
+struct auth_client_connection *
+auth_client_connection_lookup(unsigned int pid)
+{
+ struct auth_client_connection *conn;
+
+ for (conn = auth_client_connections; conn != NULL; conn = conn->next) {
+ if (conn->pid == pid)
+ return conn;
+ }
+ return NULL;
+}
+
+void auth_client_connections_destroy_all(void)
+{
+ struct auth_client_connection *conn;
+
+ while (auth_client_connections != NULL) {
+ conn = auth_client_connections;
+ auth_client_connection_destroy(&conn);
+ }
+}
diff --git a/src/auth/auth-client-connection.h b/src/auth/auth-client-connection.h
new file mode 100644
index 0000000..7b10f76
--- /dev/null
+++ b/src/auth/auth-client-connection.h
@@ -0,0 +1,37 @@
+#ifndef AUTH_CLIENT_CONNECTION_H
+#define AUTH_CLIENT_CONNECTION_H
+
+#include "master-auth.h"
+
+struct auth_client_connection {
+ struct auth_client_connection *prev, *next;
+ struct auth *auth;
+ struct event *event;
+ int refcount;
+
+ int fd;
+ struct io *io;
+ struct istream *input;
+ struct ostream *output;
+
+ unsigned int version_minor;
+ unsigned int pid;
+ unsigned int connect_uid;
+ uint8_t cookie[MASTER_AUTH_COOKIE_SIZE];
+ struct auth_request_handler *request_handler;
+
+ bool login_requests:1;
+ bool version_received:1;
+ bool token_auth:1;
+};
+
+void auth_client_connection_create(struct auth *auth, int fd,
+ bool login_requests, bool token_auth);
+void auth_client_connection_destroy(struct auth_client_connection **conn);
+
+struct auth_client_connection *
+auth_client_connection_lookup(unsigned int pid);
+
+void auth_client_connections_destroy_all(void);
+
+#endif
diff --git a/src/auth/auth-common.h b/src/auth/auth-common.h
new file mode 100644
index 0000000..5ebe8c4
--- /dev/null
+++ b/src/auth/auth-common.h
@@ -0,0 +1,17 @@
+#ifndef AUTH_COMMON_H
+#define AUTH_COMMON_H
+
+#include "lib.h"
+#include "auth.h"
+
+extern bool worker, worker_restart_request;
+extern time_t process_start_time;
+extern struct auth_penalty *auth_penalty;
+extern struct event_category event_category_auth;
+extern struct event *auth_event;
+
+void auth_refresh_proctitle(void);
+void auth_worker_refresh_proctitle(const char *state);
+void auth_module_load(const char *names);
+
+#endif
diff --git a/src/auth/auth-fields.c b/src/auth/auth-fields.c
new file mode 100644
index 0000000..0771390
--- /dev/null
+++ b/src/auth/auth-fields.c
@@ -0,0 +1,226 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "ostream.h"
+#include "auth-request.h"
+#include "auth-fields.h"
+
+struct auth_fields {
+ pool_t pool;
+ ARRAY_TYPE(auth_field) fields, snapshot_fields;
+ unsigned int snapshot_idx;
+ bool snapshotted;
+};
+
+struct auth_fields *auth_fields_init(pool_t pool)
+{
+ struct auth_fields *fields;
+
+ fields = p_new(pool, struct auth_fields, 1);
+ fields->pool = pool;
+ return fields;
+}
+
+static void auth_fields_snapshot_preserve(struct auth_fields *fields)
+{
+ if (!fields->snapshotted || array_is_created(&fields->snapshot_fields))
+ return;
+
+ p_array_init(&fields->snapshot_fields, fields->pool,
+ array_count(&fields->fields));
+ array_append_array(&fields->snapshot_fields, &fields->fields);
+}
+
+static bool
+auth_fields_find_idx(struct auth_fields *fields, const char *key,
+ unsigned int *idx_r)
+{
+ const struct auth_field *f;
+ unsigned int i, count;
+
+ if (!array_is_created(&fields->fields))
+ return FALSE;
+
+ f = array_get(&fields->fields, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(f[i].key, key) == 0) {
+ *idx_r = i;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void auth_fields_add(struct auth_fields *fields,
+ const char *key, const char *value,
+ enum auth_field_flags flags)
+{
+ struct auth_field *field;
+ unsigned int idx;
+
+ i_assert(*key != '\0');
+ i_assert(strchr(key, '\t') == NULL &&
+ strchr(key, '\n') == NULL);
+
+ if (!auth_fields_find_idx(fields, key, &idx)) {
+ if (!array_is_created(&fields->fields))
+ p_array_init(&fields->fields, fields->pool, 16);
+
+ field = array_append_space(&fields->fields);
+ field->key = p_strdup(fields->pool, key);
+ } else {
+ auth_fields_snapshot_preserve(fields);
+ field = array_idx_modifiable(&fields->fields, idx);
+ }
+ field->value = p_strdup_empty(fields->pool, value);
+ field->flags = flags | AUTH_FIELD_FLAG_CHANGED;
+}
+
+void auth_fields_remove(struct auth_fields *fields, const char *key)
+{
+ unsigned int idx;
+
+ if (auth_fields_find_idx(fields, key, &idx)) {
+ auth_fields_snapshot_preserve(fields);
+ array_delete(&fields->fields, idx, 1);
+ }
+}
+
+const char *auth_fields_find(struct auth_fields *fields, const char *key)
+{
+ const struct auth_field *field;
+ unsigned int idx;
+
+ if (!auth_fields_find_idx(fields, key, &idx))
+ return NULL;
+
+ field = array_idx(&fields->fields, idx);
+ return field->value == NULL ? "" : field->value;
+}
+
+bool auth_fields_exists(struct auth_fields *fields, const char *key)
+{
+ return auth_fields_find(fields, key) != NULL;
+}
+
+void auth_fields_reset(struct auth_fields *fields)
+{
+ if (array_is_created(&fields->fields)) {
+ auth_fields_snapshot_preserve(fields);
+ array_clear(&fields->fields);
+ }
+}
+
+void auth_fields_import_prefixed(struct auth_fields *fields, const char *prefix,
+ const char *str, enum auth_field_flags flags)
+{
+ T_BEGIN {
+ const char *const *arg = t_strsplit_tabescaped(str);
+ const char *key, *value;
+
+ for (; *arg != NULL; arg++) {
+ value = strchr(*arg, '=');
+ if (value == NULL) {
+ key = *arg;
+ value = NULL;
+ } else {
+ key = t_strdup_until(*arg, value++);
+ if (*prefix != '\0')
+ key = t_strconcat(prefix, key, NULL);
+ }
+ auth_fields_add(fields, key, value, flags);
+ }
+ } T_END;
+}
+
+void auth_fields_import(struct auth_fields *fields, const char *str,
+ enum auth_field_flags flags)
+{
+ auth_fields_import_prefixed(fields, "", str, flags);
+}
+
+const ARRAY_TYPE(auth_field) *auth_fields_export(struct auth_fields *fields)
+{
+ if (!array_is_created(&fields->fields))
+ p_array_init(&fields->fields, fields->pool, 1);
+ return &fields->fields;
+}
+
+void auth_fields_append(struct auth_fields *fields, string_t *dest,
+ enum auth_field_flags flags_mask,
+ enum auth_field_flags flags_result)
+{
+ const struct auth_field *f;
+ unsigned int i, count;
+ bool first = TRUE;
+
+ if (!array_is_created(&fields->fields))
+ return;
+
+ f = array_get(&fields->fields, &count);
+ for (i = 0; i < count; i++) {
+ if ((f[i].flags & flags_mask) != flags_result)
+ continue;
+
+ if (first)
+ first = FALSE;
+ else
+ str_append_c(dest, '\t');
+ str_append(dest, f[i].key);
+ if (f[i].value != NULL) {
+ str_append_c(dest, '=');
+ str_append_tabescaped(dest, f[i].value);
+ }
+ }
+}
+
+bool auth_fields_is_empty(struct auth_fields *fields)
+{
+ return fields == NULL || !array_is_created(&fields->fields) ||
+ array_count(&fields->fields) == 0;
+}
+
+void auth_fields_booleanize(struct auth_fields *fields, const char *key)
+{
+ struct auth_field *field;
+ unsigned int idx;
+
+ if (auth_fields_find_idx(fields, key, &idx)) {
+ field = array_idx_modifiable(&fields->fields, idx);
+ field->value = NULL;
+ }
+}
+
+void auth_fields_snapshot(struct auth_fields *fields)
+{
+ struct auth_field *field;
+
+ fields->snapshotted = TRUE;
+ if (!array_is_created(&fields->fields))
+ return;
+
+ if (!array_is_created(&fields->snapshot_fields)) {
+ /* try to avoid creating this array */
+ fields->snapshot_idx = array_count(&fields->fields);
+ } else {
+ array_clear(&fields->snapshot_fields);
+ array_append_array(&fields->snapshot_fields, &fields->fields);
+ }
+ array_foreach_modifiable(&fields->fields, field)
+ field->flags &= ENUM_NEGATE(AUTH_FIELD_FLAG_CHANGED);
+}
+
+void auth_fields_rollback(struct auth_fields *fields)
+{
+ if (array_is_created(&fields->snapshot_fields)) {
+ array_clear(&fields->fields);
+ array_append_array(&fields->fields, &fields->snapshot_fields);
+ } else if (array_is_created(&fields->fields)) {
+ array_delete(&fields->fields, fields->snapshot_idx,
+ array_count(&fields->fields) -
+ fields->snapshot_idx);
+ }
+}
diff --git a/src/auth/auth-fields.h b/src/auth/auth-fields.h
new file mode 100644
index 0000000..25c91c7
--- /dev/null
+++ b/src/auth/auth-fields.h
@@ -0,0 +1,48 @@
+#ifndef AUTH_FIELDS_H
+#define AUTH_FIELDS_H
+
+struct auth_request;
+
+enum auth_field_flags {
+ /* This field is internal to auth process and won't be sent to client */
+ AUTH_FIELD_FLAG_HIDDEN = 0x01,
+ /* Changed since last snapshot. Set/cleared automatically. */
+ AUTH_FIELD_FLAG_CHANGED = 0x02
+};
+
+struct auth_field {
+ const char *key, *value;
+ enum auth_field_flags flags;
+};
+ARRAY_DEFINE_TYPE(auth_field, struct auth_field);
+
+struct auth_fields *auth_fields_init(pool_t pool);
+void auth_fields_add(struct auth_fields *fields,
+ const char *key, const char *value,
+ enum auth_field_flags flags) ATTR_NULL(3);
+void auth_fields_reset(struct auth_fields *fields);
+void auth_fields_remove(struct auth_fields *fields, const char *key);
+
+const char *auth_fields_find(struct auth_fields *fields, const char *key);
+bool auth_fields_exists(struct auth_fields *fields, const char *key);
+
+void auth_fields_import(struct auth_fields *fields, const char *str,
+ enum auth_field_flags flags);
+void auth_fields_import_prefixed(struct auth_fields *fields, const char *prefix,
+ const char *str, enum auth_field_flags flags);
+const ARRAY_TYPE(auth_field) *auth_fields_export(struct auth_fields *fields);
+/* Append fields where (flag & flags_mask) == flags_result. */
+void auth_fields_append(struct auth_fields *fields, string_t *dest,
+ enum auth_field_flags flags_mask,
+ enum auth_field_flags flags_result);
+bool auth_fields_is_empty(struct auth_fields *fields);
+/* If the field exists, clear its value (so the exported string will be "key"
+ instead of e.g. "key=y"). */
+void auth_fields_booleanize(struct auth_fields *fields, const char *key);
+
+/* Remember the current fields. */
+void auth_fields_snapshot(struct auth_fields *fields);
+/* Rollback to previous snapshot, or clear the fields if there isn't any. */
+void auth_fields_rollback(struct auth_fields *fields);
+
+#endif
diff --git a/src/auth/auth-master-connection.c b/src/auth/auth-master-connection.c
new file mode 100644
index 0000000..3f439b8
--- /dev/null
+++ b/src/auth/auth-master-connection.c
@@ -0,0 +1,855 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "buffer.h"
+#include "hash.h"
+#include "llist.h"
+#include "str.h"
+#include "strescape.h"
+#include "str-sanitize.h"
+#include "time-util.h"
+#include "hostpid.h"
+#include "hex-binary.h"
+#include "ioloop.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "ipwd.h"
+#include "master-service.h"
+#include "userdb.h"
+#include "userdb-blocking.h"
+#include "master-interface.h"
+#include "passdb-cache.h"
+#include "auth-request-handler.h"
+#include "auth-client-connection.h"
+#include "auth-master-connection.h"
+
+#include <unistd.h>
+
+#define MAX_INBUF_SIZE 1024
+#define MAX_OUTBUF_SIZE (1024*50)
+
+struct master_userdb_request {
+ struct auth_master_connection *conn;
+ struct auth_request *auth_request;
+};
+
+struct master_list_iter_ctx {
+ struct auth_master_connection *conn;
+ struct userdb_iterate_context *iter;
+ struct auth_request *auth_request;
+ bool failed;
+};
+
+static void master_input(struct auth_master_connection *conn);
+
+static struct auth_master_connection *auth_master_connections;
+
+static const char *
+auth_master_reply_hide_passwords(struct auth_master_connection *conn,
+ const char *str)
+{
+ char **args, *p, *p2;
+ unsigned int i;
+
+ if (conn->auth->set->debug_passwords)
+ return str;
+
+ /* hide all parameters that have "pass" in their key */
+ args = p_strsplit(pool_datastack_create(), str, "\t");
+ for (i = 0; args[i] != NULL; i++) {
+ p = strstr(args[i], "pass");
+ p2 = strchr(args[i], '=');
+ if (p != NULL && p < p2) {
+ *p2 = '\0';
+ args[i] = p_strconcat(pool_datastack_create(),
+ args[i], "=<hidden>", NULL);
+ }
+ }
+ return t_strarray_join((void *)args, "\t");
+}
+
+void auth_master_request_callback(const char *reply, struct auth_master_connection *conn)
+{
+ struct const_iovec iov[2];
+
+ e_debug(auth_event, "master userdb out: %s",
+ auth_master_reply_hide_passwords(conn, reply));
+
+ iov[0].iov_base = reply;
+ iov[0].iov_len = strlen(reply);
+ iov[1].iov_base = "\n";
+ iov[1].iov_len = 1;
+
+ o_stream_nsendv(conn->output, iov, 2);
+}
+
+static const char *
+auth_master_event_log_callback(struct auth_master_connection *conn,
+ enum log_type log_type ATTR_UNUSED,
+ const char *message)
+{
+ string_t *str = t_str_new(128);
+
+ str_printfa(str, "auth-master client: %s (created %d msecs ago", message,
+ timeval_diff_msecs(&ioloop_timeval, &conn->create_time));
+ if (conn->handshake_time.tv_sec != 0) {
+ str_printfa(str, ", handshake %d msecs ago",
+ timeval_diff_msecs(&ioloop_timeval, &conn->create_time));
+ }
+ str_append_c(str, ')');
+ return str_c(str);
+}
+
+static bool
+master_input_request(struct auth_master_connection *conn, const char *args)
+{
+ struct auth_client_connection *client_conn;
+ const char *const *list, *const *params;
+ unsigned int id, client_pid, client_id;
+ uint8_t cookie[MASTER_AUTH_COOKIE_SIZE];
+ buffer_t buf;
+
+ /* <id> <client-pid> <client-id> <cookie> [<parameters>] */
+ list = t_strsplit_tabescaped(args);
+ if (str_array_length(list) < 4 ||
+ str_to_uint(list[0], &id) < 0 ||
+ str_to_uint(list[1], &client_pid) < 0 ||
+ str_to_uint(list[2], &client_id) < 0) {
+ e_error(conn->event, "BUG: Master sent broken REQUEST");
+ return FALSE;
+ }
+
+ buffer_create_from_data(&buf, cookie, sizeof(cookie));
+ if (hex_to_binary(list[3], &buf) < 0) {
+ e_error(conn->event, "BUG: Master sent broken REQUEST cookie");
+ return FALSE;
+ }
+ params = list + 4;
+
+ client_conn = auth_client_connection_lookup(client_pid);
+ if (client_conn == NULL) {
+ e_error(conn->event,
+ "Master requested auth for nonexistent client %u",
+ client_pid);
+ o_stream_nsend_str(conn->output,
+ t_strdup_printf("FAIL\t%u\n", id));
+ } else if (!mem_equals_timing_safe(client_conn->cookie, cookie, sizeof(cookie))) {
+ e_error(conn->event,
+ "Master requested auth for client %u with invalid cookie",
+ client_pid);
+ o_stream_nsend_str(conn->output,
+ t_strdup_printf("FAIL\t%u\n", id));
+ } else if (!auth_request_handler_master_request(
+ client_conn->request_handler, conn, id, client_id, params)) {
+ e_error(conn->event,
+ "Master requested auth for non-login client %u",
+ client_pid);
+ o_stream_nsend_str(conn->output,
+ t_strdup_printf("FAIL\t%u\n", id));
+ }
+ return TRUE;
+}
+
+static bool
+master_input_cache_flush(struct auth_master_connection *conn, const char *args)
+{
+ const char *const *list;
+ unsigned int count;
+
+ /* <id> [<user> [<user> [..]] */
+ list = t_strsplit_tabescaped(args);
+ if (list[0] == NULL) {
+ e_error(conn->event, "BUG: doveadm sent broken CACHE-FLUSH");
+ return FALSE;
+ }
+
+ if (passdb_cache == NULL) {
+ /* cache disabled */
+ count = 0;
+ } else if (list[1] == NULL) {
+ /* flush the whole cache */
+ count = auth_cache_clear(passdb_cache);
+ } else {
+ count = auth_cache_clear_users(passdb_cache, list+1);
+ }
+ o_stream_nsend_str(conn->output,
+ t_strdup_printf("OK\t%s\t%u\n", list[0], count));
+ return TRUE;
+}
+
+static int
+master_input_auth_request(struct auth_master_connection *conn, const char *args,
+ const char *cmd, struct auth_request **request_r,
+ const char **error_r)
+{
+ struct auth_request *auth_request;
+ const char *const *list, *name, *arg, *username;
+ unsigned int id;
+
+ /* <id> <userid> [<parameters>] */
+ list = t_strsplit_tabescaped(args);
+ if (list[0] == NULL || list[1] == NULL ||
+ str_to_uint(list[0], &id) < 0) {
+ e_error(conn->event, "BUG: Master sent broken %s", cmd);
+ return -1;
+ }
+
+ auth_request = auth_request_new_dummy(auth_event);
+ auth_request->id = id;
+ auth_request->master = conn;
+ auth_master_connection_ref(conn);
+ username = list[1];
+
+ for (list += 2; *list != NULL; list++) {
+ arg = strchr(*list, '=');
+ if (arg == NULL) {
+ name = *list;
+ arg = "";
+ } else {
+ name = t_strdup_until(*list, arg);
+ arg++;
+ }
+
+ (void)auth_request_import_info(auth_request, name, arg);
+ }
+
+ if (auth_request->fields.service == NULL) {
+ e_error(conn->event,
+ "BUG: Master sent %s request without service", cmd);
+ auth_request_unref(&auth_request);
+ auth_master_connection_unref(&conn);
+ return -1;
+ }
+
+ auth_request_init(auth_request);
+
+ if (!auth_request_set_username(auth_request, username, error_r)) {
+ *request_r = auth_request;
+ return 0;
+ }
+ *request_r = auth_request;
+ return 1;
+}
+
+static int
+user_verify_restricted_uid(struct auth_request *auth_request)
+{
+ struct auth_master_connection *conn = auth_request->master;
+ struct auth_fields *reply = auth_request->fields.userdb_reply;
+ const char *value, *reason;
+ uid_t uid;
+
+ if (conn->userdb_restricted_uid == 0)
+ return 0;
+
+ value = auth_fields_find(reply, "uid");
+ if (value == NULL)
+ reason = "userdb reply doesn't contain uid";
+ else if (str_to_uid(value, &uid) < 0)
+ reason = "userdb reply contains invalid uid";
+ else if (uid != conn->userdb_restricted_uid) {
+ reason = t_strdup_printf(
+ "userdb uid (%s) doesn't match peer uid (%s)",
+ dec2str(uid), dec2str(conn->userdb_restricted_uid));
+ } else {
+ return 0;
+ }
+
+ auth_request_log_error(auth_request, "userdb",
+ "client doesn't have lookup permissions for this user: %s "
+ "(to bypass this check, set: service auth { unix_listener %s { mode=0777 } })",
+ reason, conn->path);
+ return -1;
+}
+
+static void
+user_callback(enum userdb_result result,
+ struct auth_request *auth_request)
+{
+ struct auth_master_connection *conn = auth_request->master;
+ string_t *str;
+ const char *value;
+
+ if (auth_request->userdb_lookup_tempfailed)
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+
+ if (result == USERDB_RESULT_OK) {
+ if (user_verify_restricted_uid(auth_request) < 0)
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ str = t_str_new(128);
+ switch (result) {
+ case USERDB_RESULT_INTERNAL_FAILURE:
+ str_printfa(str, "FAIL\t%u", auth_request->id);
+ if (auth_request->userdb_lookup_tempfailed) {
+ value = auth_fields_find(auth_request->fields.userdb_reply,
+ "reason");
+ if (value != NULL)
+ str_printfa(str, "\treason=%s", value);
+ }
+ break;
+ case USERDB_RESULT_USER_UNKNOWN:
+ str_printfa(str, "NOTFOUND\t%u", auth_request->id);
+ break;
+ case USERDB_RESULT_OK:
+ str_printfa(str, "USER\t%u\t", auth_request->id);
+ str_append_tabescaped(str, auth_request->fields.user);
+ str_append_c(str, '\t');
+ auth_fields_append(auth_request->fields.userdb_reply, str,
+ AUTH_FIELD_FLAG_HIDDEN, 0);
+ break;
+ }
+
+ e_debug(auth_event, "userdb out: %s",
+ auth_master_reply_hide_passwords(conn, str_c(str)));
+
+ str_append_c(str, '\n');
+ o_stream_nsend(conn->output, str_data(str), str_len(str));
+
+ auth_request_unref(&auth_request);
+ auth_master_connection_unref(&conn);
+}
+
+static bool
+master_input_user(struct auth_master_connection *conn, const char *args)
+{
+ struct auth_request *auth_request;
+ const char *error;
+ int ret;
+
+ ret = master_input_auth_request(conn, args, "USER",
+ &auth_request, &error);
+ if (ret <= 0) {
+ if (ret < 0)
+ return FALSE;
+ auth_request_log_info(auth_request, "userdb", "%s", error);
+ user_callback(USERDB_RESULT_USER_UNKNOWN, auth_request);
+ } else {
+ auth_request_set_state(auth_request, AUTH_REQUEST_STATE_USERDB);
+ auth_request_lookup_user(auth_request, user_callback);
+ }
+ return TRUE;
+}
+
+static void pass_callback_finish(struct auth_request *auth_request,
+ enum passdb_result result)
+{
+ struct auth_master_connection *conn = auth_request->master;
+ string_t *str;
+
+ str = t_str_new(128);
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ if (auth_request->failed || !auth_request->passdb_success) {
+ str_printfa(str, "FAIL\t%u", auth_request->id);
+ break;
+ }
+ str_printfa(str, "PASS\t%u\tuser=", auth_request->id);
+ str_append_tabescaped(str, auth_request->fields.user);
+ if (!auth_fields_is_empty(auth_request->fields.extra_fields)) {
+ str_append_c(str, '\t');
+ auth_fields_append(auth_request->fields.extra_fields,
+ str, AUTH_FIELD_FLAG_HIDDEN, 0);
+ }
+ break;
+ case PASSDB_RESULT_USER_UNKNOWN:
+ case PASSDB_RESULT_USER_DISABLED:
+ case PASSDB_RESULT_PASS_EXPIRED:
+ str_printfa(str, "NOTFOUND\t%u", auth_request->id);
+ break;
+ case PASSDB_RESULT_NEXT:
+ case PASSDB_RESULT_PASSWORD_MISMATCH:
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ str_printfa(str, "FAIL\t%u", auth_request->id);
+ break;
+ case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+ str_printfa(str, "FAIL\t%u\treason=Configured passdbs don't support credentials lookups",
+ auth_request->id);
+ break;
+ }
+
+ e_debug(auth_event, "passdb out: %s", str_c(str));
+
+ str_append_c(str, '\n');
+ o_stream_nsend(conn->output, str_data(str), str_len(str));
+
+ auth_request_unref(&auth_request);
+ auth_master_connection_unref(&conn);
+}
+
+static void
+auth_master_pass_proxy_finish(bool success, struct auth_request *auth_request)
+{
+ pass_callback_finish(auth_request, success ? PASSDB_RESULT_OK :
+ PASSDB_RESULT_INTERNAL_FAILURE);
+}
+
+static void
+pass_callback(enum passdb_result result,
+ const unsigned char *credentials ATTR_UNUSED,
+ size_t size ATTR_UNUSED,
+ struct auth_request *auth_request)
+{
+ int ret;
+
+ if (result != PASSDB_RESULT_OK)
+ auth_request_proxy_finish_failure(auth_request);
+ else {
+ ret = auth_request_proxy_finish(auth_request,
+ auth_master_pass_proxy_finish);
+ if (ret == 0)
+ return;
+ if (ret < 0)
+ result = PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+ pass_callback_finish(auth_request, result);
+}
+
+static const char *auth_restricted_reason(struct auth_master_connection *conn)
+{
+ struct passwd pw;
+ const char *namestr;
+
+ if (i_getpwuid(conn->userdb_restricted_uid, &pw) <= 0)
+ namestr = "";
+ else
+ namestr = t_strdup_printf("(%s)", pw.pw_name);
+ return t_strdup_printf("%s mode=0666, but not owned by UID %lu%s",
+ conn->path,
+ (unsigned long)conn->userdb_restricted_uid,
+ namestr);
+}
+
+static bool
+master_input_pass(struct auth_master_connection *conn, const char *args)
+{
+ struct auth_request *auth_request;
+ const char *error;
+ int ret;
+
+ ret = master_input_auth_request(conn, args, "PASS",
+ &auth_request, &error);
+ if (ret <= 0) {
+ if (ret < 0)
+ return FALSE;
+ auth_request_log_info(auth_request, "passdb", "%s", error);
+ pass_callback(PASSDB_RESULT_USER_UNKNOWN,
+ uchar_empty_ptr, 0, auth_request);
+ } else if (conn->userdb_restricted_uid != 0) {
+ /* no permissions to do this lookup */
+ auth_request_log_error(auth_request, "passdb",
+ "Auth client doesn't have permissions to do "
+ "a PASS lookup: %s", auth_restricted_reason(conn));
+ pass_callback(PASSDB_RESULT_INTERNAL_FAILURE,
+ uchar_empty_ptr, 0, auth_request);
+ } else {
+ auth_request_set_state(auth_request,
+ AUTH_REQUEST_STATE_MECH_CONTINUE);
+ auth_request_lookup_credentials(auth_request, "",
+ pass_callback);
+ }
+ return TRUE;
+}
+
+static void master_input_list_finish(struct master_list_iter_ctx *ctx)
+{
+ i_assert(ctx->conn->iter_ctx == ctx);
+
+ ctx->conn->iter_ctx = NULL;
+ ctx->conn->io = io_add(ctx->conn->fd, IO_READ, master_input, ctx->conn);
+
+ if (ctx->iter != NULL)
+ (void)userdb_blocking_iter_deinit(&ctx->iter);
+ o_stream_uncork(ctx->conn->output);
+ o_stream_unset_flush_callback(ctx->conn->output);
+ auth_request_unref(&ctx->auth_request);
+ auth_master_connection_unref(&ctx->conn);
+ i_free(ctx);
+}
+
+static int master_output_list(struct master_list_iter_ctx *ctx)
+{
+ int ret;
+
+ if ((ret = o_stream_flush(ctx->conn->output)) < 0) {
+ master_input_list_finish(ctx);
+ return 1;
+ }
+ if (ret > 0) {
+ o_stream_cork(ctx->conn->output);
+ userdb_blocking_iter_next(ctx->iter);
+ }
+ return 1;
+}
+
+static void master_input_list_callback(const char *user, void *context)
+{
+ struct master_list_iter_ctx *ctx = context;
+ struct auth_userdb *userdb = ctx->auth_request->userdb;
+ int ret;
+
+ if (user == NULL) {
+ if (userdb_blocking_iter_deinit(&ctx->iter) < 0)
+ ctx->failed = TRUE;
+
+ do {
+ userdb = userdb->next;
+ } while (userdb != NULL &&
+ userdb->userdb->iface->iterate_init == NULL);
+ if (userdb == NULL) {
+ /* iteration is finished */
+ const char *str;
+
+ str = t_strdup_printf("DONE\t%u\t%s\n",
+ ctx->auth_request->id,
+ ctx->failed ? "fail" : "");
+ o_stream_nsend_str(ctx->conn->output, str);
+ master_input_list_finish(ctx);
+ return;
+ }
+
+ /* continue iterating next userdb */
+ ctx->auth_request->userdb = userdb;
+ ctx->iter = userdb_blocking_iter_init(ctx->auth_request,
+ master_input_list_callback, ctx);
+ return;
+ }
+
+ T_BEGIN {
+ const char *str;
+
+ str = t_strdup_printf("LIST\t%u\t%s\n", ctx->auth_request->id,
+ str_tabescape(user));
+ ret = o_stream_send_str(ctx->conn->output, str);
+ } T_END;
+ if (o_stream_get_buffer_used_size(ctx->conn->output) >= MAX_OUTBUF_SIZE)
+ ret = o_stream_flush(ctx->conn->output);
+ if (ret < 0) {
+ /* disconnected, don't bother finishing */
+ master_input_list_finish(ctx);
+ return;
+ }
+ if (o_stream_get_buffer_used_size(ctx->conn->output) < MAX_OUTBUF_SIZE)
+ userdb_blocking_iter_next(ctx->iter);
+ else
+ o_stream_uncork(ctx->conn->output);
+}
+
+static bool
+master_input_list(struct auth_master_connection *conn, const char *args)
+{
+ struct auth_userdb *userdb = conn->auth->userdbs;
+ struct auth_request *auth_request;
+ struct master_list_iter_ctx *ctx;
+ const char *str, *name, *arg, *const *list;
+ unsigned int id;
+
+ /* <id> [<parameters>] */
+ list = t_strsplit_tabescaped(args);
+ if (list[0] == NULL || str_to_uint(list[0], &id) < 0) {
+ e_error(conn->event, "BUG: Master sent broken LIST");
+ return FALSE;
+ }
+ list++;
+
+ if (conn->iter_ctx != NULL) {
+ e_error(conn->event,
+ "Auth client is already iterating users");
+ str = t_strdup_printf("DONE\t%u\tfail\n", id);
+ o_stream_nsend_str(conn->output, str);
+ return TRUE;
+ }
+
+ if (conn->userdb_restricted_uid != 0) {
+ e_error(conn->event,
+ "Auth client doesn't have permissions to list users: %s",
+ auth_restricted_reason(conn));
+ str = t_strdup_printf("DONE\t%u\tfail\n", id);
+ o_stream_nsend_str(conn->output, str);
+ return TRUE;
+ }
+
+ while (userdb != NULL && userdb->userdb->iface->iterate_init == NULL)
+ userdb = userdb->next;
+ if (userdb == NULL) {
+ e_error(conn->event,
+ "Trying to iterate users, but userdbs don't support it");
+ str = t_strdup_printf("DONE\t%u\tfail\n", id);
+ o_stream_nsend_str(conn->output, str);
+ return TRUE;
+ }
+
+ auth_request = auth_request_new_dummy(auth_event);
+ auth_request->id = id;
+ auth_request->master = conn;
+ auth_master_connection_ref(conn);
+
+ for (; *list != NULL; list++) {
+ arg = strchr(*list, '=');
+ if (arg == NULL) {
+ name = *list;
+ arg = "";
+ } else {
+ name = t_strdup_until(*list, arg);
+ arg++;
+ }
+
+ if (!auth_request_import_info(auth_request, name, arg) &&
+ strcmp(name, "user") == 0) {
+ /* username mask */
+ auth_request_set_username_forced(auth_request, arg);
+ }
+ }
+
+ /* rest of the code doesn't like NULL user or service */
+ if (auth_request->fields.user == NULL)
+ auth_request_set_username_forced(auth_request, "");
+ if (auth_request->fields.service == NULL) {
+ if (!auth_request_import(auth_request, "service", ""))
+ i_unreached();
+ i_assert(auth_request->fields.service != NULL);
+ }
+
+ ctx = i_new(struct master_list_iter_ctx, 1);
+ ctx->conn = conn;
+ ctx->auth_request = auth_request;
+ ctx->auth_request->userdb = userdb;
+
+ io_remove(&conn->io);
+ o_stream_cork(conn->output);
+ o_stream_set_flush_callback(conn->output, master_output_list, ctx);
+ ctx->iter = userdb_blocking_iter_init(auth_request,
+ master_input_list_callback, ctx);
+ conn->iter_ctx = ctx;
+ return TRUE;
+}
+
+static bool
+auth_master_input_line(struct auth_master_connection *conn, const char *line)
+{
+ e_debug(auth_event, "master in: %s", line);
+
+ if (str_begins(line, "USER\t"))
+ return master_input_user(conn, line + 5);
+ if (str_begins(line, "LIST\t"))
+ return master_input_list(conn, line + 5);
+ if (str_begins(line, "PASS\t"))
+ return master_input_pass(conn, line + 5);
+
+ if (!conn->userdb_only) {
+ i_assert(conn->userdb_restricted_uid == 0);
+ if (str_begins(line, "REQUEST\t"))
+ return master_input_request(conn, line + 8);
+ if (str_begins(line, "CACHE-FLUSH\t"))
+ return master_input_cache_flush(conn, line + 12);
+ if (str_begins(line, "CPID\t")) {
+ e_error(conn->event,
+ "Authentication client trying to connect to "
+ "master socket");
+ return FALSE;
+ }
+ }
+
+ e_error(conn->event, "BUG: Unknown command in %s socket: %s",
+ conn->userdb_only ? "userdb" : "master",
+ str_sanitize(line, 80));
+ return FALSE;
+}
+
+static void master_input(struct auth_master_connection *conn)
+{
+ char *line;
+ bool ret;
+
+ switch (i_stream_read(conn->input)) {
+ case 0:
+ return;
+ case -1:
+ /* disconnected */
+ auth_master_connection_destroy(&conn);
+ return;
+ case -2:
+ /* buffer full */
+ e_error(conn->event, "BUG: Master sent us more than %d bytes",
+ (int)MAX_INBUF_SIZE);
+ auth_master_connection_destroy(&conn);
+ return;
+ }
+
+ if (!conn->version_received) {
+ line = i_stream_next_line(conn->input);
+ if (line == NULL)
+ return;
+
+ /* make sure the major version matches */
+ if (!str_begins(line, "VERSION\t") ||
+ !str_uint_equals(t_strcut(line + 8, '\t'),
+ AUTH_MASTER_PROTOCOL_MAJOR_VERSION)) {
+ e_error(conn->event,
+ "Master not compatible with this server "
+ "(mixed old and new binaries?)");
+ auth_master_connection_destroy(&conn);
+ return;
+ }
+ conn->version_received = TRUE;
+ conn->handshake_time = ioloop_timeval;
+ }
+
+ while ((line = i_stream_next_line(conn->input)) != NULL) {
+ T_BEGIN {
+ ret = auth_master_input_line(conn, line);
+ } T_END;
+ if (!ret) {
+ auth_master_connection_destroy(&conn);
+ return;
+ }
+ }
+}
+
+static int master_output(struct auth_master_connection *conn)
+{
+ if (o_stream_flush(conn->output) < 0) {
+ /* transmit error, probably master died */
+ auth_master_connection_destroy(&conn);
+ return 1;
+ }
+
+ if (conn->io == NULL &&
+ o_stream_get_buffer_used_size(conn->output) <= MAX_OUTBUF_SIZE/2) {
+ /* allow input again */
+ conn->io = io_add(conn->fd, IO_READ, master_input, conn);
+ }
+ return 1;
+}
+
+static int
+auth_master_connection_set_permissions(struct auth_master_connection *conn,
+ const struct stat *st)
+{
+ struct net_unix_cred cred;
+
+ if (st == NULL)
+ return 0;
+
+ /* figure out what permissions we want to give to this client */
+ if ((st->st_mode & 0777) != 0666) {
+ /* permissions were already restricted by the socket
+ permissions. also +x bit indicates that we shouldn't do
+ any permission checks. */
+ return 0;
+ }
+
+ if (net_getunixcred(conn->fd, &cred) < 0) {
+ e_error(conn->event,
+ "userdb connection: Failed to get peer's credentials");
+ return -1;
+ }
+
+ if (cred.uid == st->st_uid || cred.gid == st->st_gid) {
+ /* full permissions */
+ return 0;
+ } else {
+ /* restrict permissions: return only lookups whose returned
+ uid matches the peer's uid */
+ conn->userdb_restricted_uid = cred.uid;
+ return 0;
+ }
+}
+
+struct auth_master_connection *
+auth_master_connection_create(struct auth *auth, int fd,
+ const char *path, const struct stat *socket_st,
+ bool userdb_only)
+{
+ struct auth_master_connection *conn;
+ const char *line;
+
+ i_assert(path != NULL);
+
+ conn = i_new(struct auth_master_connection, 1);
+ conn->refcount = 1;
+ conn->fd = fd;
+ conn->create_time = ioloop_timeval;
+ conn->path = i_strdup(path);
+ conn->auth = auth;
+ conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE);
+ conn->output = o_stream_create_fd(fd, SIZE_MAX);
+ o_stream_set_no_error_handling(conn->output, TRUE);
+ o_stream_set_flush_callback(conn->output, master_output, conn);
+ conn->io = io_add(fd, IO_READ, master_input, conn);
+ conn->userdb_only = userdb_only;
+ conn->event = event_create(auth_event);
+ event_set_log_message_callback(conn->event, auth_master_event_log_callback, conn);
+
+ line = t_strdup_printf("VERSION\t%u\t%u\nSPID\t%s\n",
+ AUTH_MASTER_PROTOCOL_MAJOR_VERSION,
+ AUTH_MASTER_PROTOCOL_MINOR_VERSION,
+ my_pid);
+ o_stream_nsend_str(conn->output, line);
+ DLLIST_PREPEND(&auth_master_connections, conn);
+
+ if (auth_master_connection_set_permissions(conn, socket_st) < 0) {
+ auth_master_connection_destroy(&conn);
+ return NULL;
+ }
+ return conn;
+}
+
+void auth_master_connection_destroy(struct auth_master_connection **_conn)
+{
+ struct auth_master_connection *conn = *_conn;
+
+ *_conn = NULL;
+ if (conn->destroyed)
+ return;
+ conn->destroyed = TRUE;
+
+ DLLIST_REMOVE(&auth_master_connections, conn);
+
+ if (conn->iter_ctx != NULL)
+ master_input_list_finish(conn->iter_ctx);
+ i_stream_close(conn->input);
+ o_stream_close(conn->output);
+ io_remove(&conn->io);
+ i_close_fd_path(&conn->fd, conn->path);
+
+ master_service_client_connection_destroyed(master_service);
+ auth_master_connection_unref(&conn);
+}
+
+void auth_master_connection_ref(struct auth_master_connection *conn)
+{
+ i_assert(conn->refcount > 0);
+
+ conn->refcount++;
+}
+
+void auth_master_connection_unref(struct auth_master_connection **_conn)
+{
+ struct auth_master_connection *conn = *_conn;
+
+ *_conn = NULL;
+ i_assert(conn->refcount > 0);
+
+ if (--conn->refcount > 0)
+ return;
+
+ i_stream_unref(&conn->input);
+ o_stream_unref(&conn->output);
+
+ event_unref(&conn->event);
+ i_free(conn->path);
+ i_free(conn);
+}
+
+void auth_master_connections_destroy_all(void)
+{
+ struct auth_master_connection *conn;
+
+ while (auth_master_connections != NULL) {
+ conn = auth_master_connections;
+ auth_master_connection_destroy(&conn);
+ }
+}
diff --git a/src/auth/auth-master-connection.h b/src/auth/auth-master-connection.h
new file mode 100644
index 0000000..c2a5dd5
--- /dev/null
+++ b/src/auth/auth-master-connection.h
@@ -0,0 +1,44 @@
+#ifndef AUTH_MASTER_CONNECTION_H
+#define AUTH_MASTER_CONNECTION_H
+
+struct stat;
+struct auth_stream_reply;
+
+struct auth_master_connection {
+ struct auth_master_connection *prev, *next;
+ struct auth *auth;
+ struct event *event;
+ int refcount;
+
+ struct timeval create_time, handshake_time;
+
+ int fd;
+ char *path;
+ struct istream *input;
+ struct ostream *output;
+ struct io *io;
+
+ struct master_list_iter_ctx *iter_ctx;
+ /* If non-zero, allow only USER lookups whose returned uid matches
+ this uid. Don't allow LIST/PASS lookups. */
+ uid_t userdb_restricted_uid;
+
+ bool version_received:1;
+ bool destroyed:1;
+ bool userdb_only:1;
+};
+
+struct auth_master_connection *
+auth_master_connection_create(struct auth *auth, int fd,
+ const char *path, const struct stat *socket_st,
+ bool userdb_only) ATTR_NULL(4);
+void auth_master_connection_destroy(struct auth_master_connection **conn);
+
+void auth_master_connection_ref(struct auth_master_connection *conn);
+void auth_master_connection_unref(struct auth_master_connection **conn);
+
+void auth_master_request_callback(const char *reply, struct auth_master_connection *conn);
+
+void auth_master_connections_destroy_all(void);
+
+#endif
diff --git a/src/auth/auth-penalty.c b/src/auth/auth-penalty.c
new file mode 100644
index 0000000..3816902
--- /dev/null
+++ b/src/auth/auth-penalty.c
@@ -0,0 +1,176 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "net.h"
+#include "crc32.h"
+#include "master-service.h"
+#include "anvil-client.h"
+#include "auth-request.h"
+#include "auth-penalty.h"
+
+#include <stdio.h>
+
+/* We don't want IPv6 hosts being able to flood our penalty
+ tracking with tons of different IPs. */
+#define PENALTY_IPV6_MASK_BITS 48
+
+struct auth_penalty_request {
+ struct auth_request *auth_request;
+ struct anvil_client *client;
+ auth_penalty_callback_t *callback;
+};
+
+struct auth_penalty {
+ struct anvil_client *client;
+
+ bool disabled:1;
+};
+
+struct auth_penalty *auth_penalty_init(const char *path)
+{
+ struct auth_penalty *penalty;
+
+ penalty = i_new(struct auth_penalty, 1);
+ penalty->client = anvil_client_init(path, NULL,
+ ANVIL_CLIENT_FLAG_HIDE_ENOENT);
+ if (anvil_client_connect(penalty->client, TRUE) < 0)
+ penalty->disabled = TRUE;
+ else {
+ anvil_client_cmd(penalty->client, t_strdup_printf(
+ "PENALTY-SET-EXPIRE-SECS\t%u", AUTH_PENALTY_TIMEOUT));
+ }
+ return penalty;
+}
+
+void auth_penalty_deinit(struct auth_penalty **_penalty)
+{
+ struct auth_penalty *penalty = *_penalty;
+
+ *_penalty = NULL;
+ anvil_client_deinit(&penalty->client);
+ i_free(penalty);
+}
+
+unsigned int auth_penalty_to_secs(unsigned int penalty)
+{
+ unsigned int i, secs = AUTH_PENALTY_INIT_SECS;
+
+ for (i = 0; i < penalty; i++)
+ secs *= 2;
+ return secs < AUTH_PENALTY_MAX_SECS ? secs : AUTH_PENALTY_MAX_SECS;
+}
+
+static void auth_penalty_anvil_callback(const char *reply, void *context)
+{
+ struct auth_penalty_request *request = context;
+ unsigned int penalty = 0;
+ unsigned long last_penalty = 0;
+ unsigned int secs, drop_penalty;
+
+ if (reply == NULL) {
+ /* internal failure. */
+ if (!anvil_client_is_connected(request->client)) {
+ /* we probably didn't have permissions to reconnect
+ back to anvil. need to restart ourself. */
+ master_service_stop(master_service);
+ }
+ } else if (sscanf(reply, "%u %lu", &penalty, &last_penalty) != 2) {
+ e_error(request->auth_request->event,
+ "Invalid PENALTY-GET reply: %s", reply);
+ } else {
+ if ((time_t)last_penalty > ioloop_time) {
+ /* time moved backwards? */
+ last_penalty = ioloop_time;
+ }
+
+ /* update penalty. */
+ drop_penalty = AUTH_PENALTY_MAX_PENALTY;
+ while (penalty > 0) {
+ secs = auth_penalty_to_secs(drop_penalty);
+ if (ioloop_time - last_penalty < secs)
+ break;
+ drop_penalty--;
+ penalty--;
+ }
+ }
+
+ request->callback(penalty, request->auth_request);
+ auth_request_unref(&request->auth_request);
+ i_free(request);
+}
+
+static const char *
+auth_penalty_get_ident(struct auth_request *auth_request)
+{
+ struct ip_addr ip;
+
+ ip = auth_request->fields.remote_ip;
+ if (IPADDR_IS_V6(&ip)) {
+ memset(ip.u.ip6.s6_addr + PENALTY_IPV6_MASK_BITS/CHAR_BIT, 0,
+ sizeof(ip.u.ip6.s6_addr) -
+ PENALTY_IPV6_MASK_BITS/CHAR_BIT);
+ }
+ return net_ip2addr(&ip);
+}
+
+void auth_penalty_lookup(struct auth_penalty *penalty,
+ struct auth_request *auth_request,
+ auth_penalty_callback_t *callback)
+{
+ struct auth_penalty_request *request;
+ const char *ident;
+
+ ident = auth_penalty_get_ident(auth_request);
+ if (penalty->disabled || ident == NULL ||
+ auth_request->fields.no_penalty) {
+ callback(0, auth_request);
+ return;
+ }
+
+ request = i_new(struct auth_penalty_request, 1);
+ request->auth_request = auth_request;
+ request->client = penalty->client;
+ request->callback = callback;
+ auth_request_ref(auth_request);
+
+ T_BEGIN {
+ anvil_client_query(penalty->client,
+ t_strdup_printf("PENALTY-GET\t%s", ident),
+ auth_penalty_anvil_callback, request);
+ } T_END;
+}
+
+static unsigned int
+get_userpass_checksum(struct auth_request *auth_request)
+{
+ return auth_request->mech_password == NULL ? 0 :
+ crc32_str_more(crc32_str(auth_request->mech_password),
+ auth_request->fields.user);
+}
+
+void auth_penalty_update(struct auth_penalty *penalty,
+ struct auth_request *auth_request, unsigned int value)
+{
+ const char *ident;
+
+ ident = auth_penalty_get_ident(auth_request);
+ if (penalty->disabled || ident == NULL ||
+ auth_request->fields.no_penalty)
+ return;
+
+ if (value > AUTH_PENALTY_MAX_PENALTY) {
+ /* even if the actual value doesn't change, the last_change
+ timestamp does. */
+ value = AUTH_PENALTY_MAX_PENALTY;
+ }
+ T_BEGIN {
+ const char *cmd;
+ unsigned int checksum;
+
+ checksum = value == 0 ? 0 : get_userpass_checksum(auth_request);
+ cmd = t_strdup_printf("PENALTY-INC\t%s\t%u\t%u",
+ ident, checksum, value);
+ anvil_client_cmd(penalty->client, cmd);
+ } T_END;
+}
diff --git a/src/auth/auth-penalty.h b/src/auth/auth-penalty.h
new file mode 100644
index 0000000..96783e4
--- /dev/null
+++ b/src/auth/auth-penalty.h
@@ -0,0 +1,28 @@
+#ifndef AUTH_PENALTY_H
+#define AUTH_PENALTY_H
+
+struct auth_request;
+
+#define AUTH_PENALTY_INIT_SECS 2
+#define AUTH_PENALTY_MAX_SECS 15
+/* timeout specifies how long it takes for penalty to be irrelevant. */
+#define AUTH_PENALTY_TIMEOUT \
+ (AUTH_PENALTY_INIT_SECS + 4 + 8 + AUTH_PENALTY_MAX_SECS)
+#define AUTH_PENALTY_MAX_PENALTY 4
+
+/* If lookup failed, penalty and last_update are both zero */
+typedef void auth_penalty_callback_t(unsigned int penalty,
+ struct auth_request *request);
+
+struct auth_penalty *auth_penalty_init(const char *path);
+void auth_penalty_deinit(struct auth_penalty **penalty);
+
+unsigned int auth_penalty_to_secs(unsigned int penalty);
+
+void auth_penalty_lookup(struct auth_penalty *penalty,
+ struct auth_request *auth_request,
+ auth_penalty_callback_t *callback);
+void auth_penalty_update(struct auth_penalty *penalty,
+ struct auth_request *auth_request, unsigned int value);
+
+#endif
diff --git a/src/auth/auth-policy.c b/src/auth/auth-policy.c
new file mode 100644
index 0000000..951f85e
--- /dev/null
+++ b/src/auth/auth-policy.c
@@ -0,0 +1,620 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "str.h"
+#include "istream.h"
+#include "ioloop.h"
+#include "base64.h"
+#include "hex-binary.h"
+#include "hash-method.h"
+#include "http-url.h"
+#include "http-client.h"
+#include "json-parser.h"
+#include "master-service.h"
+#include "master-service-ssl-settings.h"
+#include "auth-request.h"
+#include "auth-penalty.h"
+#include "auth-settings.h"
+#include "auth-policy.h"
+#include "auth-common.h"
+#include "iostream-ssl.h"
+
+#define AUTH_POLICY_DNS_SOCKET_PATH "dns-client"
+
+static struct http_client_settings http_client_set = {
+ .dns_client_socket_path = AUTH_POLICY_DNS_SOCKET_PATH,
+ .max_connect_attempts = 1,
+ .max_idle_time_msecs = 10000,
+ .max_parallel_connections = 100,
+ .debug = 0,
+ .user_agent = "dovecot/auth-policy-client"
+};
+
+static char *auth_policy_json_template;
+
+static struct http_client *http_client;
+
+struct policy_lookup_ctx {
+ pool_t pool;
+ string_t *json;
+ struct auth_request *request;
+ struct http_client_request *http_request;
+ struct json_parser *parser;
+ const struct auth_settings *set;
+ const char *url;
+ bool expect_result;
+ int result;
+ const char *message;
+ auth_policy_callback_t callback;
+ void *callback_context;
+
+ struct istream *payload;
+ struct io *io;
+ struct event *event;
+
+ enum {
+ POLICY_RESULT = 0,
+ POLICY_RESULT_VALUE_STATUS,
+ POLICY_RESULT_VALUE_MESSAGE
+ } parse_state;
+
+ bool parse_error;
+};
+
+struct policy_template_keyvalue {
+ const char *key;
+ const char *value;
+};
+
+static
+int auth_policy_attribute_comparator(const struct policy_template_keyvalue *a,
+ const struct policy_template_keyvalue *b)
+{
+ return strcmp(a->key, b->key);
+}
+
+static
+int auth_policy_strptrcmp(const char *a0, const char *a1,
+ const char *b0, const char *b1)
+{
+ i_assert(a0 <= a1 && b0 <= b1);
+ return memcmp(a0, b0, I_MIN((a1-a0),(b1-b0)));
+}
+
+static
+void auth_policy_open_key(const char *key, string_t *template)
+{
+ const char *ptr;
+ while((ptr = strchr(key, '/')) != NULL) {
+ str_append_c(template,'"');
+ json_append_escaped(template, t_strndup(key, (ptr-key)));
+ str_append_c(template,'"');
+ str_append_c(template,':');
+ str_append_c(template,'{');
+ key = ptr+1;
+ }
+}
+
+static
+void auth_policy_close_key(const char *key, string_t *template)
+{
+ while((key = strchr(key, '/')) != NULL) { str_append_c(template,'}'); key++; }
+}
+
+static
+void auth_policy_open_and_close_to_key(const char *fromkey, const char *tokey, string_t *template)
+{
+ const char *fptr,*tptr,*fdash,*tdash;
+
+ fptr = strrchr(fromkey, '/');
+ tptr = strrchr(tokey, '/');
+
+ if (fptr == NULL && tptr == NULL) return; /* nothing to do */
+
+ if (fptr == NULL && tptr != NULL) {
+ auth_policy_open_key(tokey, template);
+ return;
+ }
+
+ if (fptr != NULL && tptr == NULL) {
+ str_truncate(template, str_len(template)-1);
+
+ auth_policy_close_key(fromkey, template);
+ str_append_c(template, ',');
+ return;
+ }
+
+ if (auth_policy_strptrcmp(fromkey, fptr, tokey, tptr) == 0) {
+ /* nothing to do, again */
+ return;
+ }
+
+ fptr = fromkey;
+ tptr = tokey;
+
+ while (fptr != NULL && tptr != NULL) {
+ fdash = strchr(fptr, '/');
+ tdash = strchr(tptr, '/');
+
+ if (fdash == NULL) {
+ auth_policy_open_key(tptr, template);
+ break;
+ }
+ if (tdash == NULL) {
+ str_truncate(template, str_len(template)-1);
+ auth_policy_close_key(fptr, template);
+ str_append_c(template, ',');
+ break;
+ }
+ if (auth_policy_strptrcmp(fptr, fdash, tptr, tdash) != 0) {
+ str_truncate(template, str_len(template)-1);
+ auth_policy_close_key(fptr, template);
+ str_append_c(template, ',');
+ auth_policy_open_key(tptr, template);
+ break;
+ }
+ fptr = fdash+1;
+ tptr = tdash+1;
+ }
+}
+
+void auth_policy_init(void)
+{
+ const struct master_service_ssl_settings *master_ssl_set =
+ master_service_ssl_settings_get(master_service);
+ struct ssl_iostream_settings ssl_set;
+ i_zero(&ssl_set);
+
+ http_client_set.request_absolute_timeout_msecs = global_auth_settings->policy_server_timeout_msecs;
+ if (global_auth_settings->debug)
+ http_client_set.debug = 1;
+
+ master_service_ssl_client_settings_to_iostream_set(master_ssl_set,
+ pool_datastack_create(), &ssl_set);
+ http_client_set.ssl = &ssl_set;
+ http_client_set.event_parent = auth_event;
+ http_client = http_client_init(&http_client_set);
+
+ /* prepare template */
+
+ ARRAY(struct policy_template_keyvalue) attribute_pairs;
+ const struct policy_template_keyvalue *kvptr;
+ string_t *template = t_str_new(64);
+ const char **ptr;
+ const char *key = NULL;
+ const char **list = t_strsplit_spaces(global_auth_settings->policy_request_attributes, "= ");
+
+ t_array_init(&attribute_pairs, 8);
+ for(ptr = list; *ptr != NULL; ptr++) {
+ struct policy_template_keyvalue pair;
+ if (key == NULL) {
+ key = *ptr;
+ } else {
+ pair.key = key;
+ pair.value = *ptr;
+ key = NULL;
+ array_push_back(&attribute_pairs, &pair);
+ }
+ }
+ if (key != NULL) {
+ i_fatal("auth_policy_request_attributes contains invalid value");
+ }
+
+ /* then we sort it */
+ array_sort(&attribute_pairs, auth_policy_attribute_comparator);
+
+ /* and build a template string */
+ const char *prevkey = "";
+
+ array_foreach(&attribute_pairs, kvptr) {
+ const char *kptr = strchr(kvptr->key, '/');
+ auth_policy_open_and_close_to_key(prevkey, kvptr->key, template);
+ str_append_c(template,'"');
+ json_append_escaped(template, (kptr != NULL?kptr+1:kvptr->key));
+ str_append_c(template,'"');
+ str_append_c(template,':');
+ str_append_c(template,'"');
+ str_append(template,kvptr->value);
+ str_append_c(template,'"');
+ str_append_c(template,',');
+ prevkey = kvptr->key;
+ }
+
+ auth_policy_open_and_close_to_key(prevkey, "", template);
+ str_truncate(template, str_len(template)-1);
+ auth_policy_json_template = i_strdup(str_c(template));
+
+ if (global_auth_settings->policy_log_only)
+ i_warning("auth-policy: Currently in log-only mode. Ignoring "
+ "tarpit and disconnect instructions from policy server");
+}
+
+void auth_policy_deinit(void)
+{
+ if (http_client != NULL)
+ http_client_deinit(&http_client);
+ i_free(auth_policy_json_template);
+}
+
+static
+void auth_policy_log_result(struct policy_lookup_ctx *context)
+{
+ const char *action;
+ struct event_passthrough *e = event_create_passthrough(context->event)->
+ set_name("auth_policy_request_finished");
+ if (!context->expect_result) {
+ e_debug(e->event(), "Policy report action finished");
+ return;
+ }
+ int result = context->result;
+ e->add_int("policy_response", context->result);
+ if (result < 0)
+ action = "drop connection";
+ else if (context->result == 0)
+ action = "continue";
+ else
+ action = t_strdup_printf("tarpit %d second(s)", context->result);
+ if (context->request->set->policy_log_only && result != 0)
+ e_info(e->event(), "Policy check action '%s' ignored",
+ action);
+ else if (result != 0)
+ e_info(e->event(), "Policy check action is %s",
+ action);
+ else
+ e_debug(e->event(), "Policy check action is %s",
+ action);
+}
+
+static
+void auth_policy_finish(struct policy_lookup_ctx *context)
+{
+ if (context->parser != NULL) {
+ const char *error ATTR_UNUSED;
+ (void)json_parser_deinit(&context->parser, &error);
+ }
+ http_client_request_abort(&context->http_request);
+ if (context->request != NULL)
+ auth_request_unref(&context->request);
+ event_unref(&context->event);
+ pool_unref(&context->pool);
+}
+
+static
+void auth_policy_callback(struct policy_lookup_ctx *context)
+{
+ if (context->callback != NULL)
+ context->callback(context->result, context->callback_context);
+ if (context->event != NULL)
+ auth_policy_log_result(context);
+}
+
+static
+void auth_policy_parse_response(struct policy_lookup_ctx *context)
+{
+ enum json_type type;
+ const char *value;
+ int ret;
+
+ while((ret = json_parse_next(context->parser, &type, &value)) == 1) {
+ if (context->parse_state == POLICY_RESULT) {
+ if (type != JSON_TYPE_OBJECT_KEY)
+ continue;
+ else if (strcmp(value, "status") == 0)
+ context->parse_state = POLICY_RESULT_VALUE_STATUS;
+ else if (strcmp(value, "msg") == 0)
+ context->parse_state = POLICY_RESULT_VALUE_MESSAGE;
+ else
+ continue;
+ } else if (context->parse_state == POLICY_RESULT_VALUE_STATUS) {
+ if (type != JSON_TYPE_NUMBER || str_to_int(value, &context->result) != 0)
+ break;
+ context->parse_state = POLICY_RESULT;
+ } else if (context->parse_state == POLICY_RESULT_VALUE_MESSAGE) {
+ if (type != JSON_TYPE_STRING)
+ break;
+ if (*value != '\0')
+ context->message = p_strdup(context->pool, value);
+ context->parse_state = POLICY_RESULT;
+ } else {
+ break;
+ }
+ }
+
+ if (ret == 0 && !context->payload->eof)
+ return;
+
+ context->parse_error = TRUE;
+
+ io_remove(&context->io);
+
+ if (context->payload->stream_errno != 0) {
+ e_error(context->event,
+ "Error reading policy server result: %s",
+ i_stream_get_error(context->payload));
+ } else if (ret == 0 && context->payload->eof) {
+ e_error(context->event,
+ "Policy server result was too short");
+ } else if (ret == 1) {
+ e_error(context->event,
+ "Policy server response was malformed");
+ } else {
+ const char *error = "unknown";
+ if (json_parser_deinit(&context->parser, &error) != 0)
+ e_error(context->event,
+ "Policy server response JSON parse error: %s", error);
+ else if (context->parse_state == POLICY_RESULT)
+ context->parse_error = FALSE;
+ }
+
+ if (context->parse_error) {
+ context->result = (context->set->policy_reject_on_fail ? -1 : 0);
+ }
+
+ context->request->policy_refusal = FALSE;
+
+ if (context->result < 0) {
+ if (context->message != NULL) {
+ /* set message here */
+ e_debug(context->event,
+ "Policy response %d with message: %s",
+ context->result, context->message);
+ auth_request_set_field(context->request, "reason", context->message, NULL);
+ }
+ context->request->policy_refusal = TRUE;
+ } else {
+ e_debug(context->event,
+ "Policy response %d", context->result);
+ }
+
+ if (context->request->policy_refusal == TRUE && context->set->verbose == TRUE) {
+ e_info(context->event, "Authentication failure due to policy server refusal%s%s",
+ (context->message!=NULL?": ":""),
+ (context->message!=NULL?context->message:""));
+ }
+
+ auth_policy_callback(context);
+ i_stream_unref(&context->payload);
+}
+
+static
+void auth_policy_process_response(const struct http_response *response,
+ void *ctx)
+{
+ struct policy_lookup_ctx *context = ctx;
+
+ context->payload = response->payload;
+
+ if ((response->status / 10) != 20) {
+ e_error(context->event,
+ "Policy server HTTP error: %s",
+ http_response_get_message(response));
+ auth_policy_callback(context);
+ return;
+ }
+
+ if (response->payload == NULL) {
+ if (context->expect_result)
+ e_error(context->event,
+ "Policy server result was empty");
+ auth_policy_callback(context);
+ return;
+ }
+
+ if (context->expect_result) {
+ i_stream_ref(response->payload);
+ context->io = io_add_istream(response->payload, auth_policy_parse_response, context);
+ context->parser = json_parser_init(response->payload);
+ auth_policy_parse_response(ctx);
+ } else {
+ auth_policy_callback(context);
+ }
+}
+
+static
+void auth_policy_send_request(struct policy_lookup_ctx *context)
+{
+ const char *error;
+ struct http_url *url;
+
+ auth_request_ref(context->request);
+ if (http_url_parse(context->url, NULL, HTTP_URL_ALLOW_USERINFO_PART,
+ context->pool, &url, &error) != 0) {
+ e_error(context->event,
+ "Could not parse url %s: %s", context->url, error);
+ auth_policy_callback(context);
+ auth_policy_finish(context);
+ return;
+ }
+ context->http_request = http_client_request_url(http_client,
+ "POST", url, auth_policy_process_response, (void*)context);
+ http_client_request_set_destroy_callback(context->http_request, auth_policy_finish, context);
+ http_client_request_add_header(context->http_request, "Content-Type", "application/json");
+ if (*context->set->policy_server_api_header != 0) {
+ const char *ptr;
+ if ((ptr = strstr(context->set->policy_server_api_header, ":")) != NULL) {
+ const char *header = t_strcut(context->set->policy_server_api_header, ':');
+ http_client_request_add_header(context->http_request, header, ptr + 1);
+ } else {
+ http_client_request_add_header(context->http_request,
+ "X-API-Key", context->set->policy_server_api_header);
+ }
+ }
+ if (url->user != NULL) {
+ /* allow empty password */
+ http_client_request_set_auth_simple(context->http_request, url->user,
+ (url->password != NULL ? url->password : ""));
+ }
+ struct istream *is = i_stream_create_from_buffer(context->json);
+ http_client_request_set_payload(context->http_request, is, FALSE);
+ i_stream_unref(&is);
+ http_client_request_submit(context->http_request);
+}
+
+static
+const char *auth_policy_escape_function(const char *string,
+ const struct auth_request *auth_request ATTR_UNUSED)
+{
+ string_t *tmp = t_str_new(64);
+ json_append_escaped(tmp, string);
+ return str_c(tmp);
+}
+
+static
+const struct var_expand_table *policy_get_var_expand_table(struct auth_request *auth_request,
+ const char *hashed_password, const char *requested_username)
+{
+ struct var_expand_table *table;
+ unsigned int count = 2;
+
+ table = auth_request_get_var_expand_table_full(auth_request,
+ auth_request->fields.user, auth_policy_escape_function, &count);
+ table[0].key = '\0';
+ table[0].long_key = "hashed_password";
+ table[0].value = hashed_password;
+ table[1].key = '\0';
+ table[1].long_key = "requested_username";
+ table[1].value = requested_username;
+ if (table[0].value != NULL)
+ table[0].value = auth_policy_escape_function(table[0].value, auth_request);
+ if (table[1].value != NULL)
+ table[1].value = auth_policy_escape_function(table[1].value, auth_request);
+
+ return table;
+}
+
+static
+void auth_policy_create_json(struct policy_lookup_ctx *context,
+ const char *password, bool include_success)
+{
+ const struct var_expand_table *var_table;
+ context->json = str_new(context->pool, 64);
+ unsigned char *ptr;
+ const char *requested_username;
+ const struct hash_method *digest = hash_method_lookup(context->set->policy_hash_mech);
+
+ i_assert(digest != NULL);
+
+ void *ctx = t_malloc_no0(digest->context_size);
+ buffer_t *buffer = t_buffer_create(64);
+
+ digest->init(ctx);
+ digest->loop(ctx,
+ context->set->policy_hash_nonce,
+ strlen(context->set->policy_hash_nonce));
+ if (context->request->fields.requested_login_user != NULL)
+ requested_username = context->request->fields.requested_login_user;
+ else if (context->request->fields.user != NULL)
+ requested_username = context->request->fields.user;
+ else
+ requested_username = "";
+ /* use +1 to make sure \0 gets included */
+ digest->loop(ctx, requested_username, strlen(requested_username)+1);
+ if (password != NULL)
+ digest->loop(ctx, password, strlen(password));
+ ptr = buffer_get_modifiable_data(buffer, NULL);
+ digest->result(ctx, ptr);
+ buffer_set_used_size(buffer, digest->digest_size);
+ if (context->set->policy_hash_truncate > 0) {
+ buffer_truncate_rshift_bits(buffer, context->set->policy_hash_truncate);
+ }
+ const char *hashed_password = binary_to_hex(buffer->data, buffer->used);
+ str_append_c(context->json, '{');
+ var_table = policy_get_var_expand_table(context->request, hashed_password, requested_username);
+ const char *error;
+ if (auth_request_var_expand_with_table(context->json, auth_policy_json_template,
+ context->request, var_table,
+ auth_policy_escape_function, &error) <= 0) {
+ e_error(context->event,
+ "Failed to expand auth policy template: %s", error);
+ }
+ if (include_success) {
+ str_append(context->json, ",\"success\":");
+ if (!context->request->failed &&
+ context->request->fields.successful &&
+ !context->request->internal_failure)
+ str_append(context->json, "true");
+ else
+ str_append(context->json, "false");
+ str_append(context->json, ",\"policy_reject\":");
+ str_append(context->json, context->request->policy_refusal ? "true" : "false");
+ }
+ str_append(context->json, ",\"tls\":");
+ if (context->request->fields.secured == AUTH_REQUEST_SECURED_TLS)
+ str_append(context->json, "true");
+ else
+ str_append(context->json, "false");
+ str_append_c(context->json, '}');
+ e_debug(context->event,
+ "Policy server request JSON: %s", str_c(context->json));
+}
+
+static
+void auth_policy_url(struct policy_lookup_ctx *context, const char *command)
+{
+ size_t len = strlen(context->set->policy_server_url);
+ if (context->set->policy_server_url[len-1] == '&')
+ context->url = p_strdup_printf(context->pool, "%scommand=%s",
+ context->set->policy_server_url, command);
+ else
+ context->url = p_strdup_printf(context->pool, "%s?command=%s",
+ context->set->policy_server_url, command);
+}
+
+static const char *auth_policy_get_prefix(struct auth_request *request)
+{
+ string_t *str = t_str_new(256);
+ auth_request_get_log_prefix(str, request, "policy");
+ return str_c(str);
+}
+
+void auth_policy_check(struct auth_request *request, const char *password,
+ auth_policy_callback_t cb, void *context)
+{
+ if (request->master != NULL || *(request->set->policy_server_url) == '\0') {
+ cb(0, context);
+ return;
+ }
+ pool_t pool = pool_alloconly_create("auth policy", 512);
+ struct policy_lookup_ctx *ctx = p_new(pool, struct policy_lookup_ctx, 1);
+ ctx->pool = pool;
+ ctx->request = request;
+ ctx->expect_result = TRUE;
+ ctx->callback = cb;
+ ctx->callback_context = context;
+ ctx->set = request->set;
+ ctx->event = event_create(request->event);
+ event_add_str(ctx->event, "mode", "allow");
+ event_set_append_log_prefix(ctx->event, auth_policy_get_prefix(request));
+ auth_policy_url(ctx, "allow");
+ ctx->result = (ctx->set->policy_reject_on_fail ? -1 : 0);
+ e_debug(ctx->event, "Policy request %s", ctx->url);
+ T_BEGIN {
+ auth_policy_create_json(ctx, password, FALSE);
+ } T_END;
+ auth_policy_send_request(ctx);
+}
+
+void auth_policy_report(struct auth_request *request)
+{
+ if (request->master != NULL)
+ return;
+
+ if (*(request->set->policy_server_url) == '\0')
+ return;
+ pool_t pool = pool_alloconly_create("auth policy", 512);
+ struct policy_lookup_ctx *ctx = p_new(pool, struct policy_lookup_ctx, 1);
+ ctx->pool = pool;
+ ctx->request = request;
+ ctx->expect_result = FALSE;
+ ctx->set = request->set;
+ ctx->event = event_create(request->event);
+ event_add_str(ctx->event, "mode", "report");
+ event_set_append_log_prefix(ctx->event, auth_policy_get_prefix(request));
+ auth_policy_url(ctx, "report");
+ e_debug(ctx->event, "Policy request %s", ctx->url);
+ T_BEGIN {
+ auth_policy_create_json(ctx, request->mech_password, TRUE);
+ } T_END;
+ auth_policy_send_request(ctx);
+}
diff --git a/src/auth/auth-policy.h b/src/auth/auth-policy.h
new file mode 100644
index 0000000..1c81945
--- /dev/null
+++ b/src/auth/auth-policy.h
@@ -0,0 +1,11 @@
+#ifndef AUTH_POLICY_H
+#define AUTH_POLICY_H
+
+typedef void (*auth_policy_callback_t)(int, void *);
+
+void auth_policy_check(struct auth_request *request, const char *password, auth_policy_callback_t cb, void *context);
+void auth_policy_report(struct auth_request *request);
+void auth_policy_init(void);
+void auth_policy_deinit(void);
+
+#endif
diff --git a/src/auth/auth-request-fields.c b/src/auth/auth-request-fields.c
new file mode 100644
index 0000000..590e671
--- /dev/null
+++ b/src/auth/auth-request-fields.c
@@ -0,0 +1,525 @@
+/* Copyright (c) 2002-2020 Dovecot authors, see the included COPYING file */
+
+#define AUTH_REQUEST_FIELDS_CONST
+
+#include "auth-common.h"
+#include "str.h"
+#include "strescape.h"
+#include "str-sanitize.h"
+#include "auth-request.h"
+#include "userdb-template.h"
+
+void auth_request_fields_init(struct auth_request *request)
+{
+ request->fields.extra_fields = auth_fields_init(request->pool);
+ if (request->mech != NULL) {
+ request->fields.mech_name = request->mech->mech_name;
+ event_add_str(request->event, "mechanism",
+ request->mech->mech_name);
+ }
+ /* Default to "insecure" until it's changed later */
+ event_add_str(request->event, "transport", "insecure");
+}
+
+static void
+auth_str_add_keyvalue(string_t *dest, const char *key, const char *value)
+{
+ str_append_c(dest, '\t');
+ str_append(dest, key);
+ if (value != NULL) {
+ str_append_c(dest, '=');
+ str_append_tabescaped(dest, value);
+ }
+}
+
+static void
+auth_request_export_fields(string_t *dest, struct auth_fields *auth_fields,
+ const char *prefix)
+{
+ const ARRAY_TYPE(auth_field) *fields = auth_fields_export(auth_fields);
+ const struct auth_field *field;
+
+ array_foreach(fields, field) {
+ str_printfa(dest, "\t%s%s", prefix, field->key);
+ if (field->value != NULL) {
+ str_append_c(dest, '=');
+ str_append_tabescaped(dest, field->value);
+ }
+ }
+}
+
+void auth_request_export(struct auth_request *request, string_t *dest)
+{
+ const struct auth_request_fields *fields = &request->fields;
+
+ str_append(dest, "user=");
+ str_append_tabescaped(dest, fields->user);
+
+ auth_str_add_keyvalue(dest, "service", fields->service);
+
+ if (fields->master_user != NULL)
+ auth_str_add_keyvalue(dest, "master-user", fields->master_user);
+ auth_str_add_keyvalue(dest, "original-username",
+ fields->original_username);
+ if (fields->requested_login_user != NULL) {
+ auth_str_add_keyvalue(dest, "requested-login-user",
+ fields->requested_login_user);
+ }
+
+ if (fields->local_ip.family != 0) {
+ auth_str_add_keyvalue(dest, "lip",
+ net_ip2addr(&fields->local_ip));
+ }
+ if (fields->remote_ip.family != 0) {
+ auth_str_add_keyvalue(dest, "rip",
+ net_ip2addr(&fields->remote_ip));
+ }
+ if (fields->local_port != 0)
+ str_printfa(dest, "\tlport=%u", fields->local_port);
+ if (fields->remote_port != 0)
+ str_printfa(dest, "\trport=%u", fields->remote_port);
+ if (fields->real_local_ip.family != 0) {
+ auth_str_add_keyvalue(dest, "real_lip",
+ net_ip2addr(&fields->real_local_ip));
+ }
+ if (fields->real_remote_ip.family != 0) {
+ auth_str_add_keyvalue(dest, "real_rip",
+ net_ip2addr(&fields->real_remote_ip));
+ }
+ if (fields->real_local_port != 0)
+ str_printfa(dest, "\treal_lport=%u", fields->real_local_port);
+ if (fields->real_remote_port != 0)
+ str_printfa(dest, "\treal_rport=%u", fields->real_remote_port);
+ if (fields->local_name != 0) {
+ str_append(dest, "\tlocal_name=");
+ str_append_tabescaped(dest, fields->local_name);
+ }
+ if (fields->session_id != NULL) {
+ str_append(dest, "\tsession=");
+ str_append_tabescaped(dest, fields->session_id);
+ }
+ if (event_want_debug(request->event))
+ str_append(dest, "\tdebug");
+ switch (fields->secured) {
+ case AUTH_REQUEST_SECURED_NONE: break;
+ case AUTH_REQUEST_SECURED: str_append(dest, "\tsecured"); break;
+ case AUTH_REQUEST_SECURED_TLS: str_append(dest, "\tsecured=tls"); break;
+ default: break;
+ }
+ if (fields->skip_password_check)
+ str_append(dest, "\tskip-password-check");
+ if (fields->delayed_credentials != NULL)
+ str_append(dest, "\tdelayed-credentials");
+ if (fields->valid_client_cert)
+ str_append(dest, "\tvalid-client-cert");
+ if (fields->no_penalty)
+ str_append(dest, "\tno-penalty");
+ if (fields->successful)
+ str_append(dest, "\tsuccessful");
+ if (fields->mech_name != NULL)
+ auth_str_add_keyvalue(dest, "mech", fields->mech_name);
+ if (fields->client_id != NULL)
+ auth_str_add_keyvalue(dest, "client_id", fields->client_id);
+ /* export passdb extra fields */
+ auth_request_export_fields(dest, fields->extra_fields, "passdb_");
+ /* export any userdb fields */
+ if (fields->userdb_reply != NULL)
+ auth_request_export_fields(dest, fields->userdb_reply, "userdb_");
+}
+
+bool auth_request_import_info(struct auth_request *request,
+ const char *key, const char *value)
+{
+ struct auth_request_fields *fields = &request->fields;
+ struct event *event = request->event;
+
+ i_assert(value != NULL);
+
+ /* authentication and user lookups may set these */
+ if (strcmp(key, "service") == 0) {
+ fields->service = p_strdup(request->pool, value);
+ event_add_str(event, "service", value);
+ } else if (strcmp(key, "lip") == 0) {
+ if (net_addr2ip(value, &fields->local_ip) < 0)
+ return TRUE;
+ event_add_str(event, "local_ip", value);
+ if (fields->real_local_ip.family == 0)
+ auth_request_import_info(request, "real_lip", value);
+ } else if (strcmp(key, "rip") == 0) {
+ if (net_addr2ip(value, &fields->remote_ip) < 0)
+ return TRUE;
+ event_add_str(event, "remote_ip", value);
+ if (fields->real_remote_ip.family == 0)
+ auth_request_import_info(request, "real_rip", value);
+ } else if (strcmp(key, "lport") == 0) {
+ if (net_str2port(value, &fields->local_port) < 0)
+ return TRUE;
+ event_add_int(event, "local_port", fields->local_port);
+ if (fields->real_local_port == 0)
+ auth_request_import_info(request, "real_lport", value);
+ } else if (strcmp(key, "rport") == 0) {
+ if (net_str2port(value, &fields->remote_port) < 0)
+ return TRUE;
+ event_add_int(event, "remote_port", fields->remote_port);
+ if (fields->real_remote_port == 0)
+ auth_request_import_info(request, "real_rport", value);
+ } else if (strcmp(key, "real_lip") == 0) {
+ if (net_addr2ip(value, &fields->real_local_ip) == 0)
+ event_add_str(event, "real_local_ip", value);
+ } else if (strcmp(key, "real_rip") == 0) {
+ if (net_addr2ip(value, &fields->real_remote_ip) == 0)
+ event_add_str(event, "real_remote_ip", value);
+ } else if (strcmp(key, "real_lport") == 0) {
+ if (net_str2port(value, &fields->real_local_port) == 0)
+ event_add_int(event, "real_local_port",
+ fields->real_local_port);
+ } else if (strcmp(key, "real_rport") == 0) {
+ if (net_str2port(value, &fields->real_remote_port) == 0)
+ event_add_int(event, "real_remote_port",
+ fields->real_remote_port);
+ } else if (strcmp(key, "local_name") == 0) {
+ fields->local_name = p_strdup(request->pool, value);
+ event_add_str(event, "local_name", value);
+ } else if (strcmp(key, "session") == 0) {
+ fields->session_id = p_strdup(request->pool, value);
+ event_add_str(event, "session", value);
+ } else if (strcmp(key, "debug") == 0)
+ event_set_forced_debug(request->event, TRUE);
+ else if (strcmp(key, "client_id") == 0) {
+ fields->client_id = p_strdup(request->pool, value);
+ event_add_str(event, "client_id", value);
+ } else if (strcmp(key, "forward_fields") == 0) {
+ auth_fields_import_prefixed(fields->extra_fields,
+ "forward_", value, 0);
+ /* make sure the forward_ fields aren't deleted by
+ auth_fields_rollback() if the first passdb lookup fails. */
+ auth_fields_snapshot(fields->extra_fields);
+ } else
+ return FALSE;
+ /* NOTE: keep in sync with auth_request_export() */
+ return TRUE;
+}
+
+bool auth_request_import_auth(struct auth_request *request,
+ const char *key, const char *value)
+{
+ struct auth_request_fields *fields = &request->fields;
+
+ i_assert(value != NULL);
+
+ if (auth_request_import_info(request, key, value))
+ return TRUE;
+
+ /* auth client may set these */
+ if (strcmp(key, "secured") == 0) {
+ if (strcmp(value, "tls") == 0) {
+ fields->secured = AUTH_REQUEST_SECURED_TLS;
+ event_add_str(request->event, "transport", "TLS");
+ } else {
+ fields->secured = AUTH_REQUEST_SECURED;
+ event_add_str(request->event, "transport", "trusted");
+ }
+ }
+ else if (strcmp(key, "final-resp-ok") == 0)
+ fields->final_resp_ok = TRUE;
+ else if (strcmp(key, "no-penalty") == 0)
+ fields->no_penalty = TRUE;
+ else if (strcmp(key, "valid-client-cert") == 0)
+ fields->valid_client_cert = TRUE;
+ else if (strcmp(key, "cert_username") == 0) {
+ if (request->set->ssl_username_from_cert && *value != '\0') {
+ /* get username from SSL certificate. it overrides
+ the username given by the auth mechanism. */
+ auth_request_set_username_forced(request, value);
+ fields->cert_username = TRUE;
+ }
+ } else {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool auth_request_import(struct auth_request *request,
+ const char *key, const char *value)
+{
+ struct auth_request_fields *fields = &request->fields;
+
+ i_assert(value != NULL);
+
+ if (auth_request_import_auth(request, key, value))
+ return TRUE;
+
+ /* for communication between auth master and worker processes */
+ if (strcmp(key, "user") == 0)
+ auth_request_set_username_forced(request, value);
+ else if (strcmp(key, "master-user") == 0) {
+ fields->master_user = p_strdup(request->pool, value);
+ event_add_str(request->event, "master_user", value);
+ } else if (strcmp(key, "original-username") == 0) {
+ fields->original_username = p_strdup(request->pool, value);
+ event_add_str(request->event, "original_user", value);
+ } else if (strcmp(key, "requested-login-user") == 0)
+ auth_request_set_login_username_forced(request, value);
+ else if (strcmp(key, "successful") == 0)
+ auth_request_set_auth_successful(request);
+ else if (strcmp(key, "skip-password-check") == 0)
+ auth_request_set_password_verified(request);
+ else if (strcmp(key, "delayed-credentials") == 0) {
+ /* just make passdb_handle_credentials() work identically in
+ auth-worker as it does in auth-master. the worker shouldn't
+ care about the actual contents of the credentials. */
+ fields->delayed_credentials = &uchar_nul;
+ fields->delayed_credentials_size = 1;
+ } else if (strcmp(key, "mech") == 0) {
+ fields->mech_name = p_strdup(request->pool, value);
+ event_add_str(request->event, "mechanism", value);
+ } else if (str_begins(key, "passdb_"))
+ auth_fields_add(fields->extra_fields, key+7, value, 0);
+ else if (str_begins(key, "userdb_")) {
+ if (fields->userdb_reply == NULL)
+ auth_request_init_userdb_reply(request, FALSE);
+ auth_fields_add(fields->userdb_reply, key+7, value, 0);
+ } else
+ return FALSE;
+
+ return TRUE;
+}
+
+static int
+auth_request_fix_username(struct auth_request *request, const char **username,
+ const char **error_r)
+{
+ const struct auth_settings *set = request->set;
+ unsigned char *p;
+ char *user;
+
+ if (*set->default_realm != '\0' &&
+ strchr(*username, '@') == NULL) {
+ user = p_strconcat(unsafe_data_stack_pool, *username, "@",
+ set->default_realm, NULL);
+ } else {
+ user = t_strdup_noconst(*username);
+ }
+
+ for (p = (unsigned char *)user; *p != '\0'; p++) {
+ if (set->username_translation_map[*p & 0xff] != 0)
+ *p = set->username_translation_map[*p & 0xff];
+ if (set->username_chars_map[*p & 0xff] == 0) {
+ *error_r = t_strdup_printf(
+ "Username character disallowed by auth_username_chars: "
+ "0x%02x (username: %s)", *p,
+ str_sanitize(*username, 128));
+ return -1;
+ }
+ }
+
+ if (*set->username_format != '\0') {
+ /* username format given, put it through variable expansion.
+ we'll have to temporarily replace request->user to get
+ %u to be the wanted username */
+ const char *error;
+ string_t *dest;
+
+ dest = t_str_new(256);
+ unsigned int count = 0;
+ const struct var_expand_table *table =
+ auth_request_get_var_expand_table_full(request,
+ user, NULL, &count);
+ if (auth_request_var_expand_with_table(dest,
+ set->username_format, request,
+ table, NULL, &error) <= 0) {
+ *error_r = t_strdup_printf(
+ "Failed to expand username_format=%s: %s",
+ set->username_format, error);
+ }
+ user = str_c_modifiable(dest);
+ }
+
+ if (user[0] == '\0') {
+ /* Some PAM plugins go nuts with empty usernames */
+ *error_r = "Empty username";
+ return -1;
+ }
+ *username = user;
+ return 0;
+}
+
+bool auth_request_set_username(struct auth_request *request,
+ const char *username, const char **error_r)
+{
+ const struct auth_settings *set = request->set;
+ const char *p, *login_username = NULL;
+
+ if (*set->master_user_separator != '\0' && !request->userdb_lookup) {
+ /* check if the username contains a master user */
+ p = strchr(username, *set->master_user_separator);
+ if (p != NULL) {
+ /* it does, set it. */
+ login_username = t_strdup_until(username, p);
+
+ /* username is the master user */
+ username = p + 1;
+ }
+ }
+
+ if (request->fields.original_username == NULL) {
+ /* the username may change later, but we need to use this
+ username when verifying at least DIGEST-MD5 password. */
+ request->fields.original_username =
+ p_strdup(request->pool, username);
+ event_add_str(request->event, "original_user",
+ request->fields.original_username);
+ }
+ if (request->fields.cert_username) {
+ /* cert_username overrides the username given by
+ authentication mechanism. but still do checks and
+ translations to it. */
+ username = request->fields.user;
+ }
+
+ if (auth_request_fix_username(request, &username, error_r) < 0) {
+ request->fields.user = NULL;
+ event_field_clear(request->event, "user");
+ return FALSE;
+ }
+ auth_request_set_username_forced(request, username);
+ if (request->fields.translated_username == NULL) {
+ /* similar to original_username, but after translations */
+ request->fields.translated_username = request->fields.user;
+ event_add_str(request->event, "translated_user",
+ request->fields.translated_username);
+ }
+ request->user_changed_by_lookup = TRUE;
+
+ if (login_username != NULL) {
+ if (!auth_request_set_login_username(request,
+ login_username,
+ error_r))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void auth_request_set_username_forced(struct auth_request *request,
+ const char *username)
+{
+ i_assert(username != NULL);
+
+ request->fields.user = p_strdup(request->pool, username);
+ event_add_str(request->event, "user", request->fields.user);
+}
+
+void auth_request_set_login_username_forced(struct auth_request *request,
+ const char *username)
+{
+ i_assert(username != NULL);
+
+ request->fields.requested_login_user =
+ p_strdup(request->pool, username);
+ event_add_str(request->event, "login_user",
+ request->fields.requested_login_user);
+}
+
+bool auth_request_set_login_username(struct auth_request *request,
+ const char *username,
+ const char **error_r)
+{
+ struct auth_passdb *master_passdb;
+
+ if (username[0] == '\0') {
+ *error_r = "Master user login attempted to use empty login username";
+ return FALSE;
+ }
+
+ if (strcmp(username, request->fields.user) == 0) {
+ /* The usernames are the same, we don't really wish to log
+ in as someone else */
+ return TRUE;
+ }
+
+ /* lookup request->user from masterdb first */
+ master_passdb = auth_request_get_auth(request)->masterdbs;
+ if (master_passdb == NULL) {
+ *error_r = "Master user login attempted without master passdbs";
+ return FALSE;
+ }
+ request->passdb = master_passdb;
+
+ if (auth_request_fix_username(request, &username, error_r) < 0) {
+ request->fields.requested_login_user = NULL;
+ event_field_clear(request->event, "login_user");
+ return FALSE;
+ }
+ auth_request_set_login_username_forced(request, username);
+
+ e_debug(request->event,
+ "%sMaster user lookup for login: %s",
+ auth_request_get_log_prefix_db(request),
+ request->fields.requested_login_user);
+ return TRUE;
+}
+
+void auth_request_master_user_login_finish(struct auth_request *request)
+{
+ if (request->failed)
+ return;
+
+ /* master login successful. update user and master_user variables. */
+ e_info(authdb_event(request),
+ "Master user logging in as %s",
+ request->fields.requested_login_user);
+
+ request->fields.master_user = request->fields.user;
+ event_add_str(request->event, "master_user",
+ request->fields.master_user);
+
+ auth_request_set_username_forced(request,
+ request->fields.requested_login_user);
+ request->fields.translated_username = request->fields.requested_login_user;
+ event_add_str(request->event, "translated_user",
+ request->fields.translated_username);
+ request->fields.requested_login_user = NULL;
+ event_field_clear(request->event, "login_user");
+}
+
+void auth_request_set_realm(struct auth_request *request, const char *realm)
+{
+ i_assert(realm != NULL);
+
+ request->fields.realm = p_strdup(request->pool, realm);
+ event_add_str(request->event, "realm", request->fields.realm);
+}
+
+void auth_request_set_auth_successful(struct auth_request *request)
+{
+ request->fields.successful = TRUE;
+}
+
+void auth_request_set_password_verified(struct auth_request *request)
+{
+ request->fields.skip_password_check = TRUE;
+}
+
+void auth_request_init_userdb_reply(struct auth_request *request,
+ bool add_default_fields)
+{
+ const char *error;
+
+ request->fields.userdb_reply = auth_fields_init(request->pool);
+ if (add_default_fields) {
+ if (userdb_template_export(request->userdb->default_fields_tmpl,
+ request, &error) < 0) {
+ e_error(authdb_event(request),
+ "Failed to expand default_fields: %s", error);
+ }
+ }
+}
+
+void auth_request_set_delayed_credentials(struct auth_request *request,
+ const unsigned char *credentials,
+ size_t size)
+{
+ request->fields.delayed_credentials =
+ p_memdup(request->pool, credentials, size);
+ request->fields.delayed_credentials_size = size;
+}
diff --git a/src/auth/auth-request-handler-private.h b/src/auth/auth-request-handler-private.h
new file mode 100644
index 0000000..4d733df
--- /dev/null
+++ b/src/auth/auth-request-handler-private.h
@@ -0,0 +1,27 @@
+#ifndef AUTH_REQUEST_HANDLER_PRIVATE_H
+#define AUTH_REQUEST_HANDLER_PRIVATE_H
+
+struct auth_request;
+struct auth_client_connection;
+
+struct auth_request_handler {
+ int refcount;
+ pool_t pool;
+ HASH_TABLE(void *, struct auth_request *) requests;
+
+ unsigned int connect_uid, client_pid;
+
+ auth_client_request_callback_t *callback;
+ struct auth_client_connection *conn;
+
+ auth_master_request_callback_t *master_callback;
+ auth_request_handler_reply_callback_t *reply_callback;
+ auth_request_handler_reply_continue_callback_t *reply_continue_callback;
+ verify_plain_continue_callback_t *verify_plain_continue_callback;
+
+ bool destroyed:1;
+ bool token_auth:1;
+};
+
+
+#endif
diff --git a/src/auth/auth-request-handler.c b/src/auth/auth-request-handler.c
new file mode 100644
index 0000000..d592924
--- /dev/null
+++ b/src/auth/auth-request-handler.c
@@ -0,0 +1,991 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "ioloop.h"
+#include "array.h"
+#include "aqueue.h"
+#include "base64.h"
+#include "hash.h"
+#include "net.h"
+#include "str.h"
+#include "strescape.h"
+#include "str-sanitize.h"
+#include "master-interface.h"
+#include "auth-penalty.h"
+#include "auth-request.h"
+#include "auth-token.h"
+#include "auth-client-connection.h"
+#include "auth-master-connection.h"
+#include "auth-request-handler.h"
+#include "auth-request-handler-private.h"
+#include "auth-policy.h"
+
+#define AUTH_FAILURE_DELAY_CHECK_MSECS 500
+static ARRAY(struct auth_request *) auth_failures_arr;
+static struct aqueue *auth_failures;
+static struct timeout *to_auth_failures;
+
+static void auth_failure_timeout(void *context) ATTR_NULL(1);
+
+
+static void
+auth_request_handler_default_reply_callback(struct auth_request *request,
+ enum auth_client_result result,
+ const void *auth_reply,
+ size_t reply_size);
+
+static void
+auth_request_handler_default_reply_continue(struct auth_request *request,
+ const void *reply,
+ size_t reply_size);
+
+struct auth_request_handler *
+auth_request_handler_create(bool token_auth, auth_client_request_callback_t *callback,
+ struct auth_client_connection *conn,
+ auth_master_request_callback_t *master_callback)
+{
+ struct auth_request_handler *handler;
+ pool_t pool;
+
+ pool = pool_alloconly_create("auth request handler", 4096);
+
+ handler = p_new(pool, struct auth_request_handler, 1);
+ handler->refcount = 1;
+ handler->pool = pool;
+ hash_table_create_direct(&handler->requests, pool, 0);
+ handler->callback = callback;
+ handler->conn = conn;
+ handler->master_callback = master_callback;
+ handler->token_auth = token_auth;
+ handler->reply_callback =
+ auth_request_handler_default_reply_callback;
+ handler->reply_continue_callback =
+ auth_request_handler_default_reply_continue;
+ handler->verify_plain_continue_callback =
+ auth_request_default_verify_plain_continue;
+ return handler;
+}
+
+unsigned int
+auth_request_handler_get_request_count(struct auth_request_handler *handler)
+{
+ return hash_table_count(handler->requests);
+}
+
+void auth_request_handler_abort_requests(struct auth_request_handler *handler)
+{
+ struct hash_iterate_context *iter;
+ void *key;
+ struct auth_request *auth_request;
+
+ iter = hash_table_iterate_init(handler->requests);
+ while (hash_table_iterate(iter, handler->requests, &key, &auth_request)) {
+ switch (auth_request->state) {
+ case AUTH_REQUEST_STATE_NEW:
+ case AUTH_REQUEST_STATE_MECH_CONTINUE:
+ case AUTH_REQUEST_STATE_FINISHED:
+ auth_request->removed_from_handler = TRUE;
+ auth_request_unref(&auth_request);
+ hash_table_remove(handler->requests, key);
+ break;
+ case AUTH_REQUEST_STATE_PASSDB:
+ case AUTH_REQUEST_STATE_USERDB:
+ /* can't abort a pending passdb/userdb lookup */
+ break;
+ case AUTH_REQUEST_STATE_MAX:
+ i_unreached();
+ }
+ }
+ hash_table_iterate_deinit(&iter);
+}
+
+void auth_request_handler_unref(struct auth_request_handler **_handler)
+{
+ struct auth_request_handler *handler = *_handler;
+
+ *_handler = NULL;
+
+ i_assert(handler->refcount > 0);
+ if (--handler->refcount > 0)
+ return;
+
+ i_assert(hash_table_count(handler->requests) == 0);
+
+ /* notify parent that we're done with all requests */
+ handler->callback(NULL, handler->conn);
+
+ hash_table_destroy(&handler->requests);
+ pool_unref(&handler->pool);
+}
+
+void auth_request_handler_destroy(struct auth_request_handler **_handler)
+{
+ struct auth_request_handler *handler = *_handler;
+
+ *_handler = NULL;
+
+ i_assert(!handler->destroyed);
+
+ handler->destroyed = TRUE;
+ auth_request_handler_unref(&handler);
+}
+
+void auth_request_handler_set(struct auth_request_handler *handler,
+ unsigned int connect_uid,
+ unsigned int client_pid)
+{
+ handler->connect_uid = connect_uid;
+ handler->client_pid = client_pid;
+}
+
+static void auth_request_handler_remove(struct auth_request_handler *handler,
+ struct auth_request *request)
+{
+ i_assert(request->handler == handler);
+
+ if (request->removed_from_handler) {
+ /* already removed it */
+ return;
+ }
+ request->removed_from_handler = TRUE;
+
+ /* if db lookup is stuck, this call doesn't actually free the auth
+ request, so make sure we don't get back here. */
+ timeout_remove(&request->to_abort);
+
+ hash_table_remove(handler->requests, POINTER_CAST(request->id));
+ auth_request_unref(&request);
+}
+
+static void
+auth_str_add_keyvalue(string_t *dest, const char *key, const char *value)
+{
+ str_append_c(dest, '\t');
+ str_append(dest, key);
+ str_append_c(dest, '=');
+ str_append_tabescaped(dest, value);
+}
+
+static void
+auth_str_append_extra_fields(struct auth_request *request, string_t *dest)
+{
+ const struct auth_request_fields *fields = &request->fields;
+
+ if (!auth_fields_is_empty(fields->extra_fields)) {
+ str_append_c(dest, '\t');
+ auth_fields_append(fields->extra_fields, dest,
+ AUTH_FIELD_FLAG_HIDDEN, 0);
+ }
+
+ if (fields->original_username != NULL &&
+ null_strcmp(fields->original_username, fields->user) != 0 &&
+ !auth_fields_exists(fields->extra_fields, "original_user")) {
+ auth_str_add_keyvalue(dest, "original_user",
+ fields->original_username);
+ }
+ if (fields->master_user != NULL &&
+ !auth_fields_exists(fields->extra_fields, "auth_user"))
+ auth_str_add_keyvalue(dest, "auth_user", fields->master_user);
+ if (*request->set->anonymous_username != '\0' &&
+ null_strcmp(fields->user, request->set->anonymous_username) == 0) {
+ /* this is an anonymous login, either via ANONYMOUS
+ SASL mechanism or simply logging in as the anonymous
+ user via another mechanism */
+ str_append(dest, "\tanonymous");
+ }
+ if (!request->auth_only &&
+ auth_fields_exists(fields->extra_fields, "proxy")) {
+ /* we're proxying */
+ if (!auth_fields_exists(fields->extra_fields, "pass") &&
+ request->mech_password != NULL) {
+ /* send back the password that was sent by user
+ (not the password in passdb). */
+ auth_str_add_keyvalue(dest, "pass",
+ request->mech_password);
+ }
+ if (fields->master_user != NULL &&
+ !auth_fields_exists(fields->extra_fields, "master") &&
+ *fields->master_user != '\0') {
+ /* the master username needs to be forwarded */
+ auth_str_add_keyvalue(dest, "master",
+ fields->master_user);
+ }
+ }
+}
+
+static void
+auth_request_handle_failure(struct auth_request *request, const char *reply)
+{
+ struct auth_request_handler *handler = request->handler;
+
+ /* handle failure here */
+ auth_request_log_finished(request);
+
+ if (request->in_delayed_failure_queue) {
+ /* we came here from flush_failures() */
+ handler->callback(reply, handler->conn);
+ return;
+ }
+
+ /* remove the request from requests-list */
+ auth_request_ref(request);
+ auth_request_handler_remove(handler, request);
+
+ if (request->set->policy_report_after_auth)
+ auth_policy_report(request);
+
+ if (auth_fields_exists(request->fields.extra_fields, "nodelay")) {
+ /* passdb specifically requested not to delay the reply. */
+ handler->callback(reply, handler->conn);
+ auth_request_unref(&request);
+ return;
+ }
+
+ /* failure. don't announce it immediately to avoid
+ a) timing attacks, b) flooding */
+ request->in_delayed_failure_queue = TRUE;
+ handler->refcount++;
+
+ if (auth_penalty != NULL) {
+ auth_penalty_update(auth_penalty, request,
+ request->last_penalty + 1);
+ }
+
+ auth_request_refresh_last_access(request);
+ aqueue_append(auth_failures, &request);
+ if (to_auth_failures == NULL) {
+ to_auth_failures =
+ timeout_add_short(AUTH_FAILURE_DELAY_CHECK_MSECS,
+ auth_failure_timeout, NULL);
+ }
+}
+
+static void
+auth_request_handler_reply_success_finish(struct auth_request *request)
+{
+ struct auth_request_handler *handler = request->handler;
+ string_t *str = t_str_new(128);
+
+ auth_request_log_finished(request);
+
+ if (request->last_penalty != 0 && auth_penalty != NULL) {
+ /* reset penalty */
+ auth_penalty_update(auth_penalty, request, 0);
+ }
+
+ /* sanitize these fields, since the login code currently assumes they
+ are exactly in this format. */
+ auth_fields_booleanize(request->fields.extra_fields, "nologin");
+ auth_fields_booleanize(request->fields.extra_fields, "proxy");
+
+ str_printfa(str, "OK\t%u\tuser=", request->id);
+ str_append_tabescaped(str, request->fields.user);
+ auth_str_append_extra_fields(request, str);
+
+ if (request->set->policy_report_after_auth)
+ auth_policy_report(request);
+
+ if (handler->master_callback == NULL ||
+ auth_fields_exists(request->fields.extra_fields, "nologin") ||
+ auth_fields_exists(request->fields.extra_fields, "proxy")) {
+ /* this request doesn't have to wait for master
+ process to pick it up. delete it */
+ auth_request_handler_remove(handler, request);
+ }
+
+ handler->callback(str_c(str), handler->conn);
+}
+
+static void
+auth_request_handler_reply_failure_finish(struct auth_request *request)
+{
+ const char *code = NULL;
+ string_t *str = t_str_new(128);
+
+ auth_fields_remove(request->fields.extra_fields, "nologin");
+
+ str_printfa(str, "FAIL\t%u", request->id);
+ if (request->fields.user != NULL)
+ auth_str_add_keyvalue(str, "user", request->fields.user);
+ else if (request->fields.original_username != NULL) {
+ auth_str_add_keyvalue(str, "user",
+ request->fields.original_username);
+ }
+
+ if (request->internal_failure) {
+ code = AUTH_CLIENT_FAIL_CODE_TEMPFAIL;
+ } else if (request->fields.master_user != NULL) {
+ /* authentication succeeded, but we can't log in
+ as the wanted user */
+ code = AUTH_CLIENT_FAIL_CODE_AUTHZFAILED;
+ } else {
+ switch (request->passdb_result) {
+ case PASSDB_RESULT_NEXT:
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+ case PASSDB_RESULT_USER_UNKNOWN:
+ case PASSDB_RESULT_PASSWORD_MISMATCH:
+ case PASSDB_RESULT_OK:
+ break;
+ case PASSDB_RESULT_USER_DISABLED:
+ code = AUTH_CLIENT_FAIL_CODE_USER_DISABLED;
+ break;
+ case PASSDB_RESULT_PASS_EXPIRED:
+ code = AUTH_CLIENT_FAIL_CODE_PASS_EXPIRED;
+ break;
+ }
+ }
+
+ if (auth_fields_exists(request->fields.extra_fields, "nodelay")) {
+ /* this is normally a hidden field, need to add it explicitly */
+ str_append(str, "\tnodelay");
+ }
+
+ if (code != NULL) {
+ str_append(str, "\tcode=");
+ str_append(str, code);
+ }
+ auth_str_append_extra_fields(request, str);
+
+ auth_request_handle_failure(request, str_c(str));
+}
+
+static void
+auth_request_handler_proxy_callback(bool success, struct auth_request *request)
+{
+ struct auth_request_handler *handler = request->handler;
+
+ if (success)
+ auth_request_handler_reply_success_finish(request);
+ else
+ auth_request_handler_reply_failure_finish(request);
+ auth_request_handler_unref(&handler);
+}
+
+void auth_request_handler_reply(struct auth_request *request,
+ enum auth_client_result result,
+ const void *auth_reply, size_t reply_size)
+{
+ struct auth_request_handler *handler = request->handler;
+
+ request->handler_pending_reply = FALSE;
+ handler->reply_callback(request, result, auth_reply, reply_size);
+}
+
+static void
+auth_request_handler_default_reply_callback(struct auth_request *request,
+ enum auth_client_result result,
+ const void *auth_reply,
+ size_t reply_size)
+{
+ struct auth_request_handler *handler = request->handler;
+ string_t *str;
+ int ret;
+
+ if (handler->destroyed) {
+ /* the client connection was already closed. we can't do
+ anything but abort this request */
+ request->internal_failure = TRUE;
+ result = AUTH_CLIENT_RESULT_FAILURE;
+ /* make sure this request is set to finished state
+ (it's not with result=continue) */
+ auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED);
+ }
+
+ switch (result) {
+ case AUTH_CLIENT_RESULT_CONTINUE:
+ str = t_str_new(16 + MAX_BASE64_ENCODED_SIZE(reply_size));
+ str_printfa(str, "CONT\t%u\t", request->id);
+ base64_encode(auth_reply, reply_size, str);
+
+ request->accept_cont_input = TRUE;
+ handler->callback(str_c(str), handler->conn);
+ break;
+ case AUTH_CLIENT_RESULT_SUCCESS:
+ if (reply_size > 0) {
+ str = t_str_new(MAX_BASE64_ENCODED_SIZE(reply_size));
+ base64_encode(auth_reply, reply_size, str);
+ auth_fields_add(request->fields.extra_fields, "resp",
+ str_c(str), 0);
+ }
+ ret = auth_request_proxy_finish(request,
+ auth_request_handler_proxy_callback);
+ if (ret < 0)
+ auth_request_handler_reply_failure_finish(request);
+ else if (ret > 0)
+ auth_request_handler_reply_success_finish(request);
+ else
+ return;
+ break;
+ case AUTH_CLIENT_RESULT_FAILURE:
+ auth_request_proxy_finish_failure(request);
+ if (reply_size > 0) {
+ str = t_str_new(MAX_BASE64_ENCODED_SIZE(reply_size));
+ base64_encode(auth_reply, reply_size, str);
+ auth_fields_add(request->fields.extra_fields, "resp",
+ str_c(str), 0);
+ }
+ auth_request_handler_reply_failure_finish(request);
+ break;
+ }
+ /* NOTE: request may be destroyed now */
+
+ auth_request_handler_unref(&handler);
+}
+
+void auth_request_handler_reply_continue(struct auth_request *request,
+ const void *reply, size_t reply_size)
+{
+ request->handler->reply_continue_callback(request, reply, reply_size);
+}
+
+static void
+auth_request_handler_default_reply_continue(struct auth_request *request,
+ const void *reply,
+ size_t reply_size)
+{
+ auth_request_handler_reply(request, AUTH_CLIENT_RESULT_CONTINUE,
+ reply, reply_size);
+}
+
+void auth_request_handler_abort(struct auth_request *request)
+{
+ i_assert(request->handler_pending_reply);
+
+ /* request destroyed while waiting for auth_request_penalty_finish()
+ to be called. */
+ auth_request_handler_unref(&request->handler);
+}
+
+static void
+auth_request_handler_auth_fail_code(struct auth_request_handler *handler,
+ struct auth_request *request,
+ const char *fail_code, const char *reason)
+{
+ string_t *str = t_str_new(128);
+
+ e_info(request->mech_event, "%s", reason);
+
+ str_printfa(str, "FAIL\t%u", request->id);
+ if (*fail_code != '\0') {
+ str_append(str, "\tcode=");
+ str_append(str, fail_code);
+ }
+ str_append(str, "\treason=");
+ str_append_tabescaped(str, reason);
+
+ handler->callback(str_c(str), handler->conn);
+ auth_request_handler_remove(handler, request);
+}
+
+static void auth_request_handler_auth_fail
+(struct auth_request_handler *handler, struct auth_request *request,
+ const char *reason)
+{
+ auth_request_handler_auth_fail_code(handler, request, "", reason);
+}
+
+static void auth_request_timeout(struct auth_request *request)
+{
+ unsigned int secs = (unsigned int)(time(NULL) - request->last_access);
+
+ if (request->state != AUTH_REQUEST_STATE_MECH_CONTINUE) {
+ /* client's fault */
+ e_error(request->mech_event,
+ "Request %u.%u timed out after %u secs, state=%d",
+ request->handler->client_pid, request->id,
+ secs, request->state);
+ } else if (request->set->verbose) {
+ e_info(request->mech_event,
+ "Request timed out waiting for client to continue authentication "
+ "(%u secs)", secs);
+ }
+ auth_request_handler_remove(request->handler, request);
+}
+
+static void auth_request_penalty_finish(struct auth_request *request)
+{
+ timeout_remove(&request->to_penalty);
+ auth_request_initial(request);
+}
+
+static void
+auth_penalty_callback(unsigned int penalty, struct auth_request *request)
+{
+ unsigned int secs;
+
+ request->last_penalty = penalty;
+
+ if (penalty == 0)
+ auth_request_initial(request);
+ else {
+ secs = auth_penalty_to_secs(penalty);
+ request->to_penalty = timeout_add(secs * 1000,
+ auth_request_penalty_finish,
+ request);
+ }
+}
+
+bool auth_request_handler_auth_begin(struct auth_request_handler *handler,
+ const char *args)
+{
+ const struct mech_module *mech;
+ struct auth_request *request;
+ const char *const *list, *name, *arg, *initial_resp;
+ void *initial_resp_data;
+ unsigned int id;
+ buffer_t *buf;
+
+ i_assert(!handler->destroyed);
+
+ /* <id> <mechanism> [...] */
+ list = t_strsplit_tabescaped(args);
+ if (list[0] == NULL || list[1] == NULL ||
+ str_to_uint(list[0], &id) < 0 || id == 0) {
+ e_error(handler->conn->event,
+ "BUG: Authentication client %u "
+ "sent broken AUTH request", handler->client_pid);
+ return FALSE;
+ }
+
+ if (handler->token_auth) {
+ mech = &mech_dovecot_token;
+ if (strcmp(list[1], mech->mech_name) != 0) {
+ /* unsupported mechanism */
+ e_error(handler->conn->event,
+ "BUG: Authentication client %u requested invalid "
+ "authentication mechanism %s (DOVECOT-TOKEN required)",
+ handler->client_pid, str_sanitize(list[1], MAX_MECH_NAME_LEN));
+ return FALSE;
+ }
+ } else {
+ struct auth *auth_default = auth_default_service();
+ mech = mech_register_find(auth_default->reg, list[1]);
+ if (mech == NULL) {
+ /* unsupported mechanism */
+ e_error(handler->conn->event,
+ "BUG: Authentication client %u requested unsupported "
+ "authentication mechanism %s", handler->client_pid,
+ str_sanitize(list[1], MAX_MECH_NAME_LEN));
+ return FALSE;
+ }
+ }
+
+ request = auth_request_new(mech, handler->conn->event);
+ request->handler = handler;
+ request->connect_uid = handler->connect_uid;
+ request->client_pid = handler->client_pid;
+ request->id = id;
+ request->auth_only = handler->master_callback == NULL;
+
+ /* parse optional parameters */
+ initial_resp = NULL;
+ for (list += 2; *list != NULL; list++) {
+ arg = strchr(*list, '=');
+ if (arg == NULL) {
+ name = *list;
+ arg = "";
+ } else {
+ name = t_strdup_until(*list, arg);
+ arg++;
+ }
+
+ if (auth_request_import_auth(request, name, arg))
+ ;
+ else if (strcmp(name, "resp") == 0) {
+ initial_resp = arg;
+ /* this must be the last parameter */
+ list++;
+ break;
+ }
+ }
+
+ if (*list != NULL) {
+ e_error(handler->conn->event,
+ "BUG: Authentication client %u "
+ "sent AUTH parameters after 'resp'",
+ handler->client_pid);
+ auth_request_unref(&request);
+ return FALSE;
+ }
+
+ if (request->fields.service == NULL) {
+ e_error(handler->conn->event,
+ "BUG: Authentication client %u "
+ "didn't specify service in request",
+ handler->client_pid);
+ auth_request_unref(&request);
+ return FALSE;
+ }
+ if (hash_table_lookup(handler->requests, POINTER_CAST(id)) != NULL) {
+ e_error(handler->conn->event,
+ "BUG: Authentication client %u "
+ "sent a duplicate ID %u", handler->client_pid, id);
+ auth_request_unref(&request);
+ return FALSE;
+ }
+ auth_request_init(request);
+
+ request->to_abort = timeout_add(MASTER_AUTH_SERVER_TIMEOUT_SECS * 1000,
+ auth_request_timeout, request);
+ hash_table_insert(handler->requests, POINTER_CAST(id), request);
+
+ if (request->set->ssl_require_client_cert &&
+ !request->fields.valid_client_cert) {
+ /* we fail without valid certificate */
+ auth_request_handler_auth_fail(handler, request,
+ "Client didn't present valid SSL certificate");
+ return TRUE;
+ }
+
+ if (request->set->ssl_require_client_cert &&
+ request->set->ssl_username_from_cert &&
+ !request->fields.cert_username) {
+ auth_request_handler_auth_fail(handler, request,
+ "SSL certificate didn't contain username");
+ return TRUE;
+ }
+
+ /* Handle initial respose */
+ if (initial_resp == NULL) {
+ /* No initial response */
+ request->initial_response = NULL;
+ request->initial_response_len = 0;
+ } else if (handler->conn->version_minor < 2 && *initial_resp == '\0') {
+ /* Some authentication clients like Exim send and empty initial
+ response field when it is in fact absent in the
+ authentication command. This was allowed for older versions
+ of the Dovecot authentication protocol. */
+ request->initial_response = NULL;
+ request->initial_response_len = 0;
+ } else if (*initial_resp == '\0' || strcmp(initial_resp, "=") == 0 ) {
+ /* Empty initial response - Protocols that use SASL often
+ use '=' to indicate an empty initial response; i.e., to
+ distinguish it from an absent initial response. However, that
+ should not be conveyed to the SASL layer (it is not even
+ valid Base64); only the empty string should be passed on.
+ Still, we recognize it here anyway, because we used to make
+ the same mistake. */
+ request->initial_response = uchar_empty_ptr;
+ request->initial_response_len = 0;
+ } else {
+ size_t len = strlen(initial_resp);
+
+ /* Initial response encoded in Bas64 */
+ buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(len));
+ if (base64_decode(initial_resp, len, NULL, buf) < 0) {
+ auth_request_handler_auth_fail_code(handler, request,
+ AUTH_CLIENT_FAIL_CODE_INVALID_BASE64,
+ "Invalid base64 data in initial response");
+ return TRUE;
+ }
+ initial_resp_data =
+ p_malloc(request->pool, I_MAX(buf->used, 1));
+ memcpy(initial_resp_data, buf->data, buf->used);
+ request->initial_response = initial_resp_data;
+ request->initial_response_len = buf->used;
+ }
+
+ /* handler is referenced until auth_request_handler_reply()
+ is called. */
+ handler->refcount++;
+ request->handler_pending_reply = TRUE;
+
+ /* before we start authenticating, see if we need to wait first */
+ auth_penalty_lookup(auth_penalty, request, auth_penalty_callback);
+ return TRUE;
+}
+
+bool auth_request_handler_auth_continue(struct auth_request_handler *handler,
+ const char *args)
+{
+ struct auth_request *request;
+ const char *data;
+ size_t data_len;
+ buffer_t *buf;
+ unsigned int id;
+
+ data = strchr(args, '\t');
+ if (data == NULL || str_to_uint(t_strdup_until(args, data), &id) < 0) {
+ e_error(handler->conn->event,
+ "BUG: Authentication client sent broken CONT request");
+ return FALSE;
+ }
+ data++;
+
+ request = hash_table_lookup(handler->requests, POINTER_CAST(id));
+ if (request == NULL) {
+ const char *reply = t_strdup_printf(
+ "FAIL\t%u\treason=Authentication request timed out", id);
+ handler->callback(reply, handler->conn);
+ return TRUE;
+ }
+
+ /* accept input only once after mechanism has sent a CONT reply */
+ if (!request->accept_cont_input) {
+ auth_request_handler_auth_fail(handler, request,
+ "Unexpected continuation");
+ return TRUE;
+ }
+ request->accept_cont_input = FALSE;
+
+ data_len = strlen(data);
+ buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(data_len));
+ if (base64_decode(data, data_len, NULL, buf) < 0) {
+ auth_request_handler_auth_fail_code(handler, request,
+ AUTH_CLIENT_FAIL_CODE_INVALID_BASE64,
+ "Invalid base64 data in continued response");
+ return TRUE;
+ }
+
+ /* handler is referenced until auth_request_handler_reply()
+ is called. */
+ handler->refcount++;
+ auth_request_continue(request, buf->data, buf->used);
+ return TRUE;
+}
+
+static void auth_str_append_userdb_extra_fields(struct auth_request *request,
+ string_t *dest)
+{
+ str_append_c(dest, '\t');
+ auth_fields_append(request->fields.userdb_reply, dest,
+ AUTH_FIELD_FLAG_HIDDEN, 0);
+
+ if (request->fields.master_user != NULL &&
+ !auth_fields_exists(request->fields.userdb_reply, "master_user")) {
+ auth_str_add_keyvalue(dest, "master_user",
+ request->fields.master_user);
+ }
+ auth_str_add_keyvalue(dest, "auth_mech", request->mech->mech_name);
+ if (*request->set->anonymous_username != '\0' &&
+ strcmp(request->fields.user, request->set->anonymous_username) == 0) {
+ /* this is an anonymous login, either via ANONYMOUS
+ SASL mechanism or simply logging in as the anonymous
+ user via another mechanism */
+ str_append(dest, "\tanonymous");
+ }
+ /* generate auth_token when master service provided session_pid */
+ if (request->request_auth_token &&
+ request->session_pid != (pid_t)-1) {
+ const char *auth_token =
+ auth_token_get(request->fields.service,
+ dec2str(request->session_pid),
+ request->fields.user,
+ request->fields.session_id);
+ auth_str_add_keyvalue(dest, "auth_token", auth_token);
+ }
+ if (request->fields.master_user != NULL) {
+ auth_str_add_keyvalue(dest, "auth_user",
+ request->fields.master_user);
+ } else if (request->fields.original_username != NULL &&
+ strcmp(request->fields.original_username,
+ request->fields.user) != 0) {
+ auth_str_add_keyvalue(dest, "auth_user",
+ request->fields.original_username);
+ }
+}
+
+static void userdb_callback(enum userdb_result result,
+ struct auth_request *request)
+{
+ struct auth_request_handler *handler = request->handler;
+ string_t *str;
+ const char *value;
+
+ i_assert(request->state == AUTH_REQUEST_STATE_USERDB);
+
+ auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED);
+
+ if (request->userdb_lookup_tempfailed)
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+
+ str = t_str_new(128);
+ switch (result) {
+ case USERDB_RESULT_INTERNAL_FAILURE:
+ str_printfa(str, "FAIL\t%u", request->id);
+ if (request->userdb_lookup_tempfailed) {
+ value = auth_fields_find(request->fields.userdb_reply,
+ "reason");
+ if (value != NULL)
+ auth_str_add_keyvalue(str, "reason", value);
+ }
+ break;
+ case USERDB_RESULT_USER_UNKNOWN:
+ str_printfa(str, "NOTFOUND\t%u", request->id);
+ break;
+ case USERDB_RESULT_OK:
+ str_printfa(str, "USER\t%u\t", request->id);
+ str_append_tabescaped(str, request->fields.user);
+ auth_str_append_userdb_extra_fields(request, str);
+ break;
+ }
+ handler->master_callback(str_c(str), request->master);
+
+ auth_master_connection_unref(&request->master);
+ auth_request_unref(&request);
+ auth_request_handler_unref(&handler);
+}
+
+static bool
+auth_master_request_failed(struct auth_request_handler *handler,
+ struct auth_master_connection *master,
+ unsigned int id)
+{
+ if (handler->master_callback == NULL)
+ return FALSE;
+ handler->master_callback(t_strdup_printf("FAIL\t%u", id), master);
+ return TRUE;
+}
+
+bool auth_request_handler_master_request(struct auth_request_handler *handler,
+ struct auth_master_connection *master,
+ unsigned int id, unsigned int client_id,
+ const char *const *params)
+{
+ struct auth_request *request;
+ struct net_unix_cred cred;
+
+ request = hash_table_lookup(handler->requests, POINTER_CAST(client_id));
+ if (request == NULL) {
+ e_error(master->event, "Master request %u.%u not found",
+ handler->client_pid, client_id);
+ return auth_master_request_failed(handler, master, id);
+ }
+
+ auth_request_ref(request);
+ auth_request_handler_remove(handler, request);
+
+ for (; *params != NULL; params++) {
+ const char *name, *param = strchr(*params, '=');
+
+ if (param == NULL) {
+ name = *params;
+ param = "";
+ } else {
+ name = t_strdup_until(*params, param);
+ param++;
+ }
+
+ (void)auth_request_import_master(request, name, param);
+ }
+
+ /* verify session pid if specified and possible */
+ if (request->session_pid != (pid_t)-1 &&
+ net_getunixcred(master->fd, &cred) == 0 &&
+ cred.pid != (pid_t)-1 && request->session_pid != cred.pid) {
+ e_error(master->event,
+ "Session pid %ld provided by master for request %u.%u "
+ "did not match peer credentials (pid=%ld, uid=%ld)",
+ (long)request->session_pid,
+ handler->client_pid, client_id,
+ (long)cred.pid, (long)cred.uid);
+ return auth_master_request_failed(handler, master, id);
+ }
+
+ if (request->state != AUTH_REQUEST_STATE_FINISHED ||
+ !request->fields.successful) {
+ e_error(master->event,
+ "Master requested unfinished authentication request "
+ "%u.%u", handler->client_pid, client_id);
+ handler->master_callback(t_strdup_printf("FAIL\t%u", id),
+ master);
+ auth_request_unref(&request);
+ } else {
+ /* the request isn't being referenced anywhere anymore,
+ so we can do a bit of kludging.. replace the request's
+ old client_id with master's id. */
+ auth_request_set_state(request, AUTH_REQUEST_STATE_USERDB);
+ request->id = id;
+ request->master = master;
+
+ /* master and handler are referenced until userdb_callback i
+ s called. */
+ auth_master_connection_ref(master);
+ handler->refcount++;
+ auth_request_lookup_user(request, userdb_callback);
+ }
+ return TRUE;
+}
+
+void auth_request_handler_cancel_request(struct auth_request_handler *handler,
+ unsigned int client_id)
+{
+ struct auth_request *request;
+
+ request = hash_table_lookup(handler->requests, POINTER_CAST(client_id));
+ if (request != NULL)
+ auth_request_handler_remove(handler, request);
+}
+
+void auth_request_handler_flush_failures(bool flush_all)
+{
+ struct auth_request **auth_requests, *auth_request;
+ unsigned int i, j, count;
+ time_t diff;
+
+ count = aqueue_count(auth_failures);
+ if (count == 0) {
+ timeout_remove(&to_auth_failures);
+ return;
+ }
+
+ auth_requests = array_front_modifiable(&auth_failures_arr);
+ /* count the number of requests that we need to flush */
+ for (i = 0; i < count; i++) {
+ auth_request = auth_requests[aqueue_idx(auth_failures, i)];
+
+ /* FIXME: assumes that failure_delay is always the same. */
+ diff = ioloop_time - auth_request->last_access;
+ if (diff < (time_t)auth_request->set->failure_delay &&
+ !flush_all)
+ break;
+ }
+
+ /* shuffle these requests to try to prevent any kind of timing attacks
+ where attacker performs multiple requests in parallel and attempts
+ to figure out results based on the order of replies. */
+ count = i;
+ for (i = 0; i < count; i++) {
+ j = random() % (count - i) + i;
+ auth_request = auth_requests[aqueue_idx(auth_failures, i)];
+
+ /* swap i & j */
+ auth_requests[aqueue_idx(auth_failures, i)] =
+ auth_requests[aqueue_idx(auth_failures, j)];
+ auth_requests[aqueue_idx(auth_failures, j)] = auth_request;
+ }
+
+ /* flush the requests */
+ for (i = 0; i < count; i++) {
+ auth_request = auth_requests[aqueue_idx(auth_failures, 0)];
+ aqueue_delete_tail(auth_failures);
+
+ i_assert(auth_request != NULL);
+ i_assert(auth_request->state == AUTH_REQUEST_STATE_FINISHED);
+ auth_request_handler_reply(auth_request,
+ AUTH_CLIENT_RESULT_FAILURE,
+ uchar_empty_ptr, 0);
+ auth_request_unref(&auth_request);
+ }
+}
+
+static void auth_failure_timeout(void *context ATTR_UNUSED)
+{
+ auth_request_handler_flush_failures(FALSE);
+}
+
+void auth_request_handler_init(void)
+{
+ i_array_init(&auth_failures_arr, 128);
+ auth_failures = aqueue_init(&auth_failures_arr.arr);
+}
+
+void auth_request_handler_deinit(void)
+{
+ auth_request_handler_flush_failures(TRUE);
+ array_free(&auth_failures_arr);
+ aqueue_deinit(&auth_failures);
+
+ timeout_remove(&to_auth_failures);
+}
diff --git a/src/auth/auth-request-handler.h b/src/auth/auth-request-handler.h
new file mode 100644
index 0000000..ceba935
--- /dev/null
+++ b/src/auth/auth-request-handler.h
@@ -0,0 +1,69 @@
+#ifndef AUTH_REQUEST_HANDLER_H
+#define AUTH_REQUEST_HANDLER_H
+
+struct auth_request;
+struct auth_client_connection;
+struct auth_master_connection;
+struct auth_stream_reply;
+
+enum auth_client_result {
+ AUTH_CLIENT_RESULT_CONTINUE = 1,
+ AUTH_CLIENT_RESULT_SUCCESS,
+ AUTH_CLIENT_RESULT_FAILURE
+};
+
+typedef void
+auth_client_request_callback_t(const char *reply, struct auth_client_connection *conn);
+typedef void
+auth_master_request_callback_t(const char *reply, struct auth_master_connection *conn);
+
+typedef void
+auth_request_handler_reply_callback_t(struct auth_request *request,
+ enum auth_client_result result,
+ const void *auth_reply,
+ size_t reply_size);
+typedef void
+auth_request_handler_reply_continue_callback_t(struct auth_request *request,
+ const void *reply,
+ size_t reply_size);
+
+
+struct auth_request_handler *
+auth_request_handler_create(bool token_auth, auth_client_request_callback_t *callback,
+ struct auth_client_connection *conn,
+ auth_master_request_callback_t *master_callback);
+
+void auth_request_handler_destroy(struct auth_request_handler **handler);
+void auth_request_handler_unref(struct auth_request_handler **handler);
+void auth_request_handler_abort_requests(struct auth_request_handler *handler);
+
+void auth_request_handler_set(struct auth_request_handler *handler,
+ unsigned int connect_uid,
+ unsigned int client_pid);
+
+bool auth_request_handler_auth_begin(struct auth_request_handler *handler,
+ const char *args);
+bool auth_request_handler_auth_continue(struct auth_request_handler *handler,
+ const char *args);
+void auth_request_handler_reply(struct auth_request *request,
+ enum auth_client_result result,
+ const void *reply, size_t reply_size);
+void auth_request_handler_reply_continue(struct auth_request *request,
+ const void *reply, size_t reply_size);
+void auth_request_handler_abort(struct auth_request *request);
+
+unsigned int
+auth_request_handler_get_request_count(struct auth_request_handler *handler);
+bool auth_request_handler_master_request(struct auth_request_handler *handler,
+ struct auth_master_connection *master,
+ unsigned int id, unsigned int client_id,
+ const char *const *params);
+void auth_request_handler_cancel_request(struct auth_request_handler *handler,
+ unsigned int client_id);
+
+void auth_request_handler_flush_failures(bool flush_all);
+
+void auth_request_handler_init(void);
+void auth_request_handler_deinit(void);
+
+#endif
diff --git a/src/auth/auth-request-stats.c b/src/auth/auth-request-stats.c
new file mode 100644
index 0000000..7af4e9a
--- /dev/null
+++ b/src/auth/auth-request-stats.c
@@ -0,0 +1,77 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "str.h"
+#include "strescape.h"
+#include "buffer.h"
+#include "base64.h"
+#include "stats.h"
+#include "stats-connection.h"
+#include "auth-stats.h"
+#include "auth-request.h"
+#include "auth-request-stats.h"
+
+#define USER_STATS_SOCKET_NAME "old-stats-user"
+
+static struct stats_connection *auth_stats_conn = NULL;
+static struct stats_item *auth_stats_item;
+
+struct auth_stats *auth_request_stats_get(struct auth_request *request)
+{
+ if (request->stats == NULL)
+ request->stats = stats_alloc(request->pool);
+ return stats_fill_ptr(request->stats, auth_stats_item);
+}
+
+void auth_request_stats_add_tempfail(struct auth_request *request)
+{
+ struct auth_stats *stats = auth_request_stats_get(request);
+
+ stats->auth_db_tempfail_count++;
+}
+
+void auth_request_stats_send(struct auth_request *request)
+{
+ string_t *str;
+ buffer_t *buf;
+
+ /* we'll send stats only when the request is finished. this reduces
+ memory usage and is a bit simpler. auth requests are typically
+ pretty short lived anyway. */
+ i_assert(!request->stats_sent);
+ request->stats_sent = TRUE;
+
+ if (request->stats == NULL) {
+ /* nothing happened in this request - don't send it */
+ return;
+ }
+ if (!request->set->stats)
+ return;
+
+ buf = t_buffer_create(128);
+ stats_export(buf, request->stats);
+
+ str = t_str_new(256);
+ str_append(str, "ADD-USER\t");
+ if (request->fields.user != NULL)
+ str_append_tabescaped(str, request->fields.user);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, request->fields.service);
+ str_append_c(str, '\t');
+ base64_encode(buf->data, buf->used, str);
+
+ str_append_c(str, '\n');
+ stats_connection_send(auth_stats_conn, str);
+}
+
+void auth_request_stats_init(void)
+{
+ auth_stats_conn = stats_connection_create(USER_STATS_SOCKET_NAME);
+ auth_stats_item = stats_register(&auth_stats_vfuncs);
+}
+
+void auth_request_stats_deinit(void)
+{
+ stats_connection_unref(&auth_stats_conn);
+ stats_unregister(&auth_stats_item);
+}
diff --git a/src/auth/auth-request-stats.h b/src/auth/auth-request-stats.h
new file mode 100644
index 0000000..5095c2e
--- /dev/null
+++ b/src/auth/auth-request-stats.h
@@ -0,0 +1,15 @@
+#ifndef AUTH_REQUEST_STATS_H
+#define AUTH_REQUEST_STATS_H
+
+#include "auth-stats.h"
+
+struct auth_request;
+
+struct auth_stats *auth_request_stats_get(struct auth_request *request);
+void auth_request_stats_add_tempfail(struct auth_request *request);
+void auth_request_stats_send(struct auth_request *request);
+
+void auth_request_stats_init(void);
+void auth_request_stats_deinit(void);
+
+#endif
diff --git a/src/auth/auth-request-var-expand.c b/src/auth/auth-request-var-expand.c
new file mode 100644
index 0000000..04d774e
--- /dev/null
+++ b/src/auth/auth-request-var-expand.c
@@ -0,0 +1,303 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "str.h"
+#include "strescape.h"
+#include "auth-request.h"
+
+struct auth_request_var_expand_ctx {
+ const struct auth_request *auth_request;
+ auth_request_escape_func_t *escape_func;
+};
+
+const struct var_expand_table
+auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT+1] = {
+ { 'u', NULL, "user" },
+ { 'n', NULL, "username" },
+ { 'd', NULL, "domain" },
+ { 's', NULL, "service" },
+ { 'h', NULL, "home" },
+ { 'l', NULL, "lip" },
+ { 'r', NULL, "rip" },
+ { 'p', NULL, "pid" },
+ { 'w', NULL, "password" },
+ { '!', NULL, NULL },
+ { 'm', NULL, "mech" },
+ { 'c', NULL, "secured" },
+ { 'a', NULL, "lport" },
+ { 'b', NULL, "rport" },
+ { 'k', NULL, "cert" },
+ { '\0', NULL, "login_user" },
+ { '\0', NULL, "login_username" },
+ { '\0', NULL, "login_domain" },
+ { '\0', NULL, "session" },
+ { '\0', NULL, "real_lip" },
+ { '\0', NULL, "real_rip" },
+ { '\0', NULL, "real_lport" },
+ { '\0', NULL, "real_rport" },
+ { '\0', NULL, "domain_first" },
+ { '\0', NULL, "domain_last" },
+ { '\0', NULL, "master_user" },
+ { '\0', NULL, "session_pid" },
+ { '\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, "local_name" },
+ { '\0', NULL, "client_id" },
+
+ /* 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" },
+
+ /* be sure to update AUTH_REQUEST_VAR_TAB_COUNT */
+ { '\0', NULL, NULL }
+};
+
+static const char *
+escape_none(const char *string,
+ const struct auth_request *request ATTR_UNUSED)
+{
+ return string;
+}
+
+const char *
+auth_request_str_escape(const char *string,
+ const struct auth_request *request ATTR_UNUSED)
+{
+ return str_escape(string);
+}
+
+struct var_expand_table *
+auth_request_get_var_expand_table_full(const struct auth_request *auth_request,
+ const char *username,
+ auth_request_escape_func_t *escape_func,
+ unsigned int *count)
+{
+ const struct auth_request_fields *fields = &auth_request->fields;
+ const unsigned int auth_count =
+ N_ELEMENTS(auth_request_var_expand_static_tab);
+ struct var_expand_table *tab, *ret_tab;
+ const char *orig_user, *auth_user;
+
+ if (escape_func == NULL)
+ escape_func = escape_none;
+
+ /* keep the extra fields at the beginning. the last static_tab field
+ contains the ending NULL-fields. */
+ tab = ret_tab = t_new(struct var_expand_table,
+ MALLOC_ADD(*count, auth_count));
+ tab += *count;
+ *count += auth_count;
+
+ memcpy(tab, auth_request_var_expand_static_tab,
+ auth_count * sizeof(*tab));
+
+ if (username == NULL)
+ username = "";
+ tab[0].value = escape_func(username, auth_request);
+ tab[1].value = escape_func(t_strcut(username, '@'),
+ auth_request);
+ tab[2].value = i_strchr_to_next(username, '@');
+ if (tab[2].value != NULL)
+ tab[2].value = escape_func(tab[2].value, auth_request);
+ tab[3].value = escape_func(fields->service, auth_request);
+ /* tab[4] = we have no home dir */
+ if (fields->local_ip.family != 0)
+ tab[5].value = tab[35].value =
+ net_ip2addr(&fields->local_ip);
+ if (fields->remote_ip.family != 0)
+ tab[6].value = tab[36].value =
+ net_ip2addr(&fields->remote_ip);
+ tab[7].value = dec2str(auth_request->client_pid);
+ if (auth_request->mech_password != NULL) {
+ tab[8].value = escape_func(auth_request->mech_password,
+ auth_request);
+ }
+ if (auth_request->userdb_lookup) {
+ tab[9].value = auth_request->userdb == NULL ? "" :
+ dec2str(auth_request->userdb->userdb->id);
+ } else {
+ tab[9].value = auth_request->passdb == NULL ? "" :
+ dec2str(auth_request->passdb->passdb->id);
+ }
+ tab[10].value = tab[43].value = fields->mech_name == NULL ? "" :
+ escape_func(fields->mech_name, auth_request);
+ switch (fields->secured) {
+ case AUTH_REQUEST_SECURED_NONE: tab[11].value = ""; break;
+ case AUTH_REQUEST_SECURED: tab[11].value = "secured"; break;
+ case AUTH_REQUEST_SECURED_TLS: tab[11].value = "TLS"; break;
+ default: tab[11].value = ""; break;
+ };
+ tab[12].value = tab[37].value = dec2str(fields->local_port);
+ tab[13].value = tab[38].value = dec2str(fields->remote_port);
+ tab[14].value = fields->valid_client_cert ? "valid" : "";
+
+ if (fields->requested_login_user != NULL) {
+ const char *login_user = fields->requested_login_user;
+
+ tab[15].value = escape_func(login_user, auth_request);
+ tab[16].value = escape_func(t_strcut(login_user, '@'),
+ auth_request);
+ tab[17].value = i_strchr_to_next(login_user, '@');
+ if (tab[17].value != NULL) {
+ tab[17].value = escape_func(tab[17].value,
+ auth_request);
+ }
+ }
+ tab[18].value = fields->session_id == NULL ? NULL :
+ escape_func(fields->session_id, auth_request);
+ if (fields->real_local_ip.family != 0)
+ tab[19].value = tab[39].value =
+ net_ip2addr(&fields->real_local_ip);
+ if (fields->real_remote_ip.family != 0)
+ tab[20].value = tab[40].value =
+ net_ip2addr(&fields->real_remote_ip);
+ tab[21].value = tab[41].value = dec2str(fields->real_local_port);
+ tab[22].value = tab[42].value = dec2str(fields->real_remote_port);
+ tab[23].value = i_strchr_to_next(username, '@');
+ if (tab[23].value != NULL) {
+ tab[23].value = escape_func(t_strcut(tab[23].value, '@'),
+ auth_request);
+ }
+ tab[24].value = strrchr(username, '@');
+ if (tab[24].value != NULL)
+ tab[24].value = escape_func(tab[24].value+1, auth_request);
+ tab[25].value = fields->master_user == NULL ? NULL :
+ escape_func(fields->master_user, auth_request);
+ tab[26].value = auth_request->session_pid == (pid_t)-1 ? NULL :
+ dec2str(auth_request->session_pid);
+
+ orig_user = fields->original_username != NULL ?
+ fields->original_username : username;
+ tab[27].value = tab[44].value = escape_func(orig_user, auth_request);
+ tab[28].value = tab[45].value = escape_func(t_strcut(orig_user, '@'), auth_request);
+ tab[29].value = tab[46].value = i_strchr_to_next(orig_user, '@');
+ if (tab[29].value != NULL)
+ tab[29].value = tab[46].value =
+ escape_func(tab[29].value, auth_request);
+
+ if (fields->master_user != NULL)
+ auth_user = fields->master_user;
+ else
+ auth_user = orig_user;
+ tab[30].value = escape_func(auth_user, auth_request);
+ tab[31].value = escape_func(t_strcut(auth_user, '@'), auth_request);
+ tab[32].value = i_strchr_to_next(auth_user, '@');
+ if (tab[32].value != NULL)
+ tab[32].value = escape_func(tab[32].value, auth_request);
+ if (fields->local_name != NULL)
+ tab[33].value = escape_func(fields->local_name, auth_request);
+ if (fields->client_id != NULL)
+ tab[34].value = escape_func(fields->client_id, auth_request);
+ return ret_tab;
+}
+
+const struct var_expand_table *
+auth_request_get_var_expand_table(const struct auth_request *auth_request,
+ auth_request_escape_func_t *escape_func)
+{
+ unsigned int count = 0;
+
+ return auth_request_get_var_expand_table_full(auth_request,
+ auth_request->fields.user, escape_func, &count);
+}
+
+static const char *field_get_default(const char *data)
+{
+ const char *p;
+
+ p = strchr(data, ':');
+ if (p == NULL)
+ return "";
+ else {
+ /* default value given */
+ return p+1;
+ }
+}
+
+static int
+auth_request_var_expand_func_passdb(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct auth_request_var_expand_ctx *ctx = context;
+ const char *field_name = t_strcut(data, ':');
+ const char *value;
+
+ value = auth_fields_find(ctx->auth_request->fields.extra_fields, field_name);
+ *value_r = ctx->escape_func(value != NULL ? value : field_get_default(data),
+ ctx->auth_request);
+ return 1;
+}
+
+static int
+auth_request_var_expand_func_userdb(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct auth_request_var_expand_ctx *ctx = context;
+ const char *field_name = t_strcut(data, ':');
+ const char *value;
+
+ value = ctx->auth_request->fields.userdb_reply == NULL ? NULL :
+ auth_fields_find(ctx->auth_request->fields.userdb_reply, field_name);
+ *value_r = ctx->escape_func(value != NULL ? value : field_get_default(data),
+ ctx->auth_request);
+ return 1;
+}
+
+const struct var_expand_func_table auth_request_var_funcs_table[] = {
+ { "passdb", auth_request_var_expand_func_passdb },
+ { "userdb", auth_request_var_expand_func_userdb },
+ { NULL, NULL }
+};
+
+int auth_request_var_expand(string_t *dest, const char *str,
+ const struct auth_request *auth_request,
+ auth_request_escape_func_t *escape_func,
+ const char **error_r)
+{
+ return auth_request_var_expand_with_table(dest, str, auth_request,
+ auth_request_get_var_expand_table(auth_request, escape_func),
+ escape_func, error_r);
+}
+
+int auth_request_var_expand_with_table(string_t *dest, const char *str,
+ const struct auth_request *auth_request,
+ const struct var_expand_table *table,
+ auth_request_escape_func_t *escape_func,
+ const char **error_r)
+{
+ struct auth_request_var_expand_ctx ctx;
+
+ i_zero(&ctx);
+ ctx.auth_request = auth_request;
+ ctx.escape_func = escape_func == NULL ? escape_none : escape_func;
+ return var_expand_with_funcs(dest, str, table,
+ auth_request_var_funcs_table, &ctx, error_r);
+}
+
+int t_auth_request_var_expand(const char *str,
+ const struct auth_request *auth_request ATTR_UNUSED,
+ auth_request_escape_func_t *escape_func ATTR_UNUSED,
+ const char **value_r, const char **error_r)
+{
+ string_t *dest = t_str_new(128);
+ int ret = auth_request_var_expand(dest, str, auth_request,
+ escape_func, error_r);
+ *value_r = str_c(dest);
+ return ret;
+}
diff --git a/src/auth/auth-request-var-expand.h b/src/auth/auth-request-var-expand.h
new file mode 100644
index 0000000..57e18a8
--- /dev/null
+++ b/src/auth/auth-request-var-expand.h
@@ -0,0 +1,44 @@
+#ifndef AUTH_REQUEST_VAR_EXPAND_H
+#define AUTH_REQUEST_VAR_EXPAND_H
+
+typedef const char *
+auth_request_escape_func_t(const char *string,
+ const struct auth_request *auth_request);
+
+#define AUTH_REQUEST_VAR_TAB_USER_IDX 0
+#define AUTH_REQUEST_VAR_TAB_USERNAME_IDX 1
+#define AUTH_REQUEST_VAR_TAB_DOMAIN_IDX 2
+#define AUTH_REQUEST_VAR_TAB_COUNT 47
+extern const struct var_expand_table
+auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT+1];
+
+extern const struct var_expand_func_table auth_request_var_funcs_table[];
+
+const struct var_expand_table *
+auth_request_get_var_expand_table(const struct auth_request *auth_request,
+ auth_request_escape_func_t *escape_func)
+ ATTR_NULL(2);
+struct var_expand_table *
+auth_request_get_var_expand_table_full(const struct auth_request *auth_request,
+ const char *username,
+ auth_request_escape_func_t *escape_func,
+ unsigned int *count) ATTR_NULL(3);
+
+int auth_request_var_expand(string_t *dest, const char *str,
+ const struct auth_request *auth_request,
+ auth_request_escape_func_t *escape_func,
+ const char **error_r);
+int auth_request_var_expand_with_table(string_t *dest, const char *str,
+ const struct auth_request *auth_request,
+ const struct var_expand_table *table,
+ auth_request_escape_func_t *escape_func,
+ const char **error_r);
+int t_auth_request_var_expand(const char *str,
+ const struct auth_request *auth_request,
+ auth_request_escape_func_t *escape_func,
+ const char **value_r, const char **error_r);
+
+const char *auth_request_str_escape(const char *string,
+ const struct auth_request *request);
+
+#endif
diff --git a/src/auth/auth-request.c b/src/auth/auth-request.c
new file mode 100644
index 0000000..635ee20
--- /dev/null
+++ b/src/auth/auth-request.c
@@ -0,0 +1,2595 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "ioloop.h"
+#include "buffer.h"
+#include "hash.h"
+#include "sha1.h"
+#include "hex-binary.h"
+#include "str.h"
+#include "array.h"
+#include "safe-memset.h"
+#include "str-sanitize.h"
+#include "strescape.h"
+#include "var-expand.h"
+#include "dns-lookup.h"
+#include "auth-cache.h"
+#include "auth-request.h"
+#include "auth-request-handler.h"
+#include "auth-request-handler-private.h"
+#include "auth-request-stats.h"
+#include "auth-client-connection.h"
+#include "auth-master-connection.h"
+#include "auth-policy.h"
+#include "passdb.h"
+#include "passdb-blocking.h"
+#include "passdb-cache.h"
+#include "passdb-template.h"
+#include "userdb-blocking.h"
+#include "userdb-template.h"
+#include "password-scheme.h"
+#include "wildcard-match.h"
+
+#include <sys/stat.h>
+
+#define AUTH_SUBSYS_PROXY "proxy"
+#define AUTH_DNS_SOCKET_PATH "dns-client"
+#define AUTH_DNS_DEFAULT_TIMEOUT_MSECS (1000*10)
+#define AUTH_DNS_WARN_MSECS 500
+#define AUTH_REQUEST_MAX_DELAY_SECS (60*5)
+#define CACHED_PASSWORD_SCHEME "SHA1"
+
+struct auth_request_proxy_dns_lookup_ctx {
+ struct auth_request *request;
+ auth_request_proxy_cb_t *callback;
+ struct dns_lookup *dns_lookup;
+};
+
+struct auth_policy_check_ctx {
+ enum {
+ AUTH_POLICY_CHECK_TYPE_PLAIN,
+ AUTH_POLICY_CHECK_TYPE_LOOKUP,
+ AUTH_POLICY_CHECK_TYPE_SUCCESS,
+ } type;
+ struct auth_request *request;
+
+ buffer_t *success_data;
+
+ verify_plain_callback_t *callback_plain;
+ lookup_credentials_callback_t *callback_lookup;
+};
+
+const char auth_default_subsystems[2];
+
+unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX];
+
+static void get_log_identifier(string_t *str, struct auth_request *auth_request);
+static void
+auth_request_userdb_import(struct auth_request *request, const char *args);
+
+static
+void auth_request_lookup_credentials_policy_continue(struct auth_request *request,
+ lookup_credentials_callback_t *callback);
+static
+void auth_request_policy_check_callback(int result, void *context);
+
+static const char *get_log_prefix_mech(struct auth_request *auth_request)
+{
+ string_t *str = t_str_new(64);
+ auth_request_get_log_prefix(str, auth_request, AUTH_SUBSYS_MECH);
+ return str_c(str);
+}
+
+const char *auth_request_get_log_prefix_db(struct auth_request *auth_request)
+{
+ string_t *str = t_str_new(64);
+ auth_request_get_log_prefix(str, auth_request, AUTH_SUBSYS_DB);
+ return str_c(str);
+}
+
+static struct event *get_request_event(struct auth_request *request,
+ const char *subsystem)
+{
+ if (subsystem == AUTH_SUBSYS_DB)
+ return authdb_event(request);
+ else if (subsystem == AUTH_SUBSYS_MECH)
+ return request->mech_event;
+ else
+ return request->event;
+}
+
+static void auth_request_post_alloc_init(struct auth_request *request, struct event *parent_event)
+{
+ enum log_type level;
+ request->state = AUTH_REQUEST_STATE_NEW;
+ auth_request_state_count[AUTH_REQUEST_STATE_NEW]++;
+ request->refcount = 1;
+ request->last_access = ioloop_time;
+ request->session_pid = (pid_t)-1;
+ request->set = global_auth_settings;
+ request->event = event_create(parent_event);
+ request->mech_event = event_create(request->event);
+ auth_request_fields_init(request);
+
+ level = request->set->verbose ? LOG_TYPE_INFO : LOG_TYPE_WARNING;
+ event_set_min_log_level(request->event, level);
+ event_set_min_log_level(request->mech_event, level);
+
+ p_array_init(&request->authdb_event, request->pool, 2);
+ event_set_log_prefix_callback(request->mech_event, FALSE, get_log_prefix_mech,
+ request);
+ event_set_forced_debug(request->event, request->set->debug);
+ event_add_category(request->event, &event_category_auth);
+}
+
+struct auth_request *
+auth_request_new(const struct mech_module *mech, struct event *parent_event)
+{
+ struct auth_request *request;
+
+ request = mech->auth_new();
+ request->mech = mech;
+ auth_request_post_alloc_init(request, parent_event);
+
+ return request;
+}
+
+struct auth_request *auth_request_new_dummy(struct event *parent_event)
+{
+ struct auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"auth_request", 1024);
+ request = p_new(pool, struct auth_request, 1);
+ request->pool = pool;
+
+ auth_request_post_alloc_init(request, parent_event);
+ return request;
+}
+
+void auth_request_set_state(struct auth_request *request,
+ enum auth_request_state state)
+{
+ if (request->state == state)
+ return;
+
+ i_assert(request->to_penalty == NULL);
+
+ i_assert(auth_request_state_count[request->state] > 0);
+ auth_request_state_count[request->state]--;
+ auth_request_state_count[state]++;
+
+ request->state = state;
+ auth_refresh_proctitle();
+}
+
+void auth_request_init(struct auth_request *request)
+{
+ struct auth *auth;
+
+ auth = auth_request_get_auth(request);
+ request->set = auth->set;
+ request->passdb = auth->passdbs;
+ request->userdb = auth->userdbs;
+}
+
+struct auth *auth_request_get_auth(struct auth_request *request)
+{
+ return auth_find_service(request->fields.service);
+}
+
+void auth_request_success(struct auth_request *request,
+ const void *data, size_t data_size)
+{
+ i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (!request->set->policy_check_after_auth) {
+ struct auth_policy_check_ctx *ctx =
+ p_new(request->pool, struct auth_policy_check_ctx, 1);
+ ctx->success_data = buffer_create_dynamic(request->pool, 1);
+ ctx->request = request;
+ ctx->type = AUTH_POLICY_CHECK_TYPE_SUCCESS;
+ auth_request_policy_check_callback(0, ctx);
+ return;
+ }
+
+ /* perform second policy lookup here */
+ struct auth_policy_check_ctx *ctx = p_new(request->pool, struct auth_policy_check_ctx, 1);
+ ctx->request = request;
+ ctx->success_data = buffer_create_dynamic(request->pool, data_size);
+ buffer_append(ctx->success_data, data, data_size);
+ ctx->type = AUTH_POLICY_CHECK_TYPE_SUCCESS;
+ auth_policy_check(request, request->mech_password, auth_request_policy_check_callback, ctx);
+}
+
+struct event_passthrough *
+auth_request_finished_event(struct auth_request *request, struct event *event)
+{
+ struct event_passthrough *e = event_create_passthrough(event);
+
+ if (request->failed) {
+ if (request->internal_failure) {
+ e->add_str("error", "internal failure");
+ } else {
+ e->add_str("error", "authentication failed");
+ }
+ } else if (request->fields.successful) {
+ e->add_str("success", "yes");
+ }
+ if (request->userdb_lookup) {
+ return e;
+ }
+ if (request->policy_penalty > 0)
+ e->add_int("policy_penalty", request->policy_penalty);
+ if (request->policy_refusal) {
+ e->add_str("policy_result", "refused");
+ } else if (request->policy_processed && request->policy_penalty > 0) {
+ e->add_str("policy_result", "delayed");
+ e->add_int("policy_penalty", request->policy_penalty);
+ } else if (request->policy_processed) {
+ e->add_str("policy_result", "ok");
+ }
+ return e;
+}
+
+void auth_request_log_finished(struct auth_request *request)
+{
+ if (request->event_finished_sent)
+ return;
+ request->event_finished_sent = TRUE;
+ string_t *str = t_str_new(64);
+ auth_request_get_log_prefix(str, request, "auth");
+ struct event_passthrough *e =
+ auth_request_finished_event(request, request->event)->
+ set_name("auth_request_finished");
+ e_debug(e->event(), "%sAuth request finished", str_c(str));
+}
+
+static
+void auth_request_success_continue(struct auth_policy_check_ctx *ctx)
+{
+ struct auth_request *request = ctx->request;
+ struct auth_stats *stats;
+ i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ timeout_remove(&request->to_penalty);
+
+ if (request->failed || !request->passdb_success) {
+ /* password was valid, but some other check failed. */
+ auth_request_fail(request);
+ return;
+ }
+ auth_request_set_auth_successful(request);
+
+ /* log before delay */
+ auth_request_log_finished(request);
+
+ if (request->delay_until > ioloop_time) {
+ unsigned int delay_secs = request->delay_until - ioloop_time;
+ request->to_penalty = timeout_add(delay_secs * 1000,
+ auth_request_success_continue, ctx);
+ return;
+ }
+
+ if (ctx->success_data->used > 0 && !request->fields.final_resp_ok) {
+ /* we'll need one more SASL round, since client doesn't support
+ the final SASL response */
+ auth_request_handler_reply_continue(request,
+ ctx->success_data->data, ctx->success_data->used);
+ return;
+ }
+
+ if (request->set->stats) {
+ stats = auth_request_stats_get(request);
+ stats->auth_success_count++;
+ if (request->fields.master_user != NULL)
+ stats->auth_master_success_count++;
+ }
+
+ auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED);
+ auth_request_refresh_last_access(request);
+ auth_request_handler_reply(request, AUTH_CLIENT_RESULT_SUCCESS,
+ ctx->success_data->data, ctx->success_data->used);
+}
+
+void auth_request_fail_with_reply(struct auth_request *request,
+ const void *final_data, size_t final_data_size)
+{
+ struct auth_stats *stats;
+
+ i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (request->set->stats) {
+ stats = auth_request_stats_get(request);
+ stats->auth_failure_count++;
+ }
+
+ auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED);
+ auth_request_refresh_last_access(request);
+ auth_request_log_finished(request);
+ auth_request_handler_reply(request, AUTH_CLIENT_RESULT_FAILURE,
+ final_data, final_data_size);
+}
+
+void auth_request_fail(struct auth_request *request)
+{
+ auth_request_fail_with_reply(request, "", 0);
+}
+
+void auth_request_internal_failure(struct auth_request *request)
+{
+ request->internal_failure = TRUE;
+ auth_request_fail(request);
+}
+
+void auth_request_ref(struct auth_request *request)
+{
+ request->refcount++;
+}
+
+void auth_request_unref(struct auth_request **_request)
+{
+ struct auth_request *request = *_request;
+
+ *_request = NULL;
+ i_assert(request->refcount > 0);
+ if (--request->refcount > 0)
+ return;
+
+ i_assert(array_count(&request->authdb_event) == 0);
+
+ if (request->handler_pending_reply)
+ auth_request_handler_abort(request);
+
+ event_unref(&request->mech_event);
+ event_unref(&request->event);
+ auth_request_stats_send(request);
+ auth_request_state_count[request->state]--;
+ auth_refresh_proctitle();
+
+ if (request->mech_password != NULL) {
+ safe_memset(request->mech_password, 0,
+ strlen(request->mech_password));
+ }
+
+ if (request->dns_lookup_ctx != NULL)
+ dns_lookup_abort(&request->dns_lookup_ctx->dns_lookup);
+ timeout_remove(&request->to_abort);
+ timeout_remove(&request->to_penalty);
+
+ if (request->mech != NULL)
+ request->mech->auth_free(request);
+ else
+ pool_unref(&request->pool);
+}
+
+bool auth_request_import_master(struct auth_request *request,
+ const char *key, const char *value)
+{
+ pid_t pid;
+
+ i_assert(value != NULL);
+
+ /* master request lookups may set these */
+ if (strcmp(key, "session_pid") == 0) {
+ if (str_to_pid(value, &pid) == 0)
+ request->session_pid = pid;
+ } else if (strcmp(key, "request_auth_token") == 0)
+ request->request_auth_token = TRUE;
+ else
+ return FALSE;
+ return TRUE;
+}
+
+static bool auth_request_fail_on_nuls(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ if ((request->mech->flags & MECH_SEC_ALLOW_NULS) != 0)
+ return FALSE;
+ if (memchr(data, '\0', data_size) != NULL) {
+ e_debug(request->mech_event, "Unexpected NUL in auth data");
+ auth_request_fail(request);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void auth_request_initial(struct auth_request *request)
+{
+ i_assert(request->state == AUTH_REQUEST_STATE_NEW);
+
+ auth_request_set_state(request, AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (auth_request_fail_on_nuls(request, request->initial_response,
+ request->initial_response_len))
+ return;
+
+ request->mech->auth_initial(request, request->initial_response,
+ request->initial_response_len);
+}
+
+void auth_request_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (request->fields.successful) {
+ auth_request_success(request, "", 0);
+ return;
+ }
+
+ if (auth_request_fail_on_nuls(request, data, data_size))
+ return;
+
+ auth_request_refresh_last_access(request);
+ request->mech->auth_continue(request, data, data_size);
+}
+
+static void auth_request_save_cache(struct auth_request *request,
+ enum passdb_result result)
+{
+ struct auth_passdb *passdb = request->passdb;
+ const char *encoded_password;
+ string_t *str;
+ struct password_generate_params gen_params = {
+ .user = request->fields.user,
+ .rounds = 0
+ };
+
+ switch (result) {
+ case PASSDB_RESULT_USER_UNKNOWN:
+ case PASSDB_RESULT_PASSWORD_MISMATCH:
+ case PASSDB_RESULT_OK:
+ case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+ /* can be cached */
+ break;
+ case PASSDB_RESULT_NEXT:
+ case PASSDB_RESULT_USER_DISABLED:
+ case PASSDB_RESULT_PASS_EXPIRED:
+ /* FIXME: we can't cache this now, or cache lookup would
+ return success. */
+ return;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ i_unreached();
+ }
+
+ if (passdb_cache == NULL || passdb->cache_key == NULL)
+ return;
+
+ if (result < 0) {
+ /* lookup failed. */
+ if (result == PASSDB_RESULT_USER_UNKNOWN) {
+ auth_cache_insert(passdb_cache, request,
+ passdb->cache_key, "", FALSE);
+ }
+ return;
+ }
+
+ if (request->passdb_password == NULL &&
+ !auth_fields_exists(request->fields.extra_fields, "nopassword")) {
+ /* passdb didn't provide the correct password */
+ if (result != PASSDB_RESULT_OK ||
+ request->mech_password == NULL)
+ return;
+
+ /* we can still cache valid password lookups though.
+ strdup() it so that mech_password doesn't get
+ cleared too early. */
+ if (!password_generate_encoded(request->mech_password,
+ &gen_params,
+ CACHED_PASSWORD_SCHEME,
+ &encoded_password))
+ i_unreached();
+ request->passdb_password =
+ p_strconcat(request->pool, "{"CACHED_PASSWORD_SCHEME"}",
+ encoded_password, NULL);
+ }
+
+ /* save all except the currently given password in cache */
+ str = t_str_new(256);
+ if (request->passdb_password != NULL) {
+ if (*request->passdb_password != '{') {
+ /* cached passwords must have a known scheme */
+ str_append_c(str, '{');
+ str_append(str, passdb->passdb->default_pass_scheme);
+ str_append_c(str, '}');
+ }
+ str_append_tabescaped(str, request->passdb_password);
+ }
+
+ if (!auth_fields_is_empty(request->fields.extra_fields)) {
+ str_append_c(str, '\t');
+ /* add only those extra fields to cache that were set by this
+ passdb lookup. the CHANGED flag does this, because we
+ snapshotted the extra_fields before the current passdb
+ lookup. */
+ auth_fields_append(request->fields.extra_fields, str,
+ AUTH_FIELD_FLAG_CHANGED,
+ AUTH_FIELD_FLAG_CHANGED);
+ }
+ auth_cache_insert(passdb_cache, request, passdb->cache_key, str_c(str),
+ result == PASSDB_RESULT_OK);
+}
+
+static bool
+auth_request_mechanism_accepted(const char *const *mechs,
+ const struct mech_module *mech)
+{
+ /* no filter specified, anything goes */
+ if (mechs == NULL) return TRUE;
+ /* request has no mechanism, see if none is accepted */
+ if (mech == NULL)
+ return str_array_icase_find(mechs, "none");
+ /* check if request mechanism is accepted */
+ return str_array_icase_find(mechs, mech->mech_name);
+}
+
+/**
+
+Check if username is included in the filter. Logic is that if the username
+is not excluded by anything, and is included by something, it will be accepted.
+By default, all usernames are included, unless there is a inclusion item, when
+username will be excluded if there is no inclusion for it.
+
+Exclusions are denoted with a ! in front of the pattern.
+*/
+bool auth_request_username_accepted(const char *const *filter, const char *username)
+{
+ bool have_includes = FALSE;
+ bool matched_inc = FALSE;
+
+ for(;*filter != NULL; filter++) {
+ /* if filter has ! it means the pattern will be refused */
+ bool exclude = (**filter == '!');
+ if (!exclude)
+ have_includes = TRUE;
+ if (wildcard_match(username, (*filter)+(exclude?1:0))) {
+ if (exclude) {
+ return FALSE;
+ } else {
+ matched_inc = TRUE;
+ }
+ }
+ }
+
+ return matched_inc || !have_includes;
+}
+
+static bool
+auth_request_want_skip_passdb(struct auth_request *request,
+ struct auth_passdb *passdb)
+{
+ /* if mechanism is not supported, skip */
+ const char *const *mechs = passdb->passdb->mechanisms;
+ const char *const *username_filter = passdb->passdb->username_filter;
+ const char *username;
+
+ username = request->fields.user;
+
+ if (!auth_request_mechanism_accepted(mechs, request->mech)) {
+ auth_request_log_debug(request,
+ request->mech != NULL ? AUTH_SUBSYS_MECH
+ : "none",
+ "skipping passdb: mechanism filtered");
+ return TRUE;
+ }
+
+ if (passdb->passdb->username_filter != NULL &&
+ !auth_request_username_accepted(username_filter, username)) {
+ auth_request_log_debug(request,
+ request->mech != NULL ? AUTH_SUBSYS_MECH
+ : "none",
+ "skipping passdb: username filtered");
+ return TRUE;
+ }
+
+ /* skip_password_check basically specifies if authentication is
+ finished */
+ bool authenticated = request->fields.skip_password_check;
+
+ switch (passdb->skip) {
+ case AUTH_PASSDB_SKIP_NEVER:
+ return FALSE;
+ case AUTH_PASSDB_SKIP_AUTHENTICATED:
+ return authenticated;
+ case AUTH_PASSDB_SKIP_UNAUTHENTICATED:
+ return !authenticated;
+ }
+ i_unreached();
+}
+
+static bool
+auth_request_want_skip_userdb(struct auth_request *request,
+ struct auth_userdb *userdb)
+{
+ switch (userdb->skip) {
+ case AUTH_USERDB_SKIP_NEVER:
+ return FALSE;
+ case AUTH_USERDB_SKIP_FOUND:
+ return request->userdb_success;
+ case AUTH_USERDB_SKIP_NOTFOUND:
+ return !request->userdb_success;
+ }
+ i_unreached();
+}
+
+static const char *
+auth_request_cache_result_to_str(enum auth_request_cache_result result)
+{
+ switch(result) {
+ case AUTH_REQUEST_CACHE_NONE:
+ return "none";
+ case AUTH_REQUEST_CACHE_HIT:
+ return "hit";
+ case AUTH_REQUEST_CACHE_MISS:
+ return "miss";
+ default:
+ i_unreached();
+ }
+}
+
+void auth_request_passdb_lookup_begin(struct auth_request *request)
+{
+ struct event *event;
+ const char *name;
+
+ i_assert(request->passdb != NULL);
+ i_assert(!request->userdb_lookup);
+
+ request->passdb_cache_result = AUTH_REQUEST_CACHE_NONE;
+
+ name = (request->passdb->set->name[0] != '\0' ?
+ request->passdb->set->name :
+ request->passdb->passdb->iface.name);
+
+ event = event_create(request->event);
+ event_add_str(event, "passdb_id", dec2str(request->passdb->passdb->id));
+ event_add_str(event, "passdb_name", name);
+ event_add_str(event, "passdb", request->passdb->passdb->iface.name);
+ event_set_log_prefix_callback(event, FALSE,
+ auth_request_get_log_prefix_db, request);
+
+ /* check if we should enable verbose logging here */
+ if (*request->passdb->set->auth_verbose == 'y')
+ event_set_min_log_level(event, LOG_TYPE_INFO);
+ else if (*request->passdb->set->auth_verbose == 'n')
+ event_set_min_log_level(event, LOG_TYPE_WARNING);
+
+ e_debug(event_create_passthrough(event)->
+ set_name("auth_passdb_request_started")->
+ event(),
+ "Performing passdb lookup");
+ array_push_back(&request->authdb_event, &event);
+}
+
+void auth_request_passdb_lookup_end(struct auth_request *request,
+ enum passdb_result result)
+{
+ i_assert(array_count(&request->authdb_event) > 0);
+ struct event *event = authdb_event(request);
+ struct event_passthrough *e =
+ event_create_passthrough(event)->
+ set_name("auth_passdb_request_finished")->
+ add_str("result", passdb_result_to_string(result));
+ if (request->passdb_cache_result != AUTH_REQUEST_CACHE_NONE &&
+ request->set->cache_ttl != 0 && request->set->cache_size != 0)
+ e->add_str("cache", auth_request_cache_result_to_str(request->passdb_cache_result));
+ e_debug(e->event(), "Finished passdb lookup");
+ event_unref(&event);
+ array_pop_back(&request->authdb_event);
+}
+
+void auth_request_userdb_lookup_begin(struct auth_request *request)
+{
+ struct event *event;
+ const char *name;
+
+ i_assert(request->userdb != NULL);
+ i_assert(request->userdb_lookup);
+
+ request->userdb_cache_result = AUTH_REQUEST_CACHE_NONE;
+
+ name = (request->userdb->set->name[0] != '\0' ?
+ request->userdb->set->name :
+ request->userdb->userdb->iface->name);
+
+ event = event_create(request->event);
+ event_add_str(event, "userdb_id", dec2str(request->userdb->userdb->id));
+ event_add_str(event, "userdb_name", name);
+ event_add_str(event, "userdb", request->userdb->userdb->iface->name);
+ event_set_log_prefix_callback(event, FALSE,
+ auth_request_get_log_prefix_db, request);
+
+ /* check if we should enable verbose logging here*/
+ if (*request->userdb->set->auth_verbose == 'y')
+ event_set_min_log_level(event, LOG_TYPE_INFO);
+ else if (*request->userdb->set->auth_verbose == 'n')
+ event_set_min_log_level(event, LOG_TYPE_WARNING);
+
+ e_debug(event_create_passthrough(event)->
+ set_name("auth_userdb_request_started")->
+ event(),
+ "Performing userdb lookup");
+ array_push_back(&request->authdb_event, &event);
+}
+
+void auth_request_userdb_lookup_end(struct auth_request *request,
+ enum userdb_result result)
+{
+ i_assert(array_count(&request->authdb_event) > 0);
+ struct event *event = authdb_event(request);
+ struct event_passthrough *e =
+ event_create_passthrough(event)->
+ set_name("auth_userdb_request_finished")->
+ add_str("result", userdb_result_to_string(result));
+ if (request->userdb_cache_result != AUTH_REQUEST_CACHE_NONE &&
+ request->set->cache_ttl != 0 && request->set->cache_size != 0)
+ e->add_str("cache", auth_request_cache_result_to_str(request->userdb_cache_result));
+ e_debug(e->event(), "Finished userdb lookup");
+ event_unref(&event);
+ array_pop_back(&request->authdb_event);
+}
+
+static bool
+auth_request_handle_passdb_callback(enum passdb_result *result,
+ struct auth_request *request)
+{
+ struct auth_passdb *next_passdb;
+ enum auth_db_rule result_rule;
+ bool passdb_continue = FALSE;
+
+ if (request->passdb_password != NULL) {
+ safe_memset(request->passdb_password, 0,
+ strlen(request->passdb_password));
+ }
+
+ auth_request_passdb_lookup_end(request, *result);
+
+ if (request->passdb->set->deny &&
+ *result != PASSDB_RESULT_USER_UNKNOWN) {
+ /* deny passdb. we can get through this step only if the
+ lookup returned that user doesn't exist in it. internal
+ errors are fatal here. */
+ if (*result != PASSDB_RESULT_INTERNAL_FAILURE) {
+ e_info(authdb_event(request),
+ "User found from deny passdb");
+ *result = PASSDB_RESULT_USER_DISABLED;
+ }
+ return TRUE;
+ }
+ if (request->failed) {
+ /* The passdb didn't fail, but something inside it failed
+ (e.g. allow_nets mismatch). Make sure we'll fail this
+ lookup, but reset the failure so the next passdb can
+ succeed. */
+ if (*result == PASSDB_RESULT_OK)
+ *result = PASSDB_RESULT_USER_UNKNOWN;
+ request->failed = FALSE;
+ }
+
+ /* users that exist but can't log in are special. we don't try to match
+ any of the success/failure rules to them. they'll always fail. */
+ switch (*result) {
+ case PASSDB_RESULT_USER_DISABLED:
+ return TRUE;
+ case PASSDB_RESULT_PASS_EXPIRED:
+ auth_request_set_field(request, "reason",
+ "Password expired", NULL);
+ return TRUE;
+
+ case PASSDB_RESULT_OK:
+ result_rule = request->passdb->result_success;
+ break;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ result_rule = request->passdb->result_internalfail;
+ break;
+ case PASSDB_RESULT_NEXT:
+ e_debug(authdb_event(request),
+ "Not performing authentication (noauthenticate set)");
+ result_rule = AUTH_DB_RULE_CONTINUE;
+ break;
+ case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+ case PASSDB_RESULT_USER_UNKNOWN:
+ case PASSDB_RESULT_PASSWORD_MISMATCH:
+ default:
+ result_rule = request->passdb->result_failure;
+ break;
+ }
+
+ switch (result_rule) {
+ case AUTH_DB_RULE_RETURN:
+ break;
+ case AUTH_DB_RULE_RETURN_OK:
+ request->passdb_success = TRUE;
+ break;
+ case AUTH_DB_RULE_RETURN_FAIL:
+ request->passdb_success = FALSE;
+ break;
+ case AUTH_DB_RULE_CONTINUE:
+ passdb_continue = TRUE;
+ if (*result == PASSDB_RESULT_OK) {
+ /* password was successfully verified. don't bother
+ checking it again. */
+ auth_request_set_password_verified(request);
+ }
+ break;
+ case AUTH_DB_RULE_CONTINUE_OK:
+ passdb_continue = TRUE;
+ request->passdb_success = TRUE;
+ /* password was successfully verified. don't bother
+ checking it again. */
+ auth_request_set_password_verified(request);
+ break;
+ case AUTH_DB_RULE_CONTINUE_FAIL:
+ passdb_continue = TRUE;
+ request->passdb_success = FALSE;
+ break;
+ }
+ /* nopassword check is specific to a single passdb and shouldn't leak
+ to the next one. we already added it to cache. */
+ auth_fields_remove(request->fields.extra_fields, "nopassword");
+ auth_fields_remove(request->fields.extra_fields, "noauthenticate");
+
+ if (request->fields.requested_login_user != NULL &&
+ *result == PASSDB_RESULT_OK) {
+ auth_request_master_user_login_finish(request);
+ /* if the passdb lookup continues, it continues with non-master
+ passdbs for the requested_login_user. */
+ next_passdb = auth_request_get_auth(request)->passdbs;
+ } else {
+ next_passdb = request->passdb->next;
+ }
+
+ while (next_passdb != NULL &&
+ auth_request_want_skip_passdb(request, next_passdb))
+ next_passdb = next_passdb->next;
+
+ if (*result == PASSDB_RESULT_OK || *result == PASSDB_RESULT_NEXT) {
+ /* this passdb lookup succeeded, preserve its extra fields */
+ auth_fields_snapshot(request->fields.extra_fields);
+ request->snapshot_have_userdb_prefetch_set =
+ request->userdb_prefetch_set;
+ if (request->fields.userdb_reply != NULL)
+ auth_fields_snapshot(request->fields.userdb_reply);
+ } else {
+ /* this passdb lookup failed, remove any extra fields it set */
+ auth_fields_rollback(request->fields.extra_fields);
+ if (request->fields.userdb_reply != NULL) {
+ auth_fields_rollback(request->fields.userdb_reply);
+ request->userdb_prefetch_set =
+ request->snapshot_have_userdb_prefetch_set;
+ }
+ }
+
+ if (passdb_continue && next_passdb != NULL) {
+ /* try next passdb. */
+ request->passdb = next_passdb;
+ request->passdb_password = NULL;
+
+ if (*result == PASSDB_RESULT_USER_UNKNOWN) {
+ /* remember that we did at least one successful
+ passdb lookup */
+ request->passdbs_seen_user_unknown = TRUE;
+ } else if (*result == PASSDB_RESULT_INTERNAL_FAILURE) {
+ /* remember that we have had an internal failure. at
+ the end return internal failure if we couldn't
+ successfully login. */
+ request->passdbs_seen_internal_failure = TRUE;
+ }
+ return FALSE;
+ } else if (*result == PASSDB_RESULT_NEXT) {
+ /* admin forgot to put proper passdb last */
+ e_error(request->event,
+ "%sLast passdb had noauthenticate field, cannot authenticate user",
+ auth_request_get_log_prefix_db(request));
+ *result = PASSDB_RESULT_INTERNAL_FAILURE;
+ } else if (request->passdb_success) {
+ /* either this or a previous passdb lookup succeeded. */
+ *result = PASSDB_RESULT_OK;
+ } else if (request->passdbs_seen_internal_failure) {
+ /* last passdb lookup returned internal failure. it may have
+ had the correct password, so return internal failure
+ instead of plain failure. */
+ *result = PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+ return TRUE;
+}
+
+void
+auth_request_verify_plain_callback_finish(enum passdb_result result,
+ struct auth_request *request)
+{
+ const char *error;
+
+ if (passdb_template_export(request->passdb->override_fields_tmpl,
+ request, &error) < 0) {
+ e_error(authdb_event(request),
+ "Failed to expand override_fields: %s", error);
+ result = PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+ if (!auth_request_handle_passdb_callback(&result, request)) {
+ /* try next passdb */
+ auth_request_verify_plain(request, request->mech_password,
+ request->private_callback.verify_plain);
+ } else {
+ auth_request_ref(request);
+ request->passdb_result = result;
+ request->private_callback.verify_plain(request->passdb_result, request);
+ auth_request_unref(&request);
+ }
+}
+
+void auth_request_verify_plain_callback(enum passdb_result result,
+ struct auth_request *request)
+{
+ struct auth_passdb *passdb = request->passdb;
+
+ i_assert(request->state == AUTH_REQUEST_STATE_PASSDB);
+
+ auth_request_set_state(request, AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (result == PASSDB_RESULT_OK &&
+ auth_fields_exists(request->fields.extra_fields, "noauthenticate"))
+ result = PASSDB_RESULT_NEXT;
+
+ if (result != PASSDB_RESULT_INTERNAL_FAILURE)
+ auth_request_save_cache(request, result);
+ else {
+ /* lookup failed. if we're looking here only because the
+ request was expired in cache, fallback to using cached
+ expired record. */
+ const char *cache_key = passdb->cache_key;
+
+ auth_request_stats_add_tempfail(request);
+ if (passdb_cache_verify_plain(request, cache_key,
+ request->mech_password,
+ &result, TRUE)) {
+ e_info(authdb_event(request),
+ "Falling back to expired data from cache");
+ return;
+ }
+ }
+
+ auth_request_verify_plain_callback_finish(result, request);
+}
+
+static bool password_has_illegal_chars(const char *password)
+{
+ for (; *password != '\0'; password++) {
+ switch (*password) {
+ case '\001':
+ case '\t':
+ case '\r':
+ case '\n':
+ /* these characters have a special meaning in internal
+ protocols, make sure the password doesn't
+ accidentally get there unescaped. */
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool auth_request_is_disabled_master_user(struct auth_request *request)
+{
+ if (request->fields.requested_login_user == NULL ||
+ request->passdb != NULL)
+ return FALSE;
+
+ /* no masterdbs, master logins not supported */
+ e_info(request->mech_event,
+ "Attempted master login with no master passdbs "
+ "(trying to log in as user: %s)",
+ request->fields.requested_login_user);
+ return TRUE;
+}
+
+static
+void auth_request_policy_penalty_finish(void *context)
+{
+ struct auth_policy_check_ctx *ctx = context;
+
+ timeout_remove(&ctx->request->to_penalty);
+
+ i_assert(ctx->request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ switch(ctx->type) {
+ case AUTH_POLICY_CHECK_TYPE_PLAIN:
+ ctx->request->handler->verify_plain_continue_callback(ctx->request, ctx->callback_plain);
+ return;
+ case AUTH_POLICY_CHECK_TYPE_LOOKUP:
+ auth_request_lookup_credentials_policy_continue(ctx->request, ctx->callback_lookup);
+ return;
+ case AUTH_POLICY_CHECK_TYPE_SUCCESS:
+ auth_request_success_continue(ctx);
+ return;
+ default:
+ i_unreached();
+ }
+}
+
+static
+void auth_request_policy_check_callback(int result, void *context)
+{
+ struct auth_policy_check_ctx *ctx = context;
+
+ ctx->request->policy_processed = TRUE;
+ /* It's possible that multiple policy lookups return a penalty.
+ Sum them all up to the event. */
+ ctx->request->policy_penalty += result < 0 ? 0 : result;
+
+ if (ctx->request->set->policy_log_only && result != 0) {
+ auth_request_policy_penalty_finish(context);
+ return;
+ }
+ if (result < 0) {
+ /* fail it right here and now */
+ auth_request_fail(ctx->request);
+ } else if (ctx->type != AUTH_POLICY_CHECK_TYPE_SUCCESS && result > 0 &&
+ !ctx->request->fields.no_penalty) {
+ ctx->request->to_penalty = timeout_add(result * 1000,
+ auth_request_policy_penalty_finish,
+ context);
+ } else {
+ auth_request_policy_penalty_finish(context);
+ }
+}
+
+void auth_request_verify_plain(struct auth_request *request,
+ const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct auth_policy_check_ctx *ctx;
+
+ i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (request->mech_password == NULL)
+ request->mech_password = p_strdup(request->pool, password);
+ else
+ i_assert(request->mech_password == password);
+ request->user_changed_by_lookup = FALSE;
+
+ if (request->policy_processed || !request->set->policy_check_before_auth) {
+ request->handler->verify_plain_continue_callback(request,
+ callback);
+ } else {
+ ctx = p_new(request->pool, struct auth_policy_check_ctx, 1);
+ ctx->request = request;
+ ctx->callback_plain = callback;
+ ctx->type = AUTH_POLICY_CHECK_TYPE_PLAIN;
+ auth_policy_check(request, request->mech_password, auth_request_policy_check_callback, ctx);
+ }
+}
+
+void auth_request_default_verify_plain_continue(struct auth_request *request,
+ verify_plain_callback_t *callback)
+{
+ struct auth_passdb *passdb;
+ enum passdb_result result;
+ const char *cache_key, *error;
+ const char *password = request->mech_password;
+
+ i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (auth_request_is_disabled_master_user(request)) {
+ callback(PASSDB_RESULT_USER_UNKNOWN, request);
+ return;
+ }
+
+ if (password_has_illegal_chars(password)) {
+ e_info(authdb_event(request),
+ "Attempted login with password having illegal chars");
+ callback(PASSDB_RESULT_USER_UNKNOWN, request);
+ return;
+ }
+
+ passdb = request->passdb;
+
+ while (passdb != NULL && auth_request_want_skip_passdb(request, passdb))
+ passdb = passdb->next;
+
+ request->passdb = passdb;
+
+ if (passdb == NULL) {
+ auth_request_log_error(request,
+ request->mech != NULL ? AUTH_SUBSYS_MECH : "none",
+ "All password databases were skipped");
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
+ return;
+ }
+
+ auth_request_passdb_lookup_begin(request);
+ request->private_callback.verify_plain = callback;
+
+ cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
+ if (passdb_cache_verify_plain(request, cache_key, password,
+ &result, FALSE)) {
+ return;
+ }
+
+ auth_request_set_state(request, AUTH_REQUEST_STATE_PASSDB);
+ /* In case this request had already done a credentials lookup (is it
+ even possible?), make sure wanted_credentials_scheme is cleared
+ so passdbs don't think we're doing a credentials lookup. */
+ request->wanted_credentials_scheme = NULL;
+
+ if (passdb->passdb->iface.verify_plain == NULL) {
+ /* we're deinitializing and just want to get rid of this
+ request */
+ auth_request_verify_plain_callback(
+ PASSDB_RESULT_INTERNAL_FAILURE, request);
+ } else if (passdb->passdb->blocking) {
+ passdb_blocking_verify_plain(request);
+ } else if (passdb_template_export(passdb->default_fields_tmpl,
+ request, &error) < 0) {
+ e_error(authdb_event(request),
+ "Failed to expand default_fields: %s", error);
+ auth_request_verify_plain_callback(
+ PASSDB_RESULT_INTERNAL_FAILURE, request);
+ } else {
+ passdb->passdb->iface.verify_plain(request, password,
+ auth_request_verify_plain_callback);
+ }
+}
+
+static void
+auth_request_lookup_credentials_finish(enum passdb_result result,
+ const unsigned char *credentials,
+ size_t size,
+ struct auth_request *request)
+{
+ const char *error;
+
+ if (passdb_template_export(request->passdb->override_fields_tmpl,
+ request, &error) < 0) {
+ e_error(authdb_event(request),
+ "Failed to expand override_fields: %s", error);
+ result = PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+ if (!auth_request_handle_passdb_callback(&result, request)) {
+ /* try next passdb */
+ if (request->fields.skip_password_check &&
+ request->fields.delayed_credentials == NULL && size > 0) {
+ /* passdb continue* rule after a successful lookup.
+ remember these credentials and use them later on. */
+ auth_request_set_delayed_credentials(request,
+ credentials, size);
+ }
+ auth_request_lookup_credentials(request,
+ request->wanted_credentials_scheme,
+ request->private_callback.lookup_credentials);
+ } else {
+ if (request->fields.delayed_credentials != NULL && size == 0) {
+ /* we did multiple passdb lookups, but the last one
+ didn't provide any credentials (e.g. just wanted to
+ add some extra fields). so use the first passdb's
+ credentials instead. */
+ credentials = request->fields.delayed_credentials;
+ size = request->fields.delayed_credentials_size;
+ }
+ if (request->set->debug_passwords &&
+ result == PASSDB_RESULT_OK) {
+ e_debug(authdb_event(request),
+ "Credentials: %s",
+ binary_to_hex(credentials, size));
+ }
+ if (result == PASSDB_RESULT_SCHEME_NOT_AVAILABLE &&
+ request->passdbs_seen_user_unknown) {
+ /* one of the passdbs accepted the scheme,
+ but the user was unknown there */
+ result = PASSDB_RESULT_USER_UNKNOWN;
+ }
+ request->passdb_result = result;
+ request->private_callback.
+ lookup_credentials(result, credentials, size, request);
+ }
+}
+
+void auth_request_lookup_credentials_callback(enum passdb_result result,
+ const unsigned char *credentials,
+ size_t size,
+ struct auth_request *request)
+{
+ struct auth_passdb *passdb = request->passdb;
+ const char *cache_cred, *cache_scheme;
+
+ i_assert(request->state == AUTH_REQUEST_STATE_PASSDB);
+
+ auth_request_set_state(request, AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (result == PASSDB_RESULT_OK &&
+ auth_fields_exists(request->fields.extra_fields, "noauthenticate"))
+ result = PASSDB_RESULT_NEXT;
+
+ if (result != PASSDB_RESULT_INTERNAL_FAILURE)
+ auth_request_save_cache(request, result);
+ else {
+ /* lookup failed. if we're looking here only because the
+ request was expired in cache, fallback to using cached
+ expired record. */
+ const char *cache_key = passdb->cache_key;
+
+ auth_request_stats_add_tempfail(request);
+ if (passdb_cache_lookup_credentials(request, cache_key,
+ &cache_cred, &cache_scheme,
+ &result, TRUE)) {
+ e_info(authdb_event(request),
+ "Falling back to expired data from cache");
+ passdb_handle_credentials(
+ result, cache_cred, cache_scheme,
+ auth_request_lookup_credentials_finish,
+ request);
+ return;
+ }
+ }
+
+ auth_request_lookup_credentials_finish(result, credentials, size,
+ request);
+}
+
+void auth_request_lookup_credentials(struct auth_request *request,
+ const char *scheme,
+ lookup_credentials_callback_t *callback)
+{
+ struct auth_policy_check_ctx *ctx;
+
+ i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ if (request->wanted_credentials_scheme == NULL)
+ request->wanted_credentials_scheme =
+ p_strdup(request->pool, scheme);
+ request->user_changed_by_lookup = FALSE;
+
+ if (request->policy_processed || !request->set->policy_check_before_auth)
+ auth_request_lookup_credentials_policy_continue(request, callback);
+ else {
+ ctx = p_new(request->pool, struct auth_policy_check_ctx, 1);
+ ctx->request = request;
+ ctx->callback_lookup = callback;
+ ctx->type = AUTH_POLICY_CHECK_TYPE_LOOKUP;
+ auth_policy_check(request, ctx->request->mech_password, auth_request_policy_check_callback, ctx);
+ }
+}
+
+static
+void auth_request_lookup_credentials_policy_continue(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ struct auth_passdb *passdb;
+ const char *cache_key, *cache_cred, *cache_scheme, *error;
+ enum passdb_result result;
+
+ i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
+ if (auth_request_is_disabled_master_user(request)) {
+ callback(PASSDB_RESULT_USER_UNKNOWN, NULL, 0, request);
+ return;
+ }
+ passdb = request->passdb;
+ while (passdb != NULL && auth_request_want_skip_passdb(request, passdb))
+ passdb = passdb->next;
+ request->passdb = passdb;
+
+ if (passdb == NULL) {
+ auth_request_log_error(request,
+ request->mech != NULL ? AUTH_SUBSYS_MECH : "none",
+ "All password databases were skipped");
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, NULL, 0, request);
+ return;
+ }
+
+ auth_request_passdb_lookup_begin(request);
+ request->private_callback.lookup_credentials = callback;
+
+ cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
+ if (cache_key != NULL) {
+ if (passdb_cache_lookup_credentials(request, cache_key,
+ &cache_cred, &cache_scheme,
+ &result, FALSE)) {
+ request->passdb_cache_result = AUTH_REQUEST_CACHE_HIT;
+ passdb_handle_credentials(
+ result, cache_cred, cache_scheme,
+ auth_request_lookup_credentials_finish,
+ request);
+ return;
+ } else {
+ request->passdb_cache_result = AUTH_REQUEST_CACHE_MISS;
+ }
+ }
+
+ auth_request_set_state(request, AUTH_REQUEST_STATE_PASSDB);
+
+ if (passdb->passdb->iface.lookup_credentials == NULL) {
+ /* this passdb doesn't support credentials */
+ e_debug(authdb_event(request),
+ "passdb doesn't support credential lookups");
+ auth_request_lookup_credentials_callback(
+ PASSDB_RESULT_SCHEME_NOT_AVAILABLE,
+ uchar_empty_ptr, 0, request);
+ } else if (passdb->passdb->blocking) {
+ passdb_blocking_lookup_credentials(request);
+ } else if (passdb_template_export(passdb->default_fields_tmpl,
+ request, &error) < 0) {
+ e_error(authdb_event(request),
+ "Failed to expand default_fields: %s", error);
+ auth_request_lookup_credentials_callback(
+ PASSDB_RESULT_INTERNAL_FAILURE,
+ uchar_empty_ptr, 0, request);
+ } else {
+ passdb->passdb->iface.lookup_credentials(request,
+ auth_request_lookup_credentials_callback);
+ }
+}
+
+void auth_request_set_credentials(struct auth_request *request,
+ const char *scheme, const char *data,
+ set_credentials_callback_t *callback)
+{
+ struct auth_passdb *passdb = request->passdb;
+ const char *cache_key, *new_credentials;
+
+ cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
+ if (cache_key != NULL)
+ auth_cache_remove(passdb_cache, request, cache_key);
+
+ request->private_callback.set_credentials = callback;
+
+ new_credentials = t_strdup_printf("{%s}%s", scheme, data);
+ if (passdb->passdb->blocking)
+ passdb_blocking_set_credentials(request, new_credentials);
+ else if (passdb->passdb->iface.set_credentials != NULL) {
+ passdb->passdb->iface.set_credentials(request, new_credentials,
+ callback);
+ } else {
+ /* this passdb doesn't support credentials update */
+ callback(FALSE, request);
+ }
+}
+
+static void auth_request_userdb_save_cache(struct auth_request *request,
+ enum userdb_result result)
+{
+ struct auth_userdb *userdb = request->userdb;
+ string_t *str;
+ const char *cache_value;
+
+ if (passdb_cache == NULL || userdb->cache_key == NULL)
+ return;
+
+ if (result == USERDB_RESULT_USER_UNKNOWN)
+ cache_value = "";
+ else {
+ str = t_str_new(128);
+ auth_fields_append(request->fields.userdb_reply, str,
+ AUTH_FIELD_FLAG_CHANGED,
+ AUTH_FIELD_FLAG_CHANGED);
+ if (request->user_changed_by_lookup) {
+ /* username was changed by passdb or userdb */
+ if (str_len(str) > 0)
+ str_append_c(str, '\t');
+ str_append(str, "user=");
+ str_append_tabescaped(str, request->fields.user);
+ }
+ if (str_len(str) == 0) {
+ /* no userdb fields. but we can't save an empty string,
+ since that means "user unknown". */
+ str_append(str, AUTH_REQUEST_USER_KEY_IGNORE);
+ }
+ cache_value = str_c(str);
+ }
+ /* last_success has no meaning with userdb */
+ auth_cache_insert(passdb_cache, request, userdb->cache_key,
+ cache_value, FALSE);
+}
+
+static bool auth_request_lookup_user_cache(struct auth_request *request,
+ const char *key,
+ enum userdb_result *result_r,
+ bool use_expired)
+{
+ struct auth_stats *stats = auth_request_stats_get(request);
+ const char *value;
+ struct auth_cache_node *node;
+ bool expired, neg_expired;
+
+ value = auth_cache_lookup(passdb_cache, request, key, &node,
+ &expired, &neg_expired);
+ if (value == NULL || (expired && !use_expired)) {
+ stats->auth_cache_miss_count++;
+ request->userdb_cache_result = AUTH_REQUEST_CACHE_MISS;
+ e_debug(request->event,
+ value == NULL ? "%suserdb cache miss" :
+ "%suserdb cache expired",
+ auth_request_get_log_prefix_db(request));
+ return FALSE;
+ }
+ stats->auth_cache_hit_count++;
+ request->userdb_cache_result = AUTH_REQUEST_CACHE_HIT;
+ e_debug(request->event,
+ "%suserdb cache hit: %s",
+ auth_request_get_log_prefix_db(request), value);
+
+ if (*value == '\0') {
+ /* negative cache entry */
+ *result_r = USERDB_RESULT_USER_UNKNOWN;
+ auth_request_init_userdb_reply(request, FALSE);
+ return TRUE;
+ }
+
+ /* We want to preserve any userdb fields set by the earlier passdb
+ lookup, so initialize userdb_reply only if it doesn't exist.
+ Don't add userdb's default_fields, because the entire userdb part of
+ the result comes from the cache. */
+ if (request->fields.userdb_reply == NULL)
+ auth_request_init_userdb_reply(request, FALSE);
+ auth_request_userdb_import(request, value);
+ *result_r = USERDB_RESULT_OK;
+ return TRUE;
+}
+
+void auth_request_userdb_callback(enum userdb_result result,
+ struct auth_request *request)
+{
+ struct auth_userdb *userdb = request->userdb;
+ struct auth_userdb *next_userdb;
+ enum auth_db_rule result_rule;
+ const char *error;
+ bool userdb_continue = FALSE;
+
+ switch (result) {
+ case USERDB_RESULT_OK:
+ result_rule = userdb->result_success;
+ break;
+ case USERDB_RESULT_INTERNAL_FAILURE:
+ auth_request_stats_add_tempfail(request);
+ result_rule = userdb->result_internalfail;
+ break;
+ case USERDB_RESULT_USER_UNKNOWN:
+ default:
+ result_rule = userdb->result_failure;
+ break;
+ }
+
+ switch (result_rule) {
+ case AUTH_DB_RULE_RETURN:
+ break;
+ case AUTH_DB_RULE_RETURN_OK:
+ request->userdb_success = TRUE;
+ break;
+ case AUTH_DB_RULE_RETURN_FAIL:
+ request->userdb_success = FALSE;
+ break;
+ case AUTH_DB_RULE_CONTINUE:
+ userdb_continue = TRUE;
+ break;
+ case AUTH_DB_RULE_CONTINUE_OK:
+ userdb_continue = TRUE;
+ request->userdb_success = TRUE;
+ break;
+ case AUTH_DB_RULE_CONTINUE_FAIL:
+ userdb_continue = TRUE;
+ request->userdb_success = FALSE;
+ break;
+ }
+
+ auth_request_userdb_lookup_end(request, result);
+
+ next_userdb = userdb->next;
+ while (next_userdb != NULL &&
+ auth_request_want_skip_userdb(request, next_userdb))
+ next_userdb = next_userdb->next;
+
+ if (userdb_continue && next_userdb != NULL) {
+ /* try next userdb. */
+ if (result == USERDB_RESULT_INTERNAL_FAILURE)
+ request->userdbs_seen_internal_failure = TRUE;
+
+ if (result == USERDB_RESULT_OK) {
+ /* this userdb lookup succeeded, preserve its extra
+ fields */
+ if (userdb_template_export(userdb->override_fields_tmpl,
+ request, &error) < 0) {
+ e_error(request->event,
+ "%sFailed to expand override_fields: %s",
+ auth_request_get_log_prefix_db(request), error);
+ request->private_callback.userdb(
+ USERDB_RESULT_INTERNAL_FAILURE, request);
+ return;
+ }
+ auth_fields_snapshot(request->fields.userdb_reply);
+ } else {
+ /* this userdb lookup failed, remove any extra fields
+ it set */
+ auth_fields_rollback(request->fields.userdb_reply);
+ }
+ request->user_changed_by_lookup = FALSE;
+
+ request->userdb = next_userdb;
+ auth_request_lookup_user(request,
+ request->private_callback.userdb);
+ return;
+ }
+
+ if (request->userdb_success) {
+ if (userdb_template_export(userdb->override_fields_tmpl,
+ request, &error) < 0) {
+ e_error(request->event,
+ "%sFailed to expand override_fields: %s",
+ auth_request_get_log_prefix_db(request), error);
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+ } else {
+ result = USERDB_RESULT_OK;
+ }
+ } else if (request->userdbs_seen_internal_failure ||
+ result == USERDB_RESULT_INTERNAL_FAILURE) {
+ /* one of the userdb lookups failed. the user might have been
+ in there, so this is an internal failure */
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+ } else if (request->client_pid != 0) {
+ /* this was an actual login attempt, the user should
+ have been found. */
+ if (auth_request_get_auth(request)->userdbs->next == NULL) {
+ e_error(request->event,
+ "%suser not found from userdb",
+ auth_request_get_log_prefix_db(request));
+ } else {
+ e_error(request->mech_event,
+ "user not found from any userdbs");
+ }
+ result = USERDB_RESULT_USER_UNKNOWN;
+ } else {
+ result = USERDB_RESULT_USER_UNKNOWN;
+ }
+
+ if (request->userdb_lookup_tempfailed) {
+ /* no caching */
+ } else if (result != USERDB_RESULT_INTERNAL_FAILURE) {
+ if (request->userdb_cache_result != AUTH_REQUEST_CACHE_HIT)
+ auth_request_userdb_save_cache(request, result);
+ } else if (passdb_cache != NULL && userdb->cache_key != NULL) {
+ /* lookup failed. if we're looking here only because the
+ request was expired in cache, fallback to using cached
+ expired record. */
+ const char *cache_key = userdb->cache_key;
+
+ if (auth_request_lookup_user_cache(request, cache_key,
+ &result, TRUE)) {
+ e_info(request->event,
+ "%sFalling back to expired data from cache",
+ auth_request_get_log_prefix_db(request));
+ }
+ }
+
+ request->private_callback.userdb(result, request);
+}
+
+void auth_request_lookup_user(struct auth_request *request,
+ userdb_callback_t *callback)
+{
+ struct auth_userdb *userdb = request->userdb;
+ const char *cache_key, *error;
+
+ request->private_callback.userdb = callback;
+ request->user_changed_by_lookup = FALSE;
+ request->userdb_lookup = TRUE;
+ request->userdb_cache_result = AUTH_REQUEST_CACHE_NONE;
+ if (request->fields.userdb_reply == NULL)
+ auth_request_init_userdb_reply(request, TRUE);
+ else {
+ /* we still want to set default_fields. these override any
+ existing fields set by previous userdbs (because if that is
+ unwanted, ":protected" can be used). */
+ if (userdb_template_export(userdb->default_fields_tmpl,
+ request, &error) < 0) {
+ e_error(authdb_event(request),
+ "Failed to expand default_fields: %s", error);
+ auth_request_userdb_callback(
+ USERDB_RESULT_INTERNAL_FAILURE, request);
+ return;
+ }
+ }
+
+ auth_request_userdb_lookup_begin(request);
+
+ /* (for now) auth_cache is shared between passdb and userdb */
+ cache_key = passdb_cache == NULL ? NULL : userdb->cache_key;
+ if (cache_key != NULL) {
+ enum userdb_result result;
+
+ if (auth_request_lookup_user_cache(request, cache_key,
+ &result, FALSE)) {
+ request->userdb_cache_result = AUTH_REQUEST_CACHE_HIT;
+ auth_request_userdb_callback(result, request);
+ return;
+ } else {
+ request->userdb_cache_result = AUTH_REQUEST_CACHE_MISS;
+ }
+ }
+
+ if (userdb->userdb->iface->lookup == NULL) {
+ /* we are deinitializing */
+ auth_request_userdb_callback(USERDB_RESULT_INTERNAL_FAILURE,
+ request);
+ } else if (userdb->userdb->blocking)
+ userdb_blocking_lookup(request);
+ else
+ userdb->userdb->iface->lookup(request, auth_request_userdb_callback);
+}
+
+static void
+auth_request_validate_networks(struct auth_request *request,
+ const char *name, const char *networks,
+ const struct ip_addr *remote_ip)
+{
+ const char *const *net;
+ struct ip_addr net_ip;
+ unsigned int bits;
+ bool found = FALSE;
+
+ for (net = t_strsplit_spaces(networks, ", "); *net != NULL; net++) {
+ e_debug(authdb_event(request),
+ "%s: Matching for network %s", name, *net);
+
+ if (strcmp(*net, "local") == 0) {
+ if (remote_ip->family == 0) {
+ found = TRUE;
+ break;
+ }
+ } else if (net_parse_range(*net, &net_ip, &bits) < 0) {
+ e_info(authdb_event(request),
+ "%s: Invalid network '%s'", name, *net);
+ } else if (remote_ip->family != 0 &&
+ net_is_in_network(remote_ip, &net_ip, bits)) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ ;
+ else if (remote_ip->family == 0) {
+ e_info(authdb_event(request),
+ "%s check failed: Remote IP not known and 'local' missing", name);
+ } else {
+ e_info(authdb_event(request),
+ "%s check failed: IP %s not in allowed networks",
+ name, net_ip2addr(remote_ip));
+ }
+ if (!found)
+ request->failed = TRUE;
+}
+
+static void
+auth_request_set_password(struct auth_request *request, const char *value,
+ const char *default_scheme, bool noscheme)
+{
+ if (request->passdb_password != NULL) {
+ e_error(authdb_event(request),
+ "Multiple password values not supported");
+ return;
+ }
+
+ /* if the password starts with '{' it most likely contains
+ also '}'. check it anyway to make sure, because we
+ assert-crash later if it doesn't exist. this could happen
+ if plaintext passwords are used. */
+ if (*value == '{' && !noscheme && strchr(value, '}') != NULL)
+ request->passdb_password = p_strdup(request->pool, value);
+ else {
+ i_assert(default_scheme != NULL);
+ request->passdb_password =
+ p_strdup_printf(request->pool, "{%s}%s",
+ default_scheme, value);
+ }
+}
+
+static const char *
+get_updated_username(const char *old_username,
+ const char *name, const char *value)
+{
+ const char *p;
+
+ if (strcmp(name, "user") == 0) {
+ /* replace the whole username */
+ return value;
+ }
+
+ p = strchr(old_username, '@');
+ if (strcmp(name, "username") == 0) {
+ if (strchr(value, '@') != NULL)
+ return value;
+
+ /* preserve the current @domain */
+ return t_strconcat(value, p, NULL);
+ }
+
+ if (strcmp(name, "domain") == 0) {
+ if (p == NULL) {
+ /* add the domain */
+ return t_strconcat(old_username, "@", value, NULL);
+ } else {
+ /* replace the existing domain */
+ p = t_strdup_until(old_username, p + 1);
+ return t_strconcat(p, value, NULL);
+ }
+ }
+ return NULL;
+}
+
+static bool
+auth_request_try_update_username(struct auth_request *request,
+ const char *name, const char *value)
+{
+ const char *new_value;
+
+ new_value = get_updated_username(request->fields.user, name, value);
+ if (new_value == NULL)
+ return FALSE;
+ if (new_value[0] == '\0') {
+ e_error(authdb_event(request),
+ "username attempted to be changed to empty");
+ request->failed = TRUE;
+ return TRUE;
+ }
+
+ if (strcmp(request->fields.user, new_value) != 0) {
+ e_debug(authdb_event(request),
+ "username changed %s -> %s",
+ request->fields.user, new_value);
+ auth_request_set_username_forced(request, new_value);
+ request->user_changed_by_lookup = TRUE;
+ }
+ return TRUE;
+}
+
+static void
+auth_request_passdb_import(struct auth_request *request, const char *args,
+ const char *key_prefix, const char *default_scheme)
+{
+ const char *const *arg, *field;
+
+ for (arg = t_strsplit(args, "\t"); *arg != NULL; arg++) {
+ field = t_strconcat(key_prefix, *arg, NULL);
+ auth_request_set_field_keyvalue(request, field, default_scheme);
+ }
+}
+
+void auth_request_set_field(struct auth_request *request,
+ const char *name, const char *value,
+ const char *default_scheme)
+{
+ size_t name_len = strlen(name);
+
+ i_assert(*name != '\0');
+ i_assert(value != NULL);
+
+ i_assert(request->passdb != NULL);
+
+ if (name_len > 10 && strcmp(name+name_len-10, ":protected") == 0) {
+ /* set this field only if it hasn't been set before */
+ name = t_strndup(name, name_len-10);
+ if (auth_fields_exists(request->fields.extra_fields, name))
+ return;
+ } else if (name_len > 7 && strcmp(name+name_len-7, ":remove") == 0) {
+ /* remove this field entirely */
+ name = t_strndup(name, name_len-7);
+ auth_fields_remove(request->fields.extra_fields, name);
+ return;
+ }
+
+ if (strcmp(name, "password") == 0) {
+ auth_request_set_password(request, value,
+ default_scheme, FALSE);
+ return;
+ }
+ if (strcmp(name, "password_noscheme") == 0) {
+ auth_request_set_password(request, value, default_scheme, TRUE);
+ return;
+ }
+
+ if (auth_request_try_update_username(request, name, value)) {
+ /* don't change the original value so it gets saved correctly
+ to cache. */
+ } else if (strcmp(name, "login_user") == 0) {
+ auth_request_set_login_username_forced(request, value);
+ } else if (strcmp(name, "allow_nets") == 0) {
+ auth_request_validate_networks(request, name, value,
+ &request->fields.remote_ip);
+ } else if (strcmp(name, "fail") == 0) {
+ request->failed = TRUE;
+ } else if (strcmp(name, "delay_until") == 0) {
+ time_t timestamp;
+ unsigned int extra_secs = 0;
+ const char *p;
+
+ p = strchr(value, '+');
+ if (p != NULL) {
+ value = t_strdup_until(value, p++);
+ if (str_to_uint(p, &extra_secs) < 0) {
+ e_error(authdb_event(request),
+ "Invalid delay_until randomness number '%s'", p);
+ request->failed = TRUE;
+ } else {
+ extra_secs = i_rand_limit(extra_secs);
+ }
+ }
+ if (str_to_time(value, &timestamp) < 0) {
+ e_error(authdb_event(request),
+ "Invalid delay_until timestamp '%s'", value);
+ request->failed = TRUE;
+ } else if (timestamp <= ioloop_time) {
+ /* no more delays */
+ } else if (timestamp - ioloop_time > AUTH_REQUEST_MAX_DELAY_SECS) {
+ e_error(authdb_event(request),
+ "delay_until timestamp %s is too much in the future, failing", value);
+ request->failed = TRUE;
+ } else {
+ /* add randomness, but not too much of it */
+ timestamp += extra_secs;
+ if (timestamp - ioloop_time > AUTH_REQUEST_MAX_DELAY_SECS)
+ timestamp = ioloop_time + AUTH_REQUEST_MAX_DELAY_SECS;
+ request->delay_until = timestamp;
+ }
+ } else if (strcmp(name, "allow_real_nets") == 0) {
+ auth_request_validate_networks(request, name, value,
+ &request->fields.real_remote_ip);
+ } else if (str_begins(name, "userdb_")) {
+ /* for prefetch userdb */
+ request->userdb_prefetch_set = TRUE;
+ if (request->fields.userdb_reply == NULL)
+ auth_request_init_userdb_reply(request, TRUE);
+ if (strcmp(name, "userdb_userdb_import") == 0) {
+ /* we can't put the whole userdb_userdb_import
+ value to extra_cache_fields or it doesn't work
+ properly. so handle this explicitly. */
+ auth_request_passdb_import(request, value,
+ "userdb_", default_scheme);
+ return;
+ }
+ auth_request_set_userdb_field(request, name + 7, value);
+ } else if (strcmp(name, "noauthenticate") == 0) {
+ /* add "nopassword" also so that passdbs won't try to verify
+ the password. */
+ auth_fields_add(request->fields.extra_fields, name, value, 0);
+ auth_fields_add(request->fields.extra_fields, "nopassword", NULL, 0);
+ } else if (strcmp(name, "nopassword") == 0) {
+ /* NULL password - anything goes */
+ const char *password = request->passdb_password;
+
+ if (password != NULL &&
+ !auth_fields_exists(request->fields.extra_fields, "noauthenticate")) {
+ (void)password_get_scheme(&password);
+ if (*password != '\0') {
+ e_error(authdb_event(request),
+ "nopassword set but password is "
+ "non-empty");
+ return;
+ }
+ }
+ request->passdb_password = NULL;
+ auth_fields_add(request->fields.extra_fields, name, value, 0);
+ return;
+ } else if (strcmp(name, "passdb_import") == 0) {
+ auth_request_passdb_import(request, value, "", default_scheme);
+ return;
+ } else {
+ /* these fields are returned to client */
+ auth_fields_add(request->fields.extra_fields, name, value, 0);
+ return;
+ }
+
+ /* add the field unconditionally to extra_fields. this is required if
+ a) auth cache is used, b) if we're a worker and we'll need to send
+ this to the main auth process that can store it in the cache,
+ c) for easily checking :protected fields' existence. */
+ auth_fields_add(request->fields.extra_fields, name, value,
+ AUTH_FIELD_FLAG_HIDDEN);
+}
+
+void auth_request_set_null_field(struct auth_request *request, const char *name)
+{
+ if (str_begins(name, "userdb_")) {
+ /* make sure userdb prefetch is used even if all the fields
+ were returned as NULL. */
+ request->userdb_prefetch_set = TRUE;
+ }
+}
+
+void auth_request_set_field_keyvalue(struct auth_request *request,
+ const char *field,
+ const char *default_scheme)
+{
+ const char *key, *value;
+
+ value = strchr(field, '=');
+ if (value == NULL) {
+ key = field;
+ value = "";
+ } else {
+ key = t_strdup_until(field, value);
+ value++;
+ }
+ auth_request_set_field(request, key, value, default_scheme);
+}
+
+void auth_request_set_fields(struct auth_request *request,
+ const char *const *fields,
+ const char *default_scheme)
+{
+ for (; *fields != NULL; fields++) {
+ if (**fields == '\0')
+ continue;
+ auth_request_set_field_keyvalue(request, *fields, default_scheme);
+ }
+}
+
+static void auth_request_set_uidgid_file(struct auth_request *request,
+ const char *path_template)
+{
+ string_t *path;
+ struct stat st;
+ const char *error;
+
+ path = t_str_new(256);
+ if (auth_request_var_expand(path, path_template, request,
+ NULL, &error) <= 0) {
+ e_error(authdb_event(request),
+ "Failed to expand uidgid_file=%s: %s", path_template, error);
+ request->userdb_lookup_tempfailed = TRUE;
+ } else if (stat(str_c(path), &st) < 0) {
+ e_error(authdb_event(request),
+ "stat(%s) failed: %m", str_c(path));
+ request->userdb_lookup_tempfailed = TRUE;
+ } else {
+ auth_fields_add(request->fields.userdb_reply,
+ "uid", dec2str(st.st_uid), 0);
+ auth_fields_add(request->fields.userdb_reply,
+ "gid", dec2str(st.st_gid), 0);
+ }
+}
+
+static void
+auth_request_userdb_import(struct auth_request *request, const char *args)
+{
+ const char *key, *value, *const *arg;
+
+ for (arg = t_strsplit(args, "\t"); *arg != NULL; arg++) {
+ value = strchr(*arg, '=');
+ if (value == NULL) {
+ key = *arg;
+ value = "";
+ } else {
+ key = t_strdup_until(*arg, value);
+ value++;
+ }
+ auth_request_set_userdb_field(request, key, value);
+ }
+}
+
+void auth_request_set_userdb_field(struct auth_request *request,
+ const char *name, const char *value)
+{
+ size_t name_len = strlen(name);
+ uid_t uid;
+ gid_t gid;
+
+ i_assert(value != NULL);
+
+ if (name_len > 10 && strcmp(name+name_len-10, ":protected") == 0) {
+ /* set this field only if it hasn't been set before */
+ name = t_strndup(name, name_len-10);
+ if (auth_fields_exists(request->fields.userdb_reply, name))
+ return;
+ } else if (name_len > 7 && strcmp(name+name_len-7, ":remove") == 0) {
+ /* remove this field entirely */
+ name = t_strndup(name, name_len-7);
+ auth_fields_remove(request->fields.userdb_reply, name);
+ return;
+ }
+
+ if (strcmp(name, "uid") == 0) {
+ uid = userdb_parse_uid(request, value);
+ if (uid == (uid_t)-1) {
+ request->userdb_lookup_tempfailed = TRUE;
+ return;
+ }
+ value = dec2str(uid);
+ } else if (strcmp(name, "gid") == 0) {
+ gid = userdb_parse_gid(request, value);
+ if (gid == (gid_t)-1) {
+ request->userdb_lookup_tempfailed = TRUE;
+ return;
+ }
+ value = dec2str(gid);
+ } else if (strcmp(name, "tempfail") == 0) {
+ request->userdb_lookup_tempfailed = TRUE;
+ return;
+ } else if (auth_request_try_update_username(request, name, value)) {
+ return;
+ } else if (strcmp(name, "uidgid_file") == 0) {
+ auth_request_set_uidgid_file(request, value);
+ return;
+ } else if (strcmp(name, "userdb_import") == 0) {
+ auth_request_userdb_import(request, value);
+ return;
+ } else if (strcmp(name, "system_user") == 0) {
+ /* FIXME: the system_user is for backwards compatibility */
+ static bool warned = FALSE;
+ if (!warned) {
+ e_warning(authdb_event(request),
+ "Replace system_user with system_groups_user");
+ warned = TRUE;
+ }
+ name = "system_groups_user";
+ } else if (strcmp(name, AUTH_REQUEST_USER_KEY_IGNORE) == 0) {
+ return;
+ }
+
+ auth_fields_add(request->fields.userdb_reply, name, value, 0);
+}
+
+void auth_request_set_userdb_field_values(struct auth_request *request,
+ const char *name,
+ const char *const *values)
+{
+ if (*values == NULL)
+ return;
+
+ if (strcmp(name, "gid") == 0) {
+ /* convert gids to comma separated list */
+ string_t *value;
+ gid_t gid;
+
+ value = t_str_new(128);
+ for (; *values != NULL; values++) {
+ gid = userdb_parse_gid(request, *values);
+ if (gid == (gid_t)-1) {
+ request->userdb_lookup_tempfailed = TRUE;
+ return;
+ }
+
+ if (str_len(value) > 0)
+ str_append_c(value, ',');
+ str_append(value, dec2str(gid));
+ }
+ auth_fields_add(request->fields.userdb_reply, name, str_c(value), 0);
+ } else {
+ /* add only one */
+ if (values[1] != NULL) {
+ e_warning(authdb_event(request),
+ "Multiple values found for '%s', "
+ "using value '%s'", name, *values);
+ }
+ auth_request_set_userdb_field(request, name, *values);
+ }
+}
+
+static bool auth_request_proxy_is_self(struct auth_request *request)
+{
+ const char *port = NULL;
+
+ /* check if the port is the same */
+ port = auth_fields_find(request->fields.extra_fields, "port");
+ if (port != NULL && !str_uint_equals(port, request->fields.local_port))
+ return FALSE;
+ /* don't check destuser. in some systems destuser is intentionally
+ changed to proxied connections, but that shouldn't affect the
+ proxying decision.
+
+ it's unlikely any systems would actually want to proxy a connection
+ to itself only to change the username, since it can already be done
+ without proxying by changing the "user" field. */
+ return TRUE;
+}
+
+static bool
+auth_request_proxy_ip_is_self(struct auth_request *request,
+ const struct ip_addr *ip)
+{
+ unsigned int i;
+
+ if (net_ip_compare(ip, &request->fields.real_local_ip))
+ return TRUE;
+
+ for (i = 0; request->set->proxy_self_ips[i].family != 0; i++) {
+ if (net_ip_compare(ip, &request->set->proxy_self_ips[i]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+auth_request_proxy_finish_ip(struct auth_request *request,
+ bool proxy_host_is_self)
+{
+ const struct auth_request_fields *fields = &request->fields;
+
+ if (!auth_fields_exists(fields->extra_fields, "proxy_maybe")) {
+ /* proxying */
+ } else if (!proxy_host_is_self ||
+ !auth_request_proxy_is_self(request)) {
+ /* proxy destination isn't ourself - proxy */
+ auth_fields_remove(fields->extra_fields, "proxy_maybe");
+ auth_fields_add(fields->extra_fields, "proxy", NULL, 0);
+ } else {
+ /* proxying to ourself - log in without proxying by dropping
+ all the proxying fields. */
+ bool proxy_always = auth_fields_exists(fields->extra_fields,
+ "proxy_always");
+
+ auth_request_proxy_finish_failure(request);
+ if (proxy_always) {
+ /* setup where "self" refers to the local director
+ cluster, while "non-self" refers to remote clusters.
+
+ we've matched self here, so add proxy field and
+ let director fill the host. */
+ auth_fields_add(request->fields.extra_fields,
+ "proxy", NULL, 0);
+ }
+ }
+}
+
+static void
+auth_request_proxy_dns_callback(const struct dns_lookup_result *result,
+ struct auth_request_proxy_dns_lookup_ctx *ctx)
+{
+ struct auth_request *request = ctx->request;
+ const char *host;
+ unsigned int i;
+ bool proxy_host_is_self;
+
+ request->dns_lookup_ctx = NULL;
+ ctx->dns_lookup = NULL;
+
+ host = auth_fields_find(request->fields.extra_fields, "host");
+ i_assert(host != NULL);
+
+ if (result->ret != 0) {
+ auth_request_log_error(request, AUTH_SUBSYS_PROXY,
+ "DNS lookup for %s failed: %s", host, result->error);
+ request->internal_failure = TRUE;
+ auth_request_proxy_finish_failure(request);
+ } else {
+ if (result->msecs > AUTH_DNS_WARN_MSECS) {
+ auth_request_log_warning(request, AUTH_SUBSYS_PROXY,
+ "DNS lookup for %s took %u.%03u s",
+ host, result->msecs/1000, result->msecs % 1000);
+ }
+ auth_fields_add(request->fields.extra_fields, "hostip",
+ net_ip2addr(&result->ips[0]), 0);
+ proxy_host_is_self = FALSE;
+ for (i = 0; i < result->ips_count; i++) {
+ if (auth_request_proxy_ip_is_self(request,
+ &result->ips[i])) {
+ proxy_host_is_self = TRUE;
+ break;
+ }
+ }
+ auth_request_proxy_finish_ip(request, proxy_host_is_self);
+ }
+ if (ctx->callback != NULL)
+ ctx->callback(result->ret == 0, request);
+ auth_request_unref(&request);
+}
+
+static int auth_request_proxy_host_lookup(struct auth_request *request,
+ const char *host,
+ auth_request_proxy_cb_t *callback)
+{
+ struct auth_request_proxy_dns_lookup_ctx *ctx;
+ struct dns_lookup_settings dns_set;
+ const char *value;
+ unsigned int secs;
+
+ /* need to do dns lookup for the host */
+ i_zero(&dns_set);
+ dns_set.dns_client_socket_path = AUTH_DNS_SOCKET_PATH;
+ dns_set.timeout_msecs = AUTH_DNS_DEFAULT_TIMEOUT_MSECS;
+ dns_set.event_parent = request->event;
+ value = auth_fields_find(request->fields.extra_fields, "proxy_timeout");
+ if (value != NULL) {
+ if (str_to_uint(value, &secs) < 0) {
+ auth_request_log_error(request, AUTH_SUBSYS_PROXY,
+ "Invalid proxy_timeout value: %s", value);
+ } else {
+ dns_set.timeout_msecs = secs*1000;
+ }
+ }
+
+ ctx = p_new(request->pool, struct auth_request_proxy_dns_lookup_ctx, 1);
+ ctx->request = request;
+ auth_request_ref(request);
+ request->dns_lookup_ctx = ctx;
+
+ if (dns_lookup(host, &dns_set, auth_request_proxy_dns_callback, ctx,
+ &ctx->dns_lookup) < 0) {
+ /* failed early */
+ return -1;
+ }
+ ctx->callback = callback;
+ return 0;
+}
+
+int auth_request_proxy_finish(struct auth_request *request,
+ auth_request_proxy_cb_t *callback)
+{
+ const char *host, *hostip;
+ struct ip_addr ip;
+ bool proxy_host_is_self;
+
+ if (request->auth_only)
+ return 1;
+ if (!auth_fields_exists(request->fields.extra_fields, "proxy") &&
+ !auth_fields_exists(request->fields.extra_fields, "proxy_maybe"))
+ return 1;
+
+ host = auth_fields_find(request->fields.extra_fields, "host");
+ if (host == NULL) {
+ /* director can set the host. give it access to lip and lport
+ so it can also perform proxy_maybe internally */
+ proxy_host_is_self = FALSE;
+ if (request->fields.local_ip.family != 0) {
+ auth_fields_add(request->fields.extra_fields, "lip",
+ net_ip2addr(&request->fields.local_ip), 0);
+ }
+ if (request->fields.local_port != 0) {
+ auth_fields_add(request->fields.extra_fields, "lport",
+ dec2str(request->fields.local_port), 0);
+ }
+ } else if (net_addr2ip(host, &ip) == 0) {
+ proxy_host_is_self =
+ auth_request_proxy_ip_is_self(request, &ip);
+ } else {
+ hostip = auth_fields_find(request->fields.extra_fields, "hostip");
+ if (hostip != NULL && net_addr2ip(hostip, &ip) < 0) {
+ auth_request_log_error(request, AUTH_SUBSYS_PROXY,
+ "Invalid hostip in passdb: %s", hostip);
+ return -1;
+ }
+ if (hostip == NULL) {
+ /* asynchronous host lookup */
+ return auth_request_proxy_host_lookup(request, host, callback);
+ }
+ proxy_host_is_self =
+ auth_request_proxy_ip_is_self(request, &ip);
+ }
+
+ auth_request_proxy_finish_ip(request, proxy_host_is_self);
+ return 1;
+}
+
+void auth_request_proxy_finish_failure(struct auth_request *request)
+{
+ /* drop all proxying fields */
+ auth_fields_remove(request->fields.extra_fields, "proxy");
+ auth_fields_remove(request->fields.extra_fields, "proxy_maybe");
+ auth_fields_remove(request->fields.extra_fields, "proxy_always");
+ auth_fields_remove(request->fields.extra_fields, "host");
+ auth_fields_remove(request->fields.extra_fields, "port");
+ auth_fields_remove(request->fields.extra_fields, "destuser");
+}
+
+static void log_password_failure(struct auth_request *request,
+ const char *plain_password,
+ const char *crypted_password,
+ const char *scheme,
+ const struct password_generate_params *params,
+ const char *subsystem)
+{
+ struct event *event = get_request_event(request, subsystem);
+ static bool scheme_ok = FALSE;
+ string_t *str = t_str_new(256);
+ const char *working_scheme;
+
+ str_printfa(str, "%s(%s) != '%s'", scheme,
+ plain_password, crypted_password);
+
+ if (!scheme_ok) {
+ /* perhaps the scheme is wrong - see if we can find
+ a working one */
+ working_scheme = password_scheme_detect(plain_password,
+ crypted_password, params);
+ if (working_scheme != NULL) {
+ str_printfa(str, ", try %s scheme instead",
+ working_scheme);
+ }
+ }
+
+ e_debug(event, "%s", str_c(str));
+}
+
+static void
+auth_request_append_password(struct auth_request *request, string_t *str)
+{
+ const char *p, *log_type = request->set->verbose_passwords;
+ unsigned int max_len = 1024;
+
+ if (request->mech_password == NULL)
+ return;
+
+ p = strchr(log_type, ':');
+ if (p != NULL) {
+ if (str_to_uint(p+1, &max_len) < 0)
+ i_unreached();
+ log_type = t_strdup_until(log_type, p);
+ }
+
+ if (strcmp(log_type, "plain") == 0) {
+ str_printfa(str, "(given password: %s)",
+ t_strndup(request->mech_password, max_len));
+ } else if (strcmp(log_type, "sha1") == 0) {
+ unsigned char sha1[SHA1_RESULTLEN];
+
+ sha1_get_digest(request->mech_password,
+ strlen(request->mech_password), sha1);
+ str_printfa(str, "(SHA1 of given password: %s)",
+ t_strndup(binary_to_hex(sha1, sizeof(sha1)),
+ max_len));
+ } else {
+ i_unreached();
+ }
+}
+
+void auth_request_log_password_mismatch(struct auth_request *request,
+ const char *subsystem)
+{
+ auth_request_log_login_failure(request, subsystem, AUTH_LOG_MSG_PASSWORD_MISMATCH);
+}
+
+void auth_request_log_unknown_user(struct auth_request *request,
+ const char *subsystem)
+{
+ auth_request_log_login_failure(request, subsystem, "unknown user");
+}
+
+void auth_request_log_login_failure(struct auth_request *request,
+ const char *subsystem,
+ const char *message)
+{
+ struct event *event = get_request_event(request, subsystem);
+ string_t *str;
+
+ if (strcmp(request->set->verbose_passwords, "no") == 0) {
+ e_info(event, "%s", message);
+ return;
+ }
+
+ /* make sure this gets logged */
+ enum log_type orig_level = event_get_min_log_level(event);
+ event_set_min_log_level(event, LOG_TYPE_INFO);
+
+ str = t_str_new(128);
+ str_append(str, message);
+ str_append(str, " ");
+
+ auth_request_append_password(request, str);
+
+ if (request->userdb_lookup) {
+ if (request->userdb->next != NULL)
+ str_append(str, " - trying the next userdb");
+ } else {
+ if (request->passdb->next != NULL)
+ str_append(str, " - trying the next passdb");
+ }
+ e_info(event, "%s", str_c(str));
+ event_set_min_log_level(event, orig_level);
+}
+
+enum passdb_result
+auth_request_password_verify(struct auth_request *request,
+ const char *plain_password,
+ const char *crypted_password,
+ const char *scheme, const char *subsystem)
+{
+ return auth_request_password_verify_log(request, plain_password,
+ crypted_password, scheme, subsystem, TRUE);
+}
+
+enum passdb_result
+auth_request_password_verify_log(struct auth_request *request,
+ const char *plain_password,
+ const char *crypted_password,
+ const char *scheme, const char *subsystem,
+ bool log_password_mismatch)
+{
+ enum passdb_result result;
+ const unsigned char *raw_password;
+ size_t raw_password_size;
+ const char *error;
+ int ret;
+ struct password_generate_params gen_params = {
+ .user = request->fields.original_username,
+ .rounds = 0
+ };
+
+ if (request->fields.skip_password_check) {
+ /* passdb continue* rule after a successful authentication */
+ return PASSDB_RESULT_OK;
+ }
+
+ if (request->passdb->set->deny) {
+ /* this is a deny database, we don't care about the password */
+ return PASSDB_RESULT_PASSWORD_MISMATCH;
+ }
+
+ if (auth_fields_exists(request->fields.extra_fields, "nopassword")) {
+ auth_request_log_debug(request, subsystem,
+ "Allowing any password");
+ return PASSDB_RESULT_OK;
+ }
+
+ ret = password_decode(crypted_password, scheme,
+ &raw_password, &raw_password_size, &error);
+ if (ret <= 0) {
+ if (ret < 0) {
+ auth_request_log_error(request, subsystem,
+ "Password data is not valid for scheme %s: %s",
+ scheme, error);
+ } else {
+ auth_request_log_error(request, subsystem,
+ "Unknown scheme %s", scheme);
+ return PASSDB_RESULT_SCHEME_NOT_AVAILABLE;
+ }
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ /* Use original_username since it may be important for some
+ password schemes (eg. digest-md5). Otherwise the username is used
+ only for logging purposes. */
+ ret = password_verify(plain_password, &gen_params,
+ scheme, raw_password, raw_password_size, &error);
+ if (ret < 0) {
+ const char *password_str = request->set->debug_passwords ?
+ t_strdup_printf(" '%s'", crypted_password) : "";
+ auth_request_log_error(request, subsystem,
+ "Invalid password%s in passdb: %s",
+ password_str, error);
+ result = PASSDB_RESULT_INTERNAL_FAILURE;
+ } else if (ret == 0) {
+ if (log_password_mismatch)
+ auth_request_log_password_mismatch(request, subsystem);
+ result = PASSDB_RESULT_PASSWORD_MISMATCH;
+ } else {
+ result = PASSDB_RESULT_OK;
+ }
+ if (ret <= 0 && request->set->debug_passwords) T_BEGIN {
+ log_password_failure(request, plain_password,
+ crypted_password, scheme,
+ &gen_params,
+ subsystem);
+ } T_END;
+ return result;
+}
+
+enum passdb_result auth_request_password_missing(struct auth_request *request)
+{
+ if (request->fields.skip_password_check) {
+ /* This passdb wasn't used for authentication */
+ return PASSDB_RESULT_OK;
+ }
+ e_info(authdb_event(request),
+ "No password returned (and no nopassword)");
+ return PASSDB_RESULT_PASSWORD_MISMATCH;
+}
+
+void auth_request_get_log_prefix(string_t *str, struct auth_request *auth_request,
+ const char *subsystem)
+{
+ const char *name;
+
+ if (subsystem == AUTH_SUBSYS_DB) {
+ if (!auth_request->userdb_lookup) {
+ i_assert(auth_request->passdb != NULL);
+ name = auth_request->passdb->set->name[0] != '\0' ?
+ auth_request->passdb->set->name :
+ auth_request->passdb->passdb->iface.name;
+ } else {
+ i_assert(auth_request->userdb != NULL);
+ name = auth_request->userdb->set->name[0] != '\0' ?
+ auth_request->userdb->set->name :
+ auth_request->userdb->userdb->iface->name;
+ }
+ } else if (subsystem == AUTH_SUBSYS_MECH) {
+ i_assert(auth_request->mech != NULL);
+ name = t_str_lcase(auth_request->mech->mech_name);
+ } else {
+ name = subsystem;
+ }
+ str_append(str, name);
+ str_append_c(str, '(');
+ get_log_identifier(str, auth_request);
+ str_append(str, "): ");
+}
+
+#define MAX_LOG_USERNAME_LEN 64
+static void get_log_identifier(string_t *str, struct auth_request *auth_request)
+{
+ const char *ip;
+
+ if (auth_request->fields.user == NULL)
+ str_append(str, "?");
+ else
+ str_sanitize_append(str, auth_request->fields.user,
+ MAX_LOG_USERNAME_LEN);
+
+ ip = net_ip2addr(&auth_request->fields.remote_ip);
+ if (ip[0] != '\0') {
+ str_append_c(str, ',');
+ str_append(str, ip);
+ }
+ if (auth_request->fields.requested_login_user != NULL)
+ str_append(str, ",master");
+ if (auth_request->fields.session_id != NULL)
+ str_printfa(str, ",<%s>", auth_request->fields.session_id);
+}
+
+void auth_request_log_debug(struct auth_request *auth_request,
+ const char *subsystem,
+ const char *format, ...)
+{
+ struct event *event = get_request_event(auth_request, subsystem);
+ va_list va;
+
+ va_start(va, format);
+ T_BEGIN {
+ string_t *str = t_str_new(128);
+ str_vprintfa(str, format, va);
+ e_debug(event, "%s", str_c(str));
+ } T_END;
+ va_end(va);
+}
+
+void auth_request_log_info(struct auth_request *auth_request,
+ const char *subsystem,
+ const char *format, ...)
+{
+ struct event *event = get_request_event(auth_request, subsystem);
+ va_list va;
+
+ va_start(va, format);
+ T_BEGIN {
+ string_t *str = t_str_new(128);
+ str_vprintfa(str, format, va);
+ e_info(event, "%s", str_c(str));
+ } T_END;
+ va_end(va);
+}
+
+void auth_request_log_warning(struct auth_request *auth_request,
+ const char *subsystem,
+ const char *format, ...)
+{
+ struct event *event = get_request_event(auth_request, subsystem);
+ va_list va;
+
+ va_start(va, format);
+ T_BEGIN {
+ string_t *str = t_str_new(128);
+ str_vprintfa(str, format, va);
+ e_warning(event, "%s", str_c(str));
+ } T_END;
+ va_end(va);
+}
+
+void auth_request_log_error(struct auth_request *auth_request,
+ const char *subsystem,
+ const char *format, ...)
+{
+ struct event *event = get_request_event(auth_request, subsystem);
+ va_list va;
+
+ va_start(va, format);
+ T_BEGIN {
+ string_t *str = t_str_new(128);
+ str_vprintfa(str, format, va);
+ e_error(event, "%s", str_c(str));
+ } T_END;
+ va_end(va);
+}
+
+void auth_request_refresh_last_access(struct auth_request *request)
+{
+ request->last_access = ioloop_time;
+ if (request->to_abort != NULL)
+ timeout_reset(request->to_abort);
+}
diff --git a/src/auth/auth-request.h b/src/auth/auth-request.h
new file mode 100644
index 0000000..22f33a4
--- /dev/null
+++ b/src/auth/auth-request.h
@@ -0,0 +1,400 @@
+#ifndef AUTH_REQUEST_H
+#define AUTH_REQUEST_H
+
+#ifndef AUTH_REQUEST_FIELDS_CONST
+# define AUTH_REQUEST_FIELDS_CONST const
+#endif
+
+#include "array.h"
+#include "net.h"
+#include "var-expand.h"
+#include "mech.h"
+#include "userdb.h"
+#include "passdb.h"
+#include "auth-request-var-expand.h"
+#include "password-scheme.h"
+
+#define AUTH_REQUEST_USER_KEY_IGNORE " "
+
+struct auth_client_connection;
+
+enum auth_request_state {
+ AUTH_REQUEST_STATE_NEW,
+ AUTH_REQUEST_STATE_PASSDB,
+ AUTH_REQUEST_STATE_MECH_CONTINUE,
+ AUTH_REQUEST_STATE_FINISHED,
+ AUTH_REQUEST_STATE_USERDB,
+
+ AUTH_REQUEST_STATE_MAX
+};
+
+enum auth_request_secured {
+ AUTH_REQUEST_SECURED_NONE,
+ AUTH_REQUEST_SECURED,
+ AUTH_REQUEST_SECURED_TLS,
+};
+
+enum auth_request_cache_result {
+ AUTH_REQUEST_CACHE_NONE,
+ AUTH_REQUEST_CACHE_MISS,
+ AUTH_REQUEST_CACHE_HIT,
+};
+
+/* All auth request fields are exported to auth-worker process. */
+struct auth_request_fields {
+ /* user contains the user who is being authenticated.
+ When master user is logging in as someone else, it gets more
+ complicated. Initially user is set to master's username and the
+ requested_login_user is set to destination username. After masterdb
+ has validated user as a valid master user, master_user is set to
+ user and user is set to requested_login_user. */
+ char *user, *requested_login_user, *master_user;
+ /* original_username contains the username exactly as given by the
+ client. this is needed at least with DIGEST-MD5 for password
+ verification. however with master logins the master username has
+ been dropped from it. */
+ const char *original_username;
+ /* the username after doing all internal translations, but before
+ being changed by a db lookup */
+ const char *translated_username;
+ /* realm for the request, may be specified by some auth mechanisms */
+ const char *realm;
+
+ const char *service, *mech_name, *session_id, *local_name, *client_id;
+ struct ip_addr local_ip, remote_ip, real_local_ip, real_remote_ip;
+ in_port_t local_port, remote_port, real_local_port, real_remote_port;
+
+ /* extra_fields are returned in authentication reply. Fields prefixed
+ with "userdb_" are automatically placed to userdb_reply instead. */
+ struct auth_fields *extra_fields;
+ /* the whole userdb result reply */
+ struct auth_fields *userdb_reply;
+
+ /* Credentials from the first successful passdb lookup. These are used
+ as the final credentials, unless overridden by later passdb
+ lookups. Note that the requests in auth-worker processes see these
+ only as 1 byte sized \0 strings. */
+ const unsigned char *delayed_credentials;
+ size_t delayed_credentials_size;
+
+ enum auth_request_secured secured;
+
+ /* Authentication was successfully finished, including policy checks
+ and such. There may still be some final delay or final SASL
+ response. */
+ bool successful:1;
+ /* Password was verified successfully by a passdb. The following
+ passdbs shouldn't attempt to verify the password again. Note that
+ this differs from passdb_success, which may be set to FALSE due to
+ the result_* rules. */
+ bool skip_password_check:1;
+
+ /* flags received from auth client: */
+ bool final_resp_ok:1;
+ bool no_penalty:1;
+ bool valid_client_cert:1;
+ bool cert_username:1;
+};
+
+struct auth_request {
+ int refcount;
+
+ pool_t pool;
+
+ struct event *event;
+ struct event *mech_event;
+ ARRAY(struct event *) authdb_event;
+
+ enum auth_request_state state;
+ char *mech_password; /* set if verify_plain() is called */
+ char *passdb_password; /* set after password lookup if successful */
+ struct auth_request_proxy_dns_lookup_ctx *dns_lookup_ctx;
+ /* The final result of passdb lookup (delayed due to asynchronous
+ proxy DNS lookups) */
+ enum passdb_result passdb_result;
+
+ const struct mech_module *mech;
+ const struct auth_settings *set;
+ struct auth_passdb *passdb;
+ struct auth_userdb *userdb;
+
+ struct stats *stats;
+
+ /* passdb lookups have a handler, userdb lookups don't */
+ struct auth_request_handler *handler;
+ struct auth_master_connection *master;
+
+ /* FIXME: Remove this once mech-oauth2 correctly does the processing */
+ const char *openid_config_url;
+
+ unsigned int connect_uid;
+ unsigned int client_pid;
+ unsigned int id;
+ time_t last_access;
+ time_t delay_until;
+ pid_t session_pid;
+
+ /* These are const for most of the code, so they don't try to modify
+ the fields directly. Only auth-request-fields.c and unit tests have
+ the fields writable. This way it's more difficult to make them
+ out-of-sync with events. */
+ AUTH_REQUEST_FIELDS_CONST struct auth_request_fields fields;
+
+ struct timeout *to_abort, *to_penalty;
+ unsigned int policy_penalty;
+ unsigned int last_penalty;
+ size_t initial_response_len;
+ const unsigned char *initial_response;
+
+ union {
+ verify_plain_callback_t *verify_plain;
+ lookup_credentials_callback_t *lookup_credentials;
+ set_credentials_callback_t *set_credentials;
+ userdb_callback_t *userdb;
+ } private_callback;
+ /* Used by passdb's credentials lookup to determine which scheme is
+ wanted by the caller. For example CRAM-MD5 SASL mechanism wants
+ CRAM-MD5 scheme for passwords.
+
+ When doing a PASS lookup (without authenticating), this is set to ""
+ to imply that caller accepts any kind of credentials. After the
+ credentials lookup is finished, this is set to the scheme that was
+ actually received.
+
+ Otherwise, this is kept as NULL. */
+ const char *wanted_credentials_scheme;
+
+ void *context;
+
+ enum auth_request_cache_result passdb_cache_result;
+ enum auth_request_cache_result userdb_cache_result;
+
+ /* this is a lookup on auth socket (not login socket).
+ skip any proxying stuff if enabled. */
+ bool auth_only:1;
+ /* we're doing a userdb lookup now (we may have done passdb lookup
+ earlier) */
+ bool userdb_lookup:1;
+ /* DIGEST-MD5 kludge */
+ bool domain_is_realm:1;
+
+ bool request_auth_token:1;
+
+ /* success/failure states: */
+ bool failed:1; /* overrides any other success */
+ bool internal_failure:1;
+ bool passdbs_seen_user_unknown:1;
+ bool passdbs_seen_internal_failure:1;
+ bool userdbs_seen_internal_failure:1;
+
+ /* current state: */
+ bool handler_pending_reply:1;
+ bool accept_cont_input:1;
+ bool prefer_plain_credentials:1;
+ bool in_delayed_failure_queue:1;
+ bool removed_from_handler:1;
+ bool snapshot_have_userdb_prefetch_set:1;
+ /* username was changed by this passdb/userdb lookup. Used by
+ auth-workers to determine whether to send back a changed username. */
+ bool user_changed_by_lookup:1;
+ /* each passdb lookup can update the current success-status using the
+ result_* rules. the authentication succeeds only if this is TRUE
+ at the end. mechanisms that don't require passdb, but do a passdb
+ lookup anyway (e.g. GSSAPI) need to set this to TRUE by default. */
+ bool passdb_success:1;
+ /* userdb equivalent of passdb_success */
+ bool userdb_success:1;
+ /* the last userdb lookup failed either due to "tempfail" extra field
+ or because one of the returned uid/gid fields couldn't be translated
+ to a number */
+ bool userdb_lookup_tempfailed:1;
+ /* userdb_* fields have been set by the passdb lookup, userdb prefetch
+ will work. */
+ bool userdb_prefetch_set:1;
+ bool stats_sent:1;
+ bool policy_refusal:1;
+ bool policy_processed:1;
+
+ bool event_finished_sent:1;
+
+ /* ... mechanism specific data ... */
+};
+
+typedef void auth_request_proxy_cb_t(bool success, struct auth_request *);
+
+extern unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX];
+
+extern const char auth_default_subsystems[2];
+#define AUTH_SUBSYS_DB &auth_default_subsystems[0]
+#define AUTH_SUBSYS_MECH &auth_default_subsystems[1]
+
+struct auth_request *
+auth_request_new(const struct mech_module *mech, struct event *parent_event);
+struct auth_request *auth_request_new_dummy(struct event *parent_event);
+void auth_request_init(struct auth_request *request);
+struct auth *auth_request_get_auth(struct auth_request *request);
+
+void auth_request_set_state(struct auth_request *request,
+ enum auth_request_state state);
+
+void auth_request_ref(struct auth_request *request);
+void auth_request_unref(struct auth_request **request);
+
+void auth_request_success(struct auth_request *request,
+ const void *data, size_t data_size);
+void auth_request_fail_with_reply(struct auth_request *request,
+ const void *final_data, size_t final_data_size);
+void auth_request_fail(struct auth_request *request);
+void auth_request_internal_failure(struct auth_request *request);
+
+void auth_request_export(struct auth_request *request, string_t *dest);
+bool auth_request_import(struct auth_request *request,
+ const char *key, const char *value);
+bool auth_request_import_info(struct auth_request *request,
+ const char *key, const char *value);
+bool auth_request_import_auth(struct auth_request *request,
+ const char *key, const char *value);
+bool auth_request_import_master(struct auth_request *request,
+ const char *key, const char *value);
+
+void auth_request_initial(struct auth_request *request);
+void auth_request_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size);
+
+void auth_request_verify_plain(struct auth_request *request,
+ const char *password,
+ verify_plain_callback_t *callback);
+void auth_request_lookup_credentials(struct auth_request *request,
+ const char *scheme,
+ lookup_credentials_callback_t *callback);
+void auth_request_lookup_user(struct auth_request *request,
+ userdb_callback_t *callback);
+
+bool auth_request_set_username(struct auth_request *request,
+ const char *username, const char **error_r);
+/* Change the username without any translations or checks. */
+void auth_request_set_username_forced(struct auth_request *request,
+ const char *username);
+bool auth_request_set_login_username(struct auth_request *request,
+ const char *username,
+ const char **error_r);
+/* Change the login username without any translations or checks. */
+void auth_request_set_login_username_forced(struct auth_request *request,
+ const char *username);
+void auth_request_set_realm(struct auth_request *request, const char *realm);
+/* Request was fully successfully authenticated, including policy checks etc. */
+void auth_request_set_auth_successful(struct auth_request *request);
+/* Password was successfully verified by a passdb. */
+void auth_request_set_password_verified(struct auth_request *request);
+/* Save credentials from a successful passdb lookup. */
+void auth_request_set_delayed_credentials(struct auth_request *request,
+ const unsigned char *credentials,
+ size_t size);
+
+void auth_request_set_field(struct auth_request *request,
+ const char *name, const char *value,
+ const char *default_scheme) ATTR_NULL(4);
+void auth_request_set_null_field(struct auth_request *request, const char *name);
+void auth_request_set_field_keyvalue(struct auth_request *request,
+ const char *field,
+ const char *default_scheme) ATTR_NULL(3);
+void auth_request_set_fields(struct auth_request *request,
+ const char *const *fields,
+ const char *default_scheme) ATTR_NULL(3);
+
+void auth_request_init_userdb_reply(struct auth_request *request,
+ bool add_default_fields);
+void auth_request_set_userdb_field(struct auth_request *request,
+ const char *name, const char *value);
+void auth_request_set_userdb_field_values(struct auth_request *request,
+ const char *name,
+ const char *const *values);
+/* returns -1 = failed, 0 = callback is called later, 1 = finished */
+int auth_request_proxy_finish(struct auth_request *request,
+ auth_request_proxy_cb_t *callback);
+void auth_request_proxy_finish_failure(struct auth_request *request);
+
+void auth_request_log_password_mismatch(struct auth_request *request,
+ const char *subsystem);
+enum passdb_result
+auth_request_password_verify(struct auth_request *request,
+ const char *plain_password,
+ const char *crypted_password,
+ const char *scheme, const char *subsystem)
+ ATTR_WARN_UNUSED_RESULT;
+enum passdb_result
+auth_request_password_verify_log(struct auth_request *request,
+ const char *plain_password,
+ const char *crypted_password,
+ const char *scheme, const char *subsystem,
+ bool log_password_mismatch)
+ ATTR_WARN_UNUSED_RESULT;
+enum passdb_result auth_request_password_missing(struct auth_request *request);
+
+void auth_request_get_log_prefix(string_t *str, struct auth_request *auth_request,
+ const char *subsystem);
+
+void auth_request_log_debug(struct auth_request *auth_request,
+ const char *subsystem,
+ const char *format, ...) ATTR_FORMAT(3, 4);
+void auth_request_log_info(struct auth_request *auth_request,
+ const char *subsystem,
+ const char *format, ...) ATTR_FORMAT(3, 4);
+void auth_request_log_warning(struct auth_request *auth_request,
+ const char *subsystem,
+ const char *format, ...) ATTR_FORMAT(3, 4);
+void auth_request_log_error(struct auth_request *auth_request,
+ const char *subsystem,
+ const char *format, ...) ATTR_FORMAT(3, 4);
+void auth_request_log_unknown_user(struct auth_request *auth_request,
+ const char *subsystem);
+
+void auth_request_log_login_failure(struct auth_request *request,
+ const char *subsystem,
+ const char *message);
+void
+auth_request_verify_plain_callback_finish(enum passdb_result result,
+ struct auth_request *request);
+void auth_request_verify_plain_callback(enum passdb_result result,
+ struct auth_request *request);
+void auth_request_lookup_credentials_callback(enum passdb_result result,
+ const unsigned char *credentials,
+ size_t size,
+ struct auth_request *request);
+void auth_request_set_credentials(struct auth_request *request,
+ const char *scheme, const char *data,
+ set_credentials_callback_t *callback);
+void auth_request_userdb_callback(enum userdb_result result,
+ struct auth_request *request);
+void auth_request_default_verify_plain_continue(struct auth_request *request,
+ verify_plain_callback_t *callback);
+
+void auth_request_refresh_last_access(struct auth_request *request);
+void auth_str_append(string_t *dest, const char *key, const char *value);
+bool auth_request_username_accepted(const char *const *filter, const char *username);
+struct event_passthrough *
+auth_request_finished_event(struct auth_request *request, struct event *event);
+void auth_request_log_finished(struct auth_request *request);
+void auth_request_master_user_login_finish(struct auth_request *request);
+const char *auth_request_get_log_prefix_db(struct auth_request *auth_request);
+void auth_request_fields_init(struct auth_request *request);
+
+void auth_request_passdb_lookup_begin(struct auth_request *request);
+void auth_request_passdb_lookup_end(struct auth_request *request,
+ enum passdb_result result);
+void auth_request_userdb_lookup_begin(struct auth_request *request);
+void auth_request_userdb_lookup_end(struct auth_request *request,
+ enum userdb_result result);
+
+/* Fetches the current authdb event, this is done because
+ some lookups can recurse into new lookups, requiring new event,
+ which will be returned here. */
+static inline struct event *authdb_event(const struct auth_request *request)
+{
+ if (array_count(&request->authdb_event) == 0)
+ return request->event;
+ struct event **e = array_back_modifiable(&request->authdb_event);
+ return *e;
+}
+
+#endif
diff --git a/src/auth/auth-settings.c b/src/auth/auth-settings.c
new file mode 100644
index 0000000..4268b9a
--- /dev/null
+++ b/src/auth/auth-settings.c
@@ -0,0 +1,553 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash-method.h"
+#include "settings-parser.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+#include "service-settings.h"
+#include "auth-settings.h"
+
+#include <stddef.h>
+
+static bool auth_settings_check(void *_set, pool_t pool, const char **error_r);
+static bool auth_passdb_settings_check(void *_set, pool_t pool, const char **error_r);
+static bool auth_userdb_settings_check(void *_set, pool_t pool, const char **error_r);
+
+/* <settings checks> */
+static struct file_listener_settings auth_unix_listeners_array[] = {
+ { "login/login", 0666, "", "" },
+ { "token-login/tokenlogin", 0666, "", "" },
+ { "auth-login", 0600, "$default_internal_user", "" },
+ { "auth-client", 0600, "$default_internal_user", "" },
+ { "auth-userdb", 0666, "$default_internal_user", "" },
+ { "auth-master", 0600, "", "" }
+};
+static struct file_listener_settings *auth_unix_listeners[] = {
+ &auth_unix_listeners_array[0],
+ &auth_unix_listeners_array[1],
+ &auth_unix_listeners_array[2],
+ &auth_unix_listeners_array[3],
+ &auth_unix_listeners_array[4],
+ &auth_unix_listeners_array[5]
+};
+static buffer_t auth_unix_listeners_buf = {
+ { { auth_unix_listeners, sizeof(auth_unix_listeners) } }
+};
+/* </settings checks> */
+
+struct service_settings auth_service_settings = {
+ .name = "auth",
+ .protocol = "",
+ .type = "",
+ .executable = "auth",
+ .user = "$default_internal_user",
+ .group = "",
+ .privileged_group = "",
+ .extra_groups = "",
+ .chroot = "",
+
+ .drop_priv_before_exec = FALSE,
+
+ .process_min_avail = 0,
+ .process_limit = 1,
+ .client_limit = 0,
+ .service_count = 0,
+ .idle_kill = 0,
+ .vsz_limit = UOFF_T_MAX,
+
+ .unix_listeners = { { &auth_unix_listeners_buf,
+ sizeof(auth_unix_listeners[0]) } },
+ .fifo_listeners = ARRAY_INIT,
+ .inet_listeners = ARRAY_INIT,
+
+ .process_limit_1 = TRUE
+};
+
+/* <settings checks> */
+static struct file_listener_settings auth_worker_unix_listeners_array[] = {
+ { "auth-worker", 0600, "$default_internal_user", "" }
+};
+static struct file_listener_settings *auth_worker_unix_listeners[] = {
+ &auth_worker_unix_listeners_array[0]
+};
+static buffer_t auth_worker_unix_listeners_buf = {
+ { { auth_worker_unix_listeners, sizeof(auth_worker_unix_listeners) } }
+};
+/* </settings checks> */
+
+struct service_settings auth_worker_service_settings = {
+ .name = "auth-worker",
+ .protocol = "",
+ .type = "worker",
+ .executable = "auth -w",
+ .user = "",
+ .group = "",
+ .privileged_group = "",
+ .extra_groups = "",
+ .chroot = "",
+
+ .drop_priv_before_exec = FALSE,
+
+ .process_min_avail = 0,
+ .process_limit = 0,
+ .client_limit = 1,
+ .service_count = 0,
+ .idle_kill = 0,
+ .vsz_limit = UOFF_T_MAX,
+
+ .unix_listeners = { { &auth_worker_unix_listeners_buf,
+ sizeof(auth_worker_unix_listeners[0]) } },
+ .fifo_listeners = ARRAY_INIT,
+ .inet_listeners = ARRAY_INIT
+};
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_passdb_settings)
+
+static const struct setting_define auth_passdb_setting_defines[] = {
+ DEF(STR, name),
+ DEF(STR, driver),
+ DEF(STR, args),
+ DEF(STR, default_fields),
+ DEF(STR, override_fields),
+ DEF(STR, mechanisms),
+ DEF(STR, username_filter),
+
+ DEF(ENUM, skip),
+ DEF(ENUM, result_success),
+ DEF(ENUM, result_failure),
+ DEF(ENUM, result_internalfail),
+
+ DEF(BOOL, deny),
+ DEF(BOOL, pass),
+ DEF(BOOL, master),
+ DEF(ENUM, auth_verbose),
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct auth_passdb_settings auth_passdb_default_settings = {
+ .name = "",
+ .driver = "",
+ .args = "",
+ .default_fields = "",
+ .override_fields = "",
+ .mechanisms = "",
+ .username_filter = "",
+
+ .skip = "never:authenticated:unauthenticated",
+ .result_success = "return-ok:return:return-fail:continue:continue-ok:continue-fail",
+ .result_failure = "continue:return:return-ok:return-fail:continue-ok:continue-fail",
+ .result_internalfail = "continue:return:return-ok:return-fail:continue-ok:continue-fail",
+
+ .deny = FALSE,
+ .pass = FALSE,
+ .master = FALSE,
+ .auth_verbose = "default:yes:no"
+};
+
+const struct setting_parser_info auth_passdb_setting_parser_info = {
+ .defines = auth_passdb_setting_defines,
+ .defaults = &auth_passdb_default_settings,
+
+ .type_offset = offsetof(struct auth_passdb_settings, name),
+ .struct_size = sizeof(struct auth_passdb_settings),
+
+ .parent_offset = SIZE_MAX,
+ .parent = &auth_setting_parser_info,
+
+ .check_func = auth_passdb_settings_check
+};
+
+#undef DEF
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_userdb_settings)
+
+static const struct setting_define auth_userdb_setting_defines[] = {
+ DEF(STR, name),
+ DEF(STR, driver),
+ DEF(STR, args),
+ DEF(STR, default_fields),
+ DEF(STR, override_fields),
+
+ DEF(ENUM, skip),
+ DEF(ENUM, result_success),
+ DEF(ENUM, result_failure),
+ DEF(ENUM, result_internalfail),
+
+ DEF(ENUM, auth_verbose),
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct auth_userdb_settings auth_userdb_default_settings = {
+ /* NOTE: when adding fields, update also auth.c:userdb_dummy_set */
+ .name = "",
+ .driver = "",
+ .args = "",
+ .default_fields = "",
+ .override_fields = "",
+
+ .skip = "never:found:notfound",
+ .result_success = "return-ok:return:return-fail:continue:continue-ok:continue-fail",
+ .result_failure = "continue:return:return-ok:return-fail:continue-ok:continue-fail",
+ .result_internalfail = "continue:return:return-ok:return-fail:continue-ok:continue-fail",
+
+ .auth_verbose = "default:yes:no"
+};
+
+const struct setting_parser_info auth_userdb_setting_parser_info = {
+ .defines = auth_userdb_setting_defines,
+ .defaults = &auth_userdb_default_settings,
+
+ .type_offset = offsetof(struct auth_userdb_settings, name),
+ .struct_size = sizeof(struct auth_userdb_settings),
+
+ .parent_offset = SIZE_MAX,
+ .parent = &auth_setting_parser_info,
+
+ .check_func = auth_userdb_settings_check
+};
+
+/* we're kind of kludging here to avoid "auth_" prefix in the struct fields */
+#undef DEF
+#undef DEF_NOPREFIX
+#undef DEFLIST
+#define DEF(type, name) \
+ SETTING_DEFINE_STRUCT_##type("auth_"#name, name, struct auth_settings)
+#define DEF_NOPREFIX(type, name) \
+ SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_settings)
+#define DEFLIST(field, name, defines) \
+ { .type = SET_DEFLIST, .key = name, \
+ .offset = offsetof(struct auth_settings, field), \
+ .list_info = defines }
+
+static const struct setting_define auth_setting_defines[] = {
+ DEF(STR, mechanisms),
+ DEF(STR, realms),
+ DEF(STR, default_realm),
+ DEF(SIZE, cache_size),
+ DEF(TIME, cache_ttl),
+ DEF(TIME, cache_negative_ttl),
+ DEF(BOOL, cache_verify_password_with_worker),
+ DEF(STR, username_chars),
+ DEF(STR, username_translation),
+ DEF(STR, username_format),
+ DEF(STR, master_user_separator),
+ DEF(STR, anonymous_username),
+ DEF(STR, krb5_keytab),
+ DEF(STR, gssapi_hostname),
+ DEF(STR, winbind_helper_path),
+ DEF(STR, proxy_self),
+ DEF(TIME, failure_delay),
+
+ DEF(STR, policy_server_url),
+ DEF(STR, policy_server_api_header),
+ DEF(UINT, policy_server_timeout_msecs),
+ DEF(STR, policy_hash_mech),
+ DEF(STR, policy_hash_nonce),
+ DEF(STR, policy_request_attributes),
+ DEF(BOOL, policy_reject_on_fail),
+ DEF(BOOL, policy_check_before_auth),
+ DEF(BOOL, policy_check_after_auth),
+ DEF(BOOL, policy_report_after_auth),
+ DEF(BOOL, policy_log_only),
+ DEF(UINT, policy_hash_truncate),
+
+ DEF(BOOL, stats),
+ DEF(BOOL, verbose),
+ DEF(BOOL, debug),
+ DEF(BOOL, debug_passwords),
+ DEF(STR, verbose_passwords),
+ DEF(BOOL, ssl_require_client_cert),
+ DEF(BOOL, ssl_username_from_cert),
+ DEF(BOOL, use_winbind),
+
+ DEF(UINT, worker_max_count),
+
+ DEFLIST(passdbs, "passdb", &auth_passdb_setting_parser_info),
+ DEFLIST(userdbs, "userdb", &auth_userdb_setting_parser_info),
+
+ DEF_NOPREFIX(STR, base_dir),
+ DEF_NOPREFIX(BOOL, verbose_proctitle),
+ DEF_NOPREFIX(UINT, first_valid_uid),
+ DEF_NOPREFIX(UINT, last_valid_uid),
+ DEF_NOPREFIX(UINT, first_valid_gid),
+ DEF_NOPREFIX(UINT, last_valid_gid),
+
+ DEF_NOPREFIX(STR, ssl_client_ca_dir),
+ DEF_NOPREFIX(STR, ssl_client_ca_file),
+
+ SETTING_DEFINE_LIST_END
+};
+
+static const struct auth_settings auth_default_settings = {
+ .mechanisms = "plain",
+ .realms = "",
+ .default_realm = "",
+ .cache_size = 0,
+ .cache_ttl = 60*60,
+ .cache_negative_ttl = 60*60,
+ .cache_verify_password_with_worker = FALSE,
+ .username_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@",
+ .username_translation = "",
+ .username_format = "%Lu",
+ .master_user_separator = "",
+ .anonymous_username = "anonymous",
+ .krb5_keytab = "",
+ .gssapi_hostname = "",
+ .winbind_helper_path = "/usr/bin/ntlm_auth",
+ .proxy_self = "",
+ .failure_delay = 2,
+
+ .policy_server_url = "",
+ .policy_server_api_header = "",
+ .policy_server_timeout_msecs = 2000,
+ .policy_hash_mech = "sha256",
+ .policy_hash_nonce = "",
+ .policy_request_attributes = "login=%{requested_username} pwhash=%{hashed_password} remote=%{rip} device_id=%{client_id} protocol=%s session_id=%{session}",
+ .policy_reject_on_fail = FALSE,
+ .policy_check_before_auth = TRUE,
+ .policy_check_after_auth = TRUE,
+ .policy_report_after_auth = TRUE,
+ .policy_log_only = FALSE,
+ .policy_hash_truncate = 12,
+
+ .stats = FALSE,
+ .verbose = FALSE,
+ .debug = FALSE,
+ .debug_passwords = FALSE,
+ .verbose_passwords = "no",
+ .ssl_require_client_cert = FALSE,
+ .ssl_username_from_cert = FALSE,
+ .ssl_client_ca_dir = "",
+ .ssl_client_ca_file = "",
+
+ .use_winbind = FALSE,
+
+ .worker_max_count = 30,
+
+ .passdbs = ARRAY_INIT,
+ .userdbs = ARRAY_INIT,
+
+ .base_dir = PKG_RUNDIR,
+ .verbose_proctitle = FALSE,
+ .first_valid_uid = 500,
+ .last_valid_uid = 0,
+ .first_valid_gid = 1,
+ .last_valid_gid = 0,
+};
+
+const struct setting_parser_info auth_setting_parser_info = {
+ .module_name = "auth",
+ .defines = auth_setting_defines,
+ .defaults = &auth_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct auth_settings),
+
+ .parent_offset = SIZE_MAX,
+
+ .check_func = auth_settings_check
+};
+
+/* <settings checks> */
+static bool
+auth_settings_set_self_ips(struct auth_settings *set, pool_t pool,
+ const char **error_r)
+{
+ const char *const *tmp;
+ ARRAY(struct ip_addr) ips_array;
+ struct ip_addr *ips;
+ unsigned int ips_count;
+ int ret;
+
+ if (*set->proxy_self == '\0') {
+ set->proxy_self_ips = p_new(pool, struct ip_addr, 1);
+ return TRUE;
+ }
+
+ p_array_init(&ips_array, pool, 4);
+ tmp = t_strsplit_spaces(set->proxy_self, " ");
+ for (; *tmp != NULL; tmp++) {
+ ret = net_gethostbyname(*tmp, &ips, &ips_count);
+ if (ret != 0) {
+ *error_r = t_strdup_printf("auth_proxy_self_ips: "
+ "gethostbyname(%s) failed: %s",
+ *tmp, net_gethosterror(ret));
+ }
+ array_append(&ips_array, ips, ips_count);
+ }
+ array_append_zero(&ips_array);
+ set->proxy_self_ips = array_front(&ips_array);
+ return TRUE;
+}
+
+static bool
+auth_verify_verbose_password(struct auth_settings *set,
+ const char **error_r)
+{
+ const char *p, *value = set->verbose_passwords;
+ unsigned int num;
+
+ p = strchr(value, ':');
+ if (p != NULL) {
+ if (str_to_uint(p+1, &num) < 0 || num == 0) {
+ *error_r = t_strdup_printf("auth_verbose_passwords: "
+ "Invalid truncation number: '%s'", p+1);
+ return FALSE;
+ }
+ value = t_strdup_until(value, p);
+ }
+ if (strcmp(value, "no") == 0)
+ return TRUE;
+ else if (strcmp(value, "plain") == 0)
+ return TRUE;
+ else if (strcmp(value, "sha1") == 0)
+ return TRUE;
+ else if (strcmp(value, "yes") == 0) {
+ /* just use it as alias for "plain" */
+ set->verbose_passwords = "plain";
+ return TRUE;
+ } else {
+ *error_r = "auth_verbose_passwords: Invalid value";
+ return FALSE;
+ }
+}
+
+static bool auth_settings_check(void *_set, pool_t pool,
+ const char **error_r)
+{
+ struct auth_settings *set = _set;
+ const char *p;
+
+ if (set->debug_passwords)
+ set->debug = TRUE;
+ if (set->debug)
+ set->verbose = TRUE;
+
+ if (set->worker_max_count == 0) {
+ *error_r = "auth_worker_max_count must be above zero";
+ return FALSE;
+ }
+
+ if (set->cache_size > 0 && set->cache_size < 1024) {
+ /* probably a configuration error.
+ older versions used megabyte numbers */
+ *error_r = t_strdup_printf("auth_cache_size value is too small "
+ "(%"PRIuUOFF_T" bytes)",
+ set->cache_size);
+ return FALSE;
+ }
+
+ if (!auth_verify_verbose_password(set, error_r))
+ return FALSE;
+
+ if (*set->username_chars == '\0') {
+ /* all chars are allowed */
+ memset(set->username_chars_map, 1,
+ sizeof(set->username_chars_map));
+ } else {
+ for (p = set->username_chars; *p != '\0'; p++)
+ set->username_chars_map[(int)(uint8_t)*p] = 1;
+ }
+
+ if (*set->username_translation != '\0') {
+ p = set->username_translation;
+ for (; *p != '\0' && p[1] != '\0'; p += 2)
+ set->username_translation_map[(int)(uint8_t)*p] = p[1];
+ }
+ set->realms_arr =
+ (const char *const *)p_strsplit_spaces(pool, set->realms, " ");
+
+ if (*set->policy_server_url != '\0') {
+ if (*set->policy_hash_nonce == '\0') {
+
+ *error_r = "auth_policy_hash_nonce must be set when policy server is used";
+ return FALSE;
+ }
+ const struct hash_method *digest = hash_method_lookup(set->policy_hash_mech);
+ if (digest == NULL) {
+ *error_r = "invalid auth_policy_hash_mech given";
+ return FALSE;
+ }
+ if (set->policy_hash_truncate > 0 && set->policy_hash_truncate >= digest->digest_size*8) {
+ *error_r = t_strdup_printf("policy_hash_truncate is not smaller than digest size (%u >= %u)",
+ set->policy_hash_truncate,
+ digest->digest_size*8);
+ return FALSE;
+ }
+ }
+
+ if (!auth_settings_set_self_ips(set, pool, error_r))
+ return FALSE;
+ return TRUE;
+}
+
+static bool
+auth_passdb_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+ const char **error_r)
+{
+ struct auth_passdb_settings *set = _set;
+
+ if (set->driver == NULL || *set->driver == '\0') {
+ *error_r = "passdb is missing driver";
+ return FALSE;
+ }
+ if (set->pass && strcmp(set->result_success, "return-ok") != 0) {
+ *error_r = "Obsolete pass=yes setting mixed with non-default result_success";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+auth_userdb_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+ const char **error_r)
+{
+ struct auth_userdb_settings *set = _set;
+
+ if (set->driver == NULL || *set->driver == '\0') {
+ *error_r = "userdb is missing driver";
+ return FALSE;
+ }
+ return TRUE;
+}
+/* </settings checks> */
+
+struct auth_settings *global_auth_settings;
+
+struct auth_settings *
+auth_settings_read(const char *service, pool_t pool,
+ struct master_service_settings_output *output_r)
+{
+ static const struct setting_parser_info *set_roots[] = {
+ &auth_setting_parser_info,
+ NULL
+ };
+ struct master_service_settings_input input;
+ struct setting_parser_context *set_parser;
+ const char *error;
+ void **sets;
+
+ i_zero(&input);
+ input.roots = set_roots;
+ input.module = "auth";
+ input.service = service;
+ if (master_service_settings_read(master_service, &input,
+ output_r, &error) < 0)
+ i_fatal("Error reading configuration: %s", error);
+
+ pool_ref(pool);
+ set_parser = settings_parser_dup(master_service->set_parser, pool);
+ if (!settings_parser_check(set_parser, pool, &error))
+ i_unreached();
+
+ sets = master_service_settings_parser_get_others(master_service,
+ set_parser);
+ settings_parser_deinit(&set_parser);
+ return sets[0];
+}
diff --git a/src/auth/auth-settings.h b/src/auth/auth-settings.h
new file mode 100644
index 0000000..10ac379
--- /dev/null
+++ b/src/auth/auth-settings.h
@@ -0,0 +1,110 @@
+#ifndef AUTH_SETTINGS_H
+#define AUTH_SETTINGS_H
+
+struct master_service;
+struct master_service_settings_output;
+
+struct auth_passdb_settings {
+ const char *name;
+ const char *driver;
+ const char *args;
+ const char *default_fields;
+ const char *override_fields;
+ const char *mechanisms;
+ const char *username_filter;
+
+ const char *skip;
+ const char *result_success;
+ const char *result_failure;
+ const char *result_internalfail;
+ bool deny;
+ bool pass; /* deprecated, use result_success=continue instead */
+ bool master;
+ const char *auth_verbose;
+};
+
+struct auth_userdb_settings {
+ const char *name;
+ const char *driver;
+ const char *args;
+ const char *default_fields;
+ const char *override_fields;
+
+ const char *skip;
+ const char *result_success;
+ const char *result_failure;
+ const char *result_internalfail;
+ const char *auth_verbose;
+};
+
+struct auth_settings {
+ const char *mechanisms;
+ const char *realms;
+ const char *default_realm;
+ uoff_t cache_size;
+ unsigned int cache_ttl;
+ unsigned int cache_negative_ttl;
+ bool cache_verify_password_with_worker;
+ const char *username_chars;
+ const char *username_translation;
+ const char *username_format;
+ const char *master_user_separator;
+ const char *anonymous_username;
+ const char *krb5_keytab;
+ const char *gssapi_hostname;
+ const char *winbind_helper_path;
+ const char *proxy_self;
+ unsigned int failure_delay;
+
+ const char *policy_server_url;
+ const char *policy_server_api_header;
+ unsigned int policy_server_timeout_msecs;
+ const char *policy_hash_mech;
+ const char *policy_hash_nonce;
+ const char *policy_request_attributes;
+ bool policy_reject_on_fail;
+ bool policy_check_before_auth;
+ bool policy_check_after_auth;
+ bool policy_report_after_auth;
+ bool policy_log_only;
+ unsigned int policy_hash_truncate;
+
+ bool stats;
+ bool verbose, debug, debug_passwords;
+ const char *verbose_passwords;
+ bool ssl_require_client_cert;
+ bool ssl_username_from_cert;
+ bool use_winbind;
+
+ unsigned int worker_max_count;
+
+ /* settings that don't have auth_ prefix: */
+ ARRAY(struct auth_passdb_settings *) passdbs;
+ ARRAY(struct auth_userdb_settings *) userdbs;
+
+ const char *base_dir;
+ const char *ssl_client_ca_dir;
+ const char *ssl_client_ca_file;
+
+ bool verbose_proctitle;
+ unsigned int first_valid_uid;
+ unsigned int last_valid_uid;
+ unsigned int first_valid_gid;
+ unsigned int last_valid_gid;
+
+ /* generated: */
+ char username_chars_map[256];
+ char username_translation_map[256];
+ const char *const *realms_arr;
+ const struct ip_addr *proxy_self_ips;
+};
+
+extern const struct setting_parser_info auth_setting_parser_info;
+extern struct auth_settings *global_auth_settings;
+
+struct auth_settings *
+auth_settings_read(const char *service, pool_t pool,
+ struct master_service_settings_output *output_r)
+ ATTR_NULL(1);
+
+#endif
diff --git a/src/auth/auth-stats.c b/src/auth/auth-stats.c
new file mode 100644
index 0000000..71264a4
--- /dev/null
+++ b/src/auth/auth-stats.c
@@ -0,0 +1,116 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "stats.h"
+#include "stats-parser.h"
+#include "auth-stats.h"
+
+static struct stats_parser_field auth_stats_fields[] = {
+#define E(parsename, name, type) { parsename, offsetof(struct auth_stats, name), sizeof(((struct auth_stats *)0)->name), type }
+#define EN(parsename, name) E(parsename, name, STATS_PARSER_TYPE_UINT)
+ EN("auth_successes", auth_success_count),
+ EN("auth_master_successes", auth_master_success_count),
+ EN("auth_failures", auth_failure_count),
+ EN("auth_db_tempfails", auth_db_tempfail_count),
+
+ EN("auth_cache_hits", auth_cache_hit_count),
+ EN("auth_cache_misses", auth_cache_miss_count)
+};
+
+static size_t auth_stats_alloc_size(void)
+{
+ return sizeof(struct auth_stats);
+}
+
+static unsigned int auth_stats_field_count(void)
+{
+ return N_ELEMENTS(auth_stats_fields);
+}
+
+static const char *auth_stats_field_name(unsigned int n)
+{
+ i_assert(n < N_ELEMENTS(auth_stats_fields));
+
+ return auth_stats_fields[n].name;
+}
+
+static void
+auth_stats_field_value(string_t *str, const struct stats *stats,
+ unsigned int n)
+{
+ i_assert(n < N_ELEMENTS(auth_stats_fields));
+
+ stats_parser_value(str, &auth_stats_fields[n], stats);
+}
+
+static bool
+auth_stats_diff(const struct stats *stats1, const struct stats *stats2,
+ struct stats *diff_stats_r, const char **error_r)
+{
+ return stats_parser_diff(auth_stats_fields, N_ELEMENTS(auth_stats_fields),
+ stats1, stats2, diff_stats_r, error_r);
+}
+
+static void auth_stats_add(struct stats *dest, const struct stats *src)
+{
+ stats_parser_add(auth_stats_fields, N_ELEMENTS(auth_stats_fields),
+ dest, src);
+}
+
+static bool
+auth_stats_have_changed(const struct stats *_prev, const struct stats *_cur)
+{
+ return memcmp(_prev, _cur, sizeof(struct auth_stats)) != 0;
+}
+
+static void auth_stats_export(buffer_t *buf, const struct stats *_stats)
+{
+ const struct auth_stats *stats = (const struct auth_stats *)_stats;
+
+ buffer_append(buf, stats, sizeof(*stats));
+}
+
+static bool
+auth_stats_import(const unsigned char *data, size_t size, size_t *pos_r,
+ struct stats *_stats, const char **error_r)
+{
+ struct auth_stats *stats = (struct auth_stats *)_stats;
+
+ if (size < sizeof(*stats)) {
+ *error_r = "auth_stats too small";
+ return FALSE;
+ }
+ memcpy(stats, data, sizeof(*stats));
+ *pos_r = sizeof(*stats);
+ return TRUE;
+}
+
+const struct stats_vfuncs auth_stats_vfuncs = {
+ "auth",
+ auth_stats_alloc_size,
+ auth_stats_field_count,
+ auth_stats_field_name,
+ auth_stats_field_value,
+ auth_stats_diff,
+ auth_stats_add,
+ auth_stats_have_changed,
+ auth_stats_export,
+ auth_stats_import
+};
+
+/* for the stats_auth plugin: */
+void stats_auth_init(void);
+void stats_auth_deinit(void);
+
+static struct stats_item *auth_stats_item;
+
+void stats_auth_init(void)
+{
+ auth_stats_item = stats_register(&auth_stats_vfuncs);
+}
+
+void stats_auth_deinit(void)
+{
+ stats_unregister(&auth_stats_item);
+}
diff --git a/src/auth/auth-stats.h b/src/auth/auth-stats.h
new file mode 100644
index 0000000..a3431bd
--- /dev/null
+++ b/src/auth/auth-stats.h
@@ -0,0 +1,16 @@
+#ifndef AUTH_STATS_H
+#define AUTH_STATS_H
+
+struct auth_stats {
+ uint32_t auth_success_count;
+ uint32_t auth_master_success_count;
+ uint32_t auth_failure_count;
+ uint32_t auth_db_tempfail_count;
+
+ uint32_t auth_cache_hit_count;
+ uint32_t auth_cache_miss_count;
+};
+
+extern const struct stats_vfuncs auth_stats_vfuncs;
+
+#endif
diff --git a/src/auth/auth-token.c b/src/auth/auth-token.c
new file mode 100644
index 0000000..74a1020
--- /dev/null
+++ b/src/auth/auth-token.c
@@ -0,0 +1,177 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+/* Auth process maintains a random secret. Once a user authenticates the
+ response to the REQUEST command from a master service is augmented with an
+ auth_token value. This token is the SHA1 hash of the secret, the service
+ name and the username of the user that just logged in. Using this token the
+ service (e.g. imap) can login to another service (e.g. imap-urlauth) to
+ gain access to resources that require additional privileges (e.g. another
+ user's e-mail).
+*/
+
+#include "auth-common.h"
+#include "hex-binary.h"
+#include "hmac.h"
+#include "sha1.h"
+#include "randgen.h"
+#include "read-full.h"
+#include "write-full.h"
+#include "safe-memset.h"
+#include "auth-settings.h"
+#include "auth-token.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define AUTH_TOKEN_SECRET_LEN 32
+
+#define AUTH_TOKEN_SECRET_FNAME "auth-token-secret.dat"
+
+static unsigned char auth_token_secret[AUTH_TOKEN_SECRET_LEN];
+
+static int
+auth_token_read_secret(const char *path,
+ unsigned char secret_r[AUTH_TOKEN_SECRET_LEN])
+{
+ struct stat st, lst;
+ int fd, ret;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ if (errno != ENOENT)
+ i_error("open(%s) failed: %m", path);
+ return -1;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ i_error("fstat(%s) failed: %m", path);
+ i_close_fd(&fd);
+ return -1;
+ }
+
+ /* check secret len and file type */
+ if (st.st_size != AUTH_TOKEN_SECRET_LEN || !S_ISREG(st.st_mode)) {
+ i_error("Corrupted token secret file: %s", path);
+ i_close_fd(&fd);
+ i_unlink(path);
+ return -1;
+ }
+
+ /* verify that we're not dealing with a symbolic link */
+ if (lstat(path, &lst) < 0) {
+ i_error("lstat(%s) failed: %m", path);
+ i_close_fd(&fd);
+ return -1;
+ }
+
+ /* check security parameters for compromise */
+ if ((st.st_mode & 07777) != 0600 ||
+ st.st_uid != geteuid() || st.st_nlink > 1 ||
+ !S_ISREG(lst.st_mode) || st.st_ino != lst.st_ino ||
+ !CMP_DEV_T(st.st_dev, lst.st_dev)) {
+ i_error("Compromised token secret file: %s", path);
+ i_close_fd(&fd);
+ i_unlink(path);
+ return -1;
+ }
+
+ /* FIXME: fail here to generate new secret if stored one is too old */
+
+ ret = read_full(fd, secret_r, AUTH_TOKEN_SECRET_LEN);
+ if (ret < 0)
+ i_error("read(%s) failed: %m", path);
+ else if (ret == 0) {
+ i_error("Token secret file unexpectedly shrank: %s", path);
+ ret = -1;
+ }
+ if (close(fd) < 0)
+ i_error("close(%s) failed: %m", path);
+
+ e_debug(auth_event, "Read auth token secret from %s", path);
+ return ret;
+}
+
+static int
+auth_token_write_secret(const char *path,
+ const unsigned char secret[AUTH_TOKEN_SECRET_LEN])
+{
+ const char *temp_path;
+ mode_t old_mask;
+ int fd, ret;
+
+ temp_path = t_strconcat(path, ".tmp", NULL);
+
+ old_mask = umask(0);
+ fd = open(temp_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ umask(old_mask);
+
+ if (fd == -1) {
+ i_error("open(%s) failed: %m", temp_path);
+ return -1;
+ }
+
+ ret = write_full(fd, secret, AUTH_TOKEN_SECRET_LEN);
+ if (ret < 0)
+ i_error("write(%s) failed: %m", temp_path);
+ if (close(fd) < 0) {
+ i_error("close(%s) failed: %m", temp_path);
+ ret = -1;
+ }
+
+ if (ret < 0) {
+ i_unlink(temp_path);
+ return -1;
+ }
+
+ if (rename(temp_path, path) < 0) {
+ i_error("rename(%s, %s) failed: %m", temp_path, path);
+ i_unlink(temp_path);
+ return -1;
+ }
+
+ e_debug(auth_event, "Wrote new auth token secret to %s", path);
+ return 0;
+}
+
+void auth_token_init(void)
+{
+ const char *secret_path =
+ t_strconcat(global_auth_settings->base_dir, "/",
+ AUTH_TOKEN_SECRET_FNAME, NULL);
+
+ if (auth_token_read_secret(secret_path, auth_token_secret) < 0) {
+ random_fill(auth_token_secret, sizeof(auth_token_secret));
+
+ if (auth_token_write_secret(secret_path, auth_token_secret) < 0) {
+ i_error("Failed to write auth token secret file; "
+ "returned tokens will be invalid once auth restarts");
+ }
+ }
+}
+
+void auth_token_deinit(void)
+{
+ /* not very useful, but we do it anyway */
+ safe_memset(auth_token_secret, 0, sizeof(auth_token_secret));
+}
+
+const char *auth_token_get(const char *service, const char *session_pid,
+ const char *username, const char *session_id)
+{
+ struct hmac_context ctx;
+ unsigned char result[SHA1_RESULTLEN];
+
+ hmac_init(&ctx, (const unsigned char*)username, strlen(username),
+ &hash_method_sha1);
+ hmac_update(&ctx, session_pid, strlen(session_pid));
+ if (session_id != NULL && *session_id != '\0')
+ hmac_update(&ctx, session_id, strlen(session_id));
+ hmac_update(&ctx, service, strlen(service));
+ hmac_update(&ctx, auth_token_secret, sizeof(auth_token_secret));
+ hmac_final(&ctx, result);
+
+ return binary_to_hex(result, sizeof(result));
+}
diff --git a/src/auth/auth-token.h b/src/auth/auth-token.h
new file mode 100644
index 0000000..3e427c0
--- /dev/null
+++ b/src/auth/auth-token.h
@@ -0,0 +1,11 @@
+#ifndef AUTH_TOKEN_H
+#define AUTH_TOKEN_H
+
+void auth_token_init(void);
+void auth_token_deinit(void);
+
+const char *auth_token_get(const char *service, const char *session_pid,
+ const char *username, const char *session_id);
+
+#endif
+
diff --git a/src/auth/auth-worker-client.c b/src/auth/auth-worker-client.c
new file mode 100644
index 0000000..5405144
--- /dev/null
+++ b/src/auth/auth-worker-client.c
@@ -0,0 +1,972 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "base64.h"
+#include "connection.h"
+#include "ioloop.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "hex-binary.h"
+#include "str.h"
+#include "strescape.h"
+#include "process-title.h"
+#include "master-service.h"
+#include "auth-request.h"
+#include "auth-worker-client.h"
+
+
+#define AUTH_WORKER_WARN_DISCONNECTED_LONG_CMD_SECS 30
+#define OUTBUF_THROTTLE_SIZE (1024*10)
+
+#define CLIENT_STATE_HANDSHAKE "handshaking"
+#define CLIENT_STATE_ITER "iterating users"
+#define CLIENT_STATE_IDLE "idling"
+#define CLIENT_STATE_STOP "waiting for shutdown"
+
+static unsigned int auth_worker_max_service_count = 0;
+static unsigned int auth_worker_service_count = 0;
+
+struct auth_worker_client {
+ struct connection conn;
+ int refcount;
+
+ struct auth *auth;
+ struct event *event;
+ time_t cmd_start;
+
+ bool error_sent:1;
+ bool destroyed:1;
+};
+
+struct auth_worker_command {
+ struct auth_worker_client *client;
+ struct event *event;
+};
+
+struct auth_worker_list_context {
+ struct auth_worker_command *cmd;
+ struct auth_worker_client *client;
+ struct auth_request *auth_request;
+ struct userdb_iterate_context *iter;
+ bool sending, sent, done;
+};
+
+static struct connection_list *clients = NULL;
+static bool auth_worker_client_error = FALSE;
+
+static int auth_worker_output(struct auth_worker_client *client);
+static void auth_worker_client_destroy(struct connection *conn);
+static void auth_worker_client_unref(struct auth_worker_client **_client);
+
+void auth_worker_set_max_service_count(unsigned int count)
+{
+ auth_worker_max_service_count = count;
+}
+
+static struct auth_worker_client *auth_worker_get_client(void)
+{
+ if (!auth_worker_has_client())
+ return NULL;
+ struct auth_worker_client *client =
+ container_of(clients->connections, struct auth_worker_client, conn);
+ return client;
+}
+
+void auth_worker_refresh_proctitle(const char *state)
+{
+ if (!global_auth_settings->verbose_proctitle || !worker)
+ return;
+
+ if (auth_worker_client_error)
+ state = "error";
+ else if (!auth_worker_has_client())
+ state = "waiting for connection";
+ process_title_set(t_strdup_printf("worker: %s", state));
+}
+
+static void
+auth_worker_client_check_throttle(struct auth_worker_client *client)
+{
+ if (o_stream_get_buffer_used_size(client->conn.output) >=
+ OUTBUF_THROTTLE_SIZE) {
+ /* stop reading new requests until client has read the pending
+ replies. */
+ connection_input_halt(&client->conn);
+ }
+}
+
+static void
+auth_worker_request_finished_full(struct auth_worker_command *cmd,
+ const char *error, bool log_as_error)
+{
+ event_set_name(cmd->event, "auth_worker_request_finished");
+ if (error != NULL) {
+ event_add_str(cmd->event, "error", error);
+ if (log_as_error)
+ e_error(cmd->event, "Finished: %s", error);
+ else
+ e_debug(cmd->event, "Finished: %s", error);
+ } else {
+ e_debug(cmd->event, "Finished");
+ }
+ auth_worker_client_check_throttle(cmd->client);
+ auth_worker_client_unref(&cmd->client);
+ event_unref(&cmd->event);
+ i_free(cmd);
+
+ auth_worker_refresh_proctitle(CLIENT_STATE_IDLE);
+}
+
+static void auth_worker_request_finished(struct auth_worker_command *cmd,
+ const char *error)
+{
+ auth_worker_request_finished_full(cmd, error, FALSE);
+}
+
+static void auth_worker_request_finished_bug(struct auth_worker_command *cmd,
+ const char *error)
+{
+ auth_worker_request_finished_full(cmd, error, TRUE);
+}
+
+bool auth_worker_auth_request_new(struct auth_worker_command *cmd, unsigned int id,
+ const char *const *args, struct auth_request **request_r)
+{
+ struct auth_request *auth_request;
+ const char *key, *value;
+
+ auth_request = auth_request_new_dummy(cmd->event);
+
+ cmd->client->refcount++;
+ auth_request->context = cmd;
+ auth_request->id = id;
+
+ for (; *args != NULL; args++) {
+ value = strchr(*args, '=');
+ if (value == NULL)
+ (void)auth_request_import(auth_request, *args, "");
+ else {
+ key = t_strdup_until(*args, value++);
+ (void)auth_request_import(auth_request, key, value);
+ }
+ }
+ if (auth_request->fields.user == NULL ||
+ auth_request->fields.service == NULL) {
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+
+ /* reset changed-fields, so we'll export only the ones that were
+ changed by this lookup. */
+ auth_fields_snapshot(auth_request->fields.extra_fields);
+ if (auth_request->fields.userdb_reply != NULL)
+ auth_fields_snapshot(auth_request->fields.userdb_reply);
+
+ auth_request_init(auth_request);
+ *request_r = auth_request;
+
+ return TRUE;
+}
+
+static void auth_worker_send_reply(struct auth_worker_client *client,
+ struct auth_request *request,
+ string_t *str)
+{
+ time_t cmd_duration = time(NULL) - client->cmd_start;
+ const char *p;
+
+ if (worker_restart_request)
+ o_stream_nsend_str(client->conn.output, "RESTART\n");
+ o_stream_nsend(client->conn.output, str_data(str), str_len(str));
+ if (o_stream_flush(client->conn.output) < 0 && request != NULL &&
+ cmd_duration > AUTH_WORKER_WARN_DISCONNECTED_LONG_CMD_SECS) {
+ p = i_strchr_to_next(str_c(str), '\t');
+ p = p == NULL ? "BUG" : t_strcut(p, '\t');
+
+ e_warning(client->conn.event, "Auth master disconnected us while handling "
+ "request for %s for %ld secs (result=%s)",
+ request->fields.user, (long)cmd_duration, p);
+ }
+}
+
+static void
+reply_append_extra_fields(string_t *str, struct auth_request *request)
+{
+ if (!auth_fields_is_empty(request->fields.extra_fields)) {
+ str_append_c(str, '\t');
+ /* export only the fields changed by this lookup, so the
+ changed-flag gets preserved correctly on the master side as
+ well. */
+ auth_fields_append(request->fields.extra_fields, str,
+ AUTH_FIELD_FLAG_CHANGED,
+ AUTH_FIELD_FLAG_CHANGED);
+ }
+ if (request->fields.userdb_reply != NULL &&
+ auth_fields_is_empty(request->fields.userdb_reply)) {
+ /* all userdb_* fields had NULL values. we'll still
+ need to tell this to the master */
+ str_append(str, "\tuserdb_"AUTH_REQUEST_USER_KEY_IGNORE);
+ }
+}
+
+static void verify_plain_callback(enum passdb_result result,
+ struct auth_request *request)
+{
+ struct auth_worker_command *cmd = request->context;
+ struct auth_worker_client *client = cmd->client;
+ const char *error = NULL;
+ string_t *str;
+
+ if (request->failed && result == PASSDB_RESULT_OK)
+ result = PASSDB_RESULT_PASSWORD_MISMATCH;
+
+ str = t_str_new(128);
+ str_printfa(str, "%u\t", request->id);
+
+ if (result == PASSDB_RESULT_OK)
+ if (auth_fields_exists(request->fields.extra_fields, "noauthenticate"))
+ str_append(str, "NEXT");
+ else
+ str_append(str, "OK");
+ else {
+ str_printfa(str, "FAIL\t%d", result);
+ error = passdb_result_to_string(result);
+ }
+ if (result != PASSDB_RESULT_INTERNAL_FAILURE) {
+ str_append_c(str, '\t');
+ if (request->user_changed_by_lookup)
+ str_append_tabescaped(str, request->fields.user);
+ str_append_c(str, '\t');
+ if (request->passdb_password != NULL)
+ str_append_tabescaped(str, request->passdb_password);
+ reply_append_extra_fields(str, request);
+ }
+ str_append_c(str, '\n');
+ auth_worker_send_reply(client, request, str);
+
+ auth_request_passdb_lookup_end(request, result);
+ auth_worker_request_finished(cmd, error);
+ auth_request_unref(&request);
+}
+
+static bool
+auth_worker_handle_passv(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ /* verify plaintext password */
+ struct auth_request *auth_request;
+ struct auth_passdb *passdb;
+ const char *password;
+ unsigned int passdb_id;
+
+ /* <passdb id> <password> [<args>] */
+ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSV";
+ return FALSE;
+ }
+ password = args[1];
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSV";
+ return FALSE;
+ }
+ auth_request->mech_password =
+ p_strdup(auth_request->pool, password);
+
+ passdb = auth_request->passdb;
+ while (passdb != NULL && passdb->passdb->id != passdb_id)
+ passdb = passdb->next;
+
+ if (passdb == NULL) {
+ /* could be a masterdb */
+ passdb = auth_request_get_auth(auth_request)->masterdbs;
+ while (passdb != NULL && passdb->passdb->id != passdb_id)
+ passdb = passdb->next;
+
+ if (passdb == NULL) {
+ *error_r = "BUG: PASSV had invalid passdb ID";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+ }
+
+ auth_request->passdb = passdb;
+ auth_request_passdb_lookup_begin(auth_request);
+ passdb->passdb->iface.
+ verify_plain(auth_request, password, verify_plain_callback);
+ return TRUE;
+}
+
+static bool
+auth_worker_handle_passw(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ struct auth_worker_client *client = cmd->client;
+ struct auth_request *request;
+ string_t *str;
+ const char *password;
+ const char *crypted, *scheme, *error;
+ unsigned int passdb_id;
+ enum passdb_result ret;
+
+ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL ||
+ args[2] == NULL) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSW";
+ return FALSE;
+ }
+ password = args[1];
+ crypted = args[2];
+ scheme = password_get_scheme(&crypted);
+ if (scheme == NULL) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSW (scheme is NULL)";
+ return FALSE;
+ }
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 3, &request)) {
+ *error_r = "BUG: PASSW had missing parameters";
+ return FALSE;
+ }
+ request->mech_password =
+ p_strdup(request->pool, password);
+
+ ret = auth_request_password_verify(request, password,
+ crypted, scheme, "cache");
+ str = t_str_new(128);
+ str_printfa(str, "%u\t", request->id);
+
+ if (ret == PASSDB_RESULT_OK) {
+ str_printfa(str, "OK\t\t");
+ error = NULL;
+ } else {
+ str_printfa(str, "FAIL\t%d", ret);
+ error = passdb_result_to_string(ret);
+ }
+
+ str_append_c(str, '\n');
+ auth_worker_send_reply(client, request, str);
+
+ auth_worker_request_finished(cmd, error);
+ auth_request_unref(&request);
+ return TRUE;
+}
+
+static void
+lookup_credentials_callback(enum passdb_result result,
+ const unsigned char *credentials, size_t size,
+ struct auth_request *request)
+{
+ struct auth_worker_command *cmd = request->context;
+ struct auth_worker_client *client = cmd->client;
+ string_t *str;
+
+ if (request->failed && result == PASSDB_RESULT_OK)
+ result = PASSDB_RESULT_PASSWORD_MISMATCH;
+
+ str = t_str_new(128);
+ str_printfa(str, "%u\t", request->id);
+
+ if (result != PASSDB_RESULT_OK && result != PASSDB_RESULT_NEXT)
+ str_printfa(str, "FAIL\t%d", result);
+ else {
+ if (result == PASSDB_RESULT_NEXT)
+ str_append(str, "NEXT\t");
+ else
+ str_append(str, "OK\t");
+ if (request->user_changed_by_lookup)
+ str_append_tabescaped(str, request->fields.user);
+ str_append_c(str, '\t');
+ if (request->wanted_credentials_scheme[0] != '\0') {
+ str_printfa(str, "{%s.b64}", request->wanted_credentials_scheme);
+ base64_encode(credentials, size, str);
+ } else {
+ i_assert(size == 0);
+ }
+ reply_append_extra_fields(str, request);
+ }
+ str_append_c(str, '\n');
+ auth_worker_send_reply(client, request, str);
+
+ auth_request_passdb_lookup_end(request, result);
+ auth_request_unref(&request);
+ auth_worker_request_finished(cmd, NULL);
+}
+
+static bool
+auth_worker_handle_passl(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ /* lookup credentials */
+ struct auth_request *auth_request;
+ const char *scheme;
+ unsigned int passdb_id;
+
+ /* <passdb id> <scheme> [<args>] */
+ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSL";
+ return FALSE;
+ }
+ scheme = args[1];
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) {
+ *error_r = "BUG: PASSL had missing parameters";
+ return FALSE;
+ }
+ auth_request->wanted_credentials_scheme =
+ p_strdup(auth_request->pool, scheme);
+
+ while (auth_request->passdb->passdb->id != passdb_id) {
+ auth_request->passdb = auth_request->passdb->next;
+ if (auth_request->passdb == NULL) {
+ *error_r = "BUG: PASSL had invalid passdb ID";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+ }
+
+ if (auth_request->passdb->passdb->iface.lookup_credentials == NULL) {
+ *error_r = "BUG: PASSL lookup not supported by given passdb";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+
+ auth_request->prefer_plain_credentials = TRUE;
+ auth_request_passdb_lookup_begin(auth_request);
+ auth_request->passdb->passdb->iface.
+ lookup_credentials(auth_request, lookup_credentials_callback);
+ return TRUE;
+}
+
+static void
+set_credentials_callback(bool success, struct auth_request *request)
+{
+ struct auth_worker_command *cmd = request->context;
+ struct auth_worker_client *client = cmd->client;
+
+ string_t *str;
+
+ str = t_str_new(64);
+ str_printfa(str, "%u\t%s\n", request->id, success ? "OK" : "FAIL");
+ auth_worker_send_reply(client, request, str);
+
+ auth_worker_request_finished(cmd, success ? NULL :
+ "Failed to set credentials");
+ auth_request_unref(&request);
+}
+
+static bool
+auth_worker_handle_setcred(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ struct auth_request *auth_request;
+ unsigned int passdb_id;
+ const char *creds;
+
+ /* <passdb id> <credentials> [<args>] */
+ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) {
+ *error_r = "BUG: Auth worker server sent us invalid SETCRED";
+ return FALSE;
+ }
+ creds = args[1];
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) {
+ *error_r = "BUG: SETCRED had missing parameters";
+ return FALSE;
+ }
+
+ while (auth_request->passdb->passdb->id != passdb_id) {
+ auth_request->passdb = auth_request->passdb->next;
+ if (auth_request->passdb == NULL) {
+ *error_r = "BUG: SETCRED had invalid passdb ID";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+ }
+
+ auth_request->passdb->passdb->iface.
+ set_credentials(auth_request, creds, set_credentials_callback);
+ return TRUE;
+}
+
+static void
+lookup_user_callback(enum userdb_result result,
+ struct auth_request *auth_request)
+{
+ struct auth_worker_command *cmd = auth_request->context;
+ struct auth_worker_client *client = cmd->client;
+ const char *error;
+ string_t *str;
+
+ str = t_str_new(128);
+ str_printfa(str, "%u\t", auth_request->id);
+ switch (result) {
+ case USERDB_RESULT_INTERNAL_FAILURE:
+ str_append(str, "FAIL\t");
+ break;
+ case USERDB_RESULT_USER_UNKNOWN:
+ str_append(str, "NOTFOUND\t");
+ break;
+ case USERDB_RESULT_OK:
+ str_append(str, "OK\t");
+ if (auth_request->user_changed_by_lookup)
+ str_append_tabescaped(str, auth_request->fields.user);
+ str_append_c(str, '\t');
+ /* export only the fields changed by this lookup */
+ auth_fields_append(auth_request->fields.userdb_reply, str,
+ AUTH_FIELD_FLAG_CHANGED,
+ AUTH_FIELD_FLAG_CHANGED);
+ if (auth_request->userdb_lookup_tempfailed)
+ str_append(str, "\ttempfail");
+ break;
+ }
+ str_append_c(str, '\n');
+
+ auth_worker_send_reply(client, auth_request, str);
+
+ auth_request_userdb_lookup_end(auth_request, result);
+ error = result == USERDB_RESULT_OK ? NULL :
+ userdb_result_to_string(result);
+ auth_worker_request_finished(cmd, error);
+ auth_request_unref(&auth_request);
+}
+
+static struct auth_userdb *
+auth_userdb_find_by_id(struct auth_userdb *userdbs, unsigned int id)
+{
+ struct auth_userdb *db;
+
+ for (db = userdbs; db != NULL; db = db->next) {
+ if (db->userdb->id == id)
+ return db;
+ }
+ return NULL;
+}
+
+static bool
+auth_worker_handle_user(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ /* lookup user */
+ struct auth_request *auth_request;
+ unsigned int userdb_id;
+
+ /* <userdb id> [<args>] */
+ if (str_to_uint(args[0], &userdb_id) < 0) {
+ *error_r = "BUG: Auth worker server sent us invalid USER";
+ return FALSE;
+ }
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 1, &auth_request)) {
+ *error_r = "BUG: USER had missing parameters";
+ return FALSE;
+ }
+
+ auth_request->userdb_lookup = TRUE;
+ auth_request->userdb =
+ auth_userdb_find_by_id(auth_request->userdb, userdb_id);
+ if (auth_request->userdb == NULL) {
+ *error_r = "BUG: USER had invalid userdb ID";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+
+ if (auth_request->fields.userdb_reply == NULL)
+ auth_request_init_userdb_reply(auth_request, TRUE);
+ auth_request_userdb_lookup_begin(auth_request);
+ auth_request->userdb->userdb->iface->
+ lookup(auth_request, lookup_user_callback);
+ return TRUE;
+}
+
+static void
+auth_worker_client_idle_kill(struct connection *conn ATTR_UNUSED)
+{
+ auth_worker_client_send_shutdown();
+}
+
+static void list_iter_deinit(struct auth_worker_list_context *ctx)
+{
+ struct auth_worker_command *cmd = ctx->cmd;
+ struct auth_worker_client *client = ctx->client;
+ const char *error = NULL;
+ string_t *str;
+
+ i_assert(client->conn.io == NULL);
+
+ str = t_str_new(32);
+ if (ctx->auth_request->userdb->userdb->iface->
+ iterate_deinit(ctx->iter) < 0) {
+ error = "Iteration failed";
+ str_printfa(str, "%u\tFAIL\n", ctx->auth_request->id);
+ } else
+ str_printfa(str, "%u\tOK\n", ctx->auth_request->id);
+ auth_worker_send_reply(client, NULL, str);
+
+ connection_input_resume(&client->conn);
+ o_stream_set_flush_callback(client->conn.output, auth_worker_output,
+ client);
+ auth_request_userdb_lookup_end(ctx->auth_request, USERDB_RESULT_OK);
+ auth_worker_request_finished(cmd, error);
+ auth_request_unref(&ctx->auth_request);
+ i_free(ctx);
+}
+
+static void list_iter_callback(const char *user, void *context)
+{
+ struct auth_worker_list_context *ctx = context;
+ string_t *str;
+
+ if (user == NULL) {
+ if (ctx->sending)
+ ctx->done = TRUE;
+ else
+ list_iter_deinit(ctx);
+ return;
+ }
+
+ if (!ctx->sending)
+ o_stream_cork(ctx->client->conn.output);
+ T_BEGIN {
+ str = t_str_new(128);
+ str_printfa(str, "%u\t*\t%s\n", ctx->auth_request->id, user);
+ o_stream_nsend(ctx->client->conn.output, str_data(str), str_len(str));
+ } T_END;
+
+ if (ctx->sending) {
+ /* avoid recursively looping to this same function */
+ ctx->sent = TRUE;
+ return;
+ }
+
+ do {
+ ctx->sending = TRUE;
+ ctx->sent = FALSE;
+ T_BEGIN {
+ ctx->auth_request->userdb->userdb->iface->
+ iterate_next(ctx->iter);
+ } T_END;
+ if (o_stream_get_buffer_used_size(ctx->client->conn.output) > OUTBUF_THROTTLE_SIZE) {
+ if (o_stream_flush(ctx->client->conn.output) < 0) {
+ ctx->done = TRUE;
+ break;
+ }
+ }
+ } while (ctx->sent &&
+ o_stream_get_buffer_used_size(ctx->client->conn.output) <= OUTBUF_THROTTLE_SIZE);
+ o_stream_uncork(ctx->client->conn.output);
+ ctx->sending = FALSE;
+ if (ctx->done)
+ list_iter_deinit(ctx);
+ else
+ o_stream_set_flush_pending(ctx->client->conn.output, TRUE);
+}
+
+static int auth_worker_list_output(struct auth_worker_list_context *ctx)
+{
+ int ret;
+
+ if ((ret = o_stream_flush(ctx->client->conn.output)) < 0) {
+ list_iter_deinit(ctx);
+ return 1;
+ }
+ if (ret > 0) T_BEGIN {
+ ctx->auth_request->userdb->userdb->iface->
+ iterate_next(ctx->iter);
+ } T_END;
+ return 1;
+}
+
+static bool
+auth_worker_handle_list(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ struct auth_worker_client *client = cmd->client;
+ struct auth_worker_list_context *ctx;
+ struct auth_userdb *userdb;
+ unsigned int userdb_id;
+
+ if (str_to_uint(args[0], &userdb_id) < 0) {
+ *error_r = "BUG: Auth worker server sent us invalid LIST";
+ return FALSE;
+ }
+
+ userdb = auth_userdb_find_by_id(client->auth->userdbs, userdb_id);
+ if (userdb == NULL) {
+ *error_r = "BUG: LIST had invalid userdb ID";
+ return FALSE;
+ }
+
+ ctx = i_new(struct auth_worker_list_context, 1);
+ ctx->cmd = cmd;
+ ctx->client = client;
+ if (!auth_worker_auth_request_new(cmd, id, args + 1, &ctx->auth_request)) {
+ *error_r = "BUG: LIST had missing parameters";
+ i_free(ctx);
+ return FALSE;
+ }
+ ctx->auth_request->userdb = userdb;
+
+ connection_input_halt(&ctx->client->conn);
+
+ o_stream_set_flush_callback(ctx->client->conn.output,
+ auth_worker_list_output, ctx);
+ ctx->auth_request->userdb_lookup = TRUE;
+ auth_request_userdb_lookup_begin(ctx->auth_request);
+ ctx->iter = ctx->auth_request->userdb->userdb->iface->
+ iterate_init(ctx->auth_request, list_iter_callback, ctx);
+ ctx->auth_request->userdb->userdb->iface->iterate_next(ctx->iter);
+ return TRUE;
+}
+
+static bool auth_worker_verify_db_hash(const char *passdb_hash, const char *userdb_hash)
+{
+ string_t *str = t_str_new(MD5_RESULTLEN*2);
+ unsigned char passdb_md5[MD5_RESULTLEN];
+ unsigned char userdb_md5[MD5_RESULTLEN];
+
+ passdbs_generate_md5(passdb_md5);
+ userdbs_generate_md5(userdb_md5);
+
+ binary_to_hex_append(str, passdb_md5, sizeof(passdb_md5));
+ if (strcmp(str_c(str), passdb_hash) != 0)
+ return FALSE;
+ str_truncate(str, 0);
+ binary_to_hex_append(str, userdb_md5, sizeof(userdb_md5));
+ return strcmp(str_c(str), userdb_hash) == 0;
+};
+
+static int auth_worker_client_handshake_args(struct connection *conn, const char *const *args)
+{
+ if (!conn->version_received) {
+ if (connection_handshake_args_default(conn, args) < 0)
+ return -1;
+ return 0;
+ }
+
+ if (str_array_length(args) < 3 ||
+ strcmp(args[0], "DBHASH") != 0) {
+ e_error(conn->event, "BUG: Invalid input: %s",
+ t_strarray_join(args, "\t"));
+ return -1;
+ }
+
+ if (!auth_worker_verify_db_hash(args[1], args[2])) {
+ e_error(conn->event,
+ "Auth worker sees different passdbs/userdbs "
+ "than auth server. Maybe config just changed "
+ "and this goes away automatically?");
+ return -1;
+ }
+ return 1;
+}
+
+static int
+auth_worker_client_input_args(struct connection *conn, const char *const *args)
+{
+ unsigned int id;
+ bool ret = FALSE;
+ const char *error = NULL;
+ struct auth_worker_command *cmd;
+ struct auth_worker_client *client =
+ container_of(conn, struct auth_worker_client, conn);
+
+ if (str_array_length(args) < 3 ||
+ str_to_uint(args[0], &id) < 0) {
+ e_error(conn->event, "BUG: Invalid input: %s",
+ t_strarray_join(args, "\t"));
+ return -1;
+ }
+
+ io_loop_time_refresh();
+
+ cmd = i_new(struct auth_worker_command, 1);
+ cmd->client = client;
+ cmd->event = event_create(client->conn.event);
+ event_add_category(cmd->event, &event_category_auth);
+ event_add_str(cmd->event, "command", args[1]);
+ event_add_int(cmd->event, "command_id", id);
+ event_set_append_log_prefix(cmd->event, t_strdup_printf("auth-worker<%u>: ", id));
+ client->cmd_start = ioloop_time;
+ client->refcount++;
+ e_debug(cmd->event, "Handling %s request", args[1]);
+
+ /* Check if we have reached service_count */
+ if (auth_worker_max_service_count > 0) {
+ auth_worker_service_count++;
+ if (auth_worker_service_count >= auth_worker_max_service_count)
+ worker_restart_request = TRUE;
+ }
+
+ auth_worker_refresh_proctitle(args[1]);
+ if (strcmp(args[1], "PASSV") == 0)
+ ret = auth_worker_handle_passv(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "PASSL") == 0)
+ ret = auth_worker_handle_passl(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "PASSW") == 0)
+ ret = auth_worker_handle_passw(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "SETCRED") == 0)
+ ret = auth_worker_handle_setcred(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "USER") == 0)
+ ret = auth_worker_handle_user(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "LIST") == 0)
+ ret = auth_worker_handle_list(cmd, id, args + 2, &error);
+ else {
+ error = t_strdup_printf("BUG: Auth-worker received unknown command: %s",
+ args[1]);
+ }
+
+ i_assert(ret || error != NULL);
+
+ if (!ret) {
+ auth_worker_request_finished_bug(cmd, error);
+ return -1;
+ }
+ auth_worker_client_unref(&client);
+ return 1;
+}
+
+static int auth_worker_output(struct auth_worker_client *client)
+{
+ if (o_stream_flush(client->conn.output) < 0) {
+ auth_worker_client_destroy(&client->conn);
+ return 1;
+ }
+
+ if (o_stream_get_buffer_used_size(client->conn.output) <=
+ OUTBUF_THROTTLE_SIZE/3 && client->conn.io == NULL) {
+ /* allow input again */
+ connection_input_resume(&client->conn);
+ }
+ return 1;
+}
+
+static void auth_worker_client_unref(struct auth_worker_client **_client)
+{
+ struct auth_worker_client *client = *_client;
+ if (client == NULL)
+ return;
+ if (--client->refcount > 0)
+ return;
+
+ /* the connection should've been destroyed before getting here */
+ i_assert(client->destroyed);
+ connection_deinit(&client->conn);
+ i_free(client);
+}
+
+static void auth_worker_client_destroy(struct connection *conn)
+{
+ struct auth_worker_client *client =
+ container_of(conn, struct auth_worker_client, conn);
+
+ i_assert(!client->destroyed);
+ client->destroyed = TRUE;
+ connection_input_halt(conn);
+ i_stream_close(conn->input);
+ o_stream_close(conn->output);
+ net_disconnect(conn->fd_in);
+ conn->fd_out = conn->fd_in = -1;
+ auth_worker_client_unref(&client);
+ master_service_client_connection_destroyed(master_service);
+}
+
+static const struct connection_vfuncs auth_worker_client_v =
+{
+ .input_args = auth_worker_client_input_args,
+ .handshake_args = auth_worker_client_handshake_args,
+ .destroy = auth_worker_client_destroy,
+ .idle_timeout = auth_worker_client_idle_kill,
+};
+
+static const struct connection_settings auth_worker_client_set =
+{
+ .service_name_in = "auth-worker",
+ .service_name_out = "auth-worker",
+ .major_version = AUTH_WORKER_PROTOCOL_MAJOR_VERSION,
+ .minor_version = AUTH_WORKER_PROTOCOL_MINOR_VERSION,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX, /* we use throttling */
+};
+
+struct auth_worker_client *
+auth_worker_client_create(struct auth *auth,
+ const struct master_service_connection *master_conn)
+{
+ struct auth_worker_client *client;
+
+ if (clients == NULL)
+ clients = connection_list_init(&auth_worker_client_set, &auth_worker_client_v);
+
+ client = i_new(struct auth_worker_client, 1);
+ client->refcount = 1;
+ client->auth = auth;
+ client->conn.event_parent = auth_event;
+ client->conn.input_idle_timeout_secs = master_service_get_idle_kill_secs(master_service);
+ connection_init_server(clients, &client->conn, master_conn->name,
+ master_conn->fd, master_conn->fd);
+
+ auth_worker_refresh_proctitle(CLIENT_STATE_HANDSHAKE);
+
+ if (auth_worker_client_error)
+ auth_worker_client_send_error();
+ return client;
+}
+
+void auth_worker_client_send_error(void)
+{
+ struct auth_worker_client *auth_worker_client =
+ auth_worker_get_client();
+ auth_worker_client_error = TRUE;
+ if (auth_worker_client != NULL &&
+ !auth_worker_client->error_sent) {
+ o_stream_nsend_str(auth_worker_client->conn.output, "ERROR\n");
+ auth_worker_client->error_sent = TRUE;
+ }
+ auth_worker_refresh_proctitle("");
+}
+
+void auth_worker_client_send_success(void)
+{
+ struct auth_worker_client *auth_worker_client =
+ auth_worker_get_client();
+ auth_worker_client_error = FALSE;
+ if (auth_worker_client == NULL)
+ return;
+ if (auth_worker_client->error_sent) {
+ o_stream_nsend_str(auth_worker_client->conn.output,
+ "SUCCESS\n");
+ auth_worker_client->error_sent = FALSE;
+ }
+ if (auth_worker_client->conn.io != NULL)
+ auth_worker_refresh_proctitle(CLIENT_STATE_IDLE);
+}
+
+void auth_worker_client_send_shutdown(void)
+{
+ struct auth_worker_client *auth_worker_client =
+ auth_worker_get_client();
+ if (auth_worker_client != NULL)
+ o_stream_nsend_str(auth_worker_client->conn.output,
+ "SHUTDOWN\n");
+ auth_worker_refresh_proctitle(CLIENT_STATE_STOP);
+}
+
+void auth_worker_connections_destroy_all(void)
+{
+ if (clients == NULL)
+ return;
+ while (clients->connections != NULL)
+ connection_deinit(clients->connections);
+ connection_list_deinit(&clients);
+}
+
+bool auth_worker_has_client(void)
+{
+ return clients != NULL && clients->connections_count > 0;
+}
diff --git a/src/auth/auth-worker-client.h b/src/auth/auth-worker-client.h
new file mode 100644
index 0000000..e41ee83
--- /dev/null
+++ b/src/auth/auth-worker-client.h
@@ -0,0 +1,27 @@
+#ifndef AUTH_WORKER_CLIENT_H
+#define AUTH_WORKER_CLIENT_H
+
+#define AUTH_WORKER_PROTOCOL_MAJOR_VERSION 1
+#define AUTH_WORKER_PROTOCOL_MINOR_VERSION 0
+#define AUTH_WORKER_MAX_LINE_LENGTH 8192
+
+struct master_service_connection;
+struct auth_worker_command;
+
+struct auth_worker_client *
+auth_worker_client_create(struct auth *auth,
+ const struct master_service_connection *master_conn);
+bool auth_worker_auth_request_new(struct auth_worker_command *cmd, unsigned int id,
+ const char *const *args, struct auth_request **request_r);
+
+bool auth_worker_has_client(void);
+void auth_worker_client_send_error(void);
+void auth_worker_client_send_success(void);
+void auth_worker_client_send_shutdown(void);
+
+void auth_worker_connections_destroy_all(void);
+
+/* Stop master service after this many requests. 0 is unlimited. */
+void auth_worker_set_max_service_count(unsigned int count);
+
+#endif
diff --git a/src/auth/auth-worker-server.c b/src/auth/auth-worker-server.c
new file mode 100644
index 0000000..41b2b2b
--- /dev/null
+++ b/src/auth/auth-worker-server.c
@@ -0,0 +1,519 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "ioloop.h"
+#include "array.h"
+#include "aqueue.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "hex-binary.h"
+#include "str.h"
+#include "eacces-error.h"
+#include "auth-request.h"
+#include "auth-worker-client.h"
+#include "auth-worker-server.h"
+
+#include <unistd.h>
+
+/* Initial lookup timeout */
+#define AUTH_WORKER_LOOKUP_TIMEOUT_SECS 60
+/* Timeout for multi-line replies, e.g. listing users. This should be a much
+ higher value, because e.g. doveadm could be doing some long-running commands
+ for the users. And because of buffering this timeout is for handling
+ multiple users, not just one. */
+#define AUTH_WORKER_RESUME_TIMEOUT_SECS (30*60)
+#define AUTH_WORKER_MAX_IDLE_SECS (60*5)
+#define AUTH_WORKER_ABORT_SECS 60
+#define AUTH_WORKER_DELAY_WARN_SECS 3
+#define AUTH_WORKER_DELAY_WARN_MIN_INTERVAL_SECS 300
+
+struct auth_worker_request {
+ unsigned int id;
+ time_t created;
+ const char *username;
+ const char *data;
+ auth_worker_callback_t *callback;
+ void *context;
+};
+
+struct auth_worker_connection {
+ int fd;
+
+ struct event *event;
+ struct io *io;
+ struct istream *input;
+ struct ostream *output;
+ struct timeout *to;
+
+ struct auth_worker_request *request;
+ unsigned int id_counter;
+
+ bool received_error:1;
+ bool restart:1;
+ bool shutdown:1;
+ bool timeout_pending_resume:1;
+ bool resuming:1;
+};
+
+static ARRAY(struct auth_worker_connection *) connections = ARRAY_INIT;
+static unsigned int idle_count = 0, auth_workers_with_errors = 0;
+static ARRAY(struct auth_worker_request *) worker_request_array;
+static struct aqueue *worker_request_queue;
+static time_t auth_worker_last_warn;
+static unsigned int auth_workers_throttle_count;
+
+static const char *worker_socket_path;
+
+static void worker_input(struct auth_worker_connection *conn);
+static void auth_worker_destroy(struct auth_worker_connection **conn,
+ const char *reason, bool restart) ATTR_NULL(2);
+
+static void auth_worker_idle_timeout(struct auth_worker_connection *conn)
+{
+ i_assert(conn->request == NULL);
+
+ if (idle_count > 1)
+ auth_worker_destroy(&conn, NULL, FALSE);
+ else
+ timeout_reset(conn->to);
+}
+
+static void auth_worker_call_timeout(struct auth_worker_connection *conn)
+{
+ i_assert(conn->request != NULL);
+
+ auth_worker_destroy(&conn, "Lookup timed out", TRUE);
+}
+
+static bool auth_worker_request_send(struct auth_worker_connection *conn,
+ struct auth_worker_request *request)
+{
+ struct const_iovec iov[3];
+ unsigned int age_secs = ioloop_time - request->created;
+
+ i_assert(conn->to != NULL);
+
+ if (age_secs >= AUTH_WORKER_ABORT_SECS) {
+ e_error(conn->event,
+ "Aborting auth request that was queued for %d secs, "
+ "%d left in queue",
+ age_secs, aqueue_count(worker_request_queue));
+ request->callback(conn, t_strdup_printf(
+ "FAIL\t%d", PASSDB_RESULT_INTERNAL_FAILURE),
+ request->context);
+ return FALSE;
+ }
+ if (age_secs >= AUTH_WORKER_DELAY_WARN_SECS &&
+ ioloop_time - auth_worker_last_warn >
+ AUTH_WORKER_DELAY_WARN_MIN_INTERVAL_SECS) {
+ auth_worker_last_warn = ioloop_time;
+ e_error(conn->event, "Auth request was queued for %d "
+ "seconds, %d left in queue "
+ "(see auth_worker_max_count)",
+ age_secs, aqueue_count(worker_request_queue));
+ }
+
+ request->id = ++conn->id_counter;
+
+ iov[0].iov_base = t_strdup_printf("%d\t", request->id);
+ iov[0].iov_len = strlen(iov[0].iov_base);
+ iov[1].iov_base = request->data;
+ iov[1].iov_len = strlen(request->data);
+ iov[2].iov_base = "\n";
+ iov[2].iov_len = 1;
+
+ o_stream_nsendv(conn->output, iov, 3);
+
+ i_assert(conn->request == NULL);
+ conn->request = request;
+
+ timeout_remove(&conn->to);
+ conn->to = timeout_add(AUTH_WORKER_LOOKUP_TIMEOUT_SECS * 1000,
+ auth_worker_call_timeout, conn);
+ idle_count--;
+ return TRUE;
+}
+
+static void auth_worker_request_send_next(struct auth_worker_connection *conn)
+{
+ struct auth_worker_request *request;
+
+ do {
+ if (aqueue_count(worker_request_queue) == 0)
+ return;
+
+ request = array_idx_elem(&worker_request_array,
+ aqueue_idx(worker_request_queue, 0));
+ aqueue_delete_tail(worker_request_queue);
+ } while (!auth_worker_request_send(conn, request));
+}
+
+static void auth_worker_send_handshake(struct auth_worker_connection *conn)
+{
+ string_t *str;
+ unsigned char passdb_md5[MD5_RESULTLEN];
+ unsigned char userdb_md5[MD5_RESULTLEN];
+
+ str = t_str_new(128);
+ str_printfa(str, "VERSION\tauth-worker\t%u\t%u\n",
+ AUTH_WORKER_PROTOCOL_MAJOR_VERSION,
+ AUTH_WORKER_PROTOCOL_MINOR_VERSION);
+
+ passdbs_generate_md5(passdb_md5);
+ userdbs_generate_md5(userdb_md5);
+ str_append(str, "DBHASH\t");
+ binary_to_hex_append(str, passdb_md5, sizeof(passdb_md5));
+ str_append_c(str, '\t');
+ binary_to_hex_append(str, userdb_md5, sizeof(userdb_md5));
+ str_append_c(str, '\n');
+
+ o_stream_nsend(conn->output, str_data(str), str_len(str));
+}
+
+static struct auth_worker_connection *auth_worker_create(void)
+{
+ struct auth_worker_connection *conn;
+ struct event *event;
+ int fd;
+
+ if (array_count(&connections) >= auth_workers_throttle_count)
+ return NULL;
+
+ event = event_create(auth_event);
+ event_set_append_log_prefix(event, "auth-worker: ");
+
+ fd = net_connect_unix_with_retries(worker_socket_path, 5000);
+ if (fd == -1) {
+ if (errno == EACCES) {
+ e_error(event, "%s",
+ eacces_error_get("net_connect_unix",
+ worker_socket_path));
+ } else {
+ e_error(event, "net_connect_unix(%s) failed: %m",
+ worker_socket_path);
+ }
+ event_unref(&event);
+ return NULL;
+ }
+
+ conn = i_new(struct auth_worker_connection, 1);
+ conn->fd = fd;
+ conn->input = i_stream_create_fd(fd, AUTH_WORKER_MAX_LINE_LENGTH);
+ conn->output = o_stream_create_fd(fd, SIZE_MAX);
+ o_stream_set_no_error_handling(conn->output, TRUE);
+ conn->io = io_add(fd, IO_READ, worker_input, conn);
+ conn->to = timeout_add(AUTH_WORKER_MAX_IDLE_SECS * 1000,
+ auth_worker_idle_timeout, conn);
+ conn->event = event;
+ auth_worker_send_handshake(conn);
+
+ idle_count++;
+ array_push_back(&connections, &conn);
+ return conn;
+}
+
+static void auth_worker_destroy(struct auth_worker_connection **_conn,
+ const char *reason, bool restart)
+{
+ struct auth_worker_connection *conn = *_conn;
+ struct auth_worker_connection *const *conns;
+ unsigned int idx;
+
+ *_conn = NULL;
+
+ if (conn->received_error) {
+ i_assert(auth_workers_with_errors > 0);
+ i_assert(auth_workers_with_errors <= array_count(&connections));
+ auth_workers_with_errors--;
+ }
+
+ array_foreach(&connections, conns) {
+ if (*conns == conn) {
+ idx = array_foreach_idx(&connections, conns);
+ array_delete(&connections, idx, 1);
+ break;
+ }
+ }
+
+ if (conn->request == NULL)
+ idle_count--;
+
+ if (conn->request != NULL) {
+ e_error(conn->event, "Aborted %s request for %s: %s",
+ t_strcut(conn->request->data, '\t'),
+ conn->request->username, reason);
+ conn->request->callback(conn, t_strdup_printf(
+ "FAIL\t%d", PASSDB_RESULT_INTERNAL_FAILURE),
+ conn->request->context);
+ }
+
+ io_remove(&conn->io);
+ i_stream_destroy(&conn->input);
+ o_stream_destroy(&conn->output);
+ timeout_remove(&conn->to);
+
+ if (close(conn->fd) < 0)
+ e_error(conn->event, "close() failed: %m");
+ event_unref(&conn->event);
+ i_free(conn);
+
+ if (idle_count == 0 && restart) {
+ conn = auth_worker_create();
+ if (conn != NULL)
+ auth_worker_request_send_next(conn);
+ }
+}
+
+static struct auth_worker_connection *auth_worker_find_free(void)
+{
+ struct auth_worker_connection *conn;
+
+ if (idle_count == 0)
+ return NULL;
+
+ array_foreach_elem(&connections, conn) {
+ if (conn->request == NULL)
+ return conn;
+ }
+ i_unreached();
+ return NULL;
+}
+
+static bool auth_worker_request_handle(struct auth_worker_connection *conn,
+ struct auth_worker_request *request,
+ const char *line)
+{
+ if (str_begins(line, "*\t")) {
+ /* multi-line reply, not finished yet */
+ if (conn->resuming)
+ timeout_reset(conn->to);
+ else {
+ conn->resuming = TRUE;
+ timeout_remove(&conn->to);
+ conn->to = timeout_add(AUTH_WORKER_RESUME_TIMEOUT_SECS * 1000,
+ auth_worker_call_timeout, conn);
+ }
+ } else {
+ conn->resuming = FALSE;
+ conn->request = NULL;
+ conn->timeout_pending_resume = FALSE;
+ timeout_remove(&conn->to);
+ conn->to = timeout_add(AUTH_WORKER_MAX_IDLE_SECS * 1000,
+ auth_worker_idle_timeout, conn);
+ idle_count++;
+ }
+
+ if (!request->callback(conn, line, request->context) && conn->io != NULL) {
+ conn->timeout_pending_resume = FALSE;
+ timeout_remove(&conn->to);
+ io_remove(&conn->io);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool auth_worker_error(struct auth_worker_connection *conn)
+{
+ if (conn->received_error)
+ return TRUE;
+ conn->received_error = TRUE;
+ auth_workers_with_errors++;
+ i_assert(auth_workers_with_errors <= array_count(&connections));
+
+ if (auth_workers_with_errors == 1) {
+ /* this is the only failing auth worker connection.
+ don't create new ones until this one sends SUCCESS. */
+ auth_workers_throttle_count = array_count(&connections);
+ return TRUE;
+ }
+
+ /* too many auth workers, reduce them */
+ i_assert(array_count(&connections) > 1);
+ if (auth_workers_throttle_count >= array_count(&connections))
+ auth_workers_throttle_count = array_count(&connections)-1;
+ else if (auth_workers_throttle_count > 1)
+ auth_workers_throttle_count--;
+ auth_worker_destroy(&conn, "Internal auth worker failure", FALSE);
+ return FALSE;
+}
+
+static void auth_worker_success(struct auth_worker_connection *conn)
+{
+ unsigned int max_count = global_auth_settings->worker_max_count;
+
+ if (!conn->received_error)
+ return;
+
+ i_assert(auth_workers_with_errors > 0);
+ i_assert(auth_workers_with_errors <= array_count(&connections));
+ auth_workers_with_errors--;
+
+ if (auth_workers_with_errors == 0) {
+ /* all workers are succeeding now, set the limit back to
+ original. */
+ auth_workers_throttle_count = max_count;
+ } else if (auth_workers_throttle_count < max_count)
+ auth_workers_throttle_count++;
+ conn->received_error = FALSE;
+}
+
+static void worker_input(struct auth_worker_connection *conn)
+{
+ const char *line, *id_str;
+ unsigned int id;
+
+ switch (i_stream_read(conn->input)) {
+ case 0:
+ return;
+ case -1:
+ /* disconnected */
+ auth_worker_destroy(&conn, "Worker process died unexpectedly",
+ TRUE);
+ return;
+ case -2:
+ /* buffer full */
+ e_error(conn->event,
+ "BUG: Auth worker sent us more than %d bytes",
+ (int)AUTH_WORKER_MAX_LINE_LENGTH);
+ auth_worker_destroy(&conn, "Worker is buggy", TRUE);
+ return;
+ }
+
+ while ((line = i_stream_next_line(conn->input)) != NULL) {
+ if (strcmp(line, "RESTART") == 0) {
+ conn->restart = TRUE;
+ continue;
+ }
+ if (strcmp(line, "SHUTDOWN") == 0) {
+ conn->shutdown = TRUE;
+ continue;
+ }
+ if (strcmp(line, "ERROR") == 0) {
+ if (!auth_worker_error(conn))
+ return;
+ continue;
+ }
+ if (strcmp(line, "SUCCESS") == 0) {
+ auth_worker_success(conn);
+ continue;
+ }
+ id_str = line;
+ line = strchr(line, '\t');
+ if (line == NULL ||
+ str_to_uint(t_strdup_until(id_str, line), &id) < 0)
+ continue;
+
+ if (conn->request != NULL && id == conn->request->id) {
+ if (!auth_worker_request_handle(conn, conn->request,
+ line + 1))
+ break;
+ } else {
+ if (conn->request != NULL) {
+ e_error(conn->event,
+ "BUG: Worker sent reply with id %u, "
+ "expected %u", id, conn->request->id);
+ } else {
+ e_error(conn->event,
+ "BUG: Worker sent reply with id %u, "
+ "none was expected", id);
+ }
+ auth_worker_destroy(&conn, "Worker is buggy", TRUE);
+ return;
+ }
+ }
+
+ if (conn->request != NULL) {
+ /* there's still a pending request */
+ } else if (conn->restart)
+ auth_worker_destroy(&conn, "Max requests limit", TRUE);
+ else if (conn->shutdown)
+ auth_worker_destroy(&conn, "Idle kill", FALSE);
+ else
+ auth_worker_request_send_next(conn);
+}
+
+static void worker_input_resume(struct auth_worker_connection *conn)
+{
+ conn->timeout_pending_resume = FALSE;
+ timeout_remove(&conn->to);
+ conn->to = timeout_add(AUTH_WORKER_RESUME_TIMEOUT_SECS * 1000,
+ auth_worker_call_timeout, conn);
+ worker_input(conn);
+}
+
+void auth_worker_call(pool_t pool, const char *username, const char *data,
+ auth_worker_callback_t *callback, void *context)
+{
+ struct auth_worker_connection *conn;
+ struct auth_worker_request *request;
+
+ request = p_new(pool, struct auth_worker_request, 1);
+ request->created = ioloop_time;
+ request->username = p_strdup(pool, username);
+ request->data = p_strdup(pool, data);
+ request->callback = callback;
+ request->context = context;
+
+ if (aqueue_count(worker_request_queue) > 0) {
+ /* requests are already being queued, no chance of
+ finding/creating a worker */
+ conn = NULL;
+ } else {
+ conn = auth_worker_find_free();
+ if (conn == NULL) {
+ /* no free connections, create a new one */
+ conn = auth_worker_create();
+ }
+ }
+ if (conn != NULL) {
+ if (!auth_worker_request_send(conn, request))
+ i_unreached();
+ } else {
+ /* reached the limit, queue the request */
+ aqueue_append(worker_request_queue, &request);
+ }
+}
+
+void auth_worker_server_resume_input(struct auth_worker_connection *conn)
+{
+ if (conn->request == NULL) {
+ /* request was just finished, don't try to resume it */
+ return;
+ }
+
+ if (conn->io == NULL)
+ conn->io = io_add(conn->fd, IO_READ, worker_input, conn);
+ if (!conn->timeout_pending_resume) {
+ conn->timeout_pending_resume = TRUE;
+ timeout_remove(&conn->to);
+ conn->to = timeout_add_short(0, worker_input_resume, conn);
+ }
+}
+
+void auth_worker_server_init(void)
+{
+ worker_socket_path = "auth-worker";
+ auth_workers_throttle_count = global_auth_settings->worker_max_count;
+ i_assert(auth_workers_throttle_count > 0);
+
+ i_array_init(&worker_request_array, 128);
+ worker_request_queue = aqueue_init(&worker_request_array.arr);
+
+ i_array_init(&connections, 16);
+}
+
+void auth_worker_server_deinit(void)
+{
+ struct auth_worker_connection **connp, *conn;
+
+ while (array_count(&connections) > 0) {
+ connp = array_front_modifiable(&connections);
+ conn = *connp;
+ auth_worker_destroy(&conn, "Shutting down", FALSE);
+ }
+ array_free(&connections);
+
+ aqueue_deinit(&worker_request_queue);
+ array_free(&worker_request_array);
+}
diff --git a/src/auth/auth-worker-server.h b/src/auth/auth-worker-server.h
new file mode 100644
index 0000000..d2a3063
--- /dev/null
+++ b/src/auth/auth-worker-server.h
@@ -0,0 +1,18 @@
+#ifndef AUTH_WORKER_SERVER_H
+#define AUTH_WORKER_SERVER_H
+
+struct auth_request;
+struct auth_stream_reply;
+struct auth_worker_connection;
+
+typedef bool auth_worker_callback_t(struct auth_worker_connection *conn,
+ const char *reply, void *context);
+
+void auth_worker_call(pool_t pool, const char *username, const char *data,
+ auth_worker_callback_t *callback, void *context);
+void auth_worker_server_resume_input(struct auth_worker_connection *conn);
+
+void auth_worker_server_init(void);
+void auth_worker_server_deinit(void);
+
+#endif
diff --git a/src/auth/auth.c b/src/auth/auth.c
new file mode 100644
index 0000000..845c43c
--- /dev/null
+++ b/src/auth/auth.c
@@ -0,0 +1,450 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "settings-parser.h"
+#include "master-service-settings.h"
+#include "mech.h"
+#include "userdb.h"
+#include "passdb.h"
+#include "passdb-template.h"
+#include "userdb-template.h"
+#include "auth.h"
+
+struct event *auth_event;
+struct event_category event_category_auth = {
+ .name = "auth",
+};
+
+static const struct auth_userdb_settings userdb_dummy_set = {
+ .name = "",
+ .driver = "static",
+ .args = "",
+ .default_fields = "",
+ .override_fields = "",
+
+ .skip = "never",
+ .result_success = "return-ok",
+ .result_failure = "continue",
+ .result_internalfail = "continue",
+
+ .auth_verbose = "default",
+};
+
+ARRAY_TYPE(auth) auths;
+
+static enum auth_passdb_skip auth_passdb_skip_parse(const char *str)
+{
+ if (strcmp(str, "never") == 0)
+ return AUTH_PASSDB_SKIP_NEVER;
+ if (strcmp(str, "authenticated") == 0)
+ return AUTH_PASSDB_SKIP_AUTHENTICATED;
+ if (strcmp(str, "unauthenticated") == 0)
+ return AUTH_PASSDB_SKIP_UNAUTHENTICATED;
+ i_unreached();
+}
+
+static enum auth_userdb_skip auth_userdb_skip_parse(const char *str)
+{
+ if (strcmp(str, "never") == 0)
+ return AUTH_USERDB_SKIP_NEVER;
+ if (strcmp(str, "found") == 0)
+ return AUTH_USERDB_SKIP_FOUND;
+ if (strcmp(str, "notfound") == 0)
+ return AUTH_USERDB_SKIP_NOTFOUND;
+ i_unreached();
+}
+
+static enum auth_db_rule auth_db_rule_parse(const char *str)
+{
+ if (strcmp(str, "return") == 0)
+ return AUTH_DB_RULE_RETURN;
+ if (strcmp(str, "return-ok") == 0)
+ return AUTH_DB_RULE_RETURN_OK;
+ if (strcmp(str, "return-fail") == 0)
+ return AUTH_DB_RULE_RETURN_FAIL;
+ if (strcmp(str, "continue") == 0)
+ return AUTH_DB_RULE_CONTINUE;
+ if (strcmp(str, "continue-ok") == 0)
+ return AUTH_DB_RULE_CONTINUE_OK;
+ if (strcmp(str, "continue-fail") == 0)
+ return AUTH_DB_RULE_CONTINUE_FAIL;
+ i_unreached();
+}
+
+static void
+auth_passdb_preinit(struct auth *auth, const struct auth_passdb_settings *set,
+ struct auth_passdb **passdbs)
+{
+ struct auth_passdb *auth_passdb, **dest;
+
+ auth_passdb = p_new(auth->pool, struct auth_passdb, 1);
+ auth_passdb->set = set;
+ auth_passdb->skip = auth_passdb_skip_parse(set->skip);
+ auth_passdb->result_success =
+ auth_db_rule_parse(set->result_success);
+ auth_passdb->result_failure =
+ auth_db_rule_parse(set->result_failure);
+ auth_passdb->result_internalfail =
+ auth_db_rule_parse(set->result_internalfail);
+
+ auth_passdb->default_fields_tmpl =
+ passdb_template_build(auth->pool, set->default_fields);
+ auth_passdb->override_fields_tmpl =
+ passdb_template_build(auth->pool, set->override_fields);
+
+ /* for backwards compatibility: */
+ if (set->pass)
+ auth_passdb->result_success = AUTH_DB_RULE_CONTINUE;
+
+ for (dest = passdbs; *dest != NULL; dest = &(*dest)->next) ;
+ *dest = auth_passdb;
+
+ auth_passdb->passdb = passdb_preinit(auth->pool, set);
+ /* make sure any %variables in default_fields exist in cache_key */
+ if (auth_passdb->passdb->default_cache_key != NULL) {
+ auth_passdb->cache_key =
+ p_strconcat(auth->pool, auth_passdb->passdb->default_cache_key,
+ set->default_fields, NULL);
+ }
+ else {
+ auth_passdb->cache_key = NULL;
+ }
+}
+
+static void
+auth_userdb_preinit(struct auth *auth, const struct auth_userdb_settings *set)
+{
+ struct auth_userdb *auth_userdb, **dest;
+
+ auth_userdb = p_new(auth->pool, struct auth_userdb, 1);
+ auth_userdb->set = set;
+ auth_userdb->skip = auth_userdb_skip_parse(set->skip);
+ auth_userdb->result_success =
+ auth_db_rule_parse(set->result_success);
+ auth_userdb->result_failure =
+ auth_db_rule_parse(set->result_failure);
+ auth_userdb->result_internalfail =
+ auth_db_rule_parse(set->result_internalfail);
+
+ auth_userdb->default_fields_tmpl =
+ userdb_template_build(auth->pool, set->driver,
+ set->default_fields);
+ auth_userdb->override_fields_tmpl =
+ userdb_template_build(auth->pool, set->driver,
+ set->override_fields);
+
+ for (dest = &auth->userdbs; *dest != NULL; dest = &(*dest)->next) ;
+ *dest = auth_userdb;
+
+ auth_userdb->userdb = userdb_preinit(auth->pool, set);
+ /* make sure any %variables in default_fields exist in cache_key */
+ if (auth_userdb->userdb->default_cache_key != NULL) {
+ auth_userdb->cache_key =
+ p_strconcat(auth->pool, auth_userdb->userdb->default_cache_key,
+ set->default_fields, NULL);
+ }
+ else {
+ auth_userdb->cache_key = NULL;
+ }
+}
+
+static bool auth_passdb_list_have_verify_plain(const struct auth *auth)
+{
+ const struct auth_passdb *passdb;
+
+ for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) {
+ if (passdb->passdb->iface.verify_plain != NULL)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool auth_passdb_list_have_lookup_credentials(const struct auth *auth)
+{
+ const struct auth_passdb *passdb;
+
+ for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) {
+ if (passdb->passdb->iface.lookup_credentials != NULL)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool auth_passdb_list_have_set_credentials(const struct auth *auth)
+{
+ const struct auth_passdb *passdb;
+
+ for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next) {
+ if (passdb->passdb->iface.set_credentials != NULL)
+ return TRUE;
+ }
+ for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) {
+ if (passdb->passdb->iface.set_credentials != NULL)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool
+auth_mech_verify_passdb(const struct auth *auth, const struct mech_module_list *list)
+{
+ switch (list->module.passdb_need) {
+ case MECH_PASSDB_NEED_NOTHING:
+ break;
+ case MECH_PASSDB_NEED_VERIFY_PLAIN:
+ if (!auth_passdb_list_have_verify_plain(auth))
+ return FALSE;
+ break;
+ case MECH_PASSDB_NEED_VERIFY_RESPONSE:
+ case MECH_PASSDB_NEED_LOOKUP_CREDENTIALS:
+ if (!auth_passdb_list_have_lookup_credentials(auth))
+ return FALSE;
+ break;
+ case MECH_PASSDB_NEED_SET_CREDENTIALS:
+ if (!auth_passdb_list_have_lookup_credentials(auth))
+ return FALSE;
+ if (!auth_passdb_list_have_set_credentials(auth))
+ return FALSE;
+ break;
+ }
+ return TRUE;
+}
+
+static void auth_mech_list_verify_passdb(const struct auth *auth)
+{
+ const struct mech_module_list *list;
+
+ for (list = auth->reg->modules; list != NULL; list = list->next) {
+ if (!auth_mech_verify_passdb(auth, list))
+ break;
+ }
+
+ if (list != NULL) {
+ if (auth->passdbs == NULL) {
+ i_fatal("No passdbs specified in configuration file. "
+ "%s mechanism needs one",
+ list->module.mech_name);
+ }
+ i_fatal("%s mechanism can't be supported with given passdbs",
+ list->module.mech_name);
+ }
+}
+
+static struct auth * ATTR_NULL(2)
+auth_preinit(const struct auth_settings *set, const char *service, pool_t pool,
+ const struct mechanisms_register *reg)
+{
+ struct auth_passdb_settings *const *passdbs;
+ struct auth_userdb_settings *const *userdbs;
+ struct auth *auth;
+ unsigned int i, count, db_count, passdb_count, last_passdb = 0;
+
+ auth = p_new(pool, struct auth, 1);
+ auth->pool = pool;
+ auth->service = p_strdup(pool, service);
+ auth->set = set;
+ auth->reg = reg;
+
+ if (array_is_created(&set->passdbs))
+ passdbs = array_get(&set->passdbs, &db_count);
+ else {
+ passdbs = NULL;
+ db_count = 0;
+ }
+
+ /* initialize passdbs first and count them */
+ for (passdb_count = 0, i = 0; i < db_count; i++) {
+ if (passdbs[i]->master)
+ continue;
+
+ /* passdb { skip=unauthenticated } as the first passdb doesn't
+ make sense, since user is never authenticated at that point.
+ skip over them silently. */
+ if (auth->passdbs == NULL &&
+ auth_passdb_skip_parse(passdbs[i]->skip) == AUTH_PASSDB_SKIP_UNAUTHENTICATED)
+ continue;
+
+ auth_passdb_preinit(auth, passdbs[i], &auth->passdbs);
+ passdb_count++;
+ last_passdb = i;
+ }
+ if (passdb_count != 0 && passdbs[last_passdb]->pass)
+ i_fatal("Last passdb can't have pass=yes");
+
+ for (i = 0; i < db_count; i++) {
+ if (!passdbs[i]->master)
+ continue;
+
+ /* skip skip=unauthenticated, as explained above */
+ if (auth->masterdbs == NULL &&
+ auth_passdb_skip_parse(passdbs[i]->skip) == AUTH_PASSDB_SKIP_UNAUTHENTICATED)
+ continue;
+
+ if (passdbs[i]->deny)
+ i_fatal("Master passdb can't have deny=yes");
+ if (passdbs[i]->pass && passdb_count == 0) {
+ i_fatal("Master passdb can't have pass=yes "
+ "if there are no passdbs");
+ }
+ auth_passdb_preinit(auth, passdbs[i], &auth->masterdbs);
+ }
+
+ if (array_is_created(&set->userdbs)) {
+ userdbs = array_get(&set->userdbs, &count);
+ for (i = 0; i < count; i++)
+ auth_userdb_preinit(auth, userdbs[i]);
+ }
+
+ if (auth->userdbs == NULL) {
+ /* use a dummy userdb static. */
+ auth_userdb_preinit(auth, &userdb_dummy_set);
+ }
+ return auth;
+}
+
+static void auth_passdb_init(struct auth_passdb *passdb)
+{
+ passdb_init(passdb->passdb);
+
+ i_assert(passdb->passdb->default_pass_scheme != NULL ||
+ passdb->cache_key == NULL);
+}
+
+static void auth_init(struct auth *auth)
+{
+ struct auth_passdb *passdb;
+ struct auth_userdb *userdb;
+
+ for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next)
+ auth_passdb_init(passdb);
+ for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next)
+ auth_passdb_init(passdb);
+ for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next)
+ userdb_init(userdb->userdb);
+}
+
+static void auth_deinit(struct auth *auth)
+{
+ struct auth_passdb *passdb;
+ struct auth_userdb *userdb;
+
+ for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next)
+ passdb_deinit(passdb->passdb);
+ for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next)
+ passdb_deinit(passdb->passdb);
+ for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next)
+ userdb_deinit(userdb->userdb);
+}
+
+struct auth *auth_find_service(const char *name)
+{
+ struct auth *const *a;
+ unsigned int i, count;
+
+ a = array_get(&auths, &count);
+ if (name != NULL) {
+ for (i = 1; i < count; i++) {
+ if (strcmp(a[i]->service, name) == 0)
+ return a[i];
+ }
+ /* not found. maybe we can instead find a !service */
+ for (i = 1; i < count; i++) {
+ if (a[i]->service[0] == '!' &&
+ strcmp(a[i]->service + 1, name) != 0)
+ return a[i];
+ }
+ }
+ return a[0];
+}
+
+struct auth *auth_default_service(void)
+{
+ struct auth *const *a;
+ unsigned int count;
+
+ a = array_get(&auths, &count);
+ return a[0];
+}
+
+void auths_preinit(const struct auth_settings *set, pool_t pool,
+ const struct mechanisms_register *reg,
+ const char *const *services)
+{
+ struct master_service_settings_output set_output;
+ const struct auth_settings *service_set;
+ struct auth *auth;
+ unsigned int i;
+ const char *not_service = NULL;
+ bool check_default = TRUE;
+
+ auth_event = event_create(NULL);
+ event_set_forced_debug(auth_event, set->debug);
+ event_add_category(auth_event, &event_category_auth);
+ i_array_init(&auths, 8);
+
+ auth = auth_preinit(set, NULL, pool, reg);
+ array_push_back(&auths, &auth);
+
+ for (i = 0; services[i] != NULL; i++) {
+ if (services[i][0] == '!') {
+ if (not_service != NULL) {
+ i_fatal("Can't have multiple protocol "
+ "!services (seen %s and %s)",
+ not_service, services[i]);
+ }
+ not_service = services[i];
+ }
+ service_set = auth_settings_read(services[i], pool,
+ &set_output);
+ auth = auth_preinit(service_set, services[i], pool, reg);
+ array_push_back(&auths, &auth);
+ }
+
+ if (not_service != NULL && str_array_find(services, not_service+1))
+ check_default = FALSE;
+
+ array_foreach_elem(&auths, auth) {
+ if (auth->service != NULL || check_default)
+ auth_mech_list_verify_passdb(auth);
+ }
+}
+
+void auths_init(void)
+{
+ struct auth *auth;
+
+ /* sanity checks */
+ i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USER_IDX].key == 'u');
+ i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USERNAME_IDX].key == 'n');
+ i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX].key == 'd');
+ i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].key == '\0' &&
+ auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].long_key == NULL);
+ i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].key != '\0' ||
+ auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].long_key != NULL);
+
+ array_foreach_elem(&auths, auth)
+ auth_init(auth);
+}
+
+void auths_deinit(void)
+{
+ struct auth *auth;
+
+ array_foreach_elem(&auths, auth)
+ auth_deinit(auth);
+ event_unref(&auth_event);
+}
+
+void auths_free(void)
+{
+ struct auth **auth;
+ unsigned int i, count;
+
+ /* deinit in reverse order, because modules have been allocated by
+ the first auth pool that used them */
+ auth = array_get_modifiable(&auths, &count);
+ for (i = count; i > 0; i--)
+ pool_unref(&auth[i-1]->pool);
+ array_free(&auths);
+}
diff --git a/src/auth/auth.h b/src/auth/auth.h
new file mode 100644
index 0000000..3ca5a9b
--- /dev/null
+++ b/src/auth/auth.h
@@ -0,0 +1,91 @@
+#ifndef AUTH_H
+#define AUTH_H
+
+#include "auth-settings.h"
+
+#define PASSWORD_HIDDEN_STR "<hidden>"
+
+ARRAY_DEFINE_TYPE(auth, struct auth *);
+extern ARRAY_TYPE(auth) auths;
+
+enum auth_passdb_skip {
+ AUTH_PASSDB_SKIP_NEVER,
+ AUTH_PASSDB_SKIP_AUTHENTICATED,
+ AUTH_PASSDB_SKIP_UNAUTHENTICATED
+};
+
+enum auth_userdb_skip {
+ AUTH_USERDB_SKIP_NEVER,
+ AUTH_USERDB_SKIP_FOUND,
+ AUTH_USERDB_SKIP_NOTFOUND
+};
+
+enum auth_db_rule {
+ AUTH_DB_RULE_RETURN,
+ AUTH_DB_RULE_RETURN_OK,
+ AUTH_DB_RULE_RETURN_FAIL,
+ AUTH_DB_RULE_CONTINUE,
+ AUTH_DB_RULE_CONTINUE_OK,
+ AUTH_DB_RULE_CONTINUE_FAIL
+};
+
+struct auth_passdb {
+ struct auth_passdb *next;
+
+ const struct auth_passdb_settings *set;
+ struct passdb_module *passdb;
+
+ /* The caching key for this passdb, or NULL if caching isn't wanted. */
+ const char *cache_key;
+
+ struct passdb_template *default_fields_tmpl;
+ struct passdb_template *override_fields_tmpl;
+
+ enum auth_passdb_skip skip;
+ enum auth_db_rule result_success;
+ enum auth_db_rule result_failure;
+ enum auth_db_rule result_internalfail;
+};
+
+struct auth_userdb {
+ struct auth_userdb *next;
+
+ const struct auth_userdb_settings *set;
+ struct userdb_module *userdb;
+
+ /* The caching key for this userdb, or NULL if caching isn't wanted. */
+ const char *cache_key;
+
+ struct userdb_template *default_fields_tmpl;
+ struct userdb_template *override_fields_tmpl;
+
+ enum auth_userdb_skip skip;
+ enum auth_db_rule result_success;
+ enum auth_db_rule result_failure;
+ enum auth_db_rule result_internalfail;
+};
+
+struct auth {
+ pool_t pool;
+ const char *service;
+ const struct auth_settings *set;
+
+ const struct mechanisms_register *reg;
+ struct auth_passdb *masterdbs;
+ struct auth_passdb *passdbs;
+ struct auth_userdb *userdbs;
+};
+
+extern struct auth_penalty *auth_penalty;
+
+struct auth *auth_find_service(const char *name);
+struct auth *auth_default_service(void);
+
+void auths_preinit(const struct auth_settings *set, pool_t pool,
+ const struct mechanisms_register *reg,
+ const char *const *services);
+void auths_init(void);
+void auths_deinit(void);
+void auths_free(void);
+
+#endif
diff --git a/src/auth/checkpassword-reply.c b/src/auth/checkpassword-reply.c
new file mode 100644
index 0000000..71f231a
--- /dev/null
+++ b/src/auth/checkpassword-reply.c
@@ -0,0 +1,110 @@
+/* simple checkpassword wrapper to send userdb data back to dovecot-auth */
+
+#include "lib.h"
+#include "str.h"
+#include "strescape.h"
+#include "write-full.h"
+
+#include <unistd.h>
+
+int main(void)
+{
+ string_t *str;
+ const char *user, *home, *authorized, *orig_uid_env;
+ const char *extra_env, *key, *value, *const *tmp;
+ bool uid_found = FALSE, gid_found = FALSE;
+ uid_t orig_uid;
+
+ lib_init();
+ str = t_str_new(1024);
+
+ orig_uid_env = getenv("ORIG_UID");
+ if (orig_uid_env == NULL || str_to_uid(orig_uid_env, &orig_uid) < 0)
+ orig_uid = (uid_t)-1;
+
+ /* ORIG_UID should have the auth process's UID that forked us.
+ if the checkpassword changed the UID, this could be a security hole
+ because the UID's other processes can ptrace this process and write
+ any kind of a reply to fd 4. so we can run only if:
+
+ a) INSECURE_SETUID environment is set.
+ b) process isn't ptraceable (this binary is setuid/setgid)
+ c) checkpassword didn't actually change the UID (but used
+ userdb_uid instead)
+ */
+ if (getenv("INSECURE_SETUID") == NULL &&
+ (orig_uid == (uid_t)-1 || orig_uid != getuid()) &&
+ getuid() == geteuid() && getgid() == getegid()) {
+ if (orig_uid_env == NULL) {
+ i_error("checkpassword: ORIG_UID environment was dropped by checkpassword. "
+ "Can't verify if we're safe to run. See "
+ "http://wiki2.dovecot.org/AuthDatabase/CheckPassword#Security");
+ } else {
+ i_error("checkpassword: The checkpassword couldn't be run securely. See "
+ "http://wiki2.dovecot.org/AuthDatabase/CheckPassword#Security");
+ }
+ return 111;
+ }
+
+ user = getenv("USER");
+ if (user != NULL) {
+ if (strchr(user, '\t') != NULL) {
+ i_error("checkpassword: USER contains TAB");
+ return 1;
+ }
+ str_printfa(str, "user=");
+ str_append_tabescaped(str, user);
+ str_append_c(str, '\t');
+ }
+
+ home = getenv("HOME");
+ if (home != NULL) {
+ if (strchr(home, '\t') != NULL) {
+ i_error("checkpassword: HOME contains TAB");
+ return 1;
+ }
+ str_printfa(str, "userdb_home=");
+ str_append_tabescaped(str, home);
+ str_append_c(str, '\t');
+ }
+
+ extra_env = getenv("EXTRA");
+ if (extra_env != NULL) {
+ for (tmp = t_strsplit(extra_env, " "); *tmp != NULL; tmp++) {
+ value = getenv(*tmp);
+ if (value != NULL) {
+ key = t_str_lcase(*tmp);
+ if (strcmp(key, "userdb_uid") == 0)
+ uid_found = TRUE;
+ else if (strcmp(key, "userdb_gid") == 0)
+ gid_found = TRUE;
+ str_append_tabescaped(str, key);
+ str_append_c(str, '=');
+ str_append_tabescaped(str, value);
+ str_append_c(str, '\t');
+ }
+ }
+ }
+ if (!uid_found)
+ str_printfa(str, "userdb_uid=%s\t", dec2str(getuid()));
+ if (!gid_found)
+ str_printfa(str, "userdb_gid=%s\t", dec2str(getgid()));
+
+ i_assert(str_len(str) > 0);
+
+ if (write_full(4, str_data(str), str_len(str)) < 0) {
+ i_error("checkpassword: write_full() failed: %m");
+ lib_exit(111);
+ }
+ authorized = getenv("AUTHORIZED");
+ if (authorized == NULL) {
+ /* authentication */
+ return 0;
+ } else if (strcmp(authorized, "2") == 0) {
+ /* successful passdb/userdb lookup */
+ return 2;
+ } else {
+ i_error("checkpassword: Script doesn't support passdb/userdb lookup");
+ return 111;
+ }
+}
diff --git a/src/auth/crypt-blowfish.c b/src/auth/crypt-blowfish.c
new file mode 100644
index 0000000..460f8cf
--- /dev/null
+++ b/src/auth/crypt-blowfish.c
@@ -0,0 +1,900 @@
+/*
+ * The crypt_blowfish homepage is:
+ *
+ * http://www.openwall.com/crypt/
+ *
+ * This code comes from John the Ripper password cracker, with reentrant
+ * and crypt(3) interfaces added, but optimizations specific to password
+ * cracking removed.
+ *
+ * Written by Solar Designer <solar at openwall.com> in 1998-2014.
+ * No copyright is claimed, and the software is hereby placed in the public
+ * domain. In case this attempt to disclaim copyright and place the software
+ * in the public domain is deemed null and void, then the software is
+ * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the
+ * general public under the following terms:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * There's ABSOLUTELY NO WARRANTY, express or implied.
+ *
+ * It is my intent that you should be able to use this on your system,
+ * as part of a software package, or anywhere else to improve security,
+ * ensure compatibility, or for any other purpose. I would appreciate
+ * it if you give credit where it is due and keep your modifications in
+ * the public domain as well, but I don't require that in order to let
+ * you place this code and any modifications you make under a license
+ * of your choice.
+ *
+ * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix
+ * "$2b$", originally by Niels Provos <provos at citi.umich.edu>, and it uses
+ * some of his ideas. The password hashing algorithm was designed by David
+ * Mazieres <dm at lcs.mit.edu>. For information on the level of
+ * compatibility for bcrypt hash prefixes other than "$2b$", please refer to
+ * the comments in BF_set_key() below and to the included crypt(3) man page.
+ *
+ * There's a paper on the algorithm that explains its design decisions:
+ *
+ * http://www.usenix.org/events/usenix99/provos.html
+ *
+ * Some of the tricks in BF_ROUND might be inspired by Eric Young's
+ * Blowfish library (I can't be sure if I would think of something if I
+ * hadn't seen his code).
+ *
+ * 2017-10-10 - Adapted for dovecot code by Aki Tuomi <aki.tuomi@dovecot.fi>
+ */
+
+#include "lib.h"
+
+#include <errno.h>
+#ifndef __set_errno
+#define __set_errno(val) errno = (val)
+#endif
+
+/* Just to make sure the prototypes match the actual definitions */
+#include "crypt-blowfish.h"
+
+#ifdef __i386__
+#define BF_SCALE 1
+#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__)
+#define BF_SCALE 1
+#else
+#define BF_SCALE 0
+#endif
+
+typedef unsigned int BF_word;
+typedef signed int BF_word_signed;
+
+/* Number of Blowfish rounds, this is also hardcoded into a few places */
+#define BF_N 16
+
+typedef BF_word BF_key[BF_N + 2];
+
+typedef struct {
+ BF_word S[4][0x100];
+ BF_key P;
+} BF_ctx;
+
+/*
+ * Magic IV for 64 Blowfish encryptions that we do at the end.
+ * The string is "OrpheanBeholderScryDoubt" on big-endian.
+ */
+static BF_word BF_magic_w[6] = {
+ 0x4F727068, 0x65616E42, 0x65686F6C,
+ 0x64657253, 0x63727944, 0x6F756274
+};
+
+/*
+ * P-box and S-box tables initialized with digits of Pi.
+ */
+static BF_ctx BF_init_state = {
+ {
+ {
+ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
+ 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
+ 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+ 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
+ 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
+ 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+ 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
+ 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
+ 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+ 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
+ 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
+ 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+ 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
+ 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
+ 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+ 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
+ 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
+ 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+ 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
+ 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
+ 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+ 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
+ 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
+ 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+ 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
+ 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
+ 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+ 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
+ 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
+ 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+ 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
+ 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
+ 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+ 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
+ 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
+ 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+ 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
+ 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
+ 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+ 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
+ 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
+ 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+ 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
+ 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
+ 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+ 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
+ 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
+ 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+ 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
+ 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
+ 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+ 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
+ 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
+ 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+ 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
+ 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
+ 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+ 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
+ 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
+ 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+ 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
+ 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
+ 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+ 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
+ }, {
+ 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
+ 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
+ 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+ 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
+ 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
+ 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+ 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
+ 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
+ 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+ 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
+ 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
+ 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+ 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
+ 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
+ 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+ 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
+ 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
+ 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+ 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
+ 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
+ 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+ 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
+ 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
+ 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+ 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
+ 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
+ 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+ 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
+ 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
+ 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+ 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
+ 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
+ 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+ 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
+ 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
+ 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+ 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
+ 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
+ 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+ 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
+ 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
+ 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+ 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
+ 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
+ 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+ 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
+ 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
+ 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+ 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
+ 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
+ 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+ 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
+ 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
+ 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+ 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
+ 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
+ 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+ 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
+ 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
+ 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+ 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
+ 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
+ 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+ 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
+ }, {
+ 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
+ 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
+ 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+ 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
+ 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
+ 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+ 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
+ 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
+ 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+ 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
+ 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
+ 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+ 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
+ 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
+ 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+ 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
+ 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
+ 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+ 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
+ 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
+ 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+ 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
+ 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
+ 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+ 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
+ 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
+ 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+ 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
+ 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
+ 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+ 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
+ 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
+ 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+ 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
+ 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
+ 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+ 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
+ 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
+ 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+ 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
+ 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
+ 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+ 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
+ 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
+ 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+ 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
+ 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
+ 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+ 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
+ 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
+ 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+ 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
+ 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
+ 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+ 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
+ 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
+ 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+ 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
+ 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
+ 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+ 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
+ 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
+ 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+ 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
+ }, {
+ 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
+ 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
+ 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+ 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
+ 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
+ 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+ 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
+ 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
+ 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+ 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
+ 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
+ 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+ 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
+ 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
+ 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+ 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
+ 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
+ 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+ 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
+ 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
+ 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+ 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
+ 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
+ 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+ 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
+ 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
+ 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+ 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
+ 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
+ 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+ 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
+ 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
+ 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+ 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
+ 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
+ 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+ 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
+ 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
+ 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+ 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
+ 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
+ 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+ 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
+ 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
+ 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+ 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
+ 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
+ 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+ 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
+ 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
+ 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+ 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
+ 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
+ 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+ 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
+ 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
+ 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+ 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
+ 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
+ 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+ 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
+ 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
+ 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+ 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
+ }
+ }, {
+ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
+ 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
+ 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+ 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
+ 0x9216d5d9, 0x8979fb1b
+ }
+};
+
+static unsigned char BF_itoa64[64 + 1] =
+ "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+static unsigned char BF_atoi64[0x60] = {
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1,
+ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64,
+ 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64,
+ 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
+ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64
+};
+
+#define BF_safe_atoi64(dst, src) \
+{ \
+ tmp = (unsigned char)(src); \
+ if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \
+ tmp = BF_atoi64[tmp]; \
+ if (tmp > 63) return -1; \
+ (dst) = tmp; \
+}
+
+static int BF_decode(BF_word *dst, const char *src, size_t size)
+{
+ unsigned char *dptr = (unsigned char *)dst;
+ unsigned char *end = dptr + size;
+ const unsigned char *sptr = (const unsigned char *)src;
+ unsigned int tmp, c1, c2, c3, c4;
+
+ do {
+ BF_safe_atoi64(c1, *sptr++);
+ BF_safe_atoi64(c2, *sptr++);
+ *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4);
+ if (dptr >= end) break;
+
+ BF_safe_atoi64(c3, *sptr++);
+ *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2);
+ if (dptr >= end) break;
+
+ BF_safe_atoi64(c4, *sptr++);
+ *dptr++ = ((c3 & 0x03) << 6) | c4;
+ } while (dptr < end);
+
+ return 0;
+}
+
+static void BF_encode(char *dst, const BF_word *src, size_t size)
+{
+ const unsigned char *sptr = (const unsigned char *)src;
+ const unsigned char *end = sptr + size;
+ unsigned char *dptr = (unsigned char *)dst;
+ unsigned int c1, c2;
+
+ do {
+ c1 = *sptr++;
+ *dptr++ = BF_itoa64[c1 >> 2];
+ c1 = (c1 & 0x03) << 4;
+ if (sptr >= end) {
+ *dptr++ = BF_itoa64[c1];
+ break;
+ }
+
+ c2 = *sptr++;
+ c1 |= c2 >> 4;
+ *dptr++ = BF_itoa64[c1];
+ c1 = (c2 & 0x0f) << 2;
+ if (sptr >= end) {
+ *dptr++ = BF_itoa64[c1];
+ break;
+ }
+
+ c2 = *sptr++;
+ c1 |= c2 >> 6;
+ *dptr++ = BF_itoa64[c1];
+ *dptr++ = BF_itoa64[c2 & 0x3f];
+ } while (sptr < end);
+}
+
+static void ATTR_UNSIGNED_WRAPS
+BF_swap(BF_word *x, int count)
+{
+ static int endianness_check = 1;
+ char *is_little_endian = (char *)&endianness_check;
+ BF_word tmp;
+
+ if (*is_little_endian != 0)
+ do {
+ tmp = *x;
+ tmp = (tmp << 16) | (tmp >> 16);
+ *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF);
+ } while (--count > 0);
+}
+
+#if BF_SCALE
+/* Architectures which can shift addresses left by 2 bits with no extra cost */
+#define BF_ROUND(L, R, N) \
+ tmp1 = L & 0xFF; \
+ tmp2 = L >> 8; \
+ tmp2 &= 0xFF; \
+ tmp3 = L >> 16; \
+ tmp3 &= 0xFF; \
+ tmp4 = L >> 24; \
+ tmp1 = data.ctx.S[3][tmp1]; \
+ tmp2 = data.ctx.S[2][tmp2]; \
+ tmp3 = data.ctx.S[1][tmp3]; \
+ tmp3 += data.ctx.S[0][tmp4]; \
+ tmp3 ^= tmp2; \
+ R ^= data.ctx.P[N + 1]; \
+ tmp3 += tmp1; \
+ R ^= tmp3;
+#else
+/* Architectures with no complicated addressing modes supported */
+#define BF_INDEX(S, i) \
+ (*((BF_word *)(((unsigned char *)S) + (i))))
+#define BF_ROUND(L, R, N) \
+ tmp1 = L & 0xFF; \
+ tmp1 <<= 2; \
+ tmp2 = L >> 6; \
+ tmp2 &= 0x3FC; \
+ tmp3 = L >> 14; \
+ tmp3 &= 0x3FC; \
+ tmp4 = L >> 22; \
+ tmp4 &= 0x3FC; \
+ tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \
+ tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \
+ tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \
+ tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \
+ tmp3 ^= tmp2; \
+ R ^= data.ctx.P[N + 1]; \
+ tmp3 += tmp1; \
+ R ^= tmp3;
+#endif
+
+/*
+ * Encrypt one block, BF_N is hardcoded here.
+ */
+#define BF_ENCRYPT \
+ L ^= data.ctx.P[0]; \
+ BF_ROUND(L, R, 0); \
+ BF_ROUND(R, L, 1); \
+ BF_ROUND(L, R, 2); \
+ BF_ROUND(R, L, 3); \
+ BF_ROUND(L, R, 4); \
+ BF_ROUND(R, L, 5); \
+ BF_ROUND(L, R, 6); \
+ BF_ROUND(R, L, 7); \
+ BF_ROUND(L, R, 8); \
+ BF_ROUND(R, L, 9); \
+ BF_ROUND(L, R, 10); \
+ BF_ROUND(R, L, 11); \
+ BF_ROUND(L, R, 12); \
+ BF_ROUND(R, L, 13); \
+ BF_ROUND(L, R, 14); \
+ BF_ROUND(R, L, 15); \
+ tmp4 = R; \
+ R = L; \
+ L = tmp4 ^ data.ctx.P[BF_N + 1];
+
+#define BF_body() \
+ L = R = 0; \
+ ptr = data.ctx.P; \
+ do { \
+ ptr += 2; \
+ BF_ENCRYPT; \
+ *(ptr - 2) = L; \
+ *(ptr - 1) = R; \
+ } while (ptr < &data.ctx.P[BF_N + 2]); \
+\
+ ptr = data.ctx.S[0]; \
+ do { \
+ ptr += 2; \
+ BF_ENCRYPT; \
+ *(ptr - 2) = L; \
+ *(ptr - 1) = R; \
+ } while (ptr < &data.ctx.S[3][0xFF]);
+
+static void ATTR_UNSIGNED_WRAPS
+BF_set_key(const char *key, BF_key expanded, BF_key initial,
+ unsigned char flags)
+{
+ const char *ptr = key;
+ unsigned int bug, i, j;
+ BF_word safety, sign, diff, tmp[2];
+
+/*
+ * There was a sign extension bug in older revisions of this function. While
+ * we would have liked to simply fix the bug and move on, we have to provide
+ * a backwards compatibility feature (essentially the bug) for some systems and
+ * a safety measure for some others. The latter is needed because for certain
+ * multiple inputs to the buggy algorithm there exist easily found inputs to
+ * the correct algorithm that produce the same hash. Thus, we optionally
+ * deviate from the correct algorithm just enough to avoid such collisions.
+ * While the bug itself affected the majority of passwords containing
+ * characters with the 8th bit set (although only a percentage of those in a
+ * collision-producing way), the anti-collision safety measure affects
+ * only a subset of passwords containing the '\xff' character (not even all of
+ * those passwords, just some of them). This character is not found in valid
+ * UTF-8 sequences and is rarely used in popular 8-bit character encodings.
+ * Thus, the safety measure is unlikely to cause much annoyance, and is a
+ * reasonable tradeoff to use when authenticating against existing hashes that
+ * are not reliably known to have been computed with the correct algorithm.
+ *
+ * We use an approach that tries to minimize side-channel leaks of password
+ * information - that is, we mostly use fixed-cost bitwise operations instead
+ * of branches or table lookups. (One conditional branch based on password
+ * length remains. It is not part of the bug aftermath, though, and is
+ * difficult and possibly unreasonable to avoid given the use of C strings by
+ * the caller, which results in similar timing leaks anyway.)
+ *
+ * For actual implementation, we set an array index in the variable "bug"
+ * (0 means no bug, 1 means sign extension bug emulation) and a flag in the
+ * variable "safety" (bit 16 is set when the safety measure is requested).
+ * Valid combinations of settings are:
+ *
+ * Prefix "$2a$": bug = 0, safety = 0x10000
+ * Prefix "$2b$": bug = 0, safety = 0
+ * Prefix "$2x$": bug = 1, safety = 0
+ * Prefix "$2y$": bug = 0, safety = 0
+ */
+ bug = (unsigned int)flags & 1;
+ safety = ((BF_word)flags & 2) << 15;
+
+ sign = diff = 0;
+
+ for (i = 0; i < BF_N + 2; i++) {
+ tmp[0] = tmp[1] = 0;
+ for (j = 0; j < 4; j++) {
+ tmp[0] <<= 8;
+ tmp[0] |= (unsigned char)*ptr; /* correct */
+ tmp[1] <<= 8;
+ tmp[1] |= (BF_word)(signed char)*ptr; /* bug */
+/*
+ * Sign extension in the first char has no effect - nothing to overwrite yet,
+ * and those extra 24 bits will be fully shifted out of the 32-bit word. For
+ * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign
+ * extension in tmp[1] occurs. Once this flag is set, it remains set.
+ */
+ if (j != 0)
+ sign |= tmp[1] & 0x80;
+ if (*ptr == '\0')
+ ptr = key;
+ else
+ ptr++;
+ }
+ diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */
+
+ expanded[i] = tmp[bug];
+ initial[i] = BF_init_state.P[i] ^ tmp[bug];
+ }
+
+/*
+ * At this point, "diff" is zero iff the correct and buggy algorithms produced
+ * exactly the same result. If so and if "sign" is non-zero, which indicates
+ * that there was a non-benign sign extension, this means that we have a
+ * collision between the correctly computed hash for this password and a set of
+ * passwords that could be supplied to the buggy algorithm. Our safety measure
+ * is meant to protect from such many-buggy to one-correct collisions, by
+ * deviating from the correct algorithm in such cases. Let's check for this.
+ */
+ diff |= diff >> 16; /* still zero iff exact match */
+ diff &= 0xffff; /* ditto */
+ diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */
+ sign <<= 9; /* move the non-benign sign extension flag to bit 16 */
+ sign &= ~diff & safety; /* action needed? */
+
+/*
+ * If we have determined that we need to deviate from the correct algorithm,
+ * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but
+ * let's stick to it now. It came out of the approach we used above, and it's
+ * not any worse than any other choice we could make.)
+ *
+ * It is crucial that we don't do the same to the expanded key used in the main
+ * Eksblowfish loop. By doing it to only one of these two, we deviate from a
+ * state that could be directly specified by a password to the buggy algorithm
+ * (and to the fully correct one as well, but that's a side-effect).
+ */
+ initial[0] ^= sign;
+}
+
+static const unsigned char flags_by_subtype[26] =
+ {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0};
+
+static char * ATTR_NO_SANITIZE_UNDEFINED ATTR_NO_SANITIZE_INTEGER
+ ATTR_NO_SANITIZE_IMPLICIT_CONVERSION
+BF_crypt(const char *key, const char *setting,
+ char *output, size_t size,
+ BF_word min)
+{
+ struct {
+ BF_ctx ctx;
+ BF_key expanded_key;
+ union {
+ BF_word salt[4];
+ BF_word output[6];
+ } binary;
+ } data;
+ BF_word L, R;
+ BF_word tmp1, tmp2, tmp3, tmp4;
+ BF_word *ptr;
+ BF_word count;
+ int i;
+
+ if (size < 7 + 22 + 31 + 1) {
+ __set_errno(ERANGE);
+ return NULL;
+ }
+
+ if (setting[0] != '$' ||
+ setting[1] != '2' ||
+ setting[2] < 'a' || setting[2] > 'z' ||
+ flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] == 0 ||
+ setting[3] != '$' ||
+ setting[4] < '0' || setting[4] > '3' ||
+ setting[5] < '0' || setting[5] > '9' ||
+ (setting[4] == '3' && setting[5] > '1') ||
+ setting[6] != '$') {
+ __set_errno(EINVAL);
+ return NULL;
+ }
+
+ count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0'));
+ if (count < min || BF_decode(data.binary.salt, &setting[7], 16) != 0) {
+ __set_errno(EINVAL);
+ return NULL;
+ }
+ BF_swap(data.binary.salt, 4);
+
+ BF_set_key(key, data.expanded_key, data.ctx.P,
+ flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']);
+
+ memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S));
+
+ L = R = 0;
+ for (i = 0; i < BF_N + 2; i += 2) {
+ L ^= data.binary.salt[i & 2];
+ R ^= data.binary.salt[(i & 2) + 1];
+ BF_ENCRYPT;
+ data.ctx.P[i] = L;
+ data.ctx.P[i + 1] = R;
+ }
+
+ ptr = data.ctx.S[0];
+ do {
+ ptr += 4;
+ L ^= data.binary.salt[(BF_N + 2) & 3];
+ R ^= data.binary.salt[(BF_N + 3) & 3];
+ BF_ENCRYPT;
+ *(ptr - 4) = L;
+ *(ptr - 3) = R;
+
+ L ^= data.binary.salt[(BF_N + 4) & 3];
+ R ^= data.binary.salt[(BF_N + 5) & 3];
+ BF_ENCRYPT;
+ *(ptr - 2) = L;
+ *(ptr - 1) = R;
+ } while (ptr < &data.ctx.S[3][0xFF]);
+
+ do {
+ bool done;
+
+ for (i = 0; i < BF_N + 2; i += 2) {
+ data.ctx.P[i] ^= data.expanded_key[i];
+ data.ctx.P[i + 1] ^= data.expanded_key[i + 1];
+ }
+
+ done = FALSE;
+ do {
+ BF_body();
+ if (done)
+ break;
+ done = TRUE;
+
+ tmp1 = data.binary.salt[0];
+ tmp2 = data.binary.salt[1];
+ tmp3 = data.binary.salt[2];
+ tmp4 = data.binary.salt[3];
+ for (i = 0; i < BF_N; i += 4) {
+ data.ctx.P[i] ^= tmp1;
+ data.ctx.P[i + 1] ^= tmp2;
+ data.ctx.P[i + 2] ^= tmp3;
+ data.ctx.P[i + 3] ^= tmp4;
+ }
+ data.ctx.P[16] ^= tmp1;
+ data.ctx.P[17] ^= tmp2;
+ } while (TRUE);
+ } while (--count > 0);
+
+ for (i = 0; i < 6; i += 2) {
+ L = BF_magic_w[i];
+ R = BF_magic_w[i + 1];
+
+ count = 64;
+ do {
+ BF_ENCRYPT;
+ } while (--count > 0);
+
+ data.binary.output[i] = L;
+ data.binary.output[i + 1] = R;
+ }
+
+ memcpy(output, setting, 7 + 22 - 1);
+ output[7 + 22 - 1] = BF_itoa64[(int)
+ BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30];
+
+/* This has to be bug-compatible with the original implementation, so
+ * only encode 23 of the 24 bytes. :-) */
+ BF_swap(data.binary.output, 6);
+ BF_encode(&output[7 + 22], data.binary.output, 23);
+ output[7 + 22 + 31] = '\0';
+
+ return output;
+}
+
+int crypt_output_magic(const char *setting, char *output, size_t size)
+{
+ if (size < 3)
+ return -1;
+
+ output[0] = '*';
+ output[1] = '0';
+ output[2] = '\0';
+
+ if (setting[0] == '*' && setting[1] == '0')
+ output[1] = '1';
+
+ return 0;
+}
+
+/*
+ * Please preserve the runtime self-test. It serves two purposes at once:
+ *
+ * 1. We really can't afford the risk of producing incompatible hashes e.g.
+ * when there's something like gcc bug 26587 again, whereas an application or
+ * library integrating this code might not also integrate our external tests or
+ * it might not run them after every build. Even if it does, the miscompile
+ * might only occur on the production build, but not on a testing build (such
+ * as because of different optimization settings). It is painful to recover
+ * from incorrectly-computed hashes - merely fixing whatever broke is not
+ * enough. Thus, a proactive measure like this self-test is needed.
+ *
+ * 2. We don't want to leave sensitive data from our actual password hash
+ * computation on the stack or in registers. Previous revisions of the code
+ * would do explicit cleanups, but simply running the self-test after hash
+ * computation is more reliable.
+ *
+ * The performance cost of this quick self-test is around 0.6% at the "$2a$08"
+ * setting.
+ */
+char *crypt_blowfish_rn(const char *key, const char *setting,
+ char *output, size_t size)
+{
+ const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8";
+ const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu";
+ static const char * const test_hashes[2] =
+ {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */
+ "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */
+ const char *test_hash = test_hashes[0];
+ char *retval;
+ const char *p;
+ int save_errno;
+ bool ok;
+ struct {
+ char s[7 + 22 + 1];
+ char o[7 + 22 + 31 + 1 + 1 + 1];
+ } buf;
+
+/* Hash the supplied password */
+ crypt_output_magic(setting, output, size);
+ retval = BF_crypt(key, setting, output, size, 16);
+ save_errno = errno;
+
+/*
+ * Do a quick self-test. It is important that we make both calls to BF_crypt()
+ * from the same scope such that they likely use the same stack locations,
+ * which makes the second call overwrite the first call's sensitive data on the
+ * stack and makes it more likely that any alignment related issues would be
+ * detected by the self-test.
+ */
+ memcpy(buf.s, test_setting, sizeof(buf.s));
+ if (retval != NULL) {
+ unsigned int flags = flags_by_subtype[
+ (unsigned int)(unsigned char)setting[2] - 'a'];
+ test_hash = test_hashes[flags & 1];
+ buf.s[2] = setting[2];
+ }
+ memset(buf.o, 0x55, sizeof(buf.o));
+ buf.o[sizeof(buf.o) - 1] = 0;
+ p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1);
+
+ ok = (p == buf.o &&
+ memcmp(p, buf.s, 7 + 22) == 0 &&
+ memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1) == 0);
+
+ {
+ const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345";
+ BF_key ae, ai, ye, yi;
+ BF_set_key(k, ae, ai, 2); /* $2a$ */
+ BF_set_key(k, ye, yi, 4); /* $2y$ */
+ ai[0] ^= 0x10000; /* undo the safety (for comparison) */
+ ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 &&
+ memcmp(ae, ye, sizeof(ae)) == 0 &&
+ memcmp(ai, yi, sizeof(ai)) == 0;
+ }
+
+ __set_errno(save_errno);
+ if (ok)
+ return retval;
+
+ i_unreached();
+}
+
+char *crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count,
+ const char *input, size_t size, char *output, size_t output_size)
+{
+ if (size < 16 || output_size < 7 + 22 + 1 ||
+ (count > 0 && (count < 4 || count > 31)) ||
+ prefix[0] != '$' || prefix[1] != '2' ||
+ (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) {
+ if (output_size > 0) output[0] = '\0';
+ __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL);
+ return NULL;
+ }
+
+ if (count == 0) count = 5;
+
+ output[0] = '$';
+ output[1] = '2';
+ output[2] = prefix[2];
+ output[3] = '$';
+ output[4] = '0' + count / 10;
+ output[5] = '0' + count % 10;
+ output[6] = '$';
+
+ BF_encode(&output[7], (const BF_word *)input, 16);
+ output[7 + 22] = '\0';
+
+ return output;
+}
diff --git a/src/auth/crypt-blowfish.h b/src/auth/crypt-blowfish.h
new file mode 100644
index 0000000..e7123b3
--- /dev/null
+++ b/src/auth/crypt-blowfish.h
@@ -0,0 +1,29 @@
+/*
+ * Written by Solar Designer <solar at openwall.com> in 2000-2011.
+ * No copyright is claimed, and the software is hereby placed in the public
+ * domain. In case this attempt to disclaim copyright and place the software
+ * in the public domain is deemed null and void, then the software is
+ * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
+ * general public under the following terms:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * There's ABSOLUTELY NO WARRANTY, express or implied.
+ *
+ * See crypt_blowfish.c for more information.
+ *
+ * 2017-10-10 - Adapted for dovecot code by Aki Tuomi <aki.tuomi@dovecot.fi>
+ */
+
+#ifndef CRYPT_BLOWFISH_H
+#define CRYPT_BLOWFISH_H
+
+extern int crypt_output_magic(const char *setting, char *output, size_t size);
+extern char *crypt_blowfish_rn(const char *key, const char *setting,
+ char *output, size_t size);
+extern char *crypt_gensalt_blowfish_rn(const char *prefix,
+ unsigned long count,
+ const char *input, size_t size, char *output, size_t output_size);
+
+#endif
diff --git a/src/auth/db-checkpassword.c b/src/auth/db-checkpassword.c
new file mode 100644
index 0000000..7d52eef
--- /dev/null
+++ b/src/auth/db-checkpassword.c
@@ -0,0 +1,563 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+
+#if defined(PASSDB_CHECKPASSWORD) || defined(USERDB_CHECKPASSWORD)
+
+#include "lib-signals.h"
+#include "array.h"
+#include "buffer.h"
+#include "str.h"
+#include "ioloop.h"
+#include "hash.h"
+#include "execv-const.h"
+#include "env-util.h"
+#include "safe-memset.h"
+#include "strescape.h"
+#include "child-wait.h"
+#include "db-checkpassword.h"
+
+#include <unistd.h>
+#include <sys/wait.h>
+
+#define CHECKPASSWORD_MAX_REQUEST_LEN 512
+
+struct chkpw_auth_request {
+ struct db_checkpassword *db;
+ struct auth_request *request;
+ char *auth_password;
+
+ db_checkpassword_callback_t *callback;
+ void (*request_callback)();
+
+ pid_t pid;
+ int fd_out, fd_in;
+ struct io *io_out, *io_in;
+
+ string_t *input_buf;
+ size_t output_pos, output_len;
+
+ int exit_status;
+ bool exited:1;
+};
+
+struct db_checkpassword {
+ char *checkpassword_path, *checkpassword_reply_path;
+
+ HASH_TABLE(void *, struct chkpw_auth_request *) clients;
+ struct child_wait *child_wait;
+};
+
+static void
+env_put_extra_fields(const ARRAY_TYPE(auth_field) *extra_fields)
+{
+ const struct auth_field *field;
+ const char *key, *value;
+
+ array_foreach(extra_fields, field) {
+ key = t_str_ucase(field->key);
+ value = field->value != NULL ? field->value : "1";
+ env_put(key, value);
+ }
+}
+
+static void checkpassword_request_close(struct chkpw_auth_request *request)
+{
+ io_remove(&request->io_in);
+ io_remove(&request->io_out);
+
+ i_close_fd(&request->fd_in);
+ i_close_fd(&request->fd_out);
+}
+
+static void checkpassword_request_free(struct chkpw_auth_request **_request)
+{
+ struct chkpw_auth_request *request = *_request;
+
+ *_request = NULL;
+
+ if (!request->exited) {
+ hash_table_remove(request->db->clients,
+ POINTER_CAST(request->pid));
+ child_wait_remove_pid(request->db->child_wait, request->pid);
+ }
+ checkpassword_request_close(request);
+
+ if (request->auth_password != NULL) {
+ safe_memset(request->auth_password, 0,
+ strlen(request->auth_password));
+ i_free(request->auth_password);
+ }
+ auth_request_unref(&request->request);
+ str_free(&request->input_buf);
+ i_free(request);
+}
+
+static void checkpassword_finish(struct chkpw_auth_request **_request,
+ enum db_checkpassword_status status)
+{
+ struct chkpw_auth_request *request = *_request;
+ const char *const *extra_fields;
+
+ *_request = NULL;
+
+ extra_fields = t_strsplit_tabescaped(str_c(request->input_buf));
+ request->callback(request->request, status, extra_fields,
+ request->request_callback);
+ checkpassword_request_free(&request);
+}
+
+static void checkpassword_internal_failure(struct chkpw_auth_request **request)
+{
+ checkpassword_finish(request, DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE);
+}
+
+static void
+checkpassword_request_finish_auth(struct chkpw_auth_request *request)
+{
+ switch (request->exit_status) {
+ /* standard checkpassword exit codes: */
+ case 1:
+ e_info(authdb_event(request->request),
+ "Login failed (status=%d)",
+ request->exit_status);
+ checkpassword_finish(&request, DB_CHECKPASSWORD_STATUS_FAILURE);
+ break;
+ case 0:
+ if (request->input_buf->used == 0) {
+ e_error(authdb_event(request->request),
+ "Received no input");
+ checkpassword_internal_failure(&request);
+ break;
+ }
+ checkpassword_finish(&request, DB_CHECKPASSWORD_STATUS_OK);
+ break;
+ case 2:
+ /* checkpassword is called with wrong parameters? unlikely */
+ e_error(authdb_event(request->request),
+ "Child %s exited with status 2 (tried to use "
+ "userdb-only checkpassword program for passdb?)",
+ dec2str(request->pid));
+ checkpassword_internal_failure(&request);
+ break;
+ case 111:
+ /* temporary problem, treat as internal error */
+ default:
+ /* whatever error.. */
+ e_error(authdb_event(request->request),
+ "Child %s exited with status %d",
+ dec2str(request->pid), request->exit_status);
+ checkpassword_internal_failure(&request);
+ break;
+ }
+}
+
+static void
+checkpassword_request_finish_lookup(struct chkpw_auth_request *request)
+{
+ switch (request->exit_status) {
+ case 3:
+ /* User does not exist. */
+ e_info(authdb_event(request->request),
+ "User unknown");
+ checkpassword_finish(&request, DB_CHECKPASSWORD_STATUS_FAILURE);
+ break;
+ case 2:
+ /* This is intentionally not 0. checkpassword-reply exits with
+ 2 on success when AUTHORIZED is set. */
+ if (request->input_buf->used == 0) {
+ e_error(authdb_event(request->request),
+ "Received no input");
+ checkpassword_internal_failure(&request);
+ break;
+ }
+ checkpassword_finish(&request, DB_CHECKPASSWORD_STATUS_OK);
+ break;
+ default:
+ /* whatever error... */
+ e_error(authdb_event(request->request),
+ "Child %s exited with status %d",
+ dec2str(request->pid), request->exit_status);
+ checkpassword_internal_failure(&request);
+ break;
+ }
+}
+
+static void
+checkpassword_request_half_finish(struct chkpw_auth_request *request)
+{
+ /* the process must have exited, and the input fd must have closed */
+ if (!request->exited || request->fd_in != -1)
+ return;
+
+ if (request->auth_password != NULL)
+ checkpassword_request_finish_auth(request);
+ else
+ checkpassword_request_finish_lookup(request);
+}
+
+static void env_put_auth_vars(struct auth_request *request)
+{
+ const struct var_expand_table *tab;
+ unsigned int i;
+
+ tab = auth_request_get_var_expand_table(request, NULL);
+ for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) {
+ /* avoid keeping passwords in environment .. just in case
+ an attacker might find it from there. environment is no
+ longer world-readable in modern OSes, but maybe the attacker
+ could be running with the same UID. of course then the
+ attacker could usually ptrace() the process, except that is
+ disabled on some secured systems. so, although I find it
+ highly unlikely anyone could actually attack Dovecot this
+ way in a real system, be safe just in case. besides, lets
+ try to keep at least minimally compatible with the
+ checkpassword API. */
+ if (tab[i].long_key != NULL && tab[i].value != NULL &&
+ strcasecmp(tab[i].long_key, "password") != 0) {
+ env_put(t_strdup_printf("AUTH_%s",
+ t_str_ucase(tab[i].long_key)),
+ tab[i].value);
+ }
+ }
+}
+
+static void checkpassword_setup_env(struct auth_request *request)
+{
+ const struct auth_request_fields *fields = &request->fields;
+
+ /* Besides passing the standard username and password in a
+ pipe, also pass some other possibly interesting information
+ via environment. Use UCSPI names for local/remote IPs. */
+ env_put("PROTO", "TCP"); /* UCSPI */
+ env_put("ORIG_UID", dec2str(getuid()));
+ env_put("SERVICE", fields->service);
+ if (fields->local_ip.family != 0) {
+ env_put("TCPLOCALIP", net_ip2addr(&fields->local_ip));
+ /* FIXME: for backwards compatibility only,
+ remove some day */
+ env_put("LOCAL_IP", net_ip2addr(&fields->local_ip));
+ }
+ if (fields->remote_ip.family != 0) {
+ env_put("TCPREMOTEIP", net_ip2addr(&fields->remote_ip));
+ /* FIXME: for backwards compatibility only,
+ remove some day */
+ env_put("REMOTE_IP", net_ip2addr(&fields->remote_ip));
+ }
+ if (fields->local_port != 0)
+ env_put("TCPLOCALPORT", dec2str(fields->local_port));
+ if (fields->remote_port != 0)
+ env_put("TCPREMOTEPORT", dec2str(fields->remote_port));
+ if (fields->master_user != NULL)
+ env_put("MASTER_USER", fields->master_user);
+ if (!auth_fields_is_empty(fields->extra_fields)) {
+ const ARRAY_TYPE(auth_field) *extra_fields =
+ auth_fields_export(fields->extra_fields);
+
+ /* extra fields could come from master db */
+ env_put_extra_fields(extra_fields);
+ }
+ env_put_auth_vars(request);
+}
+
+static const char *
+checkpassword_get_cmd(struct auth_request *request, const char *args,
+ const char *checkpassword_reply_path)
+{
+ string_t *str;
+ const char *error;
+
+ str = t_str_new(256);
+ if (auth_request_var_expand(str, args, request, NULL, &error) <= 0) {
+ e_error(authdb_event(request),
+ "Failed to expand checkpassword_path=%s: %s",
+ args, error);
+ }
+
+ return t_strconcat(str_c(str), " ", checkpassword_reply_path, NULL);
+}
+
+static void checkpassword_child_input(struct chkpw_auth_request *request)
+{
+ unsigned char buf[1024];
+ ssize_t ret;
+
+ ret = read(request->fd_in, buf, sizeof(buf));
+ if (ret > 0) {
+ str_append_data(request->input_buf, buf, ret);
+ return;
+ }
+
+ if (ret < 0) {
+ e_error(authdb_event(request->request),
+ "read() failed: %m");
+ checkpassword_internal_failure(&request);
+ } else if (memchr(str_data(request->input_buf), '\0',
+ str_len(request->input_buf)) != NULL) {
+ e_error(authdb_event(request->request),
+ "NUL characters in checkpassword reply");
+ checkpassword_internal_failure(&request);
+ } else if (strchr(str_c(request->input_buf), '\n') != NULL) {
+ e_error(authdb_event(request->request),
+ "LF characters in checkpassword reply");
+ checkpassword_internal_failure(&request);
+ } else {
+ e_debug(authdb_event(request->request),
+ "Received input: %s", str_c(request->input_buf));
+ checkpassword_request_close(request);
+ checkpassword_request_half_finish(request);
+ }
+}
+
+static void checkpassword_child_output(struct chkpw_auth_request *request)
+{
+ /* Send: username \0 password \0 timestamp \0.
+ Must be 512 bytes or less. The "timestamp" parameter is actually
+ useful only for APOP authentication. We don't support it, so
+ keep it empty */
+ struct auth_request *auth_request = request->request;
+ buffer_t *buf;
+ const unsigned char *data;
+ size_t size;
+ ssize_t ret;
+
+ buf = t_buffer_create(CHECKPASSWORD_MAX_REQUEST_LEN);
+ buffer_append(buf, auth_request->fields.user,
+ strlen(auth_request->fields.user)+1);
+ if (request->auth_password != NULL) {
+ buffer_append(buf, request->auth_password,
+ strlen(request->auth_password)+1);
+ } else {
+ buffer_append_c(buf, '\0');
+ }
+ buffer_append_c(buf, '\0');
+ data = buffer_get_data(buf, &size);
+
+ i_assert(size == request->output_len);
+ /* already checked this */
+ i_assert(size <= CHECKPASSWORD_MAX_REQUEST_LEN);
+
+ ret = write(request->fd_out, data + request->output_pos,
+ size - request->output_pos);
+ if (ret <= 0) {
+ if (ret < 0) {
+ e_error(authdb_event(request->request),
+ "write() failed: %m");
+ } else {
+ e_error(authdb_event(request->request),
+ "write() returned 0");
+ }
+ checkpassword_internal_failure(&request);
+ return;
+ }
+
+ request->output_pos += ret;
+ if (request->output_pos < size)
+ return;
+
+ /* finished sending the data */
+ io_remove(&request->io_out);
+
+ if (close(request->fd_out) < 0)
+ e_error(authdb_event(request->request), "close() failed: %m");
+ request->fd_out = -1;
+}
+
+static void ATTR_NORETURN
+checkpassword_exec(struct db_checkpassword *db, struct auth_request *request,
+ int fd_in, int fd_out, bool authenticate)
+{
+ const char *cmd, *const *args;
+
+ /* fd 3 is used to send the username+password for the script
+ fd 4 is used to communicate with checkpassword-reply */
+ if (dup2(fd_out, 3) < 0 || dup2(fd_in, 4) < 0) {
+ e_error(authdb_event(request),
+ "dup2() failed: %m");
+ lib_exit(111);
+ }
+
+ if (!authenticate) {
+ /* We want to retrieve passdb/userdb data and don't do
+ authorization, so we need to signalize the
+ checkpassword program that the password shall be
+ ignored by setting AUTHORIZED. This needs a
+ special checkpassword program which knows how to
+ handle this. */
+ env_put("AUTHORIZED", "1");
+ if (request->wanted_credentials_scheme != NULL) {
+ /* passdb credentials lookup */
+ env_put("CREDENTIALS_LOOKUP", "1");
+ env_put("SCHEME", request->wanted_credentials_scheme);
+ }
+ }
+ checkpassword_setup_env(request);
+ cmd = checkpassword_get_cmd(request, db->checkpassword_path,
+ db->checkpassword_reply_path);
+ e_debug(authdb_event(request), "execute: %s", cmd);
+
+ /* very simple argument splitting. */
+ args = t_strsplit(cmd, " ");
+ execv_const(args[0], args);
+}
+
+static void sigchld_handler(const struct child_wait_status *status,
+ struct db_checkpassword *db)
+{
+ struct chkpw_auth_request *request =
+ hash_table_lookup(db->clients, POINTER_CAST(status->pid));
+
+ i_assert(request != NULL);
+
+ hash_table_remove(db->clients, POINTER_CAST(status->pid));
+ request->exited = TRUE;
+
+ if (WIFSIGNALED(status->status)) {
+ e_error(authdb_event(request->request),
+ "Child %s died with signal %d",
+ dec2str(status->pid), WTERMSIG(status->status));
+ checkpassword_internal_failure(&request);
+ } else if (WIFEXITED(status->status)) {
+ request->exit_status = WEXITSTATUS(status->status);
+
+ e_debug(authdb_event(request->request),
+ "exit_status=%d", request->exit_status);
+ checkpassword_request_half_finish(request);
+ } else {
+ /* shouldn't happen */
+ e_debug(authdb_event(request->request),
+ "Child %s exited with status=%d",
+ dec2str(status->pid), status->status);
+ checkpassword_internal_failure(&request);
+ }
+}
+
+void db_checkpassword_call(struct db_checkpassword *db,
+ struct auth_request *request,
+ const char *auth_password,
+ db_checkpassword_callback_t *callback,
+ void (*request_callback)())
+{
+ struct chkpw_auth_request *chkpw_auth_request;
+ size_t output_len;
+ int fd_in[2], fd_out[2];
+ pid_t pid;
+
+ /* <username> \0 <password> \0 timestamp \0 */
+ output_len = strlen(request->fields.user) + 3;
+ if (auth_password != NULL)
+ output_len += strlen(auth_password);
+ if (output_len > CHECKPASSWORD_MAX_REQUEST_LEN) {
+ e_info(authdb_event(request),
+ "Username+password combination too long (%zu bytes)",
+ output_len);
+ callback(request, DB_CHECKPASSWORD_STATUS_FAILURE,
+ NULL, request_callback);
+ return;
+ }
+
+ fd_in[0] = -1;
+ if (pipe(fd_in) < 0 || pipe(fd_out) < 0) {
+ e_error(authdb_event(request),
+ "pipe() failed: %m");
+ if (fd_in[0] != -1) {
+ i_close_fd(&fd_in[0]);
+ i_close_fd(&fd_in[1]);
+ }
+ callback(request, DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE,
+ NULL, request_callback);
+ return;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ e_error(authdb_event(request),
+ "fork() failed: %m");
+ i_close_fd(&fd_in[0]);
+ i_close_fd(&fd_in[1]);
+ i_close_fd(&fd_out[0]);
+ i_close_fd(&fd_out[1]);
+ callback(request, DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE,
+ NULL, request_callback);
+ return;
+ }
+
+ if (pid == 0) {
+ /* child */
+ i_close_fd(&fd_in[0]);
+ i_close_fd(&fd_out[1]);
+ checkpassword_exec(db, request, fd_in[1], fd_out[0],
+ auth_password != NULL);
+ /* not reached */
+ }
+
+ if (close(fd_in[1]) < 0) {
+ e_error(authdb_event(request),
+ "close(fd_in[1]) failed: %m");
+ }
+ if (close(fd_out[0]) < 0) {
+ e_error(authdb_event(request),
+ "close(fd_out[0]) failed: %m");
+ }
+
+ auth_request_ref(request);
+ chkpw_auth_request = i_new(struct chkpw_auth_request, 1);
+ chkpw_auth_request->db = db;
+ chkpw_auth_request->pid = pid;
+ chkpw_auth_request->fd_in = fd_in[0];
+ chkpw_auth_request->fd_out = fd_out[1];
+ chkpw_auth_request->auth_password = i_strdup(auth_password);
+ chkpw_auth_request->request = request;
+ chkpw_auth_request->output_len = output_len;
+ chkpw_auth_request->input_buf = str_new(default_pool, 256);
+ chkpw_auth_request->callback = callback;
+ chkpw_auth_request->request_callback = request_callback;
+
+ chkpw_auth_request->io_in =
+ io_add(fd_in[0], IO_READ, checkpassword_child_input,
+ chkpw_auth_request);
+ chkpw_auth_request->io_out =
+ io_add(fd_out[1], IO_WRITE, checkpassword_child_output,
+ chkpw_auth_request);
+
+ hash_table_insert(db->clients, POINTER_CAST(pid), chkpw_auth_request);
+ child_wait_add_pid(db->child_wait, pid);
+}
+
+struct db_checkpassword *
+db_checkpassword_init(const char *checkpassword_path,
+ const char *checkpassword_reply_path)
+{
+ struct db_checkpassword *db;
+
+ db = i_new(struct db_checkpassword, 1);
+ db->checkpassword_path = i_strdup(checkpassword_path);
+ db->checkpassword_reply_path = i_strdup(checkpassword_reply_path);
+ hash_table_create_direct(&db->clients, default_pool, 0);
+ db->child_wait =
+ child_wait_new_with_pid((pid_t)-1, sigchld_handler, db);
+ return db;
+}
+
+void db_checkpassword_deinit(struct db_checkpassword **_db)
+{
+ struct db_checkpassword *db = *_db;
+ struct hash_iterate_context *iter;
+ void *key;
+ struct chkpw_auth_request *request;
+
+ *_db = NULL;
+
+ iter = hash_table_iterate_init(db->clients);
+ while (hash_table_iterate(iter, db->clients, &key, &request))
+ checkpassword_internal_failure(&request);
+ hash_table_iterate_deinit(&iter);
+
+ child_wait_free(&db->child_wait);
+ hash_table_destroy(&db->clients);
+ i_free(db->checkpassword_reply_path);
+ i_free(db->checkpassword_path);
+ i_free(db);
+}
+
+#endif
diff --git a/src/auth/db-checkpassword.h b/src/auth/db-checkpassword.h
new file mode 100644
index 0000000..64eedd8
--- /dev/null
+++ b/src/auth/db-checkpassword.h
@@ -0,0 +1,29 @@
+#ifndef CHECKPASSWORD_COMMON_H
+#define CHECKPASSWORD_COMMON_H
+
+#include "auth-request.h"
+
+enum db_checkpassword_status {
+ DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE = -1,
+ /* auth unsuccessful / user not found */
+ DB_CHECKPASSWORD_STATUS_FAILURE = 0,
+ DB_CHECKPASSWORD_STATUS_OK = 1
+};
+
+typedef void db_checkpassword_callback_t(struct auth_request *request,
+ enum db_checkpassword_status status,
+ const char *const *extra_fields,
+ void (*request_callback)());
+
+struct db_checkpassword *
+db_checkpassword_init(const char *checkpassword_path,
+ const char *checkpassword_reply_path);
+void db_checkpassword_deinit(struct db_checkpassword **db);
+
+void db_checkpassword_call(struct db_checkpassword *db,
+ struct auth_request *request,
+ const char *auth_password,
+ db_checkpassword_callback_t *callback,
+ void (*request_callback)()) ATTR_NULL(3);
+
+#endif
diff --git a/src/auth/db-dict-cache-key.c b/src/auth/db-dict-cache-key.c
new file mode 100644
index 0000000..a220d8d
--- /dev/null
+++ b/src/auth/db-dict-cache-key.c
@@ -0,0 +1,64 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "str.h"
+#include "var-expand.h"
+#include "db-dict.h"
+
+const struct db_dict_key *
+db_dict_set_key_find(const ARRAY_TYPE(db_dict_key) *keys, const char *name)
+{
+ const struct db_dict_key *key;
+
+ array_foreach(keys, key) {
+ if (strcmp(key->name, name) == 0)
+ return key;
+ }
+ return NULL;
+}
+
+
+const char *
+db_dict_parse_cache_key(const ARRAY_TYPE(db_dict_key) *keys,
+ const ARRAY_TYPE(db_dict_field) *fields,
+ const ARRAY_TYPE(db_dict_key_p) *objects)
+{
+ const struct db_dict_field *field;
+ const struct db_dict_key *key;
+ const char *p, *name;
+ unsigned int idx, size;
+ string_t *str = t_str_new(128);
+
+ array_foreach(fields, field) {
+ for (p = field->value; *p != '\0'; ) {
+ if (*p != '%') {
+ p++;
+ continue;
+ }
+
+ var_get_key_range(++p, &idx, &size);
+ if (size == 0) {
+ /* broken %variable ending too early */
+ break;
+ }
+ p += idx;
+ if (size > 5 && memcmp(p, "dict:", 5) == 0) {
+ name = t_strcut(t_strndup(p+5, size-5), ':');
+ key = db_dict_set_key_find(keys, name);
+ if (key != NULL)
+ str_printfa(str, "\t%s", key->key);
+ } else if (size == 1) {
+ str_printfa(str, "\t%%%c", p[0]);
+ } else {
+ str_append(str, "\t%{");
+ str_append_data(str, p, size);
+ str_append_c(str, '}');
+ }
+ p += size;
+ }
+ }
+ array_foreach_elem(objects, key)
+ str_printfa(str, "\t%s", key->key);
+ return str_c(str);
+}
diff --git a/src/auth/db-dict.c b/src/auth/db-dict.c
new file mode 100644
index 0000000..96b1a63
--- /dev/null
+++ b/src/auth/db-dict.c
@@ -0,0 +1,654 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+
+#include "array.h"
+#include "istream.h"
+#include "str.h"
+#include "json-parser.h"
+#include "settings.h"
+#include "dict.h"
+#include "auth-request.h"
+#include "auth-worker-client.h"
+#include "db-dict.h"
+
+#include <stddef.h>
+
+enum dict_settings_section {
+ DICT_SETTINGS_SECTION_ROOT = 0,
+ DICT_SETTINGS_SECTION_KEY,
+ DICT_SETTINGS_SECTION_PASSDB,
+ DICT_SETTINGS_SECTION_USERDB
+};
+
+struct dict_settings_parser_ctx {
+ struct dict_connection *conn;
+ enum dict_settings_section section;
+ struct db_dict_key *cur_key;
+};
+
+struct db_dict_iter_key {
+ const struct db_dict_key *key;
+ bool used;
+ const char *value;
+};
+
+struct db_dict_value_iter {
+ pool_t pool;
+ struct auth_request *auth_request;
+ struct dict_connection *conn;
+ const struct var_expand_table *var_expand_table;
+ ARRAY(struct db_dict_iter_key) keys;
+
+ const ARRAY_TYPE(db_dict_field) *fields;
+ const ARRAY_TYPE(db_dict_key_p) *objects;
+ unsigned int field_idx;
+ unsigned int object_idx;
+
+ struct json_parser *json_parser;
+ string_t *tmpstr;
+ const char *error;
+};
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, db_dict_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, db_dict_settings)
+static struct setting_def setting_defs[] = {
+ DEF_STR(uri),
+ DEF_STR(default_pass_scheme),
+ DEF_STR(iterate_prefix),
+ DEF_BOOL(iterate_disable),
+
+ DEF_STR(passdb_objects),
+ DEF_STR(userdb_objects),
+ { 0, NULL, 0 }
+};
+
+static struct db_dict_settings default_dict_settings = {
+ .uri = NULL,
+ .default_pass_scheme = "MD5",
+ .iterate_prefix = "",
+ .iterate_disable = FALSE,
+ .passdb_objects = "",
+ .userdb_objects = ""
+};
+
+#undef DEF_STR
+#define DEF_STR(name) DEF_STRUCT_STR(name, db_dict_key)
+static struct setting_def key_setting_defs[] = {
+ DEF_STR(name),
+ DEF_STR(key),
+ DEF_STR(format),
+ DEF_STR(default_value),
+
+ { 0, NULL, 0 }
+};
+
+static struct db_dict_key default_key_settings = {
+ .name = NULL,
+ .key = "",
+ .format = "value",
+ .default_value = NULL
+};
+
+static struct dict_connection *connections = NULL;
+
+static struct dict_connection *dict_conn_find(const char *config_path)
+{
+ struct dict_connection *conn;
+
+ for (conn = connections; conn != NULL; conn = conn->next) {
+ if (strcmp(conn->config_path, config_path) == 0)
+ return conn;
+ }
+
+ return NULL;
+}
+
+static bool
+parse_obsolete_setting(const char *key, const char *value,
+ struct dict_settings_parser_ctx *ctx,
+ const char **error_r)
+{
+ const struct db_dict_key *dbkey;
+
+ if (strcmp(key, "password_key") == 0) {
+ /* key passdb { key=<value> format=json }
+ passdb_objects = passdb */
+ ctx->cur_key = array_append_space(&ctx->conn->set.keys);
+ *ctx->cur_key = default_key_settings;
+ ctx->cur_key->name = "passdb";
+ ctx->cur_key->format = "json";
+ ctx->cur_key->parsed_format = DB_DICT_VALUE_FORMAT_JSON;
+ ctx->cur_key->key = p_strdup(ctx->conn->pool, value);
+
+ dbkey = ctx->cur_key;
+ array_push_back(&ctx->conn->set.parsed_passdb_objects, &dbkey);
+ return TRUE;
+ }
+ if (strcmp(key, "user_key") == 0) {
+ /* key userdb { key=<value> format=json }
+ userdb_objects = userdb */
+ ctx->cur_key = array_append_space(&ctx->conn->set.keys);
+ *ctx->cur_key = default_key_settings;
+ ctx->cur_key->name = "userdb";
+ ctx->cur_key->format = "json";
+ ctx->cur_key->parsed_format = DB_DICT_VALUE_FORMAT_JSON;
+ ctx->cur_key->key = p_strdup(ctx->conn->pool, value);
+
+ dbkey = ctx->cur_key;
+ array_push_back(&ctx->conn->set.parsed_userdb_objects, &dbkey);
+ return TRUE;
+ }
+ if (strcmp(key, "value_format") == 0) {
+ if (strcmp(value, "json") == 0)
+ return TRUE;
+ *error_r = "Deprecated value_format must be 'json'";
+ return FALSE;
+ }
+ return FALSE;
+}
+
+static const char *parse_setting(const char *key, const char *value,
+ struct dict_settings_parser_ctx *ctx)
+{
+ struct db_dict_field *field;
+ const char *error = NULL;
+
+ switch (ctx->section) {
+ case DICT_SETTINGS_SECTION_ROOT:
+ if (parse_obsolete_setting(key, value, ctx, &error))
+ return NULL;
+ if (error != NULL)
+ return error;
+ return parse_setting_from_defs(ctx->conn->pool, setting_defs,
+ &ctx->conn->set, key, value);
+ case DICT_SETTINGS_SECTION_KEY:
+ return parse_setting_from_defs(ctx->conn->pool, key_setting_defs,
+ ctx->cur_key, key, value);
+ case DICT_SETTINGS_SECTION_PASSDB:
+ field = array_append_space(&ctx->conn->set.passdb_fields);
+ field->name = p_strdup(ctx->conn->pool, key);
+ field->value = p_strdup(ctx->conn->pool, value);
+ return NULL;
+ case DICT_SETTINGS_SECTION_USERDB:
+ field = array_append_space(&ctx->conn->set.userdb_fields);
+ field->name = p_strdup(ctx->conn->pool, key);
+ field->value = p_strdup(ctx->conn->pool, value);
+ return NULL;
+ }
+ i_unreached();
+}
+
+static bool parse_section(const char *type, const char *name,
+ struct dict_settings_parser_ctx *ctx,
+ const char **errormsg)
+{
+ if (type == NULL) {
+ ctx->section = DICT_SETTINGS_SECTION_ROOT;
+ if (ctx->cur_key != NULL) {
+ if (strcmp(ctx->cur_key->format, "value") == 0) {
+ ctx->cur_key->parsed_format =
+ DB_DICT_VALUE_FORMAT_VALUE;
+ } else if (strcmp(ctx->cur_key->format, "json") == 0) {
+ ctx->cur_key->parsed_format =
+ DB_DICT_VALUE_FORMAT_JSON;
+ } else {
+ *errormsg = t_strconcat("Unknown key format: ",
+ ctx->cur_key->format, NULL);
+ return FALSE;
+ }
+ }
+ ctx->cur_key = NULL;
+ return TRUE;
+ }
+ if (ctx->section != DICT_SETTINGS_SECTION_ROOT) {
+ *errormsg = "Nested sections not supported";
+ return FALSE;
+ }
+ if (strcmp(type, "key") == 0) {
+ if (name == NULL) {
+ *errormsg = "Key section is missing name";
+ return FALSE;
+ }
+ if (strchr(name, '.') != NULL) {
+ *errormsg = "Key section names must not contain '.'";
+ return FALSE;
+ }
+ ctx->section = DICT_SETTINGS_SECTION_KEY;
+ ctx->cur_key = array_append_space(&ctx->conn->set.keys);
+ *ctx->cur_key = default_key_settings;
+ ctx->cur_key->name = p_strdup(ctx->conn->pool, name);
+ return TRUE;
+ }
+ if (strcmp(type, "passdb_fields") == 0) {
+ ctx->section = DICT_SETTINGS_SECTION_PASSDB;
+ return TRUE;
+ }
+ if (strcmp(type, "userdb_fields") == 0) {
+ ctx->section = DICT_SETTINGS_SECTION_USERDB;
+ return TRUE;
+ }
+ *errormsg = "Unknown section";
+ return FALSE;
+}
+
+static void
+db_dict_settings_parse(struct db_dict_settings *set)
+{
+ const struct db_dict_key *key;
+ const char *const *tmp;
+
+ tmp = t_strsplit_spaces(set->passdb_objects, " ");
+ for (; *tmp != NULL; tmp++) {
+ key = db_dict_set_key_find(&set->keys, *tmp);
+ if (key == NULL) {
+ i_fatal("dict: passdb_objects refers to key %s, "
+ "which doesn't exist", *tmp);
+ }
+ if (key->parsed_format == DB_DICT_VALUE_FORMAT_VALUE) {
+ i_fatal("dict: passdb_objects refers to key %s, "
+ "but it's in value-only format", *tmp);
+ }
+ array_push_back(&set->parsed_passdb_objects, &key);
+ }
+
+ tmp = t_strsplit_spaces(set->userdb_objects, " ");
+ for (; *tmp != NULL; tmp++) {
+ key = db_dict_set_key_find(&set->keys, *tmp);
+ if (key == NULL) {
+ i_fatal("dict: userdb_objects refers to key %s, "
+ "which doesn't exist", *tmp);
+ }
+ if (key->parsed_format == DB_DICT_VALUE_FORMAT_VALUE) {
+ i_fatal("dict: userdb_objects refers to key %s, "
+ "but it's in value-only format", *tmp);
+ }
+ array_push_back(&set->parsed_userdb_objects, &key);
+ }
+}
+
+struct dict_connection *db_dict_init(const char *config_path)
+{
+ struct dict_settings dict_set;
+ struct dict_settings_parser_ctx ctx;
+ struct dict_connection *conn;
+ const char *error;
+ pool_t pool;
+
+ conn = dict_conn_find(config_path);
+ if (conn != NULL) {
+ conn->refcount++;
+ return conn;
+ }
+
+ if (*config_path == '\0')
+ i_fatal("dict: Configuration file path not given");
+
+ pool = pool_alloconly_create("dict_connection", 1024);
+ conn = p_new(pool, struct dict_connection, 1);
+ conn->pool = pool;
+
+ conn->refcount = 1;
+
+ conn->config_path = p_strdup(pool, config_path);
+ conn->set = default_dict_settings;
+ p_array_init(&conn->set.keys, pool, 8);
+ p_array_init(&conn->set.passdb_fields, pool, 8);
+ p_array_init(&conn->set.userdb_fields, pool, 8);
+ p_array_init(&conn->set.parsed_passdb_objects, pool, 2);
+ p_array_init(&conn->set.parsed_userdb_objects, pool, 2);
+
+ i_zero(&ctx);
+ ctx.conn = conn;
+ if (!settings_read(config_path, NULL, parse_setting,
+ parse_section, &ctx, &error))
+ i_fatal("dict %s: %s", config_path, error);
+ db_dict_settings_parse(&conn->set);
+
+ if (conn->set.uri == NULL)
+ i_fatal("dict %s: Empty uri setting", config_path);
+
+ i_zero(&dict_set);
+ dict_set.base_dir = global_auth_settings->base_dir;
+ dict_set.event_parent = auth_event;
+ if (dict_init(conn->set.uri, &dict_set, &conn->dict, &error) < 0)
+ i_fatal("dict %s: Failed to init dict: %s", config_path, error);
+
+ conn->next = connections;
+ connections = conn;
+ return conn;
+}
+
+void db_dict_unref(struct dict_connection **_conn)
+{
+ struct dict_connection *conn = *_conn;
+
+ *_conn = NULL;
+ if (--conn->refcount > 0)
+ return;
+
+ dict_deinit(&conn->dict);
+ pool_unref(&conn->pool);
+}
+
+static struct db_dict_iter_key *
+db_dict_iter_find_key(struct db_dict_value_iter *iter, const char *name)
+{
+ struct db_dict_iter_key *key;
+
+ array_foreach_modifiable(&iter->keys, key) {
+ if (strcmp(key->key->name, name) == 0)
+ return key;
+ }
+ return NULL;
+}
+
+static void db_dict_iter_find_used_keys(struct db_dict_value_iter *iter)
+{
+ const struct db_dict_field *field;
+ struct db_dict_iter_key *key;
+ const char *p, *name;
+ unsigned int idx, size;
+
+ array_foreach(iter->fields, field) {
+ for (p = field->value; *p != '\0'; ) {
+ if (*p != '%') {
+ p++;
+ continue;
+ }
+
+ var_get_key_range(++p, &idx, &size);
+ if (size == 0) {
+ /* broken %variable ending too early */
+ break;
+ }
+ p += idx;
+ if (size > 5 && memcmp(p, "dict:", 5) == 0) {
+ name = t_strcut(t_strndup(p+5, size-5), ':');
+ key = db_dict_iter_find_key(iter, name);
+ if (key != NULL)
+ key->used = TRUE;
+ }
+ p += size;
+ }
+ }
+}
+
+static void db_dict_iter_find_used_objects(struct db_dict_value_iter *iter)
+{
+ const struct db_dict_key *dict_key;
+ struct db_dict_iter_key *key;
+
+ array_foreach_elem(iter->objects, dict_key) {
+ key = db_dict_iter_find_key(iter, dict_key->name);
+ i_assert(key != NULL); /* checked at init */
+ i_assert(key->key->parsed_format != DB_DICT_VALUE_FORMAT_VALUE);
+ key->used = TRUE;
+ }
+}
+
+static int
+db_dict_iter_key_cmp(const struct db_dict_iter_key *k1,
+ const struct db_dict_iter_key *k2)
+{
+ return null_strcmp(k1->key->default_value, k2->key->default_value);
+}
+
+static int db_dict_iter_lookup_key_values(struct db_dict_value_iter *iter)
+{
+ struct db_dict_iter_key *key;
+ string_t *path;
+ const char *error;
+ int ret;
+
+ /* sort the keys so that we'll first lookup the keys without
+ default value. if their lookup fails, the user doesn't exist. */
+ array_sort(&iter->keys, db_dict_iter_key_cmp);
+
+ path = t_str_new(128);
+ str_append(path, DICT_PATH_SHARED);
+
+ struct dict_op_settings set = {
+ .username = iter->auth_request->fields.user,
+ };
+
+ array_foreach_modifiable(&iter->keys, key) {
+ if (!key->used)
+ continue;
+
+ str_truncate(path, strlen(DICT_PATH_SHARED));
+ str_append(path, key->key->key);
+ ret = dict_lookup(iter->conn->dict, &set, iter->pool,
+ str_c(path), &key->value, &error);
+ if (ret > 0) {
+ e_debug(authdb_event(iter->auth_request),
+ "Lookup: %s = %s", str_c(path),
+ key->value);
+ } else if (ret < 0) {
+ e_error(authdb_event(iter->auth_request),
+ "Failed to lookup key %s: %s", str_c(path), error);
+ return -1;
+ } else if (key->key->default_value != NULL) {
+ e_debug(authdb_event(iter->auth_request),
+ "Lookup: %s not found, using default value %s",
+ str_c(path), key->key->default_value);
+ key->value = key->key->default_value;
+ } else {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int db_dict_value_iter_init(struct dict_connection *conn,
+ struct auth_request *auth_request,
+ const ARRAY_TYPE(db_dict_field) *fields,
+ const ARRAY_TYPE(db_dict_key_p) *objects,
+ struct db_dict_value_iter **iter_r)
+{
+ struct db_dict_value_iter *iter;
+ struct db_dict_iter_key *iterkey;
+ const struct db_dict_key *key;
+ pool_t pool;
+ int ret;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"auth dict lookup", 1024);
+ iter = p_new(pool, struct db_dict_value_iter, 1);
+ iter->pool = pool;
+ iter->conn = conn;
+ iter->fields = fields;
+ iter->objects = objects;
+ iter->tmpstr = str_new(pool, 128);
+ iter->auth_request = auth_request;
+ iter->var_expand_table = auth_request_get_var_expand_table(auth_request, NULL);
+
+ /* figure out what keys we need to lookup, and lookup them */
+ p_array_init(&iter->keys, pool, array_count(&conn->set.keys));
+ array_foreach(&conn->set.keys, key) {
+ iterkey = array_append_space(&iter->keys);
+ struct db_dict_key *new_key = p_new(iter->pool, struct db_dict_key, 1);
+ memcpy(new_key, key, sizeof(struct db_dict_key));
+ string_t *expanded_key = str_new(iter->pool, strlen(key->key));
+ const char *error;
+ if (auth_request_var_expand_with_table(expanded_key, key->key, auth_request,
+ iter->var_expand_table,
+ NULL, &error) <= 0) {
+ e_error(authdb_event(iter->auth_request),
+ "Failed to expand key %s: %s", key->key, error);
+ pool_unref(&pool);
+ return -1;
+ }
+ new_key->key = str_c(expanded_key);
+ iterkey->key = new_key;
+ }
+ T_BEGIN {
+ db_dict_iter_find_used_keys(iter);
+ db_dict_iter_find_used_objects(iter);
+ ret = db_dict_iter_lookup_key_values(iter);
+ } T_END;
+ if (ret <= 0) {
+ pool_unref(&pool);
+ return ret;
+ }
+ *iter_r = iter;
+ return 1;
+}
+
+static bool
+db_dict_value_iter_json_next(struct db_dict_value_iter *iter,
+ string_t *tmpstr,
+ const char **key_r, const char **value_r)
+{
+ enum json_type type;
+ const char *value;
+
+ if (json_parse_next(iter->json_parser, &type, &value) < 0)
+ return FALSE;
+ if (type != JSON_TYPE_OBJECT_KEY) {
+ iter->error = "Object expected";
+ return FALSE;
+ }
+ if (*value == '\0') {
+ iter->error = "Empty object key";
+ return FALSE;
+ }
+ str_truncate(tmpstr, 0);
+ str_append(tmpstr, value);
+
+ if (json_parse_next(iter->json_parser, &type, &value) < 0) {
+ iter->error = "Missing value";
+ return FALSE;
+ }
+ if (type == JSON_TYPE_OBJECT) {
+ iter->error = "Nested objects not supported";
+ return FALSE;
+ }
+ *key_r = str_c(tmpstr);
+ *value_r = value;
+ return TRUE;
+}
+
+static void
+db_dict_value_iter_json_init(struct db_dict_value_iter *iter, const char *data)
+{
+ struct istream *input;
+
+ i_assert(iter->json_parser == NULL);
+
+ input = i_stream_create_from_data(data, strlen(data));
+ iter->json_parser = json_parser_init(input);
+ i_stream_unref(&input);
+}
+
+static bool
+db_dict_value_iter_object_next(struct db_dict_value_iter *iter,
+ const char **key_r, const char **value_r)
+{
+ const struct db_dict_key *dict_key;
+ struct db_dict_iter_key *key;
+
+ if (iter->json_parser != NULL)
+ return db_dict_value_iter_json_next(iter, iter->tmpstr, key_r, value_r);
+ if (iter->object_idx == array_count(iter->objects))
+ return FALSE;
+
+ dict_key = array_idx_elem(iter->objects, iter->object_idx);
+ key = db_dict_iter_find_key(iter, dict_key->name);
+ i_assert(key != NULL); /* checked at init */
+
+ switch (key->key->parsed_format) {
+ case DB_DICT_VALUE_FORMAT_VALUE:
+ i_unreached();
+ case DB_DICT_VALUE_FORMAT_JSON:
+ db_dict_value_iter_json_init(iter, key->value);
+ return db_dict_value_iter_json_next(iter, iter->tmpstr, key_r, value_r);
+ }
+ i_unreached();
+}
+
+static int
+db_dict_field_find(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct db_dict_value_iter *iter = context;
+ struct db_dict_iter_key *key;
+ const char *name, *value, *dotname = strchr(data, '.');
+ string_t *tmpstr;
+
+ *value_r = NULL;
+
+ if (dotname != NULL)
+ data = t_strdup_until(data, dotname++);
+ key = db_dict_iter_find_key(iter, data);
+ if (key == NULL)
+ return 1;
+
+ switch (key->key->parsed_format) {
+ case DB_DICT_VALUE_FORMAT_VALUE:
+ *value_r = dotname != NULL ? NULL :
+ (key->value == NULL ? "" : key->value);
+ return 1;
+ case DB_DICT_VALUE_FORMAT_JSON:
+ if (dotname == NULL)
+ return 1;
+ db_dict_value_iter_json_init(iter, key->value);
+ *value_r = "";
+ tmpstr = t_str_new(64);
+ while (db_dict_value_iter_json_next(iter, tmpstr, &name, &value)) {
+ if (strcmp(name, dotname) == 0) {
+ *value_r = t_strdup(value);
+ break;
+ }
+ }
+ (void)json_parser_deinit(&iter->json_parser, &iter->error);
+ return 1;
+ }
+ i_unreached();
+}
+
+bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
+ const char **key_r, const char **value_r)
+{
+ static struct var_expand_func_table var_funcs_table[] = {
+ { "dict", db_dict_field_find },
+ { NULL, NULL }
+ };
+ const struct db_dict_field *field;
+ const char *error;
+
+ if (iter->field_idx == array_count(iter->fields))
+ return db_dict_value_iter_object_next(iter, key_r, value_r);
+ field = array_idx(iter->fields, iter->field_idx++);
+
+ str_truncate(iter->tmpstr, 0);
+ if (var_expand_with_funcs(iter->tmpstr, field->value,
+ iter->var_expand_table, var_funcs_table,
+ iter, &error) <= 0) {
+ iter->error = p_strdup_printf(iter->pool,
+ "Failed to expand %s=%s: %s",
+ field->name, field->value, error);
+ return FALSE;
+ }
+ *key_r = field->name;
+ *value_r = str_c(iter->tmpstr);
+ return TRUE;
+}
+
+int db_dict_value_iter_deinit(struct db_dict_value_iter **_iter,
+ const char **error_r)
+{
+ struct db_dict_value_iter *iter = *_iter;
+
+ *_iter = NULL;
+
+ *error_r = iter->error;
+ if (iter->json_parser != NULL) {
+ if (json_parser_deinit(&iter->json_parser, &iter->error) < 0 &&
+ *error_r == NULL)
+ *error_r = iter->error;
+ }
+
+ pool_unref(&iter->pool);
+ return *error_r != NULL ? -1 : 0;
+}
diff --git a/src/auth/db-dict.h b/src/auth/db-dict.h
new file mode 100644
index 0000000..f4feb0a
--- /dev/null
+++ b/src/auth/db-dict.h
@@ -0,0 +1,82 @@
+#ifndef DB_DICT_H
+#define DB_DICT_H
+
+#include "sql-api.h"
+
+struct auth_request;
+struct db_dict_value_iter;
+
+enum db_dict_value_format {
+ DB_DICT_VALUE_FORMAT_VALUE = 0,
+ DB_DICT_VALUE_FORMAT_JSON
+};
+
+struct db_dict_key {
+ const char *name;
+ const char *key;
+ const char *format;
+ const char *default_value;
+
+ enum db_dict_value_format parsed_format;
+};
+ARRAY_DEFINE_TYPE(db_dict_key, struct db_dict_key);
+ARRAY_DEFINE_TYPE(db_dict_key_p, const struct db_dict_key *);
+
+struct db_dict_field {
+ const char *name;
+ const char *value;
+};
+ARRAY_DEFINE_TYPE(db_dict_field, struct db_dict_field);
+
+struct db_dict_settings {
+ const char *uri;
+ const char *default_pass_scheme;
+ const char *iterate_prefix;
+ bool iterate_disable;
+
+ ARRAY_TYPE(db_dict_key) keys;
+
+ const char *passdb_objects;
+ const char *userdb_objects;
+ ARRAY_TYPE(db_dict_field) passdb_fields;
+ ARRAY_TYPE(db_dict_field) userdb_fields;
+
+ ARRAY_TYPE(db_dict_key_p) parsed_passdb_objects;
+ ARRAY_TYPE(db_dict_key_p) parsed_userdb_objects;
+};
+
+struct dict_connection {
+ struct dict_connection *next;
+
+ pool_t pool;
+ int refcount;
+
+ char *config_path;
+ struct db_dict_settings set;
+ struct dict *dict;
+};
+
+struct dict_connection *db_dict_init(const char *config_path);
+void db_dict_unref(struct dict_connection **conn);
+
+/* Returns 1 if ok, 0 if a key without default_value wasn't returned
+ ("user doesn't exist"), -1 if internal error */
+int db_dict_value_iter_init(struct dict_connection *conn,
+ struct auth_request *auth_request,
+ const ARRAY_TYPE(db_dict_field) *fields,
+ const ARRAY_TYPE(db_dict_key_p) *objects,
+ struct db_dict_value_iter **iter_r);
+bool db_dict_value_iter_next(struct db_dict_value_iter *iter,
+ const char **key_r, const char **value_r);
+int db_dict_value_iter_deinit(struct db_dict_value_iter **iter,
+ const char **error_r);
+
+const char *db_dict_parse_cache_key(const ARRAY_TYPE(db_dict_key) *keys,
+ const ARRAY_TYPE(db_dict_field) *fields,
+ const ARRAY_TYPE(db_dict_key_p) *objects);
+
+/* private: */
+const struct db_dict_key *
+db_dict_set_key_find(const ARRAY_TYPE(db_dict_key) *keys, const char *name);
+
+#endif
diff --git a/src/auth/db-ldap.c b/src/auth/db-ldap.c
new file mode 100644
index 0000000..8083108
--- /dev/null
+++ b/src/auth/db-ldap.c
@@ -0,0 +1,2035 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+
+#if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "net.h"
+#include "ioloop.h"
+#include "array.h"
+#include "hash.h"
+#include "aqueue.h"
+#include "str.h"
+#include "time-util.h"
+#include "env-util.h"
+#include "var-expand.h"
+#include "settings.h"
+#include "userdb.h"
+#include "db-ldap.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+#define HAVE_LDAP_SASL
+#ifdef HAVE_SASL_SASL_H
+# include <sasl/sasl.h>
+#elif defined (HAVE_SASL_H)
+# include <sasl.h>
+#else
+# undef HAVE_LDAP_SASL
+#endif
+#ifdef LDAP_OPT_X_TLS
+# define OPENLDAP_TLS_OPTIONS
+#endif
+#if !defined(SASL_VERSION_MAJOR) || SASL_VERSION_MAJOR < 2
+# undef HAVE_LDAP_SASL
+#endif
+
+#ifndef LDAP_SASL_QUIET
+# define LDAP_SASL_QUIET 0 /* Doesn't exist in Solaris LDAP */
+#endif
+
+/* Older versions may require calling ldap_result() twice */
+#if LDAP_VENDOR_VERSION <= 20112
+# define OPENLDAP_ASYNC_WORKAROUND
+#endif
+
+/* Solaris LDAP library doesn't have LDAP_OPT_SUCCESS */
+#ifndef LDAP_OPT_SUCCESS
+# define LDAP_OPT_SUCCESS LDAP_SUCCESS
+#endif
+
+#define DB_LDAP_REQUEST_MAX_ATTEMPT_COUNT 3
+
+static const char *LDAP_ESCAPE_CHARS = "*,\\#+<>;\"()= ";
+
+struct db_ldap_result {
+ int refcount;
+ LDAPMessage *msg;
+};
+
+struct db_ldap_value {
+ const char **values;
+ bool used;
+};
+
+struct db_ldap_result_iterate_context {
+ pool_t pool;
+
+ struct ldap_request *ldap_request;
+ const ARRAY_TYPE(ldap_field) *attr_map;
+ unsigned int attr_idx;
+
+ /* attribute name => value */
+ HASH_TABLE(char *, struct db_ldap_value *) ldap_attrs;
+
+ const char *val_1_arr[2];
+ string_t *var, *debug;
+
+ bool skip_null_values;
+ bool iter_dn_values;
+ LDAPMessage *ldap_msg;
+ LDAP *ld;
+};
+
+struct db_ldap_sasl_bind_context {
+ const char *authcid;
+ const char *passwd;
+ const char *realm;
+ const char *authzid;
+};
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, ldap_settings)
+#define DEF_INT(name) DEF_STRUCT_INT(name, ldap_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, ldap_settings)
+
+static struct setting_def setting_defs[] = {
+ DEF_STR(hosts),
+ DEF_STR(uris),
+ DEF_STR(dn),
+ DEF_STR(dnpass),
+ DEF_BOOL(auth_bind),
+ DEF_STR(auth_bind_userdn),
+ DEF_BOOL(tls),
+ DEF_BOOL(sasl_bind),
+ DEF_STR(sasl_mech),
+ DEF_STR(sasl_realm),
+ DEF_STR(sasl_authz_id),
+ DEF_STR(tls_ca_cert_file),
+ DEF_STR(tls_ca_cert_dir),
+ DEF_STR(tls_cert_file),
+ DEF_STR(tls_key_file),
+ DEF_STR(tls_cipher_suite),
+ DEF_STR(tls_require_cert),
+ DEF_STR(deref),
+ DEF_STR(scope),
+ DEF_STR(base),
+ DEF_INT(ldap_version),
+ DEF_STR(debug_level),
+ DEF_STR(ldaprc_path),
+ DEF_STR(user_attrs),
+ DEF_STR(user_filter),
+ DEF_STR(pass_attrs),
+ DEF_STR(pass_filter),
+ DEF_STR(iterate_attrs),
+ DEF_STR(iterate_filter),
+ DEF_STR(default_pass_scheme),
+ DEF_BOOL(userdb_warning_disable),
+ DEF_BOOL(blocking),
+
+ { 0, NULL, 0 }
+};
+
+static struct ldap_settings default_ldap_settings = {
+ .hosts = NULL,
+ .uris = NULL,
+ .dn = NULL,
+ .dnpass = NULL,
+ .auth_bind = FALSE,
+ .auth_bind_userdn = NULL,
+ .tls = FALSE,
+ .sasl_bind = FALSE,
+ .sasl_mech = NULL,
+ .sasl_realm = NULL,
+ .sasl_authz_id = NULL,
+ .tls_ca_cert_file = NULL,
+ .tls_ca_cert_dir = NULL,
+ .tls_cert_file = NULL,
+ .tls_key_file = NULL,
+ .tls_cipher_suite = NULL,
+ .tls_require_cert = NULL,
+ .deref = "never",
+ .scope = "subtree",
+ .base = NULL,
+ .ldap_version = 3,
+ .debug_level = "0",
+ .ldaprc_path = "",
+ .user_attrs = "homeDirectory=home,uidNumber=uid,gidNumber=gid",
+ .user_filter = "(&(objectClass=posixAccount)(uid=%u))",
+ .pass_attrs = "uid=user,userPassword=password",
+ .pass_filter = "(&(objectClass=posixAccount)(uid=%u))",
+ .iterate_attrs = "uid=user",
+ .iterate_filter = "(objectClass=posixAccount)",
+ .default_pass_scheme = "crypt",
+ .userdb_warning_disable = FALSE,
+ .blocking = FALSE
+};
+
+static struct ldap_connection *ldap_connections = NULL;
+
+static int db_ldap_bind(struct ldap_connection *conn);
+static void db_ldap_conn_close(struct ldap_connection *conn);
+struct db_ldap_result_iterate_context *
+db_ldap_result_iterate_init_full(struct ldap_connection *conn,
+ struct ldap_request_search *ldap_request,
+ LDAPMessage *res, bool skip_null_values,
+ bool iter_dn_values);
+static bool db_ldap_abort_requests(struct ldap_connection *conn,
+ unsigned int max_count,
+ unsigned int timeout_secs,
+ bool error, const char *reason);
+static void db_ldap_request_free(struct ldap_request *request);
+
+static int deref2str(const char *str, int *ref_r)
+{
+ if (strcasecmp(str, "never") == 0)
+ *ref_r = LDAP_DEREF_NEVER;
+ else if (strcasecmp(str, "searching") == 0)
+ *ref_r = LDAP_DEREF_SEARCHING;
+ else if (strcasecmp(str, "finding") == 0)
+ *ref_r = LDAP_DEREF_FINDING;
+ else if (strcasecmp(str, "always") == 0)
+ *ref_r = LDAP_DEREF_ALWAYS;
+ else
+ return -1;
+ return 0;
+}
+
+static int scope2str(const char *str, int *scope_r)
+{
+ if (strcasecmp(str, "base") == 0)
+ *scope_r = LDAP_SCOPE_BASE;
+ else if (strcasecmp(str, "onelevel") == 0)
+ *scope_r = LDAP_SCOPE_ONELEVEL;
+ else if (strcasecmp(str, "subtree") == 0)
+ *scope_r = LDAP_SCOPE_SUBTREE;
+ else
+ return -1;
+ return 0;
+}
+
+#ifdef OPENLDAP_TLS_OPTIONS
+static int tls_require_cert2str(const char *str, int *value_r)
+{
+ if (strcasecmp(str, "never") == 0)
+ *value_r = LDAP_OPT_X_TLS_NEVER;
+ else if (strcasecmp(str, "hard") == 0)
+ *value_r = LDAP_OPT_X_TLS_HARD;
+ else if (strcasecmp(str, "demand") == 0)
+ *value_r = LDAP_OPT_X_TLS_DEMAND;
+ else if (strcasecmp(str, "allow") == 0)
+ *value_r = LDAP_OPT_X_TLS_ALLOW;
+ else if (strcasecmp(str, "try") == 0)
+ *value_r = LDAP_OPT_X_TLS_TRY;
+ else
+ return -1;
+ return 0;
+}
+#endif
+
+static int ldap_get_errno(struct ldap_connection *conn)
+{
+ int ret, err;
+
+ ret = ldap_get_option(conn->ld, LDAP_OPT_ERROR_NUMBER, (void *) &err);
+ if (ret != LDAP_SUCCESS) {
+ e_error(conn->event, "Can't get error number: %s",
+ ldap_err2string(ret));
+ return LDAP_UNAVAILABLE;
+ }
+
+ return err;
+}
+
+const char *ldap_get_error(struct ldap_connection *conn)
+{
+ const char *ret;
+ char *str = NULL;
+
+ ret = ldap_err2string(ldap_get_errno(conn));
+
+ ldap_get_option(conn->ld, LDAP_OPT_ERROR_STRING, (void *)&str);
+ if (str != NULL) {
+ ret = t_strconcat(ret, ", ", str, NULL);
+ ldap_memfree(str);
+ }
+ ldap_set_option(conn->ld, LDAP_OPT_ERROR_STRING, NULL);
+ return ret;
+}
+
+static void ldap_conn_reconnect(struct ldap_connection *conn)
+{
+ db_ldap_conn_close(conn);
+ if (db_ldap_connect(conn) < 0)
+ db_ldap_conn_close(conn);
+}
+
+static int ldap_handle_error(struct ldap_connection *conn)
+{
+ int err = ldap_get_errno(conn);
+
+ switch (err) {
+ case LDAP_SUCCESS:
+ i_unreached();
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ case LDAP_UNDEFINED_TYPE:
+ case LDAP_INAPPROPRIATE_MATCHING:
+ case LDAP_CONSTRAINT_VIOLATION:
+ case LDAP_TYPE_OR_VALUE_EXISTS:
+ case LDAP_INVALID_SYNTAX:
+ case LDAP_NO_SUCH_OBJECT:
+ case LDAP_ALIAS_PROBLEM:
+ case LDAP_INVALID_DN_SYNTAX:
+ case LDAP_IS_LEAF:
+ case LDAP_ALIAS_DEREF_PROBLEM:
+ case LDAP_FILTER_ERROR:
+ /* invalid input */
+ return -1;
+ case LDAP_SERVER_DOWN:
+ case LDAP_TIMEOUT:
+ case LDAP_UNAVAILABLE:
+ case LDAP_BUSY:
+#ifdef LDAP_CONNECT_ERROR
+ case LDAP_CONNECT_ERROR:
+#endif
+ case LDAP_LOCAL_ERROR:
+ case LDAP_INVALID_CREDENTIALS:
+ case LDAP_OPERATIONS_ERROR:
+ default:
+ /* connection problems */
+ ldap_conn_reconnect(conn);
+ return 0;
+ }
+}
+
+static int db_ldap_request_bind(struct ldap_connection *conn,
+ struct ldap_request *request)
+{
+ struct ldap_request_bind *brequest =
+ (struct ldap_request_bind *)request;
+
+ i_assert(request->type == LDAP_REQUEST_TYPE_BIND);
+ i_assert(request->msgid == -1);
+ i_assert(conn->conn_state == LDAP_CONN_STATE_BOUND_AUTH ||
+ conn->conn_state == LDAP_CONN_STATE_BOUND_DEFAULT);
+ i_assert(conn->pending_count == 0);
+
+ request->msgid = ldap_bind(conn->ld, brequest->dn,
+ request->auth_request->mech_password,
+ LDAP_AUTH_SIMPLE);
+ if (request->msgid == -1) {
+ e_error(authdb_event(request->auth_request),
+ "ldap_bind(%s) failed: %s",
+ brequest->dn, ldap_get_error(conn));
+ if (ldap_handle_error(conn) < 0) {
+ /* broken request, remove it */
+ return 0;
+ }
+ return -1;
+ }
+ conn->conn_state = LDAP_CONN_STATE_BINDING;
+ return 1;
+}
+
+static int db_ldap_request_search(struct ldap_connection *conn,
+ struct ldap_request *request)
+{
+ struct ldap_request_search *srequest =
+ (struct ldap_request_search *)request;
+
+ i_assert(conn->conn_state == LDAP_CONN_STATE_BOUND_DEFAULT);
+ i_assert(request->msgid == -1);
+
+ request->msgid =
+ ldap_search(conn->ld, *srequest->base == '\0' ? NULL :
+ srequest->base, conn->set.ldap_scope,
+ srequest->filter, srequest->attributes, 0);
+ if (request->msgid == -1) {
+ e_error(authdb_event(request->auth_request),
+ "ldap_search(%s) parsing failed: %s",
+ srequest->filter, ldap_get_error(conn));
+ if (ldap_handle_error(conn) < 0) {
+ /* broken request, remove it */
+ return 0;
+ }
+ return -1;
+ }
+ return 1;
+}
+
+static bool db_ldap_request_queue_next(struct ldap_connection *conn)
+{
+ struct ldap_request *request;
+ int ret = -1;
+
+ /* connecting may call db_ldap_connect_finish(), which gets us back
+ here. so do the connection before checking the request queue. */
+ if (db_ldap_connect(conn) < 0)
+ return FALSE;
+
+ if (conn->pending_count == aqueue_count(conn->request_queue)) {
+ /* no non-pending requests */
+ return FALSE;
+ }
+ if (conn->pending_count > DB_LDAP_MAX_PENDING_REQUESTS) {
+ /* wait until server has replied to some requests */
+ return FALSE;
+ }
+
+ request = array_idx_elem(&conn->request_array,
+ aqueue_idx(conn->request_queue,
+ conn->pending_count));
+
+ if (conn->pending_count > 0 &&
+ request->type == LDAP_REQUEST_TYPE_BIND) {
+ /* we can't do binds until all existing requests are finished */
+ return FALSE;
+ }
+
+ switch (conn->conn_state) {
+ case LDAP_CONN_STATE_DISCONNECTED:
+ case LDAP_CONN_STATE_BINDING:
+ /* wait until we're in bound state */
+ return FALSE;
+ case LDAP_CONN_STATE_BOUND_AUTH:
+ if (request->type == LDAP_REQUEST_TYPE_BIND)
+ break;
+
+ /* bind to default dn first */
+ i_assert(conn->pending_count == 0);
+ (void)db_ldap_bind(conn);
+ return FALSE;
+ case LDAP_CONN_STATE_BOUND_DEFAULT:
+ /* we can do anything in this state */
+ break;
+ }
+
+ if (request->send_count >= DB_LDAP_REQUEST_MAX_ATTEMPT_COUNT) {
+ /* Enough many times retried. Server just keeps disconnecting
+ whenever attempting to send the request. */
+ ret = 0;
+ } else {
+ /* clear away any partial results saved before reconnecting */
+ db_ldap_request_free(request);
+
+ switch (request->type) {
+ case LDAP_REQUEST_TYPE_BIND:
+ ret = db_ldap_request_bind(conn, request);
+ break;
+ case LDAP_REQUEST_TYPE_SEARCH:
+ ret = db_ldap_request_search(conn, request);
+ break;
+ }
+ }
+
+ if (ret > 0) {
+ /* success */
+ i_assert(request->msgid != -1);
+ request->send_count++;
+ conn->pending_count++;
+ return TRUE;
+ } else if (ret < 0) {
+ /* disconnected */
+ return FALSE;
+ } else {
+ /* broken request, remove from queue */
+ aqueue_delete_tail(conn->request_queue);
+ request->callback(conn, request, NULL);
+ return TRUE;
+ }
+}
+
+static void
+db_ldap_check_hanging(struct ldap_connection *conn)
+{
+ struct ldap_request *first_request;
+ unsigned int count;
+ time_t secs_diff;
+
+ count = aqueue_count(conn->request_queue);
+ if (count == 0)
+ return;
+
+ first_request = array_idx_elem(&conn->request_array,
+ aqueue_idx(conn->request_queue, 0));
+ secs_diff = ioloop_time - first_request->create_time;
+ if (secs_diff > DB_LDAP_REQUEST_LOST_TIMEOUT_SECS) {
+ db_ldap_abort_requests(conn, UINT_MAX, 0, TRUE,
+ "LDAP connection appears to be hanging");
+ ldap_conn_reconnect(conn);
+ }
+}
+
+void db_ldap_request(struct ldap_connection *conn,
+ struct ldap_request *request)
+{
+ i_assert(request->auth_request != NULL);
+
+ request->msgid = -1;
+ request->create_time = ioloop_time;
+
+ db_ldap_check_hanging(conn);
+
+ aqueue_append(conn->request_queue, &request);
+ (void)db_ldap_request_queue_next(conn);
+}
+
+static int db_ldap_connect_finish(struct ldap_connection *conn, int ret)
+{
+ if (ret == LDAP_SERVER_DOWN) {
+ e_error(conn->event, "Can't connect to server: %s",
+ conn->set.uris != NULL ?
+ conn->set.uris : conn->set.hosts);
+ return -1;
+ }
+ if (ret != LDAP_SUCCESS) {
+ e_error(conn->event, "binding failed (dn %s): %s",
+ conn->set.dn == NULL ? "(none)" : conn->set.dn,
+ ldap_get_error(conn));
+ return -1;
+ }
+
+ timeout_remove(&conn->to);
+ conn->conn_state = LDAP_CONN_STATE_BOUND_DEFAULT;
+ while (db_ldap_request_queue_next(conn))
+ ;
+ return 0;
+}
+
+static void db_ldap_default_bind_finished(struct ldap_connection *conn,
+ struct db_ldap_result *res)
+{
+ int ret;
+
+ i_assert(conn->pending_count == 0);
+ conn->default_bind_msgid = -1;
+
+ ret = ldap_result2error(conn->ld, res->msg, FALSE);
+ if (db_ldap_connect_finish(conn, ret) < 0) {
+ /* lost connection, close it */
+ db_ldap_conn_close(conn);
+ }
+}
+
+static bool db_ldap_abort_requests(struct ldap_connection *conn,
+ unsigned int max_count,
+ unsigned int timeout_secs,
+ bool error, const char *reason)
+{
+ struct ldap_request *request;
+ time_t diff;
+ bool aborts = FALSE;
+
+ while (aqueue_count(conn->request_queue) > 0 && max_count > 0) {
+ request = array_idx_elem(&conn->request_array,
+ aqueue_idx(conn->request_queue, 0));
+
+ diff = ioloop_time - request->create_time;
+ if (diff < (time_t)timeout_secs)
+ break;
+
+ /* timed out, abort */
+ aqueue_delete_tail(conn->request_queue);
+
+ if (request->msgid != -1) {
+ i_assert(conn->pending_count > 0);
+ conn->pending_count--;
+ }
+ if (error) {
+ e_error(authdb_event(request->auth_request),
+ "%s", reason);
+ } else {
+ e_info(authdb_event(request->auth_request),
+ "%s", reason);
+ }
+ request->callback(conn, request, NULL);
+ max_count--;
+ aborts = TRUE;
+ }
+ return aborts;
+}
+
+static struct ldap_request *
+db_ldap_find_request(struct ldap_connection *conn, int msgid,
+ unsigned int *idx_r)
+{
+ struct ldap_request *const *requests, *request = NULL;
+ unsigned int i, count;
+
+ count = aqueue_count(conn->request_queue);
+ if (count == 0)
+ return NULL;
+
+ requests = array_front(&conn->request_array);
+ for (i = 0; i < count; i++) {
+ request = requests[aqueue_idx(conn->request_queue, i)];
+ if (request->msgid == msgid) {
+ *idx_r = i;
+ return request;
+ }
+ if (request->msgid == -1)
+ break;
+ }
+ return NULL;
+}
+
+static int db_ldap_fields_get_dn(struct ldap_connection *conn,
+ struct ldap_request_search *request,
+ struct db_ldap_result *res)
+{
+ struct auth_request *auth_request = request->request.auth_request;
+ struct ldap_request_named_result *named_res;
+ struct db_ldap_result_iterate_context *ldap_iter;
+ const char *name, *const *values;
+
+ ldap_iter = db_ldap_result_iterate_init_full(conn, request, res->msg,
+ TRUE, TRUE);
+ while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) {
+ if (values[1] != NULL) {
+ e_warning(authdb_event(auth_request),
+ "Multiple values found for '%s', "
+ "using value '%s'", name, values[0]);
+ }
+ array_foreach_modifiable(&request->named_results, named_res) {
+ if (strcmp(named_res->field->name, name) != 0)
+ continue;
+ /* In future we could also support LDAP URLs here */
+ named_res->dn = p_strdup(auth_request->pool,
+ values[0]);
+ }
+ }
+ db_ldap_result_iterate_deinit(&ldap_iter);
+ return 0;
+}
+
+struct ldap_field_find_subquery_context {
+ ARRAY_TYPE(string) attr_names;
+ const char *name;
+};
+
+static int
+db_ldap_field_subquery_find(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct ldap_field_find_subquery_context *ctx = context;
+ char *ldap_attr;
+ const char *p;
+
+ if (*data != '\0') {
+ data = t_strcut(data, ':');
+ p = strchr(data, '@');
+ if (p != NULL && strcmp(p+1, ctx->name) == 0) {
+ ldap_attr = p_strdup_until(unsafe_data_stack_pool,
+ data, p);
+ array_push_back(&ctx->attr_names, &ldap_attr);
+ }
+ }
+ *value_r = NULL;
+ return 1;
+}
+
+static int
+ldap_request_send_subquery(struct ldap_connection *conn,
+ struct ldap_request_search *request,
+ struct ldap_request_named_result *named_res)
+{
+ const struct ldap_field *field;
+ const char *p, *error;
+ char *name;
+ struct auth_request *auth_request = request->request.auth_request;
+ struct ldap_field_find_subquery_context ctx;
+ const struct var_expand_table *table =
+ auth_request_get_var_expand_table(auth_request, NULL);
+ const struct var_expand_func_table *ptr;
+ struct var_expand_func_table *ftable;
+ string_t *tmp_str = t_str_new(64);
+ ARRAY(struct var_expand_func_table) var_funcs_table;
+ t_array_init(&var_funcs_table, 8);
+
+ for(ptr = auth_request_var_funcs_table; ptr->key != NULL; ptr++) {
+ array_push_back(&var_funcs_table, ptr);
+ }
+ ftable = array_append_space(&var_funcs_table);
+ ftable->key = "ldap";
+ ftable->func = db_ldap_field_subquery_find;
+ ftable = array_append_space(&var_funcs_table);
+ ftable->key = "ldap_ptr";
+ ftable->func = db_ldap_field_subquery_find;
+ array_append_zero(&var_funcs_table);
+
+ i_zero(&ctx);
+ t_array_init(&ctx.attr_names, 8);
+ ctx.name = named_res->field->name;
+
+ /* get the attributes names into array (ldapAttr@name -> ldapAttr) */
+ array_foreach(request->attr_map, field) {
+ if (field->ldap_attr_name[0] == '\0') {
+ str_truncate(tmp_str, 0);
+ if (var_expand_with_funcs(tmp_str, field->value, table,
+ array_front(&var_funcs_table), &ctx, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand subquery %s: %s",
+ field->value, error);
+ return -1;
+ }
+ } else {
+ p = strchr(field->ldap_attr_name, '@');
+ if (p != NULL &&
+ strcmp(p+1, named_res->field->name) == 0) {
+ name = p_strdup_until(unsafe_data_stack_pool,
+ field->ldap_attr_name, p);
+ array_push_back(&ctx.attr_names, &name);
+ }
+ }
+ }
+ array_append_zero(&ctx.attr_names);
+
+ request->request.msgid =
+ ldap_search(conn->ld, named_res->dn, LDAP_SCOPE_BASE,
+ NULL, array_front_modifiable(&ctx.attr_names), 0);
+ if (request->request.msgid == -1) {
+ e_error(authdb_event(auth_request),
+ "ldap_search(dn=%s) failed: %s",
+ named_res->dn, ldap_get_error(conn));
+ return -1;
+ }
+ return 0;
+}
+
+static int db_ldap_search_save_result(struct ldap_request_search *request,
+ struct db_ldap_result *res)
+{
+ struct ldap_request_named_result *named_res;
+
+ if (!array_is_created(&request->named_results)) {
+ if (request->result != NULL)
+ return -1;
+ request->result = res;
+ } else {
+ named_res = array_idx_modifiable(&request->named_results,
+ request->name_idx);
+ if (named_res->result != NULL)
+ return -1;
+ named_res->result = res;
+ }
+ res->refcount++;
+ return 0;
+}
+
+static int db_ldap_search_next_subsearch(struct ldap_connection *conn,
+ struct ldap_request_search *request,
+ struct db_ldap_result *res)
+{
+ struct ldap_request_named_result *named_res;
+ const struct ldap_field *field;
+
+ if (request->result != NULL)
+ res = request->result;
+
+ if (!array_is_created(&request->named_results)) {
+ /* see if we need to do more LDAP queries */
+ p_array_init(&request->named_results,
+ request->request.auth_request->pool, 2);
+ array_foreach(request->attr_map, field) {
+ if (!field->value_is_dn)
+ continue;
+ named_res = array_append_space(&request->named_results);
+ named_res->field = field;
+ }
+ if (db_ldap_fields_get_dn(conn, request, res) < 0)
+ return -1;
+ } else {
+ request->name_idx++;
+ }
+ while (request->name_idx < array_count(&request->named_results)) {
+ /* send the next LDAP query */
+ named_res = array_idx_modifiable(&request->named_results,
+ request->name_idx);
+ if (named_res->dn != NULL) {
+ if (ldap_request_send_subquery(conn, request,
+ named_res) < 0)
+ return -1;
+ return 1;
+ }
+ /* dn field wasn't returned, skip this */
+ request->name_idx++;
+ }
+ return 0;
+}
+
+static bool
+db_ldap_handle_request_result(struct ldap_connection *conn,
+ struct ldap_request *request, unsigned int idx,
+ struct db_ldap_result *res)
+{
+ struct ldap_request_search *srequest = NULL;
+ const struct ldap_request_named_result *named_res;
+ int ret;
+ bool final_result;
+
+ i_assert(conn->pending_count > 0);
+
+ if (request->type == LDAP_REQUEST_TYPE_BIND) {
+ i_assert(conn->conn_state == LDAP_CONN_STATE_BINDING);
+ i_assert(conn->pending_count == 1);
+ conn->conn_state = LDAP_CONN_STATE_BOUND_AUTH;
+ } else {
+ srequest = (struct ldap_request_search *)request;
+ switch (ldap_msgtype(res->msg)) {
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_RESULT:
+ break;
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* we're going to ignore this */
+ return FALSE;
+ default:
+ e_error(conn->event, "Reply with unexpected type %d",
+ ldap_msgtype(res->msg));
+ return TRUE;
+ }
+ }
+ if (ldap_msgtype(res->msg) == LDAP_RES_SEARCH_ENTRY) {
+ ret = LDAP_SUCCESS;
+ final_result = FALSE;
+ } else {
+ final_result = TRUE;
+ ret = ldap_result2error(conn->ld, res->msg, 0);
+ }
+ /* LDAP_NO_SUCH_OBJECT is returned for nonexistent base */
+ if (ret != LDAP_SUCCESS && ret != LDAP_NO_SUCH_OBJECT &&
+ request->type == LDAP_REQUEST_TYPE_SEARCH) {
+ /* handle search failures here */
+ struct ldap_request_search *srequest =
+ (struct ldap_request_search *)request;
+
+ if (!array_is_created(&srequest->named_results)) {
+ e_error(authdb_event(request->auth_request),
+ "ldap_search(base=%s filter=%s) failed: %s",
+ srequest->base, srequest->filter,
+ ldap_err2string(ret));
+ } else {
+ named_res = array_idx(&srequest->named_results,
+ srequest->name_idx);
+ e_error(authdb_event(request->auth_request),
+ "ldap_search(base=%s) failed: %s",
+ named_res->dn, ldap_err2string(ret));
+ }
+ res = NULL;
+ }
+ if (ret == LDAP_SUCCESS && srequest != NULL && !srequest->multi_entry) {
+ /* expand any @results */
+ if (!final_result) {
+ if (db_ldap_search_save_result(srequest, res) < 0) {
+ e_error(authdb_event(request->auth_request),
+ "LDAP search returned multiple entries");
+ res = NULL;
+ } else {
+ /* wait for finish */
+ return FALSE;
+ }
+ } else {
+ ret = db_ldap_search_next_subsearch(conn, srequest, res);
+ if (ret > 0) {
+ /* more LDAP queries left */
+ return FALSE;
+ }
+ if (ret < 0)
+ res = NULL;
+ }
+ }
+ if (res == NULL && !final_result) {
+ /* wait for the final reply */
+ request->failed = TRUE;
+ return TRUE;
+ }
+ if (request->failed)
+ res = NULL;
+ if (final_result) {
+ conn->pending_count--;
+ aqueue_delete(conn->request_queue, idx);
+ }
+
+ T_BEGIN {
+ if (res != NULL && srequest != NULL && srequest->result != NULL)
+ request->callback(conn, request, srequest->result->msg);
+
+ request->callback(conn, request, res == NULL ? NULL : res->msg);
+ } T_END;
+
+ if (idx > 0) {
+ /* see if there are timed out requests */
+ if (db_ldap_abort_requests(conn, idx,
+ DB_LDAP_REQUEST_LOST_TIMEOUT_SECS,
+ TRUE, "Request lost"))
+ ldap_conn_reconnect(conn);
+ }
+ return TRUE;
+}
+
+static void db_ldap_result_unref(struct db_ldap_result **_res)
+{
+ struct db_ldap_result *res = *_res;
+
+ *_res = NULL;
+ i_assert(res->refcount > 0);
+ if (--res->refcount == 0) {
+ ldap_msgfree(res->msg);
+ i_free(res);
+ }
+}
+
+static void
+db_ldap_request_free(struct ldap_request *request)
+{
+ if (request->type == LDAP_REQUEST_TYPE_SEARCH) {
+ struct ldap_request_search *srequest =
+ (struct ldap_request_search *)request;
+ struct ldap_request_named_result *named_res;
+
+ if (srequest->result != NULL)
+ db_ldap_result_unref(&srequest->result);
+
+ if (array_is_created(&srequest->named_results)) {
+ array_foreach_modifiable(&srequest->named_results, named_res) {
+ if (named_res->result != NULL)
+ db_ldap_result_unref(&named_res->result);
+ }
+ array_free(&srequest->named_results);
+ srequest->name_idx = 0;
+ }
+ }
+}
+
+static void
+db_ldap_handle_result(struct ldap_connection *conn, struct db_ldap_result *res)
+{
+ struct auth_request *auth_request;
+ struct ldap_request *request;
+ unsigned int idx;
+ int msgid;
+
+ msgid = ldap_msgid(res->msg);
+ if (msgid == conn->default_bind_msgid) {
+ db_ldap_default_bind_finished(conn, res);
+ return;
+ }
+
+ request = db_ldap_find_request(conn, msgid, &idx);
+ if (request == NULL) {
+ e_error(conn->event, "Reply with unknown msgid %d", msgid);
+ ldap_conn_reconnect(conn);
+ return;
+ }
+ /* request is allocated from auth_request's pool */
+ auth_request = request->auth_request;
+ auth_request_ref(auth_request);
+ if (db_ldap_handle_request_result(conn, request, idx, res))
+ db_ldap_request_free(request);
+ auth_request_unref(&auth_request);
+}
+
+static void ldap_input(struct ldap_connection *conn)
+{
+ struct timeval timeout;
+ struct db_ldap_result *res;
+ LDAPMessage *msg;
+ time_t prev_reply_diff;
+ int ret;
+
+ do {
+ if (conn->ld == NULL)
+ return;
+
+ i_zero(&timeout);
+ ret = ldap_result(conn->ld, LDAP_RES_ANY, 0, &timeout, &msg);
+#ifdef OPENLDAP_ASYNC_WORKAROUND
+ if (ret == 0) {
+ /* try again, there may be another in buffer */
+ ret = ldap_result(conn->ld, LDAP_RES_ANY, 0,
+ &timeout, &msg);
+ }
+#endif
+ if (ret <= 0)
+ break;
+
+ res = i_new(struct db_ldap_result, 1);
+ res->refcount = 1;
+ res->msg = msg;
+ db_ldap_handle_result(conn, res);
+ db_ldap_result_unref(&res);
+ } while (conn->io != NULL);
+
+ prev_reply_diff = ioloop_time - conn->last_reply_stamp;
+ conn->last_reply_stamp = ioloop_time;
+
+ if (ret > 0) {
+ /* input disabled, continue once it's enabled */
+ i_assert(conn->io == NULL);
+ } else if (ret == 0) {
+ /* send more requests */
+ while (db_ldap_request_queue_next(conn))
+ ;
+ } else if (ldap_get_errno(conn) != LDAP_SERVER_DOWN) {
+ e_error(conn->event, "ldap_result() failed: %s", ldap_get_error(conn));
+ ldap_conn_reconnect(conn);
+ } else if (aqueue_count(conn->request_queue) > 0 ||
+ prev_reply_diff < DB_LDAP_IDLE_RECONNECT_SECS) {
+ e_error(conn->event, "Connection lost to LDAP server, reconnecting");
+ ldap_conn_reconnect(conn);
+ } else {
+ /* server probably disconnected an idle connection. don't
+ reconnect until the next request comes. */
+ db_ldap_conn_close(conn);
+ }
+}
+
+#ifdef HAVE_LDAP_SASL
+static int
+sasl_interact(LDAP *ld ATTR_UNUSED, unsigned flags ATTR_UNUSED,
+ void *defaults, void *interact)
+{
+ struct db_ldap_sasl_bind_context *context = defaults;
+ sasl_interact_t *in;
+ const char *str;
+
+ for (in = interact; in->id != SASL_CB_LIST_END; in++) {
+ switch (in->id) {
+ case SASL_CB_GETREALM:
+ str = context->realm;
+ break;
+ case SASL_CB_AUTHNAME:
+ str = context->authcid;
+ break;
+ case SASL_CB_USER:
+ str = context->authzid;
+ break;
+ case SASL_CB_PASS:
+ str = context->passwd;
+ break;
+ default:
+ str = NULL;
+ break;
+ }
+ if (str != NULL) {
+ in->len = strlen(str);
+ in->result = str;
+ }
+
+ }
+ return LDAP_SUCCESS;
+}
+#endif
+
+static void ldap_connection_timeout(struct ldap_connection *conn)
+{
+ i_assert(conn->conn_state == LDAP_CONN_STATE_BINDING);
+
+ e_error(conn->event, "Initial binding to LDAP server timed out");
+ db_ldap_conn_close(conn);
+}
+
+#ifdef HAVE_LDAP_SASL
+static int db_ldap_bind_sasl(struct ldap_connection *conn)
+{
+ struct db_ldap_sasl_bind_context context;
+ int ret;
+
+ i_zero(&context);
+ context.authcid = conn->set.dn;
+ context.passwd = conn->set.dnpass;
+ context.realm = conn->set.sasl_realm;
+ context.authzid = conn->set.sasl_authz_id;
+
+ /* There doesn't seem to be a way to do SASL binding
+ asynchronously.. */
+ ret = ldap_sasl_interactive_bind_s(conn->ld, NULL,
+ conn->set.sasl_mech,
+ NULL, NULL, LDAP_SASL_QUIET,
+ sasl_interact, &context);
+ if (db_ldap_connect_finish(conn, ret) < 0)
+ return -1;
+
+ conn->conn_state = LDAP_CONN_STATE_BOUND_DEFAULT;
+
+ return 0;
+}
+#else
+static int db_ldap_bind_sasl(struct ldap_connection *conn ATTR_UNUSED)
+{
+ i_unreached(); /* already checked at init */
+
+ return -1;
+}
+#endif
+
+static int db_ldap_bind_simple(struct ldap_connection *conn)
+{
+ int msgid;
+
+ i_assert(conn->conn_state != LDAP_CONN_STATE_BINDING);
+ i_assert(conn->default_bind_msgid == -1);
+ i_assert(conn->pending_count == 0);
+
+ msgid = ldap_bind(conn->ld, conn->set.dn, conn->set.dnpass,
+ LDAP_AUTH_SIMPLE);
+ if (msgid == -1) {
+ i_assert(ldap_get_errno(conn) != LDAP_SUCCESS);
+ if (db_ldap_connect_finish(conn, ldap_get_errno(conn)) < 0) {
+ /* lost connection, close it */
+ db_ldap_conn_close(conn);
+ }
+ return -1;
+ }
+
+ conn->conn_state = LDAP_CONN_STATE_BINDING;
+ conn->default_bind_msgid = msgid;
+
+ timeout_remove(&conn->to);
+ conn->to = timeout_add(DB_LDAP_REQUEST_LOST_TIMEOUT_SECS*1000,
+ ldap_connection_timeout, conn);
+ return 0;
+}
+
+static int db_ldap_bind(struct ldap_connection *conn)
+{
+ if (conn->set.sasl_bind) {
+ if (db_ldap_bind_sasl(conn) < 0)
+ return -1;
+ } else {
+ if (db_ldap_bind_simple(conn) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static void db_ldap_get_fd(struct ldap_connection *conn)
+{
+ int ret;
+
+ /* get the connection's fd */
+ ret = ldap_get_option(conn->ld, LDAP_OPT_DESC, (void *)&conn->fd);
+ if (ret != LDAP_SUCCESS) {
+ i_fatal("LDAP %s: Can't get connection fd: %s",
+ conn->config_path, ldap_err2string(ret));
+ }
+ if (conn->fd <= STDERR_FILENO) {
+ /* Solaris LDAP library seems to be broken */
+ i_fatal("LDAP %s: Buggy LDAP library returned wrong fd: %d",
+ conn->config_path, conn->fd);
+ }
+ i_assert(conn->fd != -1);
+ net_set_nonblock(conn->fd, TRUE);
+}
+
+static void ATTR_NULL(1)
+db_ldap_set_opt(struct ldap_connection *conn, LDAP *ld, int opt,
+ const void *value, const char *optname, const char *value_str)
+{
+ int ret;
+
+ ret = ldap_set_option(ld, opt, value);
+ if (ret != LDAP_SUCCESS) {
+ i_fatal("LDAP %s: Can't set option %s to %s: %s",
+ conn->config_path, optname, value_str, ldap_err2string(ret));
+ }
+}
+
+static void ATTR_NULL(1)
+db_ldap_set_opt_str(struct ldap_connection *conn, LDAP *ld, int opt,
+ const char *value, const char *optname)
+{
+ if (value != NULL)
+ db_ldap_set_opt(conn, ld, opt, value, optname, value);
+}
+
+static void db_ldap_set_tls_options(struct ldap_connection *conn)
+{
+#ifdef OPENLDAP_TLS_OPTIONS
+ db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_CACERTFILE,
+ conn->set.tls_ca_cert_file, "tls_ca_cert_file");
+ db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_CACERTDIR,
+ conn->set.tls_ca_cert_dir, "tls_ca_cert_dir");
+ db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_CERTFILE,
+ conn->set.tls_cert_file, "tls_cert_file");
+ db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_KEYFILE,
+ conn->set.tls_key_file, "tls_key_file");
+ db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_CIPHER_SUITE,
+ conn->set.tls_cipher_suite, "tls_cipher_suite");
+ if (conn->set.tls_require_cert != NULL) {
+ db_ldap_set_opt(conn, NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &conn->set.ldap_tls_require_cert_parsed,
+ "tls_require_cert", conn->set.tls_require_cert);
+ }
+#else
+ if (conn->set.tls_ca_cert_file != NULL ||
+ conn->set.tls_ca_cert_dir != NULL ||
+ conn->set.tls_cert_file != NULL ||
+ conn->set.tls_key_file != NULL ||
+ conn->set.tls_cipher_suite != NULL) {
+ i_fatal("LDAP %s: tls_* settings aren't supported by your LDAP library - they must not be set",
+ conn->config_path);
+ }
+#endif
+}
+
+static void db_ldap_set_options(struct ldap_connection *conn)
+{
+ unsigned int ldap_version;
+ int value;
+
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+ struct timeval tv;
+ int ret;
+
+ tv.tv_sec = DB_LDAP_CONNECT_TIMEOUT_SECS; tv.tv_usec = 0;
+ ret = ldap_set_option(conn->ld, LDAP_OPT_NETWORK_TIMEOUT, &tv);
+ if (ret != LDAP_SUCCESS) {
+ i_fatal("LDAP %s: Can't set network-timeout: %s",
+ conn->config_path, ldap_err2string(ret));
+ }
+#endif
+
+ db_ldap_set_opt(conn, conn->ld, LDAP_OPT_DEREF, &conn->set.ldap_deref,
+ "deref", conn->set.deref);
+#ifdef LDAP_OPT_DEBUG_LEVEL
+ if (str_to_int(conn->set.debug_level, &value) >= 0 && value != 0) {
+ db_ldap_set_opt(conn, NULL, LDAP_OPT_DEBUG_LEVEL, &value,
+ "debug_level", conn->set.debug_level);
+ event_set_forced_debug(conn->event, TRUE);
+ }
+#endif
+
+ ldap_version = conn->set.ldap_version;
+ db_ldap_set_opt(conn, conn->ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_version,
+ "protocol_version", dec2str(ldap_version));
+ db_ldap_set_tls_options(conn);
+}
+
+static void db_ldap_init_ld(struct ldap_connection *conn)
+{
+ int ret;
+
+ if (conn->set.uris != NULL) {
+#ifdef LDAP_HAVE_INITIALIZE
+ ret = ldap_initialize(&conn->ld, conn->set.uris);
+ if (ret != LDAP_SUCCESS) {
+ i_fatal("LDAP %s: ldap_initialize() failed with uris %s: %s",
+ conn->config_path, conn->set.uris,
+ ldap_err2string(ret));
+ }
+#else
+ i_unreached(); /* already checked at init */
+#endif
+ } else {
+ conn->ld = ldap_init(conn->set.hosts, LDAP_PORT);
+ if (conn->ld == NULL) {
+ i_fatal("LDAP %s: ldap_init() failed with hosts: %s",
+ conn->config_path, conn->set.hosts);
+ }
+ }
+ db_ldap_set_options(conn);
+}
+
+int db_ldap_connect(struct ldap_connection *conn)
+{
+ struct timeval start, end;
+ int ret;
+
+ if (conn->conn_state != LDAP_CONN_STATE_DISCONNECTED)
+ return 0;
+
+ i_gettimeofday(&start);
+ i_assert(conn->pending_count == 0);
+
+ if (conn->delayed_connect) {
+ conn->delayed_connect = FALSE;
+ timeout_remove(&conn->to);
+ }
+ if (conn->ld == NULL)
+ db_ldap_init_ld(conn);
+
+ if (conn->set.tls) {
+#ifdef LDAP_HAVE_START_TLS_S
+ ret = ldap_start_tls_s(conn->ld, NULL, NULL);
+ if (ret != LDAP_SUCCESS) {
+ if (ret == LDAP_OPERATIONS_ERROR &&
+ conn->set.uris != NULL &&
+ str_begins(conn->set.uris, "ldaps:")) {
+ i_fatal("LDAP %s: Don't use both tls=yes "
+ "and ldaps URI", conn->config_path);
+ }
+ e_error(conn->event, "ldap_start_tls_s() failed: %s",
+ ldap_err2string(ret));
+ return -1;
+ }
+#else
+ i_unreached(); /* already checked at init */
+#endif
+ }
+
+ if (db_ldap_bind(conn) < 0)
+ return -1;
+
+ i_gettimeofday(&end);
+ int msecs = timeval_diff_msecs(&end, &start);
+ e_debug(conn->event, "LDAP initialization took %d msecs", msecs);
+
+ db_ldap_get_fd(conn);
+ conn->io = io_add(conn->fd, IO_READ, ldap_input, conn);
+ return 0;
+}
+
+static void db_ldap_connect_callback(struct ldap_connection *conn)
+{
+ i_assert(conn->conn_state == LDAP_CONN_STATE_DISCONNECTED);
+ (void)db_ldap_connect(conn);
+}
+
+void db_ldap_connect_delayed(struct ldap_connection *conn)
+{
+ if (conn->delayed_connect)
+ return;
+ conn->delayed_connect = TRUE;
+
+ i_assert(conn->to == NULL);
+ conn->to = timeout_add_short(0, db_ldap_connect_callback, conn);
+}
+
+void db_ldap_enable_input(struct ldap_connection *conn, bool enable)
+{
+ if (!enable) {
+ io_remove(&conn->io);
+ } else {
+ if (conn->io == NULL && conn->fd != -1) {
+ conn->io = io_add(conn->fd, IO_READ, ldap_input, conn);
+ ldap_input(conn);
+ }
+ }
+}
+
+static void db_ldap_disconnect_timeout(struct ldap_connection *conn)
+{
+ db_ldap_abort_requests(conn, UINT_MAX,
+ DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS, FALSE,
+ "Aborting (timeout), we're not connected to LDAP server");
+
+ if (aqueue_count(conn->request_queue) == 0) {
+ /* no requests left, remove this timeout handler */
+ timeout_remove(&conn->to);
+ }
+}
+
+static void db_ldap_conn_close(struct ldap_connection *conn)
+{
+ struct ldap_request *const *requests, *request;
+ unsigned int i;
+
+ conn->conn_state = LDAP_CONN_STATE_DISCONNECTED;
+ conn->delayed_connect = FALSE;
+ conn->default_bind_msgid = -1;
+
+ timeout_remove(&conn->to);
+
+ if (conn->pending_count != 0) {
+ requests = array_front(&conn->request_array);
+ for (i = 0; i < conn->pending_count; i++) {
+ request = requests[aqueue_idx(conn->request_queue, i)];
+
+ i_assert(request->msgid != -1);
+ request->msgid = -1;
+ }
+ conn->pending_count = 0;
+ }
+
+ if (conn->ld != NULL) {
+ ldap_unbind(conn->ld);
+ conn->ld = NULL;
+ }
+ conn->fd = -1;
+
+ /* the fd may have already been closed before ldap_unbind(),
+ so we'll have to use io_remove_closed(). */
+ io_remove_closed(&conn->io);
+
+ if (aqueue_count(conn->request_queue) > 0) {
+ conn->to = timeout_add(DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS *
+ 1000/2, db_ldap_disconnect_timeout, conn);
+ }
+}
+
+struct ldap_field_find_context {
+ ARRAY_TYPE(string) attr_names;
+ pool_t pool;
+};
+
+static int
+db_ldap_field_find(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct ldap_field_find_context *ctx = context;
+ char *ldap_attr;
+
+ if (*data != '\0') {
+ ldap_attr = p_strdup(ctx->pool, t_strcut(data, ':'));
+ if (strchr(ldap_attr, '@') == NULL)
+ array_push_back(&ctx->attr_names, &ldap_attr);
+ }
+ *value_r = NULL;
+ return 1;
+}
+
+void db_ldap_set_attrs(struct ldap_connection *conn, const char *attrlist,
+ char ***attr_names_r, ARRAY_TYPE(ldap_field) *attr_map,
+ const char *skip_attr)
+{
+ static struct var_expand_func_table var_funcs_table[] = {
+ { "ldap", db_ldap_field_find },
+ { "ldap_ptr", db_ldap_field_find },
+ { NULL, NULL }
+ };
+ struct ldap_field_find_context ctx;
+ struct ldap_field *field;
+ string_t *tmp_str;
+ const char *const *attr, *attr_data, *p, *error;
+ char *ldap_attr, *name, *templ;
+ unsigned int i;
+
+ if (*attrlist == '\0')
+ return;
+
+ attr = t_strsplit_spaces(attrlist, ",");
+
+ tmp_str = t_str_new(128);
+ ctx.pool = conn->pool;
+ p_array_init(&ctx.attr_names, conn->pool, 16);
+ for (i = 0; attr[i] != NULL; i++) {
+ /* allow spaces here so "foo=1, bar=2" works */
+ attr_data = attr[i];
+ while (*attr_data == ' ') attr_data++;
+
+ p = strchr(attr_data, '=');
+ if (p == NULL)
+ ldap_attr = name = p_strdup(conn->pool, attr_data);
+ else if (attr_data[0] == '@') {
+ ldap_attr = "";
+ name = p_strdup(conn->pool, attr_data);
+ } else {
+ ldap_attr = p_strdup_until(conn->pool, attr_data, p);
+ name = p_strdup(conn->pool, p + 1);
+ }
+
+ templ = strchr(name, '=');
+ if (templ == NULL) {
+ if (*ldap_attr == '\0') {
+ /* =foo static value */
+ templ = "";
+ }
+ } else {
+ *templ++ = '\0';
+ str_truncate(tmp_str, 0);
+ if (var_expand_with_funcs(tmp_str, templ, NULL,
+ var_funcs_table, &ctx, &error) <= 0) {
+ /* This var_expand_with_funcs call fills the
+ * ldap_field_find_context in ctx, but the
+ * resulting string_t is not used, and the
+ * return value or error_r is not checked since
+ * it gives errors for non-ldap variable
+ * expansions. */
+ }
+ if (strchr(templ, '%') == NULL) {
+ /* backwards compatibility:
+ attr=name=prefix means same as
+ attr=name=prefix%$ when %vars are missing */
+ templ = p_strconcat(conn->pool, templ,
+ "%$", NULL);
+ }
+ }
+
+ if (*name == '\0')
+ e_error(conn->event, "Invalid attrs entry: %s", attr_data);
+ else if (skip_attr == NULL || strcmp(skip_attr, name) != 0) {
+ field = array_append_space(attr_map);
+ if (name[0] == '@') {
+ /* @name=ldapField */
+ name++;
+ field->value_is_dn = TRUE;
+ } else if (name[0] == '!' && name == ldap_attr) {
+ /* !ldapAttr */
+ name = "";
+ i_assert(ldap_attr[0] == '!');
+ ldap_attr++;
+ field->skip = TRUE;
+ }
+ field->name = name;
+ field->value = templ;
+ field->ldap_attr_name = ldap_attr;
+ if (*ldap_attr != '\0' &&
+ strchr(ldap_attr, '@') == NULL) {
+ /* root request's attribute */
+ array_push_back(&ctx.attr_names, &ldap_attr);
+ }
+ }
+ }
+ array_append_zero(&ctx.attr_names);
+ *attr_names_r = array_front_modifiable(&ctx.attr_names);
+}
+
+static const struct var_expand_table *
+db_ldap_value_get_var_expand_table(struct auth_request *auth_request,
+ const char *ldap_value)
+{
+ struct var_expand_table *table;
+ unsigned int count = 1;
+
+ table = auth_request_get_var_expand_table_full(auth_request,
+ auth_request->fields.user, NULL, &count);
+ table[0].key = '$';
+ table[0].value = ldap_value;
+ return table;
+}
+
+#define IS_LDAP_ESCAPED_CHAR(c) \
+ ((((unsigned char)(c)) & 0x80) != 0 || strchr(LDAP_ESCAPE_CHARS, (c)) != NULL)
+
+const char *ldap_escape(const char *str,
+ const struct auth_request *auth_request ATTR_UNUSED)
+{
+ string_t *ret = NULL;
+
+ for (const char *p = str; *p != '\0'; p++) {
+ if (IS_LDAP_ESCAPED_CHAR(*p)) {
+ if (ret == NULL) {
+ ret = t_str_new((size_t) (p - str) + 64);
+ str_append_data(ret, str, (size_t) (p - str));
+ }
+ str_printfa(ret, "\\%02X", (unsigned char)*p);
+ } else if (ret != NULL)
+ str_append_c(ret, *p);
+ }
+
+ return ret == NULL ? str : str_c(ret);
+}
+
+static bool
+ldap_field_hide_password(struct db_ldap_result_iterate_context *ctx,
+ const char *attr)
+{
+ const struct ldap_field *field;
+
+ if (ctx->ldap_request->auth_request->set->debug_passwords)
+ return FALSE;
+
+ array_foreach(ctx->attr_map, field) {
+ if (strcmp(field->ldap_attr_name, attr) == 0) {
+ if (strcmp(field->name, "password") == 0 ||
+ strcmp(field->name, "password_noscheme") == 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+get_ldap_fields(struct db_ldap_result_iterate_context *ctx,
+ struct ldap_connection *conn, LDAPMessage *entry,
+ const char *suffix)
+{
+ struct db_ldap_value *ldap_value;
+ char *attr, **vals;
+ unsigned int i, count;
+ BerElement *ber;
+
+ attr = ldap_first_attribute(conn->ld, entry, &ber);
+ while (attr != NULL) {
+ vals = ldap_get_values(conn->ld, entry, attr);
+
+ ldap_value = p_new(ctx->pool, struct db_ldap_value, 1);
+ if (vals == NULL) {
+ ldap_value->values = p_new(ctx->pool, const char *, 1);
+ count = 0;
+ } else {
+ for (count = 0; vals[count] != NULL; count++) ;
+ }
+
+ ldap_value->values = p_new(ctx->pool, const char *, count + 1);
+ for (i = 0; i < count; i++)
+ ldap_value->values[i] = p_strdup(ctx->pool, vals[i]);
+
+ if (ctx->debug != NULL) {
+ str_printfa(ctx->debug, " %s%s=", attr, suffix);
+ if (count == 0)
+ str_append(ctx->debug, "<no values>");
+ else if (ldap_field_hide_password(ctx, attr))
+ str_append(ctx->debug, PASSWORD_HIDDEN_STR);
+ else {
+ str_append(ctx->debug, ldap_value->values[0]);
+ for (i = 1; i < count; i++) {
+ str_printfa(ctx->debug, ",%s",
+ ldap_value->values[0]);
+ }
+ }
+ }
+ hash_table_insert(ctx->ldap_attrs,
+ p_strconcat(ctx->pool, attr, suffix, NULL),
+ ldap_value);
+
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ attr = ldap_next_attribute(conn->ld, entry, ber);
+ }
+ ber_free(ber, 0);
+}
+
+struct db_ldap_result_iterate_context *
+db_ldap_result_iterate_init_full(struct ldap_connection *conn,
+ struct ldap_request_search *ldap_request,
+ LDAPMessage *res, bool skip_null_values,
+ bool iter_dn_values)
+{
+ struct db_ldap_result_iterate_context *ctx;
+ const struct ldap_request_named_result *named_res;
+ const char *suffix;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"ldap result iter", 1024);
+ ctx = p_new(pool, struct db_ldap_result_iterate_context, 1);
+ ctx->pool = pool;
+ ctx->ldap_request = &ldap_request->request;
+ ctx->attr_map = ldap_request->attr_map;
+ ctx->skip_null_values = skip_null_values;
+ ctx->iter_dn_values = iter_dn_values;
+ hash_table_create(&ctx->ldap_attrs, pool, 0, strcase_hash, strcasecmp);
+ ctx->var = str_new(ctx->pool, 256);
+ if (event_want_debug(ctx->ldap_request->auth_request->event))
+ ctx->debug = t_str_new(256);
+ ctx->ldap_msg = res;
+ ctx->ld = conn->ld;
+
+ get_ldap_fields(ctx, conn, res, "");
+ if (array_is_created(&ldap_request->named_results)) {
+ array_foreach(&ldap_request->named_results, named_res) {
+ suffix = t_strdup_printf("@%s", named_res->field->name);
+ if (named_res->result != NULL) {
+ get_ldap_fields(ctx, conn,
+ named_res->result->msg, suffix);
+ }
+ }
+ }
+ return ctx;
+}
+
+struct db_ldap_result_iterate_context *
+db_ldap_result_iterate_init(struct ldap_connection *conn,
+ struct ldap_request_search *ldap_request,
+ LDAPMessage *res, bool skip_null_values)
+{
+ return db_ldap_result_iterate_init_full(conn, ldap_request, res,
+ skip_null_values, FALSE);
+}
+
+static const char *db_ldap_field_get_default(const char *data)
+{
+ const char *p;
+
+ p = i_strchr_to_next(data, ':');
+ if (p == NULL)
+ return "";
+ else {
+ /* default value given */
+ return p;
+ }
+}
+
+static int
+db_ldap_field_expand(const char *data, void *context,
+ const char **value_r, const char **error_r ATTR_UNUSED)
+{
+ struct db_ldap_result_iterate_context *ctx = context;
+ struct db_ldap_value *ldap_value;
+ const char *field_name = t_strcut(data, ':');
+
+ ldap_value = hash_table_lookup(ctx->ldap_attrs, field_name);
+ if (ldap_value == NULL) {
+ /* requested ldap attribute wasn't returned at all */
+ if (ctx->debug != NULL)
+ str_printfa(ctx->debug, "; %s missing", field_name);
+ *value_r = db_ldap_field_get_default(data);
+ return 1;
+ }
+ ldap_value->used = TRUE;
+
+ if (ldap_value->values[0] == NULL) {
+ /* no value for ldap attribute */
+ *value_r = db_ldap_field_get_default(data);
+ return 1;
+ }
+ if (ldap_value->values[1] != NULL) {
+ e_warning(authdb_event(ctx->ldap_request->auth_request),
+ "Multiple values found for '%s', using value '%s'",
+ field_name, ldap_value->values[0]);
+ }
+ *value_r = ldap_value->values[0];
+ return 1;
+}
+
+static int
+db_ldap_field_ptr_expand(const char *data, void *context,
+ const char **value_r, const char **error_r)
+{
+ struct db_ldap_result_iterate_context *ctx = context;
+ const char *field_name, *suffix;
+
+ suffix = strchr(t_strcut(data, ':'), '@');
+ if (db_ldap_field_expand(data, ctx, &field_name, error_r) <= 0)
+ i_unreached();
+ if (field_name[0] == '\0') {
+ *value_r = "";
+ return 1;
+ }
+ field_name = t_strconcat(field_name, suffix, NULL);
+ return db_ldap_field_expand(field_name, ctx, value_r, error_r);
+}
+
+static int
+db_ldap_field_dn_expand(const char *data ATTR_UNUSED, void *context ATTR_UNUSED,
+ const char **value_r, const char **error_r ATTR_UNUSED)
+{
+ struct db_ldap_result_iterate_context *ctx = context;
+ char *dn = ldap_get_dn(ctx->ld, ctx->ldap_msg);
+ *value_r = t_strdup(dn);
+ ldap_memfree(dn);
+ return 1;
+}
+
+static struct var_expand_func_table ldap_var_funcs_table[] = {
+ { "ldap", db_ldap_field_expand },
+ { "ldap_ptr", db_ldap_field_ptr_expand },
+ { "ldap_dn", db_ldap_field_dn_expand },
+ { NULL, NULL }
+};
+
+static const char *const *
+db_ldap_result_return_value(struct db_ldap_result_iterate_context *ctx,
+ const struct ldap_field *field,
+ struct db_ldap_value *ldap_value)
+{
+ const struct var_expand_table *var_table;
+ const char *const *values, *error;
+
+ if (ldap_value != NULL)
+ values = ldap_value->values;
+ else {
+ /* LDAP attribute doesn't exist */
+ ctx->val_1_arr[0] = NULL;
+ values = ctx->val_1_arr;
+ }
+
+ if (field->value == NULL) {
+ /* use the LDAP attribute's value */
+ } else {
+ /* template */
+ if (values[0] == NULL && *field->ldap_attr_name != '\0') {
+ /* ldapAttr=key=template%$, but ldapAttr doesn't
+ exist. */
+ return values;
+ }
+ if (values[0] != NULL && values[1] != NULL) {
+ e_warning(authdb_event(ctx->ldap_request->auth_request),
+ "Multiple values found for '%s', "
+ "using value '%s'",
+ field->name, values[0]);
+ }
+
+ /* do this lookup separately for each expansion, because:
+ 1) the values are allocated from data stack
+ 2) if "user" field is updated, we want %u/%n/%d updated
+ (and less importantly the same for other variables) */
+ var_table = db_ldap_value_get_var_expand_table(
+ ctx->ldap_request->auth_request, values[0]);
+ if (var_expand_with_funcs(ctx->var, field->value, var_table,
+ ldap_var_funcs_table, ctx, &error) <= 0) {
+ e_warning(authdb_event(ctx->ldap_request->auth_request),
+ "Failed to expand template %s: %s",
+ field->value, error);
+ }
+ ctx->val_1_arr[0] = str_c(ctx->var);
+ values = ctx->val_1_arr;
+ }
+ return values;
+}
+
+bool db_ldap_result_iterate_next(struct db_ldap_result_iterate_context *ctx,
+ const char **name_r,
+ const char *const **values_r)
+{
+ const struct var_expand_table *tab;
+ const struct ldap_field *field;
+ struct db_ldap_value *ldap_value;
+ unsigned int pos;
+ const char *error;
+
+ do {
+ if (ctx->attr_idx == array_count(ctx->attr_map))
+ return FALSE;
+ field = array_idx(ctx->attr_map, ctx->attr_idx++);
+ } while (field->value_is_dn != ctx->iter_dn_values ||
+ field->skip);
+
+ ldap_value = *field->ldap_attr_name == '\0' ? NULL :
+ hash_table_lookup(ctx->ldap_attrs, field->ldap_attr_name);
+ if (ldap_value != NULL)
+ ldap_value->used = TRUE;
+ else if (ctx->debug != NULL && *field->ldap_attr_name != '\0')
+ str_printfa(ctx->debug, "; %s missing", field->ldap_attr_name);
+
+ str_truncate(ctx->var, 0);
+ *values_r = db_ldap_result_return_value(ctx, field, ldap_value);
+
+ if (strchr(field->name, '%') == NULL)
+ *name_r = field->name;
+ else {
+ /* expand %variables also for LDAP name fields. we'll use the
+ same ctx->var, which may already contain the value. */
+ str_append_c(ctx->var, '\0');
+ pos = str_len(ctx->var);
+
+ tab = auth_request_get_var_expand_table(
+ ctx->ldap_request->auth_request, NULL);
+ if (var_expand_with_funcs(ctx->var, field->name, tab,
+ ldap_var_funcs_table, ctx, &error) <= 0) {
+ e_warning(authdb_event(ctx->ldap_request->auth_request),
+ "Failed to expand %s: %s", field->name, error);
+ }
+ *name_r = str_c(ctx->var) + pos;
+ }
+
+ if (ctx->skip_null_values && (*values_r)[0] == NULL) {
+ /* no values. don't confuse the caller with this reply. */
+ return db_ldap_result_iterate_next(ctx, name_r, values_r);
+ }
+ return TRUE;
+}
+
+static void
+db_ldap_result_finish_debug(struct db_ldap_result_iterate_context *ctx)
+{
+ struct hash_iterate_context *iter;
+ char *name;
+ struct db_ldap_value *value;
+ unsigned int unused_count = 0;
+ size_t orig_len;
+
+ if (ctx->ldap_request->result_logged)
+ return;
+
+ orig_len = str_len(ctx->debug);
+ if (orig_len == 0) {
+ e_debug(authdb_event(ctx->ldap_request->auth_request),
+ "no fields returned by the server");
+ return;
+ }
+
+ str_append(ctx->debug, "; ");
+
+ iter = hash_table_iterate_init(ctx->ldap_attrs);
+ while (hash_table_iterate(iter, ctx->ldap_attrs, &name, &value)) {
+ if (!value->used) {
+ str_printfa(ctx->debug, "%s,", name);
+ unused_count++;
+ }
+ }
+ hash_table_iterate_deinit(&iter);
+
+ if (unused_count == 0)
+ str_truncate(ctx->debug, orig_len);
+ else {
+ str_truncate(ctx->debug, str_len(ctx->debug)-1);
+ str_append(ctx->debug, " unused");
+ }
+ e_debug(authdb_event(ctx->ldap_request->auth_request),
+ "result: %s", str_c(ctx->debug) + 1);
+
+ ctx->ldap_request->result_logged = TRUE;
+}
+
+void db_ldap_result_iterate_deinit(struct db_ldap_result_iterate_context **_ctx)
+{
+ struct db_ldap_result_iterate_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+
+ if (ctx->debug != NULL)
+ db_ldap_result_finish_debug(ctx);
+ hash_table_destroy(&ctx->ldap_attrs);
+ pool_unref(&ctx->pool);
+}
+
+static const char *parse_setting(const char *key, const char *value,
+ struct ldap_connection *conn)
+{
+ return parse_setting_from_defs(conn->pool, setting_defs,
+ &conn->set, key, value);
+}
+
+static struct ldap_connection *ldap_conn_find(const char *config_path)
+{
+ struct ldap_connection *conn;
+
+ for (conn = ldap_connections; conn != NULL; conn = conn->next) {
+ if (strcmp(conn->config_path, config_path) == 0)
+ return conn;
+ }
+
+ return NULL;
+}
+
+struct ldap_connection *db_ldap_init(const char *config_path, bool userdb)
+{
+ struct ldap_connection *conn;
+ const char *str, *error;
+ pool_t pool;
+
+ /* see if it already exists */
+ conn = ldap_conn_find(config_path);
+ if (conn != NULL) {
+ if (userdb)
+ conn->userdb_used = TRUE;
+ conn->refcount++;
+ return conn;
+ }
+
+ if (*config_path == '\0')
+ i_fatal("LDAP: Configuration file path not given");
+
+ pool = pool_alloconly_create("ldap_connection", 1024);
+ conn = p_new(pool, struct ldap_connection, 1);
+ conn->pool = pool;
+ conn->refcount = 1;
+
+ conn->userdb_used = userdb;
+ conn->conn_state = LDAP_CONN_STATE_DISCONNECTED;
+ conn->default_bind_msgid = -1;
+ conn->fd = -1;
+ conn->config_path = p_strdup(pool, config_path);
+ conn->set = default_ldap_settings;
+ if (!settings_read_nosection(config_path, parse_setting, conn, &error))
+ i_fatal("ldap %s: %s", config_path, error);
+
+ if (conn->set.base == NULL)
+ i_fatal("LDAP %s: No base given", config_path);
+
+ if (conn->set.uris == NULL && conn->set.hosts == NULL)
+ i_fatal("LDAP %s: No uris or hosts set", config_path);
+#ifndef LDAP_HAVE_INITIALIZE
+ if (conn->set.uris != NULL) {
+ i_fatal("LDAP %s: uris set, but Dovecot compiled without support for LDAP uris "
+ "(ldap_initialize() not supported by LDAP library)", config_path);
+ }
+#endif
+#ifndef LDAP_HAVE_START_TLS_S
+ if (conn->set.tls)
+ i_fatal("LDAP %s: tls=yes, but your LDAP library doesn't support TLS", config_path);
+#endif
+#ifndef HAVE_LDAP_SASL
+ if (conn->set.sasl_bind)
+ i_fatal("LDAP %s: sasl_bind=yes but no SASL support compiled in", conn->config_path);
+#endif
+ if (conn->set.ldap_version < 3) {
+ if (conn->set.sasl_bind)
+ i_fatal("LDAP %s: sasl_bind=yes requires ldap_version=3", config_path);
+ if (conn->set.tls)
+ i_fatal("LDAP %s: tls=yes requires ldap_version=3", config_path);
+ }
+#ifdef OPENLDAP_TLS_OPTIONS
+ if (conn->set.tls_require_cert != NULL) {
+ if (tls_require_cert2str(conn->set.tls_require_cert,
+ &conn->set.ldap_tls_require_cert_parsed) < 0)
+ i_fatal("LDAP %s: Unknown tls_require_cert value '%s'",
+ config_path, conn->set.tls_require_cert);
+ }
+#endif
+
+ if (*conn->set.ldaprc_path != '\0') {
+ str = getenv("LDAPRC");
+ if (str != NULL && strcmp(str, conn->set.ldaprc_path) != 0) {
+ i_fatal("LDAP %s: Multiple different ldaprc_path "
+ "settings not allowed (%s and %s)",
+ config_path, str, conn->set.ldaprc_path);
+ }
+ env_put("LDAPRC", conn->set.ldaprc_path);
+ }
+
+ if (deref2str(conn->set.deref, &conn->set.ldap_deref) < 0)
+ i_fatal("LDAP %s: Unknown deref option '%s'", config_path, conn->set.deref);
+ if (scope2str(conn->set.scope, &conn->set.ldap_scope) < 0)
+ i_fatal("LDAP %s: Unknown scope option '%s'", config_path, conn->set.scope);
+
+ conn->event = event_create(auth_event);
+ event_set_append_log_prefix(conn->event, t_strdup_printf(
+ "ldap(%s): ", conn->config_path));
+
+ i_array_init(&conn->request_array, 512);
+ conn->request_queue = aqueue_init(&conn->request_array.arr);
+
+ conn->next = ldap_connections;
+ ldap_connections = conn;
+
+ db_ldap_init_ld(conn);
+ return conn;
+}
+
+void db_ldap_unref(struct ldap_connection **_conn)
+{
+ struct ldap_connection *conn = *_conn;
+ struct ldap_connection **p;
+
+ *_conn = NULL;
+ i_assert(conn->refcount >= 0);
+ if (--conn->refcount > 0)
+ return;
+
+ for (p = &ldap_connections; *p != NULL; p = &(*p)->next) {
+ if (*p == conn) {
+ *p = conn->next;
+ break;
+ }
+ }
+
+ db_ldap_abort_requests(conn, UINT_MAX, 0, FALSE, "Shutting down");
+ i_assert(conn->pending_count == 0);
+ db_ldap_conn_close(conn);
+ i_assert(conn->to == NULL);
+
+ array_free(&conn->request_array);
+ aqueue_deinit(&conn->request_queue);
+
+ event_unref(&conn->event);
+ pool_unref(&conn->pool);
+}
+
+#ifndef BUILTIN_LDAP
+/* Building a plugin */
+extern struct passdb_module_interface passdb_ldap_plugin;
+extern struct userdb_module_interface userdb_ldap_plugin;
+
+void authdb_ldap_init(void);
+void authdb_ldap_deinit(void);
+
+void authdb_ldap_init(void)
+{
+ passdb_register_module(&passdb_ldap_plugin);
+ userdb_register_module(&userdb_ldap_plugin);
+
+}
+void authdb_ldap_deinit(void)
+{
+ passdb_unregister_module(&passdb_ldap_plugin);
+ userdb_unregister_module(&userdb_ldap_plugin);
+}
+#endif
+
+#endif
diff --git a/src/auth/db-ldap.h b/src/auth/db-ldap.h
new file mode 100644
index 0000000..e919e79
--- /dev/null
+++ b/src/auth/db-ldap.h
@@ -0,0 +1,221 @@
+#ifndef DB_LDAP_H
+#define DB_LDAP_H
+
+/* Functions like ldap_bind() have been deprecated in OpenLDAP 2.3
+ This define enables them until the code here can be refactored */
+#define LDAP_DEPRECATED 1
+
+/* Maximum number of pending requests before delaying new requests. */
+#define DB_LDAP_MAX_PENDING_REQUESTS 8
+/* connect() timeout to LDAP */
+#define DB_LDAP_CONNECT_TIMEOUT_SECS 5
+/* If LDAP connection is down, fail requests after waiting for this long. */
+#define DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS 4
+/* If request is still in queue after this many seconds and other requests
+ have been replied, assume the request was lost and abort it. */
+#define DB_LDAP_REQUEST_LOST_TIMEOUT_SECS 60
+/* If server disconnects us, don't reconnect if no requests have been sent
+ for this many seconds. */
+#define DB_LDAP_IDLE_RECONNECT_SECS 60
+
+#include <ldap.h>
+
+struct auth_request;
+struct ldap_connection;
+struct ldap_request;
+
+typedef void db_search_callback_t(struct ldap_connection *conn,
+ struct ldap_request *request,
+ LDAPMessage *res);
+
+struct ldap_settings {
+ const char *hosts;
+ const char *uris;
+ const char *dn;
+ const char *dnpass;
+ bool auth_bind;
+ const char *auth_bind_userdn;
+
+ bool tls;
+ bool sasl_bind;
+ const char *sasl_mech;
+ const char *sasl_realm;
+ const char *sasl_authz_id;
+
+ const char *tls_ca_cert_file;
+ const char *tls_ca_cert_dir;
+ const char *tls_cert_file;
+ const char *tls_key_file;
+ const char *tls_cipher_suite;
+ const char *tls_require_cert;
+
+ const char *deref;
+ const char *scope;
+ const char *base;
+ unsigned int ldap_version;
+
+ const char *ldaprc_path;
+ const char *debug_level;
+
+ const char *user_attrs;
+ const char *user_filter;
+ const char *pass_attrs;
+ const char *pass_filter;
+ const char *iterate_attrs;
+ const char *iterate_filter;
+
+ const char *default_pass_scheme;
+ bool userdb_warning_disable; /* deprecated for now at least */
+ bool blocking;
+
+ /* ... */
+ int ldap_deref, ldap_scope, ldap_tls_require_cert_parsed;
+ uid_t uid;
+ gid_t gid;
+};
+
+enum ldap_request_type {
+ LDAP_REQUEST_TYPE_SEARCH,
+ LDAP_REQUEST_TYPE_BIND
+};
+
+struct ldap_field {
+ /* Dovecot field name. */
+ const char *name;
+ /* Field value template with %vars. NULL = same as LDAP value. */
+ const char *value;
+ /* LDAP attribute name, or "" if this is a static field. */
+ const char *ldap_attr_name;
+
+ /* LDAP value contains a DN, which is looked up and used for @name
+ attributes. */
+ bool value_is_dn;
+ /* This attribute is used internally only via %{ldap_ptr},
+ it shouldn't be returned in iteration. */
+ bool skip;
+};
+ARRAY_DEFINE_TYPE(ldap_field, struct ldap_field);
+
+struct ldap_request {
+ enum ldap_request_type type;
+
+ /* msgid for sent requests, -1 if not sent */
+ int msgid;
+ /* timestamp when request was created */
+ time_t create_time;
+
+ /* Number of times this request has been sent to LDAP server. This
+ increases when LDAP gets disconnected and reconnect send the request
+ again. */
+ unsigned int send_count;
+
+ bool failed:1;
+ /* This is to prevent double logging the result */
+ bool result_logged:1;
+
+ db_search_callback_t *callback;
+ struct auth_request *auth_request;
+};
+
+struct ldap_request_named_result {
+ const struct ldap_field *field;
+ const char *dn;
+ struct db_ldap_result *result;
+};
+
+struct ldap_request_search {
+ struct ldap_request request;
+
+ const char *base;
+ const char *filter;
+ char **attributes; /* points to pass_attr_names / user_attr_names */
+ const ARRAY_TYPE(ldap_field) *attr_map;
+
+ struct db_ldap_result *result;
+ ARRAY(struct ldap_request_named_result) named_results;
+ unsigned int name_idx;
+
+ bool multi_entry;
+};
+
+struct ldap_request_bind {
+ struct ldap_request request;
+
+ const char *dn;
+};
+
+enum ldap_connection_state {
+ /* Not connected */
+ LDAP_CONN_STATE_DISCONNECTED,
+ /* Binding - either to default dn or doing auth bind */
+ LDAP_CONN_STATE_BINDING,
+ /* Bound to auth dn */
+ LDAP_CONN_STATE_BOUND_AUTH,
+ /* Bound to default dn */
+ LDAP_CONN_STATE_BOUND_DEFAULT
+};
+
+struct ldap_connection {
+ struct ldap_connection *next;
+
+ pool_t pool;
+ int refcount;
+ struct event *event;
+
+ char *config_path;
+ struct ldap_settings set;
+
+ LDAP *ld;
+ enum ldap_connection_state conn_state;
+ int default_bind_msgid;
+
+ int fd;
+ struct io *io;
+ struct timeout *to;
+
+ /* Request queue contains sent requests at tail (msgid != -1) and
+ queued requests at head (msgid == -1). */
+ struct aqueue *request_queue;
+ ARRAY(struct ldap_request *) request_array;
+ /* Number of messages in queue with msgid != -1 */
+ unsigned int pending_count;
+
+ /* Timestamp when we last received a reply */
+ time_t last_reply_stamp;
+
+ char **pass_attr_names, **user_attr_names, **iterate_attr_names;
+ ARRAY_TYPE(ldap_field) pass_attr_map, user_attr_map, iterate_attr_map;
+ bool userdb_used;
+ bool delayed_connect;
+};
+
+/* Send/queue request */
+void db_ldap_request(struct ldap_connection *conn,
+ struct ldap_request *request);
+
+void db_ldap_set_attrs(struct ldap_connection *conn, const char *attrlist,
+ char ***attr_names_r, ARRAY_TYPE(ldap_field) *attr_map,
+ const char *skip_attr) ATTR_NULL(5);
+
+struct ldap_connection *db_ldap_init(const char *config_path, bool userdb);
+void db_ldap_unref(struct ldap_connection **conn);
+
+int db_ldap_connect(struct ldap_connection *conn);
+void db_ldap_connect_delayed(struct ldap_connection *conn);
+
+void db_ldap_enable_input(struct ldap_connection *conn, bool enable);
+
+const char *ldap_escape(const char *str,
+ const struct auth_request *auth_request);
+const char *ldap_get_error(struct ldap_connection *conn);
+
+struct db_ldap_result_iterate_context *
+db_ldap_result_iterate_init(struct ldap_connection *conn,
+ struct ldap_request_search *ldap_request,
+ LDAPMessage *res, bool skip_null_values);
+bool db_ldap_result_iterate_next(struct db_ldap_result_iterate_context *ctx,
+ const char **name_r,
+ const char *const **values_r);
+void db_ldap_result_iterate_deinit(struct db_ldap_result_iterate_context **ctx);
+
+#endif
diff --git a/src/auth/db-lua.c b/src/auth/db-lua.c
new file mode 100644
index 0000000..e31d5d9
--- /dev/null
+++ b/src/auth/db-lua.c
@@ -0,0 +1,814 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD)
+
+#include "llist.h"
+#include "istream.h"
+#include "array.h"
+#include "sha1.h"
+#include "hex-binary.h"
+#include "strescape.h"
+#include "auth.h"
+#include "passdb.h"
+#include "userdb.h"
+#include "auth-request.h"
+#include "userdb-template.h"
+#include "passdb-template.h"
+#include "password-scheme.h"
+#include "auth-request-var-expand.h"
+
+#define AUTH_LUA_PASSDB_LOOKUP "auth_passdb_lookup"
+#define AUTH_LUA_USERDB_LOOKUP "auth_userdb_lookup"
+#define AUTH_LUA_USERDB_ITERATE "auth_userdb_iterate"
+
+#define AUTH_LUA_DOVECOT_AUTH "dovecot_auth"
+#define AUTH_LUA_AUTH_REQUEST "auth_request*"
+
+#include "db-lua.h"
+#include "dlua-script-private.h"
+
+struct auth_lua_userdb_iterate_context {
+ struct userdb_iterate_context ctx;
+ pool_t pool;
+ unsigned int idx;
+ ARRAY_TYPE(const_string) users;
+};
+
+static struct auth_request *
+auth_lua_check_auth_request(lua_State *L, int arg);
+
+static int
+auth_request_lua_do_var_expand(struct auth_request *req, const char *tpl,
+ const char **value_r, const char **error_r)
+{
+ const char *error;
+ if (t_auth_request_var_expand(tpl, req, NULL, value_r, &error) < 0) {
+ *error_r = t_strdup_printf("var_expand(%s) failed: %s",
+ tpl, error);
+ return -1;
+ }
+ return 0;
+}
+
+static int auth_request_lua_var_expand(lua_State *L)
+{
+ struct auth_request *req = auth_lua_check_auth_request(L, 1);
+ const char *tpl = luaL_checkstring(L, 2);
+ const char *value, *error;
+
+ if (auth_request_lua_do_var_expand(req, tpl, &value, &error) < 0) {
+ return luaL_error(L, "%s", error);
+ } else {
+ lua_pushstring(L, value);
+ }
+ return 1;
+}
+
+static const char *const *
+auth_request_template_build(struct auth_request *req, const char *str,
+ unsigned int *count_r)
+{
+ if (req->userdb_lookup) {
+ struct userdb_template *tpl =
+ userdb_template_build(pool_datastack_create(), "lua", str);
+ if (userdb_template_is_empty(tpl))
+ return NULL;
+ return userdb_template_get_args(tpl, count_r);
+ } else {
+ struct passdb_template *tpl =
+ passdb_template_build(pool_datastack_create(), str);
+ if (passdb_template_is_empty(tpl))
+ return NULL;
+ return passdb_template_get_args(tpl, count_r);
+ }
+}
+
+static int auth_request_lua_response_from_template(lua_State *L)
+{
+ struct auth_request *req = auth_lua_check_auth_request(L, 1);
+ const char *tplstr = luaL_checkstring(L, 2);
+ const char *error,*expanded;
+ unsigned int count,i;
+
+ const char *const *fields = auth_request_template_build(req, tplstr, &count);
+
+ /* push new table to stack */
+ lua_newtable(L);
+
+ if (fields == NULL)
+ return 1;
+
+ i_assert((count % 2) == 0);
+
+ for(i = 0; i < count; i+=2) {
+ const char *key = fields[i];
+ const char *value = fields[i+1];
+
+ if (value == NULL) {
+ lua_pushnil(L);
+ } else if (auth_request_lua_do_var_expand(req, value, &expanded, &error) < 0) {
+ return luaL_error(L, "%s", error);
+ } else {
+ lua_pushstring(L, expanded);
+ }
+ lua_setfield(L, -2, key);
+ }
+
+ /* stack should be left with table */
+ return 1;
+}
+
+static int auth_request_lua_log_debug(lua_State *L)
+{
+ if (global_auth_settings->debug) {
+ struct auth_request *request = auth_lua_check_auth_request(L, 1);
+ const char *msg = luaL_checkstring(L, 2);
+ e_debug(authdb_event(request), "db-lua: %s", msg);
+ }
+ return 0;
+}
+
+static int auth_request_lua_log_info(lua_State *L)
+{
+ struct auth_request *request = auth_lua_check_auth_request(L, 1);
+ const char *msg = luaL_checkstring(L, 2);
+ e_info(authdb_event(request), "db-lua: %s", msg);
+ return 0;
+}
+
+static int auth_request_lua_log_warning(lua_State *L)
+{
+ struct auth_request *request = auth_lua_check_auth_request(L, 1);
+ const char *msg = luaL_checkstring(L, 2);
+ e_warning(authdb_event(request), "db-lua: %s", msg);
+ return 0;
+}
+
+static int auth_request_lua_log_error(lua_State *L)
+{
+ struct auth_request *request = auth_lua_check_auth_request(L, 1);
+ const char *msg = luaL_checkstring(L, 2);
+ e_error(authdb_event(request), "db-lua: %s", msg);
+ return 0;
+}
+
+static int auth_request_lua_passdb(lua_State *L)
+{
+ struct auth_request *request = auth_lua_check_auth_request(L, 1);
+ const char *key = luaL_checkstring(L, 2);
+ lua_pop(L, 1);
+
+ if (request->fields.extra_fields == NULL) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushstring(L, auth_fields_find(request->fields.extra_fields, key));
+ return 1;
+}
+
+static int auth_request_lua_userdb(lua_State *L)
+{
+ struct auth_request *request = auth_lua_check_auth_request(L, 1);
+ const char *key = luaL_checkstring(L, 2);
+ lua_pop(L, 1);
+
+ if (request->fields.userdb_reply == NULL) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushstring(L, auth_fields_find(request->fields.userdb_reply, key));
+ return 1;
+}
+
+static int auth_request_lua_password_verify(lua_State *L)
+{
+ struct auth_request *request = auth_lua_check_auth_request(L, 1);
+ const char *crypted_password = lua_tostring(L, 2);
+ const char *scheme;
+ const char *plain_password = lua_tostring(L, 3);
+ const char *error = NULL;
+ const unsigned char *raw_password = NULL;
+ size_t raw_password_size;
+ int ret;
+ struct password_generate_params gen_params = {
+ .user = request->fields.original_username,
+ .rounds = 0
+ };
+ scheme = password_get_scheme(&crypted_password);
+ if (scheme == NULL)
+ scheme = "PLAIN";
+ ret = password_decode(crypted_password, scheme,
+ &raw_password, &raw_password_size, &error);
+ if (ret <= 0) {
+ if (ret < 0) {
+ error = t_strdup_printf("Password data is not valid for scheme %s: %s",
+ scheme, error);
+ } else {
+ error = t_strdup_printf("Unknown scheme %s", scheme);
+ }
+ } else {
+ /* Use original_username since it may be important for some
+ password schemes (eg. digest-md5).
+ */
+ ret = password_verify(plain_password, &gen_params,
+ scheme, raw_password, raw_password_size, &error);
+ }
+
+ lua_pushnumber(L, ret);
+ lua_pushstring(L, error);
+
+ return 2;
+}
+
+static int auth_request_lua_event(lua_State *L)
+{
+ struct auth_request *request = auth_lua_check_auth_request(L, 1);
+ struct event *event = event_create(authdb_event(request));
+
+ dlua_push_event(L, event);
+ event_unref(&event);
+ return 1;
+}
+
+/* put all methods here */
+static const luaL_Reg auth_request_methods[] ={
+ { "var_expand", auth_request_lua_var_expand },
+ { "response_from_template", auth_request_lua_response_from_template },
+ { "log_debug", auth_request_lua_log_debug },
+ { "log_info", auth_request_lua_log_info },
+ { "log_warning", auth_request_lua_log_warning },
+ { "log_error", auth_request_lua_log_error },
+ { "password_verify", auth_request_lua_password_verify },
+ { "event", auth_request_lua_event },
+ { NULL, NULL }
+};
+
+static int auth_request_lua_index(lua_State *L)
+{
+ struct auth_request *req = auth_lua_check_auth_request(L, 1);
+ const char *key = luaL_checkstring(L, 2);
+ lua_pop(L, 1);
+
+ const struct var_expand_table *table =
+ auth_request_get_var_expand_table(req, NULL);
+
+ /* check if it's variable */
+ for(unsigned int i = 0; i < AUTH_REQUEST_VAR_TAB_COUNT; i++) {
+ if (null_strcmp(table[i].long_key, key) == 0) {
+ lua_pushstring(L, table[i].value);
+ return 1;
+ }
+ }
+
+ /* check if it's function, then */
+ const luaL_Reg *ptr = auth_request_methods;
+ while(ptr->name != NULL) {
+ if (null_strcmp(key, ptr->name) == 0) {
+ lua_pushcfunction(L, ptr->func);
+ return 1;
+ }
+ ptr++;
+ }
+
+ lua_pushstring(L, key);
+ lua_rawget(L, 1);
+
+ return 1;
+}
+
+static void auth_lua_push_auth_request(lua_State *L, struct auth_request *req)
+{
+ luaL_checkstack(L, 4, "out of memory");
+ /* create a table for holding few things */
+ lua_createtable(L, 0, 3);
+ luaL_setmetatable(L, AUTH_LUA_AUTH_REQUEST);
+
+ lua_pushlightuserdata(L, req);
+ lua_setfield(L, -2, "item");
+
+ lua_newtable(L);
+ lua_pushlightuserdata(L, req);
+ lua_setfield(L, -2, "item");
+ luaL_setmetatable(L, "passdb_"AUTH_LUA_AUTH_REQUEST);
+ lua_setfield(L, -2, "passdb");
+
+ lua_newtable(L);
+ lua_pushlightuserdata(L, req);
+ lua_setfield(L, -2, "item");
+ luaL_setmetatable(L, "userdb_"AUTH_LUA_AUTH_REQUEST);
+ lua_setfield(L, -2, "userdb");
+
+ lua_pushboolean(L, req->fields.skip_password_check);
+ lua_setfield(L, -2, "skip_password_check");
+
+#undef LUA_TABLE_SET_BOOL
+#define LUA_TABLE_SET_BOOL(field) \
+ lua_pushboolean(L, req->field); \
+ lua_setfield(L, -2, #field);
+
+ LUA_TABLE_SET_BOOL(passdbs_seen_user_unknown);
+ LUA_TABLE_SET_BOOL(passdbs_seen_internal_failure);
+ LUA_TABLE_SET_BOOL(userdbs_seen_internal_failure);
+}
+
+static struct auth_request *
+auth_lua_check_auth_request(lua_State *L, int arg)
+{
+ if (!lua_istable(L, arg)) {
+ (void)luaL_error(L, "Bad argument #%d, expected %s got %s",
+ arg, "auth_request",
+ lua_typename(L, lua_type(L, arg)));
+ }
+ lua_pushstring(L, "item");
+ lua_rawget(L, arg);
+ void *bp = (void*)lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ return (struct auth_request*)bp;
+}
+
+static void auth_lua_auth_request_register(lua_State *L)
+{
+ luaL_newmetatable(L, AUTH_LUA_AUTH_REQUEST);
+ lua_pushcfunction(L, auth_request_lua_index);
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+
+ /* register passdb */
+ luaL_newmetatable(L, "passdb_"AUTH_LUA_AUTH_REQUEST);
+ lua_pushcfunction(L, auth_request_lua_passdb);
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+
+ /* register userdb */
+ luaL_newmetatable(L, "userdb_"AUTH_LUA_AUTH_REQUEST);
+ lua_pushcfunction(L, auth_request_lua_userdb);
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+}
+
+static struct dlua_table_values auth_lua_dovecot_auth_values[] = {
+ DLUA_TABLE_ENUM(PASSDB_RESULT_INTERNAL_FAILURE),
+ DLUA_TABLE_ENUM(PASSDB_RESULT_SCHEME_NOT_AVAILABLE),
+ DLUA_TABLE_ENUM(PASSDB_RESULT_USER_UNKNOWN),
+ DLUA_TABLE_ENUM(PASSDB_RESULT_USER_DISABLED),
+ DLUA_TABLE_ENUM(PASSDB_RESULT_PASS_EXPIRED),
+ DLUA_TABLE_ENUM(PASSDB_RESULT_NEXT),
+ DLUA_TABLE_ENUM(PASSDB_RESULT_PASSWORD_MISMATCH),
+ DLUA_TABLE_ENUM(PASSDB_RESULT_OK),
+
+ DLUA_TABLE_ENUM(USERDB_RESULT_INTERNAL_FAILURE),
+ DLUA_TABLE_ENUM(USERDB_RESULT_USER_UNKNOWN),
+ DLUA_TABLE_ENUM(USERDB_RESULT_OK),
+
+ DLUA_TABLE_END
+};
+static luaL_Reg auth_lua_dovecot_auth_methods[] = {
+ { NULL, NULL }
+};
+
+static void auth_lua_dovecot_auth_register(lua_State *L)
+{
+ dlua_get_dovecot(L);
+ /* Create new table for holding values */
+ lua_newtable(L);
+
+ /* register constants */
+ dlua_set_members(L, auth_lua_dovecot_auth_values, -1);
+
+ /* push new metatable to stack */
+ luaL_newmetatable(L, AUTH_LUA_DOVECOT_AUTH);
+ /* this will register functions to the metatable itself */
+ luaL_setfuncs(L, auth_lua_dovecot_auth_methods, 0);
+ /* point __index to self */
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -1, "__index");
+ /* set table's metatable, pops stack */
+ lua_setmetatable(L, -2);
+
+ /* put this as "dovecot.auth" */
+ lua_setfield(L, -2, "auth");
+
+ /* pop dovecot */
+ lua_pop(L, 1);
+}
+
+int auth_lua_script_init(struct dlua_script *script, const char **error_r)
+{
+ dlua_dovecot_register(script);
+ auth_lua_dovecot_auth_register(script->L);
+ auth_lua_auth_request_register(script->L);
+ return dlua_script_init(script, error_r);
+}
+
+static int auth_lua_call_lookup(lua_State *L, const char *fn,
+ struct auth_request *req, const char **error_r)
+{
+ int err = 0;
+
+ e_debug(authdb_event(req), "Calling %s", fn);
+
+ /* call with auth request as parameter */
+ auth_lua_push_auth_request(L, req);
+ if (dlua_pcall(L, fn, 1, 2, error_r) < 0)
+ return -1;
+
+ if (!lua_isnumber(L, -2)) {
+ *error_r = t_strdup_printf("db-lua: %s(req) invalid return value "
+ "(expected number got %s)",
+ fn, luaL_typename(L, -2));
+ err = -1;
+ } else if (!lua_isstring(L, -1) && !lua_istable(L, -1)) {
+ *error_r = t_strdup_printf("db-lua: %s(req) invalid return value "
+ "(expected string or table, got %s)",
+ fn, luaL_typename(L, -1));
+ err = -1;
+ }
+
+ if (err != 0) {
+ lua_pop(L, 2);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ return 0;
+}
+
+static void
+auth_lua_export_fields(struct auth_request *req,
+ const char *str,
+ const char **scheme_r, const char **password_r)
+{
+ const char *const *fields = t_strsplit_spaces(str, " ");
+ while(*fields != NULL) {
+ const char *value = strchr(*fields, '=');
+ const char *key;
+
+ if (value == NULL) {
+ key = *fields;
+ value = "";
+ } else {
+ key = t_strdup_until(*fields, value++);
+ }
+
+ if (password_r != NULL && strcmp(key, "password") == 0) {
+ *scheme_r = password_get_scheme(&value);
+ *password_r = value;
+ } else if (req->userdb_lookup) {
+ auth_request_set_userdb_field(req, key, value);
+ } else {
+ auth_request_set_field(req, key, value, STATIC_PASS_SCHEME);
+ }
+ fields++;
+ }
+}
+
+static void auth_lua_export_table(lua_State *L, struct auth_request *req,
+ const char **scheme_r, const char **password_r)
+{
+ lua_pushvalue(L, -1);
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ const char *key = t_strdup(lua_tostring(L, -2));
+ if (*key == '\0') {
+ e_warning(authdb_event(req),
+ "db-lua: Field key cannot be empty - ignoring");
+ lua_pop(L, 1);
+ continue;
+ }
+ if (strpbrk(key, "\t\n\r") != NULL) {
+ e_warning(authdb_event(req),
+ "db-lua: Field key cannot contain <CR>, <LF> or <TAB> - ignoring");
+ lua_pop(L, 1);
+ continue;
+ }
+
+ const char *value;
+ int type = lua_type(L, -1);
+ switch(type) {
+ case LUA_TNUMBER:
+ value = dec2str(lua_tointeger(L, -1));
+ break;
+ case LUA_TBOOLEAN:
+ value = lua_toboolean(L, -1) ? "yes" : "no";
+ break;
+ case LUA_TSTRING:
+ value = t_strdup(lua_tostring(L, -1));
+ break;
+ case LUA_TNIL:
+ value = "";
+ break;
+ default:
+ e_warning(authdb_event(req),
+ "db-lua: '%s' has invalid value type %s - ignoring",
+ key, lua_typename(L, -1));
+ value = NULL;
+ }
+
+ if (value == NULL) {
+ /* do not add */
+ } else if (password_r != NULL && strcmp(key, "password") == 0) {
+ *scheme_r = password_get_scheme(&value);
+ *password_r = value;
+ } else if (req->userdb_lookup) {
+ auth_request_set_userdb_field(req, key, value);
+ } else {
+ auth_request_set_field(req, key, value, STATIC_PASS_SCHEME);
+ }
+ lua_pop(L, 1);
+ }
+
+ /* stack has
+ key
+ table
+ passdb_result
+ */
+ lua_pop(L, 3);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+}
+
+static enum userdb_result
+auth_lua_export_userdb_table(lua_State *L, struct auth_request *req,
+ const char **error_r)
+{
+ enum userdb_result ret = lua_tointeger(L, -2);
+
+ if (ret != USERDB_RESULT_OK) {
+ lua_pop(L, 2);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ *error_r = "userdb failed";
+ return ret;
+ }
+
+ auth_lua_export_table(L, req, NULL, NULL);
+ return USERDB_RESULT_OK;
+}
+
+static enum passdb_result
+auth_lua_export_passdb_table(lua_State *L, struct auth_request *req,
+ const char **scheme_r, const char **password_r,
+ const char **error_r)
+{
+ enum passdb_result ret = lua_tointeger(L, -2);
+
+ if (ret != PASSDB_RESULT_OK) {
+ lua_pop(L, 2);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ *error_r = "passb failed";
+ return ret;
+ }
+
+ auth_lua_export_table(L, req, scheme_r, password_r);
+ return PASSDB_RESULT_OK;
+}
+
+static enum passdb_result
+auth_lua_call_lookup_finish(lua_State *L, struct auth_request *req,
+ const char **scheme_r, const char **password_r,
+ const char **error_r)
+{
+ if (lua_istable(L, -1)) {
+ return auth_lua_export_passdb_table(L, req, scheme_r,
+ password_r, error_r);
+ }
+
+ enum passdb_result ret = lua_tointeger(L, -2);
+ const char *str = t_strdup(lua_tostring(L, -1));
+ lua_pop(L, 2);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ /* stack should be empty now */
+ i_assert(lua_gettop(L) == 0);
+
+ if (ret != PASSDB_RESULT_OK && ret != PASSDB_RESULT_NEXT) {
+ *error_r = str;
+ } else {
+ auth_lua_export_fields(req, str, scheme_r, password_r);
+ }
+
+ if (scheme_r != NULL && *scheme_r == NULL)
+ *scheme_r = "PLAIN";
+
+ return ret;
+}
+
+enum passdb_result
+auth_lua_call_password_verify(struct dlua_script *script,
+ struct auth_request *req, const char *password, const char **error_r)
+{
+ lua_State *L = script->L;
+ int err = 0;
+
+ e_debug(authdb_event(req), "Calling %s", AUTH_LUA_PASSWORD_VERIFY);
+
+ /* call with auth request, password as parameters */
+ auth_lua_push_auth_request(L, req);
+ lua_pushstring(L, password);
+
+ if (dlua_pcall(L, AUTH_LUA_PASSWORD_VERIFY, 2, 2, error_r) < 0)
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+
+ if (!lua_isnumber(L, -2)) {
+ *error_r = t_strdup_printf("db-lua: %s invalid return value "
+ "(expected number got %s)",
+ AUTH_LUA_PASSWORD_VERIFY,
+ luaL_typename(L, -2));
+ err = -1;
+ } else if (!lua_isstring(L, -1) && !lua_istable(L, -1)) {
+ *error_r = t_strdup_printf("db-lua: %s invalid return value "
+ "(expected string or table, got %s)",
+ AUTH_LUA_PASSWORD_VERIFY,
+ luaL_typename(L, -1));
+ err = -1;
+ }
+
+ if (err != 0) {
+ lua_pop(L, 2);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+
+
+ return auth_lua_call_lookup_finish(L, req, NULL, NULL, error_r);
+}
+
+
+enum passdb_result
+auth_lua_call_passdb_lookup(struct dlua_script *script,
+ struct auth_request *req, const char **scheme_r,
+ const char **password_r, const char **error_r)
+{
+ lua_State *L = script->L;
+
+ *scheme_r = *password_r = NULL;
+ if (auth_lua_call_lookup(L, AUTH_LUA_PASSDB_LOOKUP, req, error_r) < 0) {
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ return auth_lua_call_lookup_finish(L, req, scheme_r, password_r, error_r);
+}
+
+
+enum userdb_result
+auth_lua_call_userdb_lookup(struct dlua_script *script,
+ struct auth_request *req, const char **error_r)
+{
+ lua_State *L = script->L;
+
+ if (auth_lua_call_lookup(L, AUTH_LUA_USERDB_LOOKUP, req, error_r) < 0) {
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+ return USERDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ if (lua_istable(L, -1))
+ return auth_lua_export_userdb_table(L, req, error_r);
+
+ enum userdb_result ret = lua_tointeger(L, -2);
+ const char *str = t_strdup(lua_tostring(L, -1));
+ lua_pop(L, 2);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+
+ if (ret != USERDB_RESULT_OK) {
+ *error_r = str;
+ return ret;
+ }
+ auth_lua_export_fields(req, str, NULL, NULL);
+
+ return USERDB_RESULT_OK;
+}
+
+struct userdb_iterate_context *
+auth_lua_call_userdb_iterate_init(struct dlua_script *script, struct auth_request *req,
+ userdb_iter_callback_t *callback, void *context)
+{
+ lua_State *L = script->L;
+
+ pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"lua userdb iterate", 128);
+ struct auth_lua_userdb_iterate_context *actx =
+ p_new(pool, struct auth_lua_userdb_iterate_context, 1);
+
+ actx->pool = pool;
+ actx->ctx.auth_request = req;
+ actx->ctx.callback = callback;
+ actx->ctx.context = context;
+
+ if (!dlua_script_has_function(script, AUTH_LUA_USERDB_ITERATE)) {
+ actx->ctx.failed = TRUE;
+ return &actx->ctx;
+ }
+
+ e_debug(authdb_event(req), "Calling %s", AUTH_LUA_USERDB_ITERATE);
+
+ const char *error;
+ if (dlua_pcall(L, AUTH_LUA_USERDB_ITERATE, 0, 1, &error) < 0) {
+ e_error(authdb_event(req),
+ "db-lua: " AUTH_LUA_USERDB_ITERATE " failed: %s",
+ error);
+ actx->ctx.failed = TRUE;
+ return &actx->ctx;
+ }
+
+ if (!lua_istable(L, -1)) {
+ e_error(authdb_event(req),
+ "db-lua: Cannot iterate, return value is not table");
+ actx->ctx.failed = TRUE;
+ lua_pop(L, 1);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+ return &actx->ctx;
+ }
+
+ p_array_init(&actx->users, pool, 8);
+
+ /* stack is now
+ table */
+
+ /* see lua_next documentation */
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ /* stack is now
+ value
+ key
+ table */
+ if (!lua_isstring(L, -1)) {
+ e_error(authdb_event(req),
+ "db-lua: Value is not string");
+ actx->ctx.failed = TRUE;
+ lua_pop(L, 3);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+ return &actx->ctx;
+ }
+ const char *str = p_strdup(pool, lua_tostring(L, -1));
+ array_push_back(&actx->users, &str);
+ lua_pop(L, 1);
+ /* stack is now
+ key
+ table */
+ }
+
+ /* stack is now
+ table
+ */
+
+ lua_pop(L, 1);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ i_assert(lua_gettop(L) == 0);
+
+ return &actx->ctx;
+}
+
+void auth_lua_userdb_iterate_next(struct userdb_iterate_context *ctx)
+{
+ struct auth_lua_userdb_iterate_context *actx =
+ container_of(ctx, struct auth_lua_userdb_iterate_context, ctx);
+
+ if (ctx->failed || actx->idx >= array_count(&actx->users)) {
+ ctx->callback(NULL, ctx->context);
+ return;
+ }
+
+ const char *user = array_idx_elem(&actx->users, actx->idx++);
+ ctx->callback(user, ctx->context);
+}
+
+int auth_lua_userdb_iterate_deinit(struct userdb_iterate_context *ctx)
+{
+ struct auth_lua_userdb_iterate_context *actx =
+ container_of(ctx, struct auth_lua_userdb_iterate_context, ctx);
+
+ int ret = ctx->failed ? -1 : 0;
+ pool_unref(&actx->pool);
+ return ret;
+}
+
+#ifndef BUILTIN_LUA
+/* Building a plugin */
+extern struct passdb_module_interface passdb_lua_plugin;
+extern struct userdb_module_interface userdb_lua_plugin;
+
+void authdb_lua_init(void);
+void authdb_lua_deinit(void);
+
+void authdb_lua_init(void)
+{
+ passdb_register_module(&passdb_lua_plugin);
+ userdb_register_module(&userdb_lua_plugin);
+
+}
+void authdb_lua_deinit(void)
+{
+ passdb_unregister_module(&passdb_lua_plugin);
+ userdb_unregister_module(&userdb_lua_plugin);
+}
+#endif
+
+#endif
diff --git a/src/auth/db-lua.h b/src/auth/db-lua.h
new file mode 100644
index 0000000..ebb697a
--- /dev/null
+++ b/src/auth/db-lua.h
@@ -0,0 +1,33 @@
+#ifndef DB_LUA_H
+#define DB_LUA_H 1
+
+#include "dlua-script.h"
+
+#define DB_LUA_CACHE_KEY "%u"
+
+#define AUTH_LUA_PASSWORD_VERIFY "auth_password_verify"
+
+struct dlua_script;
+
+int auth_lua_script_init(struct dlua_script *script, const char **error_r);
+
+int auth_lua_call_password_verify(struct dlua_script *script,
+ struct auth_request *req, const char *password,
+ const char **error_r);
+
+enum passdb_result
+auth_lua_call_passdb_lookup(struct dlua_script *script,
+ struct auth_request *req, const char **scheme_r,
+ const char **password_r, const char **error_r);
+
+enum userdb_result
+auth_lua_call_userdb_lookup(struct dlua_script *script,
+ struct auth_request *req, const char **error_r);
+
+struct userdb_iterate_context *
+auth_lua_call_userdb_iterate_init(struct dlua_script *script, struct auth_request *req,
+ userdb_iter_callback_t *callback, void *context);
+void auth_lua_userdb_iterate_next(struct userdb_iterate_context *ctx);
+int auth_lua_userdb_iterate_deinit(struct userdb_iterate_context *ctx);
+
+#endif
diff --git a/src/auth/db-oauth2.c b/src/auth/db-oauth2.c
new file mode 100644
index 0000000..b36a4ce
--- /dev/null
+++ b/src/auth/db-oauth2.c
@@ -0,0 +1,915 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "str.h"
+#include "var-expand.h"
+#include "env-util.h"
+#include "var-expand.h"
+#include "settings.h"
+#include "oauth2.h"
+#include "http-client.h"
+#include "http-url.h"
+#include "iostream-ssl.h"
+#include "auth-request.h"
+#include "auth-settings.h"
+#include "passdb.h"
+#include "passdb-template.h"
+#include "llist.h"
+#include "db-oauth2.h"
+#include "dcrypt.h"
+#include "dict.h"
+
+#include <stddef.h>
+
+struct passdb_oauth2_settings {
+ /* tokeninfo endpoint, format https://endpoint/somewhere?token= */
+ const char *tokeninfo_url;
+ /* password grant endpoint, format https://endpoint/somewhere */
+ const char *grant_url;
+ /* introspection endpoint, format https://endpoint/somewhere */
+ const char *introspection_url;
+ /* expected scope, optional */
+ const char *scope;
+ /* mode of introspection, one of get, get-auth, post
+ - get: append token to url
+ - get-auth: send token with header Authorization: Bearer token
+ - post: send token=<token> as POST request
+ */
+ const char *introspection_mode;
+ /* normalization var-expand template for username, defaults to %Lu */
+ const char *username_format;
+ /* name of username attribute to lookup, mandatory */
+ const char *username_attribute;
+ /* name of account is active attribute, optional */
+ const char *active_attribute;
+ /* expected active value for active attribute, optional */
+ const char *active_value;
+ /* client identificator for oauth2 server */
+ const char *client_id;
+ /* not really used, but have to present by oauth2 specs */
+ const char *client_secret;
+ /* template to expand into passdb */
+ const char *pass_attrs;
+ /* template to expand into key path, turns on local validation support */
+ const char *local_validation_key_dict;
+ /* valid token issuers */
+ const char *issuers;
+ /* The URL for a document following the OpenID Provider Configuration
+ Information schema, see
+
+ https://datatracker.ietf.org/doc/html/rfc7628#section-3.2.2
+ */
+ const char *openid_configuration_url;
+
+ /* TLS options */
+ const char *tls_ca_cert_file;
+ const char *tls_ca_cert_dir;
+ const char *tls_cert_file;
+ const char *tls_key_file;
+ const char *tls_cipher_suite;
+
+ /* HTTP rawlog directory */
+ const char *rawlog_dir;
+
+ /* HTTP client options */
+ unsigned int timeout_msecs;
+ unsigned int max_idle_time_msecs;
+ unsigned int max_parallel_connections;
+ unsigned int max_pipelined_requests;
+ bool tls_allow_invalid_cert;
+
+ bool debug;
+ /* Should introspection be done even if not necessary */
+ bool force_introspection;
+ /* Should we send service and local/remote endpoints as X-Dovecot-Auth headers */
+ bool send_auth_headers;
+ bool use_grant_password;
+};
+
+struct db_oauth2 {
+ struct db_oauth2 *prev,*next;
+
+ pool_t pool;
+
+ const char *config_path;
+ struct passdb_oauth2_settings set;
+ struct http_client *client;
+ struct passdb_template *tmpl;
+ struct oauth2_settings oauth2_set;
+
+ struct db_oauth2_request *head;
+
+ unsigned int refcount;
+};
+
+static struct db_oauth2 *db_oauth2_head = NULL;
+
+#undef DEF_STR
+#undef DEF_BOOL
+#undef DEF_INT
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, passdb_oauth2_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, passdb_oauth2_settings)
+#define DEF_INT(name) DEF_STRUCT_INT(name, passdb_oauth2_settings)
+
+static struct setting_def setting_defs[] = {
+ DEF_STR(tokeninfo_url),
+ DEF_STR(grant_url),
+ DEF_STR(introspection_url),
+ DEF_STR(scope),
+ DEF_BOOL(force_introspection),
+ DEF_STR(introspection_mode),
+ DEF_STR(username_format),
+ DEF_STR(username_attribute),
+ DEF_STR(pass_attrs),
+ DEF_STR(local_validation_key_dict),
+ DEF_STR(active_attribute),
+ DEF_STR(active_value),
+ DEF_STR(client_id),
+ DEF_STR(client_secret),
+ DEF_STR(issuers),
+ DEF_STR(openid_configuration_url),
+ DEF_INT(timeout_msecs),
+ DEF_INT(max_idle_time_msecs),
+ DEF_INT(max_parallel_connections),
+ DEF_INT(max_pipelined_requests),
+ DEF_BOOL(send_auth_headers),
+ DEF_BOOL(use_grant_password),
+
+ DEF_STR(tls_ca_cert_file),
+ DEF_STR(tls_ca_cert_dir),
+ DEF_STR(tls_cert_file),
+ DEF_STR(tls_key_file),
+ DEF_STR(tls_cipher_suite),
+ DEF_BOOL(tls_allow_invalid_cert),
+
+ DEF_STR(rawlog_dir),
+
+ DEF_BOOL(debug),
+
+ { 0, NULL, 0 }
+};
+
+static struct passdb_oauth2_settings default_oauth2_settings = {
+ .tokeninfo_url = "",
+ .grant_url = "",
+ .introspection_url = "",
+ .scope = "",
+ .force_introspection = FALSE,
+ .introspection_mode = "",
+ .username_format = "%Lu",
+ .username_attribute = "email",
+ .active_attribute = "",
+ .active_value = "",
+ .client_id = "",
+ .client_secret = "",
+ .issuers = "",
+ .openid_configuration_url = "",
+ .pass_attrs = "",
+ .local_validation_key_dict = "",
+ .rawlog_dir = "",
+ .timeout_msecs = 0,
+ .max_idle_time_msecs = 60000,
+ .max_parallel_connections = 10,
+ .max_pipelined_requests = 1,
+ .tls_ca_cert_file = NULL,
+ .tls_ca_cert_dir = NULL,
+ .tls_cert_file = NULL,
+ .tls_key_file = NULL,
+ .tls_cipher_suite = "HIGH:!SSLv2",
+ .tls_allow_invalid_cert = FALSE,
+ .send_auth_headers = FALSE,
+ .use_grant_password = FALSE,
+ .debug = FALSE,
+};
+
+static const char *parse_setting(const char *key, const char *value,
+ struct db_oauth2 *db)
+{
+ return parse_setting_from_defs(db->pool, setting_defs,
+ &db->set, key, value);
+}
+
+struct db_oauth2 *db_oauth2_init(const char *config_path)
+{
+ struct db_oauth2 *db;
+ const char *error;
+ struct ssl_iostream_settings ssl_set;
+ struct http_client_settings http_set;
+
+ for(db = db_oauth2_head; db != NULL; db = db->next) {
+ if (strcmp(db->config_path, config_path) == 0) {
+ db->refcount++;
+ return db;
+ }
+ }
+
+ pool_t pool = pool_alloconly_create("db_oauth2", 128);
+ db = p_new(pool, struct db_oauth2, 1);
+ db->pool = pool;
+ db->refcount = 1;
+ db->config_path = p_strdup(pool, config_path);
+ db->set = default_oauth2_settings;
+
+ if (!settings_read_nosection(config_path, parse_setting, db, &error))
+ i_fatal("oauth2 %s: %s", config_path, error);
+
+ db->tmpl = passdb_template_build(pool, db->set.pass_attrs);
+
+ i_zero(&ssl_set);
+ i_zero(&http_set);
+
+ ssl_set.cipher_list = db->set.tls_cipher_suite;
+ ssl_set.ca_file = db->set.tls_ca_cert_file;
+ ssl_set.ca_dir = db->set.tls_ca_cert_dir;
+ if (db->set.tls_cert_file != NULL && *db->set.tls_cert_file != '\0') {
+ ssl_set.cert.cert = db->set.tls_cert_file;
+ ssl_set.cert.key = db->set.tls_key_file;
+ }
+ ssl_set.prefer_server_ciphers = TRUE;
+ ssl_set.allow_invalid_cert = db->set.tls_allow_invalid_cert;
+ ssl_set.verbose = db->set.debug;
+ ssl_set.verbose_invalid_cert = db->set.debug;
+ http_set.ssl = &ssl_set;
+
+ http_set.dns_client_socket_path = "dns-client";
+ http_set.user_agent = "dovecot-oauth2-passdb/" DOVECOT_VERSION;
+
+ if (*db->set.local_validation_key_dict == '\0' &&
+ *db->set.tokeninfo_url == '\0' &&
+ (*db->set.grant_url == '\0' || *db->set.client_id == '\0') &&
+ *db->set.introspection_url == '\0')
+ i_fatal("oauth2: Password grant, tokeninfo, introspection URL or "
+ "validation key dictionary must be given");
+
+ if (*db->set.rawlog_dir != '\0')
+ http_set.rawlog_dir = db->set.rawlog_dir;
+
+ http_set.max_idle_time_msecs = db->set.max_idle_time_msecs;
+ http_set.max_parallel_connections = db->set.max_parallel_connections;
+ http_set.max_pipelined_requests = db->set.max_pipelined_requests;
+ http_set.no_auto_redirect = FALSE;
+ http_set.no_auto_retry = TRUE;
+ http_set.debug = db->set.debug;
+ http_set.event_parent = auth_event;
+
+ db->client = http_client_init(&http_set);
+
+ i_zero(&db->oauth2_set);
+ db->oauth2_set.client = db->client;
+ db->oauth2_set.tokeninfo_url = db->set.tokeninfo_url,
+ db->oauth2_set.grant_url = db->set.grant_url,
+ db->oauth2_set.introspection_url = db->set.introspection_url;
+ db->oauth2_set.client_id = db->set.client_id;
+ db->oauth2_set.client_secret = db->set.client_secret;
+ db->oauth2_set.timeout_msecs = db->set.timeout_msecs;
+ db->oauth2_set.send_auth_headers = db->set.send_auth_headers;
+ db->oauth2_set.use_grant_password = db->set.use_grant_password;
+ db->oauth2_set.scope = db->set.scope;
+
+ if (*db->set.active_attribute == '\0' &&
+ *db->set.active_value != '\0')
+ i_fatal("oauth2: Cannot have empty active_attribute is active_value is set");
+
+ if (*db->set.introspection_mode == '\0' ||
+ strcmp(db->set.introspection_mode, "auth") == 0) {
+ db->oauth2_set.introspection_mode = INTROSPECTION_MODE_GET_AUTH;
+ } else if (strcmp(db->set.introspection_mode, "get") == 0) {
+ db->oauth2_set.introspection_mode = INTROSPECTION_MODE_GET;
+ } else if (strcmp(db->set.introspection_mode, "post") == 0) {
+ db->oauth2_set.introspection_mode = INTROSPECTION_MODE_POST;
+ } else if (strcmp(db->set.introspection_mode, "local") == 0) {
+ if (*db->set.local_validation_key_dict == '\0')
+ i_fatal("oauth2: local_validation_key_dict is required "
+ "for local introspection.");
+ db->oauth2_set.introspection_mode = INTROSPECTION_MODE_LOCAL;
+ } else {
+ i_fatal("oauth2: Invalid value '%s' for introspection mode, must be on auth, get, post or local",
+ db->set.introspection_mode);
+ }
+
+ if (db->oauth2_set.introspection_mode == INTROSPECTION_MODE_LOCAL) {
+ struct dict_settings dict_set = {
+ .base_dir = global_auth_settings->base_dir,
+ .event_parent = auth_event,
+ };
+ if (dict_init(db->set.local_validation_key_dict, &dict_set,
+ &db->oauth2_set.key_dict, &error) < 0)
+ i_fatal("Cannot initialize key dict: %s", error);
+ /* failure to initialize dcrypt is not fatal - we can still
+ validate HMAC based keys */
+ (void)dcrypt_initialize(NULL, NULL, NULL);
+ /* initialize key cache */
+ db->oauth2_set.key_cache = oauth2_validation_key_cache_init();
+ }
+
+ if (*db->set.issuers != '\0')
+ db->oauth2_set.issuers = (const char *const *)
+ p_strsplit_spaces(pool, db->set.issuers, " ");
+
+ if (*db->set.openid_configuration_url != '\0') {
+ struct http_url *parsed_url ATTR_UNUSED;
+ if (http_url_parse(db->set.openid_configuration_url, NULL, 0,
+ pool_datastack_create(), &parsed_url,
+ &error) < 0) {
+ i_fatal("Invalid openid_configuration_url: %s",
+ error);
+ }
+ }
+
+ DLLIST_PREPEND(&db_oauth2_head, db);
+
+ return db;
+}
+
+void db_oauth2_ref(struct db_oauth2 *db)
+{
+ i_assert(db->refcount > 0);
+ db->refcount++;
+}
+
+void db_oauth2_unref(struct db_oauth2 **_db)
+{
+ struct db_oauth2 *ptr, *db = *_db;
+ i_assert(db->refcount > 0);
+
+ if (--db->refcount > 0) return;
+
+ for(ptr = db_oauth2_head; ptr != NULL; ptr = ptr->next) {
+ if (ptr == db) {
+ DLLIST_REMOVE(&db_oauth2_head, ptr);
+ break;
+ }
+ }
+
+ i_assert(ptr != NULL && ptr == db);
+
+ /* make sure all requests are aborted */
+ while (db->head != NULL)
+ oauth2_request_abort(&db->head->req);
+
+ http_client_deinit(&db->client);
+ if (db->oauth2_set.key_dict != NULL)
+ dict_deinit(&db->oauth2_set.key_dict);
+ oauth2_validation_key_cache_deinit(&db->oauth2_set.key_cache);
+ pool_unref(&db->pool);
+}
+
+static void
+db_oauth2_add_openid_config_url(struct db_oauth2_request *req)
+{
+ /* FIXME: HORRIBLE HACK - REMOVE ME!!!
+ It is because the mech has not been implemented properly
+ that we need to pass the config url in this strange way.
+
+ This **must** be moved to mech-oauth2 once the validation
+ result et al is handled there.
+ */
+ req->auth_request->openid_config_url =
+ p_strdup_empty(req->auth_request->pool,
+ req->db->set.openid_configuration_url);
+}
+
+const char *db_oauth2_get_openid_configuration_url(const struct db_oauth2 *db)
+{
+ return db->set.openid_configuration_url;
+}
+
+static bool
+db_oauth2_have_all_fields(struct db_oauth2_request *req)
+{
+ unsigned int n,i;
+ unsigned int size,idx;
+ const char *const *args = passdb_template_get_args(req->db->tmpl, &n);
+
+ if (req->fields == NULL)
+ return FALSE;
+
+ for(i=1;i<n;i+=2) {
+ const char *ptr = args[i];
+ while(ptr != NULL) {
+ ptr = strchr(ptr, '%');
+ if (ptr != NULL) {
+ const char *field;
+ ptr++;
+ var_get_key_range(ptr, &idx, &size);
+ ptr = ptr+idx;
+ field = t_strndup(ptr,size);
+ if (str_begins(field, "oauth2:") &&
+ !auth_fields_exists(req->fields, field+7))
+ return FALSE;
+ ptr = ptr+size;
+ }
+ }
+ }
+
+ if (!auth_fields_exists(req->fields, req->db->set.username_attribute))
+ return FALSE;
+ if (*req->db->set.active_attribute != '\0' && !auth_fields_exists(req->fields, req->db->set.active_attribute))
+ return FALSE;
+
+ return TRUE;
+}
+
+static const char *field_get_default(const char *data)
+{
+ const char *p;
+
+ p = strchr(data, ':');
+ if (p == NULL)
+ return "";
+ else {
+ /* default value given */
+ return p+1;
+ }
+}
+
+static int db_oauth2_var_expand_func_oauth2(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct db_oauth2_request *ctx = context;
+ const char *field_name = t_strcut(data, ':');
+ const char *value = NULL;
+
+ if (ctx->fields != NULL)
+ value = auth_fields_find(ctx->fields, field_name);
+ *value_r = value != NULL ? value : field_get_default(data);
+
+ return 1;
+}
+
+static const char *escape_none(const char *value, const struct auth_request *req ATTR_UNUSED)
+{
+ return value;
+}
+
+static const struct var_expand_table *
+db_oauth2_value_get_var_expand_table(struct auth_request *auth_request,
+ const char *oauth2_value)
+{
+ struct var_expand_table *table;
+ unsigned int count = 1;
+
+ table = auth_request_get_var_expand_table_full(auth_request,
+ auth_request->fields.user, NULL, &count);
+ table[0].key = '$';
+ table[0].value = oauth2_value;
+ return table;
+}
+
+static bool
+db_oauth2_template_export(struct db_oauth2_request *req,
+ enum passdb_result *result_r, const char **error_r)
+{
+ /* var=$ expands into var=${oauth2:var} */
+ const struct var_expand_func_table funcs_table[] = {
+ { "oauth2", db_oauth2_var_expand_func_oauth2 },
+ { NULL, NULL }
+ };
+ string_t *dest;
+ const char *const *args, *value, *error;
+ struct passdb_template *tmpl = req->db->tmpl;
+ unsigned int i, count;
+
+ if (passdb_template_is_empty(tmpl))
+ return TRUE;
+
+ dest = t_str_new(256);
+ args = passdb_template_get_args(tmpl, &count);
+ i_assert((count % 2) == 0);
+ for (i = 0; i < count; i += 2) {
+ if (args[i+1] == NULL)
+ value = "";
+ else {
+ str_truncate(dest, 0);
+ const struct var_expand_table *
+ table = db_oauth2_value_get_var_expand_table(req->auth_request,
+ auth_fields_find(req->fields, args[i]));
+ if (var_expand_with_funcs(dest, args[i+1], table, funcs_table,
+ req, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "var_expand(%s) failed: %s",
+ args[i+1], error);
+ *result_r = PASSDB_RESULT_INTERNAL_FAILURE;
+ return FALSE;
+ }
+ value = str_c(dest);
+ }
+
+ auth_request_set_field(req->auth_request, args[i], value,
+ STATIC_PASS_SCHEME);
+ }
+ return TRUE;
+}
+
+static void db_oauth2_fields_merge(struct db_oauth2_request *req,
+ ARRAY_TYPE(oauth2_field) *fields)
+{
+ const struct oauth2_field *field;
+
+ if (req->fields == NULL)
+ req->fields = auth_fields_init(req->pool);
+
+ array_foreach(fields, field) {
+ e_debug(authdb_event(req->auth_request),
+ "Processing field %s",
+ field->name);
+ auth_fields_add(req->fields, field->name, field->value, 0);
+ }
+}
+
+static const char *
+db_oauth2_field_find(const ARRAY_TYPE(oauth2_field) *fields, const char *name)
+{
+ const struct oauth2_field *f;
+
+ array_foreach(fields, f) {
+ if (strcmp(f->name, name) == 0)
+ return f->value;
+ }
+ return NULL;
+}
+
+static void db_oauth2_callback(struct db_oauth2_request *req,
+ enum passdb_result result,
+ const char *error_prefix, const char *error)
+{
+ db_oauth2_lookup_callback_t *callback = req->callback;
+ req->callback = NULL;
+
+ i_assert(result == PASSDB_RESULT_OK || error != NULL);
+
+ if (result != PASSDB_RESULT_OK)
+ db_oauth2_add_openid_config_url(req);
+
+ /* Successful lookups were logged by the caller. Failed lookups will be
+ logged either with e_error() or e_info() by the callback. */
+ if (callback != NULL) {
+ DLLIST_REMOVE(&req->db->head, req);
+ if (result != PASSDB_RESULT_OK)
+ error = t_strconcat(error_prefix, error, NULL);
+ callback(req, result, error, req->context);
+ }
+}
+
+static bool
+db_oauth2_validate_username(struct db_oauth2_request *req,
+ enum passdb_result *result_r, const char **error_r)
+{
+ const char *error;
+ struct var_expand_table table[] = {
+ { 'u', NULL, "user" },
+ { 'n', NULL, "username" },
+ { 'd', NULL, "domain" },
+ { '\0', NULL, NULL }
+ };
+ const char *username_value =
+ auth_fields_find(req->fields, req->db->set.username_attribute);
+
+ if (username_value == NULL) {
+ *result_r = PASSDB_RESULT_INTERNAL_FAILURE;
+ *error_r = "No username returned";
+ return FALSE;
+ }
+
+ table[0].value = username_value;
+ table[1].value = t_strcut(username_value, '@');
+ table[2].value = i_strchr_to_next(username_value, '@');
+
+ string_t *username_req = t_str_new(32);
+ string_t *username_val = t_str_new(strlen(username_value));
+
+ if (auth_request_var_expand(username_req, req->db->set.username_format, req->auth_request, escape_none, &error) < 0 ||
+ var_expand(username_val, req->db->set.username_format, table, &error) < 0) {
+ *error_r = t_strdup_printf("var_expand(%s) failed: %s",
+ req->db->set.username_format, error);
+ *result_r = PASSDB_RESULT_INTERNAL_FAILURE;
+ return FALSE;
+ } else if (!str_equals(username_req, username_val)) {
+ *error_r = t_strdup_printf("Username '%s' did not match '%s'",
+ str_c(username_req), str_c(username_val));
+ *result_r = PASSDB_RESULT_USER_UNKNOWN;
+ return FALSE;
+ } else {
+ req->username = p_strdup(req->pool, str_c(username_val));
+ return TRUE;
+ }
+}
+
+static bool
+db_oauth2_user_is_enabled(struct db_oauth2_request *req,
+ enum passdb_result *result_r, const char **error_r)
+{
+ if (*req->db->set.active_attribute == '\0' ) {
+ e_debug(authdb_event(req->auth_request),
+ "oauth2 active_attribute is not configured; skipping the check");
+ return TRUE;
+ }
+
+ const char *active_value =
+ auth_fields_find(req->fields, req->db->set.active_attribute);
+
+ if (active_value == NULL) {
+ e_debug(authdb_event(req->auth_request),
+ "oauth2 active_attribute \"%s\" is not present in the oauth2 server's response",
+ req->db->set.active_attribute);
+ *error_r = "Missing active_attribute from token";
+ *result_r = PASSDB_RESULT_PASSWORD_MISMATCH;
+ return FALSE;
+ }
+
+ if (*req->db->set.active_value == '\0') {
+ e_debug(authdb_event(req->auth_request),
+ "oauth2 active_attribute \"%s\" present; skipping the check on value",
+ req->db->set.active_attribute);
+ return TRUE;
+ }
+
+ if (strcmp(req->db->set.active_value, active_value) != 0) {
+ e_debug(authdb_event(req->auth_request),
+ "oauth2 active_attribute check failed: expected %s=\"%s\" but got \"%s\"",
+ req->db->set.active_attribute,
+ req->db->set.active_value,
+ active_value);
+ *error_r = "Provided token is not valid";
+ *result_r = PASSDB_RESULT_PASSWORD_MISMATCH;
+ return FALSE;
+ }
+
+ e_debug(authdb_event(req->auth_request),
+ "oauth2 active_attribute check succeeded");
+ return TRUE;
+}
+
+static bool
+db_oauth2_token_in_scope(struct db_oauth2_request *req,
+ enum passdb_result *result_r, const char **error_r)
+{
+ if (*req->db->set.scope != '\0') {
+ bool found = FALSE;
+ const char *value = auth_fields_find(req->fields, "scope");
+ if (value == NULL)
+ value = auth_fields_find(req->fields, "aud");
+ e_debug(authdb_event(req->auth_request),
+ "Token scope(s): %s",
+ value);
+ if (value != NULL) {
+ const char **wanted_scopes =
+ t_strsplit_spaces(req->db->set.scope, " ");
+ const char **scopes = t_strsplit_spaces(value, " ");
+ for (; !found && *wanted_scopes != NULL; wanted_scopes++)
+ found = str_array_find(scopes, *wanted_scopes);
+ }
+ if (!found) {
+ *error_r = t_strdup_printf("Token is not valid for scope '%s'",
+ req->db->set.scope);
+ *result_r = PASSDB_RESULT_USER_DISABLED;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void db_oauth2_process_fields(struct db_oauth2_request *req,
+ enum passdb_result *result_r,
+ const char **error_r)
+{
+ *error_r = NULL;
+
+ if (db_oauth2_user_is_enabled(req, result_r, error_r) &&
+ db_oauth2_validate_username(req, result_r, error_r) &&
+ db_oauth2_token_in_scope(req, result_r, error_r) &&
+ db_oauth2_template_export(req, result_r, error_r)) {
+ *result_r = PASSDB_RESULT_OK;
+ } else {
+ i_assert(*result_r != PASSDB_RESULT_OK && *error_r != NULL);
+ }
+}
+
+static void
+db_oauth2_introspect_continue(struct oauth2_request_result *result,
+ struct db_oauth2_request *req)
+{
+ enum passdb_result passdb_result;
+ const char *error;
+
+ req->req = NULL;
+
+ if (result->error != NULL) {
+ /* fail here */
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ error = result->error;
+ } else {
+ e_debug(authdb_event(req->auth_request),
+ "Introspection succeeded");
+ db_oauth2_fields_merge(req, result->fields);
+ db_oauth2_process_fields(req, &passdb_result, &error);
+ }
+ db_oauth2_callback(req, passdb_result, "Introspection failed: ", error);
+}
+
+static void db_oauth2_lookup_introspect(struct db_oauth2_request *req)
+{
+ struct oauth2_request_input input;
+ i_zero(&input);
+
+ e_debug(authdb_event(req->auth_request),
+ "Making introspection request to %s",
+ req->db->set.introspection_url);
+ input.token = req->token;
+ input.local_ip = req->auth_request->fields.local_ip;
+ input.local_port = req->auth_request->fields.local_port;
+ input.remote_ip = req->auth_request->fields.remote_ip;
+ input.remote_port = req->auth_request->fields.remote_port;
+ input.real_local_ip = req->auth_request->fields.real_local_ip;
+ input.real_local_port = req->auth_request->fields.real_local_port;
+ input.real_remote_ip = req->auth_request->fields.real_remote_ip;
+ input.real_remote_port = req->auth_request->fields.real_remote_port;
+ input.service = req->auth_request->fields.service;
+
+ req->req = oauth2_introspection_start(&req->db->oauth2_set, &input,
+ db_oauth2_introspect_continue, req);
+}
+
+static void db_oauth2_local_validation(struct db_oauth2_request *req,
+ const char *token)
+{
+ bool is_jwt ATTR_UNUSED;
+ const char *error = NULL;
+ enum passdb_result passdb_result;
+ ARRAY_TYPE(oauth2_field) fields;
+ t_array_init(&fields, 8);
+ if (oauth2_try_parse_jwt(&req->db->oauth2_set, token,
+ &fields, &is_jwt, &error) < 0) {
+ passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH;
+ } else {
+ db_oauth2_fields_merge(req, &fields);
+ db_oauth2_process_fields(req, &passdb_result, &error);
+ }
+ if (passdb_result == PASSDB_RESULT_OK) {
+ e_debug(authdb_event(req->auth_request),
+ "Local validation succeeded");
+ }
+ db_oauth2_callback(req, passdb_result,
+ "Local validation failed: ", error);
+}
+
+static void
+db_oauth2_lookup_continue_valid(struct db_oauth2_request *req,
+ ARRAY_TYPE(oauth2_field) *fields,
+ const char *error_prefix)
+{
+ enum passdb_result passdb_result;
+ const char *error;
+
+ db_oauth2_fields_merge(req, fields);
+ if (db_oauth2_have_all_fields(req) &&
+ !req->db->set.force_introspection) {
+ /* pass */
+ } else if (req->db->oauth2_set.introspection_mode ==
+ INTROSPECTION_MODE_LOCAL) {
+ e_debug(authdb_event(req->auth_request),
+ "Attempting to locally validate token");
+ db_oauth2_local_validation(req, req->token);
+ return;
+ } else if (!db_oauth2_user_is_enabled(req, &passdb_result, &error)) {
+ db_oauth2_callback(req, passdb_result,
+ "Token is not valid: ", error);
+ return;
+ } else if (*req->db->set.introspection_url != '\0') {
+ db_oauth2_lookup_introspect(req);
+ return;
+ }
+ db_oauth2_process_fields(req, &passdb_result, &error);
+ db_oauth2_callback(req, passdb_result, error_prefix, error);
+}
+
+static void
+db_oauth2_lookup_continue(struct oauth2_request_result *result,
+ struct db_oauth2_request *req)
+{
+ i_assert(req->token != NULL);
+ req->req = NULL;
+
+ if (result->error != NULL) {
+ db_oauth2_callback(req, PASSDB_RESULT_INTERNAL_FAILURE,
+ "Token validation failed: ", result->error);
+ } else if (!result->valid) {
+ db_oauth2_callback(req, PASSDB_RESULT_PASSWORD_MISMATCH,
+ "Token validation failed: ",
+ "Invalid token");
+ } else {
+ e_debug(authdb_event(req->auth_request),
+ "Token validation succeeded");
+ db_oauth2_lookup_continue_valid(req, result->fields,
+ "Token validation failed: ");
+ }
+}
+
+static void
+db_oauth2_lookup_passwd_grant(struct oauth2_request_result *result,
+ struct db_oauth2_request *req)
+{
+ enum passdb_result passdb_result;
+ const char *token, *error;
+
+ i_assert(req->token == NULL);
+ req->req = NULL;
+
+ if (result->valid) {
+ e_debug(authdb_event(req->auth_request),
+ "Password grant succeeded");
+ token = db_oauth2_field_find(result->fields, "access_token");
+ if (token == NULL) {
+ db_oauth2_callback(req, PASSDB_RESULT_INTERNAL_FAILURE,
+ "Password grant failed: ",
+ "OAuth2 token missing from reply");
+ } else {
+ req->token = p_strdup(req->pool, token);
+ db_oauth2_lookup_continue_valid(req, result->fields,
+ "Password grant failed: ");
+ }
+ return;
+ }
+
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ if (result->error != NULL)
+ error = result->error;
+ else {
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ error = db_oauth2_field_find(result->fields, "error");
+ if (error == NULL)
+ error = "OAuth2 server returned failure without error field";
+ else if (strcmp("invalid_grant", error) == 0)
+ passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH;
+ }
+ db_oauth2_callback(req, passdb_result,
+ "Password grant failed: ", error);
+}
+
+#undef db_oauth2_lookup
+void db_oauth2_lookup(struct db_oauth2 *db, struct db_oauth2_request *req,
+ const char *token, struct auth_request *request,
+ db_oauth2_lookup_callback_t *callback, void *context)
+{
+ struct oauth2_request_input input;
+ i_zero(&input);
+
+ req->db = db;
+ req->token = p_strdup(req->pool, token);
+ req->callback = callback;
+ req->context = context;
+ req->auth_request = request;
+
+ input.token = token;
+ input.local_ip = req->auth_request->fields.local_ip;
+ input.local_port = req->auth_request->fields.local_port;
+ input.remote_ip = req->auth_request->fields.remote_ip;
+ input.remote_port = req->auth_request->fields.remote_port;
+ input.real_local_ip = req->auth_request->fields.real_local_ip;
+ input.real_local_port = req->auth_request->fields.real_local_port;
+ input.real_remote_ip = req->auth_request->fields.real_remote_ip;
+ input.real_remote_port = req->auth_request->fields.real_remote_port;
+ input.service = req->auth_request->fields.service;
+
+ if (db->oauth2_set.introspection_mode == INTROSPECTION_MODE_LOCAL &&
+ !db_oauth2_uses_password_grant(db)) {
+ /* try to validate token locally */
+ e_debug(authdb_event(req->auth_request),
+ "Attempting to locally validate token");
+ db_oauth2_local_validation(req, request->mech_password);
+ return;
+
+ }
+ if (db->oauth2_set.use_grant_password) {
+ e_debug(authdb_event(req->auth_request),
+ "Making grant url request to %s",
+ db->set.grant_url);
+ /* There is no valid token until grant looks it up. */
+ req->token = NULL;
+ req->req = oauth2_passwd_grant_start(&db->oauth2_set, &input,
+ request->fields.user, request->mech_password,
+ db_oauth2_lookup_passwd_grant, req);
+ } else if (*db->oauth2_set.tokeninfo_url == '\0') {
+ e_debug(authdb_event(req->auth_request),
+ "Making introspection request to %s",
+ db->set.introspection_url);
+ req->req = oauth2_introspection_start(&req->db->oauth2_set, &input,
+ db_oauth2_introspect_continue, req);
+ } else {
+ e_debug(authdb_event(req->auth_request),
+ "Making token validation lookup to %s",
+ db->oauth2_set.tokeninfo_url);
+ req->req = oauth2_token_validation_start(&db->oauth2_set, &input,
+ db_oauth2_lookup_continue, req);
+ }
+ i_assert(req->req != NULL);
+ DLLIST_PREPEND(&db->head, req);
+}
+
+bool db_oauth2_uses_password_grant(const struct db_oauth2 *db)
+{
+ return db->set.use_grant_password;
+}
diff --git a/src/auth/db-oauth2.h b/src/auth/db-oauth2.h
new file mode 100644
index 0000000..c2d2d37
--- /dev/null
+++ b/src/auth/db-oauth2.h
@@ -0,0 +1,48 @@
+#ifndef DB_OAUTH2_H
+#define DB_OAUTH2_H 1
+
+struct db_oauth2;
+struct oauth2_request;
+struct db_oauth2_request;
+
+typedef void db_oauth2_lookup_callback_t(struct db_oauth2_request *request,
+ enum passdb_result result,
+ const char *error,
+ void *context);
+struct db_oauth2_request {
+ pool_t pool;
+ struct db_oauth2_request *prev,*next;
+
+ struct db_oauth2 *db;
+ struct oauth2_request *req;
+
+ /* username to match */
+ const char *username;
+ /* token to use */
+ const char *token;
+
+ struct auth_request *auth_request;
+ struct auth_fields *fields;
+
+ db_oauth2_lookup_callback_t *callback;
+ void *context;
+ verify_plain_callback_t *verify_callback;
+};
+
+
+struct db_oauth2 *db_oauth2_init(const char *config_path);
+
+void db_oauth2_ref(struct db_oauth2 *);
+void db_oauth2_unref(struct db_oauth2 **);
+
+bool db_oauth2_uses_password_grant(const struct db_oauth2 *db);
+
+const char *db_oauth2_get_openid_configuration_url(const struct db_oauth2 *db);
+
+void db_oauth2_lookup(struct db_oauth2 *db, struct db_oauth2_request *req, const char *token, struct auth_request *request, db_oauth2_lookup_callback_t *callback, void *context);
+#define db_oauth2_lookup(db, req, token, request, callback, context) \
+ db_oauth2_lookup(db, req, token - \
+ CALLBACK_TYPECHECK(callback, void(*)(struct db_oauth2_request *, enum passdb_result, const char*, typeof(context))), \
+ request, (db_oauth2_lookup_callback_t*)callback, (void*)context)
+
+#endif
diff --git a/src/auth/db-passwd-file.c b/src/auth/db-passwd-file.c
new file mode 100644
index 0000000..e26c146
--- /dev/null
+++ b/src/auth/db-passwd-file.c
@@ -0,0 +1,493 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+
+#if defined (USERDB_PASSWD_FILE) || defined(PASSDB_PASSWD_FILE)
+
+#include "userdb.h"
+#include "db-passwd-file.h"
+
+#include "array.h"
+#include "buffer.h"
+#include "istream.h"
+#include "hash.h"
+#include "str.h"
+#include "eacces-error.h"
+#include "ioloop.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#define PARSE_TIME_STARTUP_WARN_SECS 60
+#define PARSE_TIME_RELOAD_WARN_SECS 10
+
+static struct db_passwd_file *passwd_files;
+
+static void ATTR_NULL(3)
+passwd_file_add(struct passwd_file *pw, const char *username,
+ const char *pass, const char *const *args)
+{
+ /* args = uid, gid, user info, home dir, shell, extra_fields */
+ struct passwd_user *pu;
+ const char *extra_fields = NULL;
+ char *user;
+ size_t len;
+
+ if (hash_table_lookup(pw->users, username) != NULL) {
+ e_error(pw->event, "User %s exists more than once", username);
+ return;
+ }
+
+ pu = p_new(pw->pool, struct passwd_user, 1);
+ user = p_strdup(pw->pool, username);
+
+ len = pass == NULL ? 0 : strlen(pass);
+ if (len > 4 && pass[0] != '{' && pass[0] != '$' &&
+ pass[len-1] == ']' && pass[len-4] == '[') {
+ /* password[type] - we're being libpam-pwdfile compatible
+ here. it uses 13 = DES and 34 = MD5. For backwards
+ compatibility with ourself, we have also 56 = Digest-MD5. */
+ int num = (pass[len-3] - '0') * 10 + (pass[len-2] - '0');
+
+ pass = t_strndup(pass, len-4);
+ if (num == 34) {
+ pu->password = p_strconcat(pw->pool, "{PLAIN-MD5}",
+ pass, NULL);
+ } else if (num == 56) {
+ pu->password = p_strconcat(pw->pool, "{DIGEST-MD5}",
+ pass, NULL);
+ if (strlen(pu->password) != 32 + 12) {
+ e_error(pw->event, "User %s "
+ "has invalid password", username);
+ return;
+ }
+ } else {
+ pu->password = p_strconcat(pw->pool, "{CRYPT}",
+ pass, NULL);
+ }
+ } else {
+ pu->password = p_strdup(pw->pool, pass);
+ }
+
+ pu->uid = (uid_t)-1;
+ pu->gid = (gid_t)-1;
+
+ if (*args == NULL)
+ ;
+ else if (!pw->db->userdb || **args == '\0') {
+ args++;
+ } else {
+ pu->uid = userdb_parse_uid(NULL, *args);
+ if (pu->uid == 0 || pu->uid == (uid_t)-1) {
+ e_error(pw->event, "User %s has invalid UID '%s'",
+ username, *args);
+ return;
+ }
+ args++;
+ }
+
+ if (*args == NULL) {
+ if (pw->db->userdb_warn_missing) {
+ e_error(pw->event, "User %s is missing userdb info",
+ username);
+ }
+ /* don't allow userdb lookups */
+ pu->uid = 0;
+ pu->gid = 0;
+ } else if (!pw->db->userdb || **args == '\0')
+ args++;
+ else {
+ pu->gid = userdb_parse_gid(NULL, *args);
+ if (pu->gid == 0 || pu->gid == (gid_t)-1) {
+ e_error(pw->event, "User %s has invalid GID '%s'",
+ username, *args);
+ return;
+ }
+ args++;
+ }
+
+ /* user info */
+ if (*args != NULL)
+ args++;
+
+ /* home */
+ if (*args != NULL) {
+ if (pw->db->userdb)
+ pu->home = p_strdup_empty(pw->pool, *args);
+ args++;
+ }
+
+ /* shell */
+ if (*args != NULL)
+ args++;
+
+ if (*args != NULL && **args == '\0') {
+ /* old format, this field is empty and next field may
+ contain MAIL */
+ args++;
+ if (*args != NULL && **args != '\0' && pw->db->userdb) {
+ extra_fields =
+ t_strconcat("userdb_mail=",
+ t_strarray_join(args, ":"), NULL);
+ }
+ } else if (*args != NULL) {
+ /* new format, contains a space separated list of
+ extra fields */
+ extra_fields = t_strarray_join(args, ":");
+ }
+
+ if (extra_fields != NULL) {
+ pu->extra_fields =
+ p_strsplit_spaces(pw->pool, extra_fields, " ");
+ }
+
+ hash_table_insert(pw->users, user, pu);
+}
+
+static struct passwd_file *
+passwd_file_new(struct db_passwd_file *db, const char *expanded_path)
+{
+ struct passwd_file *pw;
+
+ pw = i_new(struct passwd_file, 1);
+ pw->db = db;
+ pw->path = i_strdup(expanded_path);
+ pw->fd = -1;
+ pw->event = event_create(db->event);
+ event_set_append_log_prefix(pw->event,
+ t_strdup_printf("passwd-file %s:", pw->path));
+
+ if (hash_table_is_created(db->files))
+ hash_table_insert(db->files, pw->path, pw);
+ return pw;
+}
+
+static int passwd_file_open(struct passwd_file *pw, bool startup,
+ const char **error_r)
+{
+ const char *no_args = NULL;
+ struct istream *input;
+ const char *line;
+ struct stat st;
+ time_t start_time, end_time;
+ unsigned int time_secs;
+ int fd;
+
+ fd = open(pw->path, O_RDONLY);
+ if (fd == -1) {
+ if (errno == EACCES)
+ *error_r = eacces_error_get("open", pw->path);
+ else {
+ *error_r = t_strdup_printf("open(%s) failed: %m",
+ pw->path);
+ }
+ return -1;
+ }
+
+ if (fstat(fd, &st) != 0) {
+ *error_r = t_strdup_printf("fstat(%s) failed: %m",
+ pw->path);
+ i_close_fd(&fd);
+ return -1;
+ }
+
+ pw->fd = fd;
+ pw->stamp = st.st_mtime;
+ pw->size = st.st_size;
+
+ pw->pool = pool_alloconly_create(MEMPOOL_GROWING"passwd_file", 10240);
+ hash_table_create(&pw->users, pw->pool, 0, str_hash, strcmp);
+
+ start_time = time(NULL);
+ input = i_stream_create_fd(pw->fd, SIZE_MAX);
+ i_stream_set_return_partial_line(input, TRUE);
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ if (*line == '\0' || *line == ':' || *line == '#')
+ continue; /* no username or comment */
+
+ T_BEGIN {
+ const char *const *args = t_strsplit(line, ":");
+ if (args[1] != NULL) {
+ /* at least username+password */
+ passwd_file_add(pw, args[0], args[1], args+2);
+ } else {
+ /* only username */
+ passwd_file_add(pw, args[0], NULL, &no_args);
+ }
+ } T_END;
+ }
+ i_stream_destroy(&input);
+ end_time = time(NULL);
+ time_secs = end_time - start_time;
+
+ if ((time_secs > PARSE_TIME_STARTUP_WARN_SECS && startup) ||
+ (time_secs > PARSE_TIME_RELOAD_WARN_SECS && !startup)) {
+ e_warning(pw->event, "Reading %u users took %u secs",
+ hash_table_count(pw->users), time_secs);
+ } else {
+ e_debug(pw->event, "Read %u users in %u secs",
+ hash_table_count(pw->users), time_secs);
+ }
+ return 0;
+}
+
+static void passwd_file_close(struct passwd_file *pw)
+{
+ i_close_fd_path(&pw->fd, pw->path);
+
+ hash_table_destroy(&pw->users);
+ pool_unref(&pw->pool);
+}
+
+static void passwd_file_free(struct passwd_file *pw)
+{
+ if (hash_table_is_created(pw->db->files))
+ hash_table_remove(pw->db->files, pw->path);
+
+ passwd_file_close(pw);
+ event_unref(&pw->event);
+ i_free(pw->path);
+ i_free(pw);
+}
+
+static int passwd_file_sync(struct auth_request *request,
+ struct passwd_file *pw)
+{
+ struct stat st;
+ const char *error;
+
+ if (pw->last_sync_time == ioloop_time)
+ return hash_table_is_created(pw->users) ? 1 : -1;
+ pw->last_sync_time = ioloop_time;
+
+ if (stat(pw->path, &st) < 0) {
+ /* with variables don't give hard errors, or errors about
+ nonexistent files */
+ int ret = -1;
+
+ if (errno == EACCES) {
+ e_error(authdb_event(request),
+ "%s", eacces_error_get("stat", pw->path));
+ } else if (errno == ENOENT) {
+ auth_request_log_info(request, "passwd-file",
+ "missing passwd file: %s", pw->path);
+ ret = 0;
+ } else {
+ e_error(authdb_event(request),
+ "stat(%s) failed: %m", pw->path);
+ }
+
+ if (pw->db->default_file != pw)
+ passwd_file_free(pw);
+ return ret;
+ }
+
+ if (st.st_mtime != pw->stamp || st.st_size != pw->size) {
+ passwd_file_close(pw);
+ if (passwd_file_open(pw, FALSE, &error) < 0) {
+ e_error(authdb_event(request),
+ "%s", error);
+ return -1;
+ }
+ }
+ return 1;
+}
+
+static struct db_passwd_file *db_passwd_file_find(const char *path)
+{
+ struct db_passwd_file *f;
+
+ for (f = passwd_files; f != NULL; f = f->next) {
+ if (strcmp(f->path, path) == 0)
+ return f;
+ }
+
+ return NULL;
+}
+
+static void db_passwd_file_set_userdb(struct db_passwd_file *db)
+{
+ db->userdb = TRUE;
+ /* warn about missing userdb fields only when there aren't any other
+ userdbs. */
+ db->userdb_warn_missing =
+ array_is_created(&global_auth_settings->userdbs) &&
+ array_count(&global_auth_settings->userdbs) == 1;
+}
+
+struct db_passwd_file *
+db_passwd_file_init(const char *path, bool userdb, bool debug)
+{
+ struct db_passwd_file *db;
+ const char *p;
+ bool percents = FALSE;
+
+ db = db_passwd_file_find(path);
+ if (db != NULL) {
+ db->refcount++;
+ if (userdb)
+ db_passwd_file_set_userdb(db);
+ return db;
+ }
+
+ db = i_new(struct db_passwd_file, 1);
+ db->refcount = 1;
+ if (userdb)
+ db_passwd_file_set_userdb(db);
+ db->event = event_create(auth_event);
+ event_set_forced_debug(db->event, debug);
+
+ for (p = path; *p != '\0'; p++) {
+ if (*p == '%' && p[1] != '\0') {
+ if (var_get_key(++p) == '%')
+ percents = TRUE;
+ else
+ db->vars = TRUE;
+ }
+ }
+
+ if (percents && !db->vars) {
+ /* just extra escaped % chars. remove them. */
+ struct var_expand_table empty_table[1] = {
+ { .key = '\0' },
+ };
+ string_t *dest;
+ const char *error;
+
+ dest = t_str_new(256);
+ if (var_expand(dest, path, empty_table, &error) <= 0)
+ i_unreached();
+ path = str_c(dest);
+ }
+
+ db->path = i_strdup(path);
+ if (db->vars) {
+ hash_table_create(&db->files, default_pool, 0,
+ str_hash, strcmp);
+ } else {
+ db->default_file = passwd_file_new(db, path);
+ }
+
+ db->next = passwd_files;
+ passwd_files = db;
+ return db;
+}
+
+void db_passwd_file_parse(struct db_passwd_file *db)
+{
+ const char *error;
+
+ if (db->default_file != NULL && db->default_file->stamp == 0) {
+ /* no variables, open the file immediately */
+ if (passwd_file_open(db->default_file, TRUE, &error) < 0)
+ e_error(db->default_file->event, "%s", error);
+ }
+}
+
+void db_passwd_file_unref(struct db_passwd_file **_db)
+{
+ struct db_passwd_file *db = *_db;
+ struct db_passwd_file **p;
+ struct hash_iterate_context *iter;
+ char *path;
+ struct passwd_file *file;
+
+ *_db = NULL;
+ i_assert(db->refcount >= 0);
+ if (--db->refcount > 0)
+ return;
+
+ for (p = &passwd_files; *p != NULL; p = &(*p)->next) {
+ if (*p == db) {
+ *p = db->next;
+ break;
+ }
+ }
+
+ if (db->default_file != NULL)
+ passwd_file_free(db->default_file);
+ else {
+ iter = hash_table_iterate_init(db->files);
+ while (hash_table_iterate(iter, db->files, &path, &file))
+ passwd_file_free(file);
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&db->files);
+ }
+ event_unref(&db->event);
+ i_free(db->path);
+ i_free(db);
+}
+
+static const char *
+path_fix(const char *path,
+ const struct auth_request *auth_request ATTR_UNUSED)
+{
+ const char *p;
+
+ p = strchr(path, '/');
+ if (p == NULL)
+ return path;
+
+ /* most likely this is an invalid request. just cut off the '/' and
+ everything after it. */
+ return t_strdup_until(path, p);
+}
+
+int db_passwd_file_lookup(struct db_passwd_file *db,
+ struct auth_request *request,
+ const char *username_format,
+ struct passwd_user **user_r)
+{
+ struct passwd_file *pw;
+ string_t *username, *dest;
+ const char *error;
+ int ret;
+
+ if (!db->vars)
+ pw = db->default_file;
+ else {
+ dest = t_str_new(256);
+ if (auth_request_var_expand(dest, db->path, request, path_fix,
+ &error) <= 0) {
+ e_error(authdb_event(request),
+ "Failed to expand passwd-file path %s: %s",
+ db->path, error);
+ return -1;
+ }
+
+ pw = hash_table_lookup(db->files, str_c(dest));
+ if (pw == NULL) {
+ /* doesn't exist yet. create lookup for it. */
+ pw = passwd_file_new(db, str_c(dest));
+ }
+ }
+
+ if ((ret = passwd_file_sync(request, pw)) <= 0) {
+ /* pw may be freed now */
+ return ret;
+ }
+
+ username = t_str_new(256);
+ if (auth_request_var_expand(username, username_format, request,
+ auth_request_str_escape, &error) <= 0) {
+ e_error(authdb_event(request),
+ "Failed to expand username_format=%s: %s",
+ username_format, error);
+ return -1;
+ }
+
+ e_debug(authdb_event(request),
+ "lookup: user=%s file=%s",
+ str_c(username), pw->path);
+
+ *user_r = hash_table_lookup(pw->users, str_c(username));
+ if (*user_r == NULL) {
+ auth_request_log_unknown_user(request, AUTH_SUBSYS_DB);
+ return 0;
+ }
+ return 1;
+}
+
+#endif
diff --git a/src/auth/db-passwd-file.h b/src/auth/db-passwd-file.h
new file mode 100644
index 0000000..498f9c3
--- /dev/null
+++ b/src/auth/db-passwd-file.h
@@ -0,0 +1,58 @@
+#ifndef DB_PASSWD_FILE_H
+#define DB_PASSWD_FILE_H
+
+#include "hash.h"
+
+#define PASSWD_FILE_DEFAULT_USERNAME_FORMAT "%u"
+#define PASSWD_FILE_DEFAULT_SCHEME "CRYPT"
+
+struct passwd_user {
+ uid_t uid;
+ gid_t gid;
+
+ char *home;
+ char *password;
+ char **extra_fields;
+};
+
+struct passwd_file {
+ struct db_passwd_file *db;
+ pool_t pool;
+ int refcount;
+ struct event *event;
+
+ time_t last_sync_time;
+ char *path;
+ time_t stamp;
+ off_t size;
+ int fd;
+
+ HASH_TABLE(char *, struct passwd_user *) users;
+};
+
+struct db_passwd_file {
+ struct db_passwd_file *next;
+
+ int refcount;
+ struct event *event;
+
+ char *path;
+ HASH_TABLE(char *, struct passwd_file *) files;
+ struct passwd_file *default_file;
+
+ bool vars:1;
+ bool userdb:1;
+ bool userdb_warn_missing:1;
+};
+
+int db_passwd_file_lookup(struct db_passwd_file *db,
+ struct auth_request *request,
+ const char *username_format,
+ struct passwd_user **user_r);
+
+struct db_passwd_file *
+db_passwd_file_init(const char *path, bool userdb, bool debug);
+void db_passwd_file_parse(struct db_passwd_file *db);
+void db_passwd_file_unref(struct db_passwd_file **db);
+
+#endif
diff --git a/src/auth/db-sql.c b/src/auth/db-sql.c
new file mode 100644
index 0000000..b77edd1
--- /dev/null
+++ b/src/auth/db-sql.c
@@ -0,0 +1,178 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+
+#if defined(PASSDB_SQL) || defined(USERDB_SQL)
+
+#include "settings.h"
+#include "auth-request.h"
+#include "auth-worker-client.h"
+#include "db-sql.h"
+
+#include <stddef.h>
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, db_sql_settings)
+#define DEF_INT(name) DEF_STRUCT_INT(name, db_sql_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, db_sql_settings)
+
+static struct setting_def setting_defs[] = {
+ DEF_STR(driver),
+ DEF_STR(connect),
+ DEF_STR(password_query),
+ DEF_STR(user_query),
+ DEF_STR(update_query),
+ DEF_STR(iterate_query),
+ DEF_STR(default_pass_scheme),
+ DEF_BOOL(userdb_warning_disable),
+
+ { 0, NULL, 0 }
+};
+
+static struct db_sql_settings default_db_sql_settings = {
+ .driver = NULL,
+ .connect = NULL,
+ .password_query = "SELECT username, domain, password FROM users WHERE username = '%n' AND domain = '%d'",
+ .user_query = "SELECT home, uid, gid FROM users WHERE username = '%n' AND domain = '%d'",
+ .update_query = "UPDATE users SET password = '%w' WHERE username = '%n' AND domain = '%d'",
+ .iterate_query = "SELECT username, domain FROM users",
+ .default_pass_scheme = "MD5",
+ .userdb_warning_disable = FALSE
+};
+
+static struct db_sql_connection *connections = NULL;
+
+static struct db_sql_connection *sql_conn_find(const char *config_path)
+{
+ struct db_sql_connection *conn;
+
+ for (conn = connections; conn != NULL; conn = conn->next) {
+ if (strcmp(conn->config_path, config_path) == 0)
+ return conn;
+ }
+
+ return NULL;
+}
+
+static const char *parse_setting(const char *key, const char *value,
+ struct db_sql_connection *conn)
+{
+ return parse_setting_from_defs(conn->pool, setting_defs,
+ &conn->set, key, value);
+}
+
+struct db_sql_connection *db_sql_init(const char *config_path, bool userdb)
+{
+ struct db_sql_connection *conn;
+ struct sql_settings set;
+ const char *error;
+ pool_t pool;
+
+ conn = sql_conn_find(config_path);
+ if (conn != NULL) {
+ if (userdb)
+ conn->userdb_used = TRUE;
+ conn->refcount++;
+ return conn;
+ }
+
+ if (*config_path == '\0')
+ i_fatal("sql: Configuration file path not given");
+
+ pool = pool_alloconly_create("db_sql_connection", 1024);
+ conn = p_new(pool, struct db_sql_connection, 1);
+ conn->pool = pool;
+ conn->userdb_used = userdb;
+
+ conn->refcount = 1;
+
+ conn->config_path = p_strdup(pool, config_path);
+ conn->set = default_db_sql_settings;
+ if (!settings_read_nosection(config_path, parse_setting, conn, &error))
+ i_fatal("sql %s: %s", config_path, error);
+
+ if (conn->set.password_query == default_db_sql_settings.password_query)
+ conn->default_password_query = TRUE;
+ if (conn->set.user_query == default_db_sql_settings.user_query)
+ conn->default_user_query = TRUE;
+ if (conn->set.update_query == default_db_sql_settings.update_query)
+ conn->default_update_query = TRUE;
+ if (conn->set.iterate_query == default_db_sql_settings.iterate_query)
+ conn->default_iterate_query = TRUE;
+
+ if (conn->set.driver == NULL) {
+ i_fatal("sql: driver not set in configuration file %s",
+ config_path);
+ }
+ if (conn->set.connect == NULL) {
+ i_fatal("sql: connect string not set in configuration file %s",
+ config_path);
+ }
+ i_zero(&set);
+ set.driver = conn->set.driver;
+ set.connect_string = conn->set.connect;
+ set.event_parent = auth_event;
+ if (sql_init_full(&set, &conn->db, &error) < 0) {
+ i_fatal("sql: %s", error);
+ }
+
+ conn->next = connections;
+ connections = conn;
+ return conn;
+}
+
+void db_sql_unref(struct db_sql_connection **_conn)
+{
+ struct db_sql_connection *conn = *_conn;
+
+ /* abort all pending auth requests before setting conn to NULL,
+ so that callbacks can still access it */
+ sql_disconnect(conn->db);
+
+ *_conn = NULL;
+ if (--conn->refcount > 0)
+ return;
+
+ sql_unref(&conn->db);
+ pool_unref(&conn->pool);
+}
+
+void db_sql_connect(struct db_sql_connection *conn)
+{
+ if (sql_connect(conn->db) < 0 && worker) {
+ /* auth worker's sql connection failed. we can't do anything
+ useful until the connection works. there's no point in
+ having tons of worker processes all logging failures,
+ so tell the auth master to stop creating new workers (and
+ maybe close old ones). this handling is especially useful if
+ we reach the max. number of connections for sql server. */
+ auth_worker_client_send_error();
+ }
+}
+
+void db_sql_success(struct db_sql_connection *conn ATTR_UNUSED)
+{
+ if (worker)
+ auth_worker_client_send_success();
+}
+
+void db_sql_check_userdb_warning(struct db_sql_connection *conn)
+{
+ if (worker || conn->userdb_used || conn->set.userdb_warning_disable)
+ return;
+
+ if (strcmp(conn->set.user_query,
+ default_db_sql_settings.user_query) != 0) {
+ i_warning("sql: Ignoring changed user_query in %s, "
+ "because userdb sql not used. "
+ "(If this is intentional, set userdb_warning_disable=yes)",
+ conn->config_path);
+ } else if (strcmp(conn->set.iterate_query,
+ default_db_sql_settings.iterate_query) != 0) {
+ i_warning("sql: Ignoring changed iterate_query in %s, "
+ "because userdb sql not used. "
+ "(If this is intentional, set userdb_warning_disable=yes)",
+ conn->config_path);
+ }
+}
+
+#endif
diff --git a/src/auth/db-sql.h b/src/auth/db-sql.h
new file mode 100644
index 0000000..27e177b
--- /dev/null
+++ b/src/auth/db-sql.h
@@ -0,0 +1,42 @@
+#ifndef DB_SQL_H
+#define DB_SQL_H
+
+#include "sql-api.h"
+
+struct db_sql_settings {
+ const char *driver;
+ const char *connect;
+ const char *password_query;
+ const char *user_query;
+ const char *update_query;
+ const char *iterate_query;
+ const char *default_pass_scheme;
+ bool userdb_warning_disable;
+};
+
+struct db_sql_connection {
+ struct db_sql_connection *next;
+
+ pool_t pool;
+ int refcount;
+
+ char *config_path;
+ struct db_sql_settings set;
+ struct sql_db *db;
+
+ bool default_password_query:1;
+ bool default_user_query:1;
+ bool default_update_query:1;
+ bool default_iterate_query:1;
+ bool userdb_used:1;
+};
+
+struct db_sql_connection *db_sql_init(const char *config_path, bool userdb);
+void db_sql_unref(struct db_sql_connection **conn);
+
+void db_sql_connect(struct db_sql_connection *conn);
+void db_sql_success(struct db_sql_connection *conn);
+
+void db_sql_check_userdb_warning(struct db_sql_connection *conn);
+
+#endif
diff --git a/src/auth/main.c b/src/auth/main.c
new file mode 100644
index 0000000..5f09fca
--- /dev/null
+++ b/src/auth/main.c
@@ -0,0 +1,398 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "ioloop.h"
+#include "net.h"
+#include "lib-signals.h"
+#include "restrict-access.h"
+#include "child-wait.h"
+#include "sql-api.h"
+#include "module-dir.h"
+#include "randgen.h"
+#include "process-title.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "master-interface.h"
+#include "dict.h"
+#include "password-scheme.h"
+#include "passdb-cache.h"
+#include "mech.h"
+#include "otp.h"
+#include "mech-otp-common.h"
+#include "auth.h"
+#include "auth-penalty.h"
+#include "auth-token.h"
+#include "auth-request-handler.h"
+#include "auth-request-stats.h"
+#include "auth-worker-server.h"
+#include "auth-worker-client.h"
+#include "auth-master-connection.h"
+#include "auth-client-connection.h"
+#include "auth-policy.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define AUTH_PENALTY_ANVIL_PATH "anvil-auth-penalty"
+
+enum auth_socket_type {
+ AUTH_SOCKET_UNKNOWN = 0,
+ AUTH_SOCKET_CLIENT,
+ AUTH_SOCKET_LOGIN_CLIENT,
+ AUTH_SOCKET_MASTER,
+ AUTH_SOCKET_USERDB,
+ AUTH_SOCKET_TOKEN,
+ AUTH_SOCKET_TOKEN_LOGIN
+};
+
+struct auth_socket_listener {
+ enum auth_socket_type type;
+ struct stat st;
+ char *path;
+};
+
+bool worker = FALSE, worker_restart_request = FALSE;
+time_t process_start_time;
+struct auth_penalty *auth_penalty;
+
+static pool_t auth_set_pool;
+static struct module *modules = NULL;
+static struct mechanisms_register *mech_reg;
+static ARRAY(struct auth_socket_listener) listeners;
+
+void auth_refresh_proctitle(void)
+{
+ if (!global_auth_settings->verbose_proctitle || worker)
+ return;
+
+ process_title_set(t_strdup_printf(
+ "[%u wait, %u passdb, %u userdb]",
+ auth_request_state_count[AUTH_REQUEST_STATE_NEW] +
+ auth_request_state_count[AUTH_REQUEST_STATE_MECH_CONTINUE] +
+ auth_request_state_count[AUTH_REQUEST_STATE_FINISHED],
+ auth_request_state_count[AUTH_REQUEST_STATE_PASSDB],
+ auth_request_state_count[AUTH_REQUEST_STATE_USERDB]));
+}
+
+static const char *const *read_global_settings(void)
+{
+ struct master_service_settings_output set_output;
+ const char **services;
+ unsigned int i, count;
+
+ auth_set_pool = pool_alloconly_create("auth settings", 8192);
+ global_auth_settings =
+ auth_settings_read(NULL, auth_set_pool, &set_output);
+
+ /* strdup() the service names, because they're allocated from
+ set parser pool, and we'll later clear it. */
+ count = str_array_length(set_output.specific_services);
+ services = p_new(auth_set_pool, const char *, count + 1);
+ for (i = 0; i < count; i++) {
+ services[i] = p_strdup(auth_set_pool,
+ set_output.specific_services[i]);
+ }
+ return services;
+}
+
+static enum auth_socket_type
+auth_socket_type_get(const char *path)
+{
+ const char *name, *suffix;
+
+ name = strrchr(path, '/');
+ if (name == NULL)
+ name = path;
+ else
+ name++;
+
+ suffix = strrchr(name, '-');
+ if (suffix == NULL)
+ suffix = name;
+ else
+ suffix++;
+
+ if (strcmp(suffix, "login") == 0)
+ return AUTH_SOCKET_LOGIN_CLIENT;
+ else if (strcmp(suffix, "master") == 0)
+ return AUTH_SOCKET_MASTER;
+ else if (strcmp(suffix, "userdb") == 0)
+ return AUTH_SOCKET_USERDB;
+ else if (strcmp(suffix, "token") == 0)
+ return AUTH_SOCKET_TOKEN;
+ else if (strcmp(suffix, "tokenlogin") == 0)
+ return AUTH_SOCKET_TOKEN_LOGIN;
+ else
+ return AUTH_SOCKET_CLIENT;
+}
+
+static void listeners_init(void)
+{
+ unsigned int i, n;
+ const char *path;
+
+ i_array_init(&listeners, 8);
+ n = master_service_get_socket_count(master_service);
+ for (i = 0; i < n; i++) {
+ int fd = MASTER_LISTEN_FD_FIRST + i;
+ struct auth_socket_listener *l;
+
+ l = array_idx_get_space(&listeners, fd);
+ if (net_getunixname(fd, &path) < 0) {
+ if (errno != ENOTSOCK)
+ i_fatal("getunixname(%d) failed: %m", fd);
+ /* not a unix socket, set its name and type lazily */
+ } else {
+ l->type = auth_socket_type_get(path);
+ l->path = i_strdup(path);
+ if (l->type == AUTH_SOCKET_USERDB) {
+ if (stat(path, &l->st) < 0)
+ i_error("stat(%s) failed: %m", path);
+ }
+ }
+ }
+}
+
+static bool auth_module_filter(const char *name, void *context ATTR_UNUSED)
+{
+ if (str_begins(name, "authdb_") ||
+ str_begins(name, "mech_")) {
+ /* this is lazily loaded */
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void main_preinit(void)
+{
+ struct module_dir_load_settings mod_set;
+ const char *const *services;
+
+ /* Load built-in SQL drivers (if any) */
+ sql_drivers_init();
+ sql_drivers_register_all();
+
+ /* Initialize databases so their configuration files can be readable
+ only by root. Also load all modules here. */
+ passdbs_init();
+ userdbs_init();
+ /* init schemes before plugins are loaded */
+ password_schemes_init();
+
+ services = read_global_settings();
+
+ i_zero(&mod_set);
+ mod_set.abi_version = DOVECOT_ABI_VERSION;
+ mod_set.require_init_funcs = TRUE;
+ mod_set.debug = global_auth_settings->debug;
+ mod_set.filter_callback = auth_module_filter;
+
+ modules = module_dir_load(AUTH_MODULE_DIR, NULL, &mod_set);
+ module_dir_init(modules);
+
+ if (!worker)
+ auth_penalty = auth_penalty_init(AUTH_PENALTY_ANVIL_PATH);
+ auth_request_stats_init();
+ mech_init(global_auth_settings);
+ mech_reg = mech_register_init(global_auth_settings);
+ dict_drivers_register_builtin();
+ auths_preinit(global_auth_settings, auth_set_pool,
+ mech_reg, services);
+
+ listeners_init();
+ if (!worker)
+ auth_token_init();
+
+ /* Password lookups etc. may require roots, allow it. */
+ restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL);
+ restrict_access_allow_coredumps(TRUE);
+}
+
+void auth_module_load(const char *names)
+{
+ struct module_dir_load_settings mod_set;
+
+ i_zero(&mod_set);
+ mod_set.abi_version = DOVECOT_ABI_VERSION;
+ mod_set.require_init_funcs = TRUE;
+ mod_set.debug = global_auth_settings->debug;
+ mod_set.ignore_missing = TRUE;
+
+ modules = module_dir_load_missing(modules, AUTH_MODULE_DIR, names,
+ &mod_set);
+ module_dir_init(modules);
+}
+
+static void main_init(void)
+{
+ process_start_time = ioloop_time;
+
+ /* If auth caches aren't used, just ignore these signals */
+ lib_signals_ignore(SIGHUP, TRUE);
+ lib_signals_ignore(SIGUSR2, TRUE);
+
+ /* set proctitles before init()s, since they may set them to error */
+ auth_refresh_proctitle();
+ auth_worker_refresh_proctitle("");
+
+ child_wait_init();
+ auth_worker_server_init();
+ auths_init();
+ auth_request_handler_init();
+ auth_policy_init();
+
+ if (worker) {
+ /* workers have only a single connection from the master
+ auth process */
+ master_service_set_client_limit(master_service, 1);
+ auth_worker_set_max_service_count(
+ master_service_get_service_count(master_service));
+ /* make sure this process cycles if auth connection drops */
+ master_service_set_service_count(master_service, 1);
+ } else {
+ /* caching is handled only by the main auth process */
+ passdb_cache_init(global_auth_settings);
+ }
+}
+
+static void main_deinit(void)
+{
+ struct auth_socket_listener *l;
+
+ if (auth_penalty != NULL) {
+ /* cancel all pending anvil penalty lookups */
+ auth_penalty_deinit(&auth_penalty);
+ }
+ /* deinit auth workers, which aborts pending requests */
+ auth_worker_server_deinit();
+ /* deinit passdbs and userdbs. it aborts any pending async requests. */
+ auths_deinit();
+ /* flush pending requests */
+ auth_request_handler_deinit();
+ /* there are no more auth requests */
+ auths_free();
+ dict_drivers_unregister_builtin();
+
+ auth_token_deinit();
+
+ auth_client_connections_destroy_all();
+ auth_master_connections_destroy_all();
+ auth_worker_connections_destroy_all();
+
+ auth_policy_deinit();
+ mech_register_deinit(&mech_reg);
+ mech_otp_deinit();
+ mech_deinit(global_auth_settings);
+
+ /* allow modules to unregister their dbs/drivers/etc. before freeing
+ the whole data structures containing them. */
+ module_dir_unload(&modules);
+
+ userdbs_deinit();
+ passdbs_deinit();
+ passdb_cache_deinit();
+ password_schemes_deinit();
+ auth_request_stats_deinit();
+
+ sql_drivers_deinit();
+ child_wait_deinit();
+
+ array_foreach_modifiable(&listeners, l)
+ i_free(l->path);
+ array_free(&listeners);
+ pool_unref(&auth_set_pool);
+}
+
+static void worker_connected(struct master_service_connection *conn)
+{
+ if (auth_worker_has_client()) {
+ i_error("Auth workers can handle only a single client");
+ return;
+ }
+
+ master_service_client_connection_accept(conn);
+ (void)auth_worker_client_create(auth_default_service(), conn);
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+ struct auth_socket_listener *l;
+ struct auth *auth;
+
+ l = array_idx_modifiable(&listeners, conn->listen_fd);
+ if (l->type == AUTH_SOCKET_UNKNOWN) {
+ /* first connection from inet socket, figure out its type
+ from the listener name */
+ l->type = auth_socket_type_get(conn->name);
+ l->path = i_strdup(conn->name);
+ }
+ auth = auth_default_service();
+ switch (l->type) {
+ case AUTH_SOCKET_MASTER:
+ (void)auth_master_connection_create(auth, conn->fd,
+ l->path, NULL, FALSE);
+ break;
+ case AUTH_SOCKET_USERDB:
+ (void)auth_master_connection_create(auth, conn->fd,
+ l->path, &l->st, TRUE);
+ break;
+ case AUTH_SOCKET_LOGIN_CLIENT:
+ auth_client_connection_create(auth, conn->fd, TRUE, FALSE);
+ break;
+ case AUTH_SOCKET_CLIENT:
+ auth_client_connection_create(auth, conn->fd, FALSE, FALSE);
+ break;
+ case AUTH_SOCKET_TOKEN_LOGIN:
+ auth_client_connection_create(auth, conn->fd, TRUE, TRUE);
+ break;
+ case AUTH_SOCKET_TOKEN:
+ auth_client_connection_create(auth, conn->fd, FALSE, TRUE);
+ break;
+ default:
+ i_unreached();
+ }
+ master_service_client_connection_accept(conn);
+}
+
+static void auth_die(void)
+{
+ if (!worker) {
+ /* do nothing. auth clients should disconnect soon. */
+ } else {
+ /* ask auth master to disconnect us */
+ auth_worker_client_send_shutdown();
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_NO_SSL_INIT;
+
+ master_service = master_service_init("auth", service_flags, &argc, &argv, "w");
+ master_service_init_log(master_service);
+
+ while ((c = master_getopt(master_service)) > 0) {
+ switch (c) {
+ case 'w':
+ master_service_init_log_with_pid(master_service);
+ worker = TRUE;
+ break;
+ default:
+ return FATAL_DEFAULT;
+ }
+ }
+
+ main_preinit();
+ master_service_set_die_callback(master_service, auth_die);
+ main_init();
+ master_service_init_finish(master_service);
+ master_service_run(master_service, worker ? worker_connected :
+ client_connected);
+ main_deinit();
+ master_service_deinit(&master_service);
+ return 0;
+}
diff --git a/src/auth/mech-anonymous.c b/src/auth/mech-anonymous.c
new file mode 100644
index 0000000..fbbfccd
--- /dev/null
+++ b/src/auth/mech-anonymous.c
@@ -0,0 +1,48 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "mech.h"
+
+static void
+mech_anonymous_auth_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ i_assert(*request->set->anonymous_username != '\0');
+
+ if (request->set->verbose) {
+ /* temporarily set the user to the one that was given,
+ so that the log message goes right */
+ auth_request_set_username_forced(request,
+ t_strndup(data, data_size));
+ e_info(request->mech_event, "login");
+ }
+
+ auth_request_set_username_forced(request,
+ request->set->anonymous_username);
+
+ request->passdb_success = TRUE;
+ auth_request_success(request, "", 0);
+}
+
+static struct auth_request *mech_anonymous_auth_new(void)
+{
+ struct auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"anonymous_auth_request", 2048);
+ request = p_new(pool, struct auth_request, 1);
+ request->pool = pool;
+ return request;
+}
+
+const struct mech_module mech_anonymous = {
+ "ANONYMOUS",
+
+ .flags = MECH_SEC_ANONYMOUS | MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_NOTHING,
+
+ mech_anonymous_auth_new,
+ mech_generic_auth_initial,
+ mech_anonymous_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-apop.c b/src/auth/mech-apop.c
new file mode 100644
index 0000000..f28171f
--- /dev/null
+++ b/src/auth/mech-apop.c
@@ -0,0 +1,173 @@
+/*
+ * APOP (RFC-1460) authentication mechanism.
+ *
+ * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "auth-common.h"
+#include "mech.h"
+#include "passdb.h"
+#include "md5.h"
+#include "buffer.h"
+#include "auth-client-connection.h"
+#include "auth-master-connection.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+struct apop_auth_request {
+ struct auth_request auth_request;
+
+ pool_t pool;
+
+ /* requested: */
+ char *challenge;
+
+ /* received: */
+ unsigned char response_digest[16];
+};
+
+static bool verify_credentials(struct apop_auth_request *request,
+ const unsigned char *credentials, size_t size)
+{
+ unsigned char digest[16];
+ struct md5_context ctx;
+
+ md5_init(&ctx);
+ md5_update(&ctx, request->challenge, strlen(request->challenge));
+ md5_update(&ctx, credentials, size);
+ md5_final(&ctx, digest);
+
+ return mem_equals_timing_safe(digest, request->response_digest, 16);
+}
+
+static void
+apop_credentials_callback(enum passdb_result result,
+ const unsigned char *credentials, size_t size,
+ struct auth_request *auth_request)
+{
+ struct apop_auth_request *request =
+ (struct apop_auth_request *)auth_request;
+
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ if (verify_credentials(request, credentials, size))
+ auth_request_success(auth_request, "", 0);
+ else
+ auth_request_fail(auth_request);
+ break;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ auth_request_internal_failure(auth_request);
+ break;
+ default:
+ auth_request_fail(auth_request);
+ break;
+ }
+}
+
+static void
+mech_apop_auth_initial(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ struct apop_auth_request *request =
+ (struct apop_auth_request *)auth_request;
+ const unsigned char *tmp, *end, *username = NULL;
+ unsigned long pid, connect_uid, timestamp;
+ const char *error;
+
+ /* pop3-login handles sending the challenge and getting the response.
+ Our input here is: <challenge> \0 <username> \0 <response> */
+
+ if (data_size == 0) {
+ /* Should never happen */
+ e_info(auth_request->mech_event,
+ "no initial response");
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ tmp = data;
+ end = data + data_size;
+
+ /* get the challenge */
+ while (tmp != end && *tmp != '\0')
+ tmp++;
+ request->challenge = p_strdup_until(request->pool, data, tmp);
+
+ if (tmp != end) {
+ /* get the username */
+ username = ++tmp;
+ while (tmp != end && *tmp != '\0')
+ tmp++;
+ } else {
+ /* should never happen */
+ e_info(auth_request->mech_event,
+ "malformed data");
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ if (tmp + 1 + 16 != end) {
+ /* Should never happen */
+ e_info(auth_request->mech_event,
+ "malformed data");
+ auth_request_fail(auth_request);
+ return;
+ }
+ memcpy(request->response_digest, tmp + 1,
+ sizeof(request->response_digest));
+
+ /* the challenge must begin with trusted unique ID. we trust only
+ ourself, so make sure it matches our connection specific UID
+ which we told to client in handshake. Also require a timestamp
+ which is later than this process's start time. */
+
+ if (sscanf(request->challenge, "<%lx.%lx.%lx.",
+ &pid, &connect_uid, &timestamp) != 3 ||
+ connect_uid != auth_request->connect_uid ||
+ pid != (unsigned long)getpid() ||
+ (time_t)timestamp < process_start_time) {
+ e_info(auth_request->mech_event,
+ "invalid challenge");
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ if (!auth_request_set_username(auth_request, (const char *)username,
+ &error)) {
+ e_info(auth_request->mech_event, "%s", error);
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ auth_request_lookup_credentials(auth_request, "PLAIN",
+ apop_credentials_callback);
+}
+
+static struct auth_request *mech_apop_auth_new(void)
+{
+ struct apop_auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"apop_auth_request", 2048);
+ request = p_new(pool, struct apop_auth_request, 1);
+ request->pool = pool;
+
+ request->auth_request.pool = pool;
+ return &request->auth_request;
+}
+
+const struct mech_module mech_apop = {
+ "APOP",
+
+ .flags = MECH_SEC_PRIVATE | MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE |
+ MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_VERIFY_RESPONSE,
+
+ mech_apop_auth_new,
+ mech_apop_auth_initial,
+ NULL,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-cram-md5.c b/src/auth/mech-cram-md5.c
new file mode 100644
index 0000000..e6ab888
--- /dev/null
+++ b/src/auth/mech-cram-md5.c
@@ -0,0 +1,193 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+/* CRAM-MD5 SASL authentication, see RFC-2195
+ Joshua Goodall <joshua@roughtrade.net> */
+
+#include "auth-common.h"
+#include "ioloop.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "hmac-cram-md5.h"
+#include "hmac.h"
+#include "md5.h"
+#include "randgen.h"
+#include "mech.h"
+#include "passdb.h"
+#include "hostpid.h"
+
+#include <time.h>
+
+struct cram_auth_request {
+ struct auth_request auth_request;
+
+ pool_t pool;
+
+ /* requested: */
+ char *challenge;
+
+ /* received: */
+ char *username;
+ char *response;
+ unsigned long maxbuf;
+};
+
+static const char *get_cram_challenge(void)
+{
+ unsigned char buf[17];
+ size_t i;
+
+ random_fill(buf, sizeof(buf)-1);
+
+ for (i = 0; i < sizeof(buf)-1; i++)
+ buf[i] = (buf[i] % 10) + '0';
+ buf[sizeof(buf)-1] = '\0';
+
+ return t_strdup_printf("<%s.%s@%s>", (const char *)buf,
+ dec2str(ioloop_time), my_hostname);
+}
+
+static bool verify_credentials(struct cram_auth_request *request,
+ const unsigned char *credentials, size_t size)
+{
+
+ unsigned char digest[MD5_RESULTLEN];
+ struct hmac_context ctx;
+ const char *response_hex;
+
+ if (size != CRAM_MD5_CONTEXTLEN) {
+ e_error(request->auth_request.mech_event,
+ "invalid credentials length");
+ return FALSE;
+ }
+
+ hmac_init(&ctx, NULL, 0, &hash_method_md5);
+ hmac_md5_set_cram_context(&ctx, credentials);
+ hmac_update(&ctx, request->challenge, strlen(request->challenge));
+ hmac_final(&ctx, digest);
+
+ response_hex = binary_to_hex(digest, sizeof(digest));
+
+ if (!mem_equals_timing_safe(response_hex, request->response, sizeof(digest)*2)) {
+ e_info(request->auth_request.mech_event,
+ AUTH_LOG_MSG_PASSWORD_MISMATCH);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool parse_cram_response(struct cram_auth_request *request,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ size_t i, space;
+
+ *error_r = NULL;
+
+ /* <username> SPACE <response>. Username may contain spaces, so assume
+ the rightmost space is the response separator. */
+ for (i = space = 0; i < size; i++) {
+ if (data[i] == '\0') {
+ *error_r = "NULs in response";
+ return FALSE;
+ }
+ if (data[i] == ' ')
+ space = i;
+ }
+
+ if (space == 0) {
+ *error_r = "missing digest";
+ return FALSE;
+ }
+
+ request->username = p_strndup(request->pool, data, space);
+ space++;
+ request->response =
+ p_strndup(request->pool, data + space, size - space);
+ return TRUE;
+}
+
+static void credentials_callback(enum passdb_result result,
+ const unsigned char *credentials, size_t size,
+ struct auth_request *auth_request)
+{
+ struct cram_auth_request *request =
+ (struct cram_auth_request *)auth_request;
+
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ if (verify_credentials(request, credentials, size))
+ auth_request_success(auth_request, "", 0);
+ else
+ auth_request_fail(auth_request);
+ break;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ auth_request_internal_failure(auth_request);
+ break;
+ default:
+ auth_request_fail(auth_request);
+ break;
+ }
+}
+
+static void
+mech_cram_md5_auth_continue(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ struct cram_auth_request *request =
+ (struct cram_auth_request *)auth_request;
+ const char *error;
+
+ if (parse_cram_response(request, data, data_size, &error)) {
+ if (auth_request_set_username(auth_request, request->username,
+ &error)) {
+ auth_request_lookup_credentials(auth_request,
+ "CRAM-MD5", credentials_callback);
+ return;
+ }
+ }
+
+ if (error == NULL)
+ error = "authentication failed";
+
+ e_info(auth_request->mech_event, "%s", error);
+ auth_request_fail(auth_request);
+}
+
+static void
+mech_cram_md5_auth_initial(struct auth_request *auth_request,
+ const unsigned char *data ATTR_UNUSED,
+ size_t data_size ATTR_UNUSED)
+{
+ struct cram_auth_request *request =
+ (struct cram_auth_request *)auth_request;
+
+ request->challenge = p_strdup(request->pool, get_cram_challenge());
+ auth_request_handler_reply_continue(auth_request, request->challenge,
+ strlen(request->challenge));
+}
+
+static struct auth_request *mech_cram_md5_auth_new(void)
+{
+ struct cram_auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"cram_md5_auth_request", 2048);
+ request = p_new(pool, struct cram_auth_request, 1);
+ request->pool = pool;
+
+ request->auth_request.pool = pool;
+ return &request->auth_request;
+}
+
+const struct mech_module mech_cram_md5 = {
+ "CRAM-MD5",
+
+ .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE,
+ .passdb_need = MECH_PASSDB_NEED_VERIFY_RESPONSE,
+
+ mech_cram_md5_auth_new,
+ mech_cram_md5_auth_initial,
+ mech_cram_md5_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-digest-md5-private.h b/src/auth/mech-digest-md5-private.h
new file mode 100644
index 0000000..fb9ff80
--- /dev/null
+++ b/src/auth/mech-digest-md5-private.h
@@ -0,0 +1,38 @@
+#ifndef MECH_DIGEST_MD5_PRIVATE_H
+#define MECH_DIGEST_MD5_PRIVATE_H
+
+#include "auth-request.h"
+
+enum qop_option {
+ QOP_AUTH = 0x01, /* authenticate */
+ QOP_AUTH_INT = 0x02, /* + integrity protection, not supported yet */
+ QOP_AUTH_CONF = 0x04, /* + encryption, not supported yet */
+
+ QOP_COUNT = 3
+};
+
+struct digest_auth_request {
+ struct auth_request auth_request;
+
+ pool_t pool;
+
+ /* requested: */
+ char *nonce;
+ enum qop_option qop;
+
+ /* received: */
+ char *username;
+ char *cnonce;
+ char *nonce_count;
+ char *qop_value;
+ char *digest_uri; /* may be NULL */
+ char *authzid; /* may be NULL, authorization ID */
+ unsigned char response[32];
+ unsigned long maxbuf;
+ bool nonce_found:1;
+
+ /* final reply: */
+ char *rspauth;
+};
+
+#endif
diff --git a/src/auth/mech-digest-md5.c b/src/auth/mech-digest-md5.c
new file mode 100644
index 0000000..d7f4383
--- /dev/null
+++ b/src/auth/mech-digest-md5.c
@@ -0,0 +1,592 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+/* Digest-MD5 SASL authentication, see RFC-2831 */
+
+#include "auth-common.h"
+#include "base64.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "mech-digest-md5-private.h"
+#include "md5.h"
+#include "randgen.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mech.h"
+#include "passdb.h"
+
+
+#define MAX_REALM_LEN 64
+
+/* Linear whitespace */
+#define IS_LWS(c) ((c) == ' ' || (c) == '\t')
+
+static const char *qop_names[] = { "auth", "auth-int", "auth-conf" };
+
+static string_t *get_digest_challenge(struct digest_auth_request *request)
+{
+ const struct auth_settings *set = request->auth_request.set;
+ buffer_t buf;
+ string_t *str;
+ const char *const *tmp;
+ unsigned char nonce[16];
+ unsigned char nonce_base64[MAX_BASE64_ENCODED_SIZE(sizeof(nonce))+1];
+ int i;
+ bool first_qop;
+
+ /*
+ realm="hostname" (multiple allowed)
+ nonce="randomized data, at least 64bit"
+ qop="auth,auth-int,auth-conf"
+ maxbuf=number (with auth-int, auth-conf, defaults to 64k)
+ charset="utf-8" (iso-8859-1 if it doesn't exist)
+ algorithm="md5-sess"
+ cipher="3des,des,rc4-40,rc4,rc4-56" (with auth-conf)
+ */
+
+ /* get 128bit of random data as nonce */
+ random_fill(nonce, sizeof(nonce));
+
+ buffer_create_from_data(&buf, nonce_base64, sizeof(nonce_base64));
+ base64_encode(nonce, sizeof(nonce), &buf);
+ buffer_append_c(&buf, '\0');
+ request->nonce = p_strdup(request->pool, buf.data);
+
+ str = t_str_new(256);
+ if (*set->realms_arr == NULL) {
+ /* If no realms are given, at least Cyrus SASL client defaults
+ to destination host name */
+ str_append(str, "realm=\"\",");
+ } else {
+ for (tmp = set->realms_arr; *tmp != NULL; tmp++)
+ str_printfa(str, "realm=\"%s\",", *tmp);
+ }
+
+ str_printfa(str, "nonce=\"%s\",", request->nonce);
+
+ str_append(str, "qop=\""); first_qop = TRUE;
+ for (i = 0; i < QOP_COUNT; i++) {
+ if ((request->qop & (1 << i)) != 0) {
+ if (first_qop)
+ first_qop = FALSE;
+ else
+ str_append_c(str, ',');
+ str_append(str, qop_names[i]);
+ }
+ }
+ str_append(str, "\",");
+
+ str_append(str, "charset=\"utf-8\","
+ "algorithm=\"md5-sess\"");
+ return str;
+}
+
+static bool verify_credentials(struct digest_auth_request *request,
+ const unsigned char *credentials, size_t size)
+{
+ struct md5_context ctx;
+ unsigned char digest[MD5_RESULTLEN];
+ const char *a1_hex, *a2_hex, *response_hex;
+ int i;
+
+ /* get the MD5 password */
+ if (size != MD5_RESULTLEN) {
+ e_error(request->auth_request.mech_event,
+ "invalid credentials length");
+ return FALSE;
+ }
+
+ /*
+ response =
+ HEX( KD ( HEX(H(A1)),
+ { nonce-value, ":" nc-value, ":",
+ cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))
+
+ and if authzid is not empty:
+
+ A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
+ ":", nonce-value, ":", cnonce-value, ":", authzid }
+
+ else:
+
+ A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
+ ":", nonce-value, ":", cnonce-value }
+
+ If the "qop" directive's value is "auth", then A2 is:
+
+ A2 = { "AUTHENTICATE:", digest-uri-value }
+
+ If the "qop" value is "auth-int" or "auth-conf" then A2 is:
+
+ A2 = { "AUTHENTICATE:", digest-uri-value,
+ ":00000000000000000000000000000000" }
+ */
+
+ /* A1 */
+ md5_init(&ctx);
+ md5_update(&ctx, credentials, size);
+ md5_update(&ctx, ":", 1);
+ md5_update(&ctx, request->nonce, strlen(request->nonce));
+ md5_update(&ctx, ":", 1);
+ md5_update(&ctx, request->cnonce, strlen(request->cnonce));
+ if (request->authzid != NULL) {
+ md5_update(&ctx, ":", 1);
+ md5_update(&ctx, request->authzid, strlen(request->authzid));
+ }
+ md5_final(&ctx, digest);
+ a1_hex = binary_to_hex(digest, 16);
+
+ /* do it twice, first verify the user's response, the second is
+ sent for client as a reply */
+ for (i = 0; i < 2; i++) {
+ /* A2 */
+ md5_init(&ctx);
+ if (i == 0)
+ md5_update(&ctx, "AUTHENTICATE:", 13);
+ else
+ md5_update(&ctx, ":", 1);
+
+ if (request->digest_uri != NULL) {
+ md5_update(&ctx, request->digest_uri,
+ strlen(request->digest_uri));
+ }
+ if (request->qop == QOP_AUTH_INT ||
+ request->qop == QOP_AUTH_CONF) {
+ md5_update(&ctx, ":00000000000000000000000000000000",
+ 33);
+ }
+ md5_final(&ctx, digest);
+ a2_hex = binary_to_hex(digest, 16);
+
+ /* response */
+ md5_init(&ctx);
+ md5_update(&ctx, a1_hex, 32);
+ md5_update(&ctx, ":", 1);
+ md5_update(&ctx, request->nonce, strlen(request->nonce));
+ md5_update(&ctx, ":", 1);
+ md5_update(&ctx, request->nonce_count,
+ strlen(request->nonce_count));
+ md5_update(&ctx, ":", 1);
+ md5_update(&ctx, request->cnonce, strlen(request->cnonce));
+ md5_update(&ctx, ":", 1);
+ md5_update(&ctx, request->qop_value,
+ strlen(request->qop_value));
+ md5_update(&ctx, ":", 1);
+ md5_update(&ctx, a2_hex, 32);
+ md5_final(&ctx, digest);
+ response_hex = binary_to_hex(digest, 16);
+
+ if (i == 0) {
+ /* verify response */
+ if (!mem_equals_timing_safe(response_hex, request->response, 32)) {
+ auth_request_log_info(&request->auth_request,
+ AUTH_SUBSYS_MECH,
+ AUTH_LOG_MSG_PASSWORD_MISMATCH);
+ return FALSE;
+ }
+ } else {
+ request->rspauth =
+ p_strconcat(request->pool, "rspauth=",
+ response_hex, NULL);
+ }
+ }
+
+ return TRUE;
+}
+
+static bool parse_next(char **data, char **key, char **value)
+{
+ /* @UNSAFE */
+ char *p, *dest;
+
+ p = *data;
+ while (IS_LWS(*p)) p++;
+
+ /* get key */
+ *key = p;
+ while (*p != '\0' && *p != '=' && *p != ',')
+ p++;
+
+ if (*p != '=') {
+ *data = p;
+ return FALSE;
+ }
+
+ *value = p+1;
+
+ /* skip trailing whitespace in key */
+ while (p > *data && IS_LWS(p[-1]))
+ p--;
+ *p = '\0';
+
+ /* get value */
+ p = *value;
+ while (IS_LWS(*p)) p++;
+
+ if (*p != '"') {
+ while (*p != '\0' && *p != ',')
+ p++;
+
+ *data = p;
+ /* If there is more to parse, ensure it won't get skipped
+ because *p is set to NUL below */
+ if (**data != '\0') (*data)++;
+ while (IS_LWS(p[-1]))
+ p--;
+ *p = '\0';
+ } else {
+ /* quoted string */
+ *value = dest = ++p;
+ while (*p != '\0' && *p != '"') {
+ if (*p == '\\' && p[1] != '\0')
+ p++;
+ *dest++ = *p++;
+ }
+
+ *data = *p == '"' ? p+1 : p;
+ *dest = '\0';
+ }
+
+ return TRUE;
+}
+
+static bool auth_handle_response(struct digest_auth_request *request,
+ char *key, char *value, const char **error)
+{
+ unsigned int i;
+
+ (void)str_lcase(key);
+
+ if (strcmp(key, "realm") == 0) {
+ if (request->auth_request.fields.realm == NULL && *value != '\0')
+ auth_request_set_realm(&request->auth_request, value);
+ return TRUE;
+ }
+
+ if (strcmp(key, "username") == 0) {
+ if (request->username != NULL) {
+ *error = "username must not exist more than once";
+ return FALSE;
+ }
+
+ if (*value == '\0') {
+ *error = "empty username";
+ return FALSE;
+ }
+
+ request->username = p_strdup(request->pool, value);
+ return TRUE;
+ }
+
+ if (strcmp(key, "nonce") == 0) {
+ /* nonce must be same */
+ if (strcmp(value, request->nonce) != 0) {
+ *error = "Invalid nonce";
+ return FALSE;
+ }
+
+ request->nonce_found = TRUE;
+ return TRUE;
+ }
+
+ if (strcmp(key, "cnonce") == 0) {
+ if (request->cnonce != NULL) {
+ *error = "cnonce must not exist more than once";
+ return FALSE;
+ }
+
+ if (*value == '\0') {
+ *error = "cnonce can't contain empty value";
+ return FALSE;
+ }
+
+ request->cnonce = p_strdup(request->pool, value);
+ return TRUE;
+ }
+
+ if (strcmp(key, "nc") == 0) {
+ unsigned int nc;
+
+ if (request->nonce_count != NULL) {
+ *error = "nonce-count must not exist more than once";
+ return FALSE;
+ }
+
+ if (str_to_uint(value, &nc) < 0) {
+ *error = "nonce-count value invalid";
+ return FALSE;
+ }
+
+ if (nc != 1) {
+ *error = "re-auth not supported currently";
+ return FALSE;
+ }
+
+ request->nonce_count = p_strdup(request->pool, value);
+ return TRUE;
+ }
+
+ if (strcmp(key, "qop") == 0) {
+ for (i = 0; i < QOP_COUNT; i++) {
+ if (strcasecmp(qop_names[i], value) == 0)
+ break;
+ }
+
+ if (i == QOP_COUNT) {
+ *error = t_strdup_printf("Unknown QoP value: %s",
+ str_sanitize(value, 32));
+ return FALSE;
+ }
+
+ request->qop &= (1 << i);
+ if (request->qop == 0) {
+ *error = "Nonallowed QoP requested";
+ return FALSE;
+ }
+
+ request->qop_value = p_strdup(request->pool, value);
+ return TRUE;
+ }
+
+ if (strcmp(key, "digest-uri") == 0) {
+ /* type / host / serv-name */
+ const char *const *uri = t_strsplit(value, "/");
+
+ if (uri[0] == NULL || uri[1] == NULL) {
+ *error = "Invalid digest-uri";
+ return FALSE;
+ }
+
+ /* FIXME: RFC recommends that we verify the host/serv-type.
+ But isn't the realm enough already? That'd be just extra
+ configuration.. Maybe optionally list valid hosts in
+ config file? */
+ request->digest_uri = p_strdup(request->pool, value);
+ return TRUE;
+ }
+
+ if (strcmp(key, "maxbuf") == 0) {
+ if (request->maxbuf != 0) {
+ *error = "maxbuf must not exist more than once";
+ return FALSE;
+ }
+
+ if (str_to_ulong(value, &request->maxbuf) < 0 ||
+ request->maxbuf == 0) {
+ *error = "Invalid maxbuf value";
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ if (strcmp(key, "charset") == 0) {
+ if (strcasecmp(value, "utf-8") != 0) {
+ *error = "Only utf-8 charset is allowed";
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ if (strcmp(key, "response") == 0) {
+ if (strlen(value) != 32) {
+ *error = "Invalid response value";
+ return FALSE;
+ }
+
+ memcpy(request->response, value, 32);
+ return TRUE;
+ }
+
+ if (strcmp(key, "cipher") == 0) {
+ /* not supported, ignore */
+ return TRUE;
+ }
+
+ if (strcmp(key, "authzid") == 0) {
+ if (request->authzid != NULL) {
+ *error = "authzid must not exist more than once";
+ return FALSE;
+ }
+
+ if (*value == '\0') {
+ *error = "empty authzid";
+ return FALSE;
+ }
+
+ request->authzid = p_strdup(request->pool, value);
+ return TRUE;
+ }
+
+ /* unknown key, ignore */
+ return TRUE;
+}
+
+static bool parse_digest_response(struct digest_auth_request *request,
+ const unsigned char *data, size_t size,
+ const char **error)
+{
+ char *copy, *key, *value;
+ bool failed;
+
+ /*
+ realm="realm"
+ username="username"
+ nonce="randomized data"
+ cnonce="??"
+ nc=00000001
+ qop="auth|auth-int|auth-conf"
+ digest-uri="serv-type/host[/serv-name]"
+ response=32 HEX digits
+ maxbuf=number (with auth-int, auth-conf, defaults to 64k)
+ charset="utf-8" (iso-8859-1 if it doesn't exist)
+ cipher="cipher-value"
+ authzid="authzid-value"
+ */
+
+ *error = NULL;
+ failed = FALSE;
+
+ if (size == 0) {
+ *error = "Client sent no input";
+ return FALSE;
+ }
+
+ /* treating response as NUL-terminated string also gets rid of all
+ potential problems with NUL characters in strings. */
+ copy = t_strdup_noconst(t_strndup(data, size));
+ while (*copy != '\0') {
+ if (parse_next(&copy, &key, &value)) {
+ if (!auth_handle_response(request, key, value, error)) {
+ failed = TRUE;
+ break;
+ }
+ }
+
+ if (*copy == ',')
+ copy++;
+ }
+
+ if (!failed) {
+ if (!request->nonce_found) {
+ *error = "Missing nonce parameter";
+ failed = TRUE;
+ } else if (request->cnonce == NULL) {
+ *error = "Missing cnonce parameter";
+ failed = TRUE;
+ } else if (request->username == NULL) {
+ *error = "Missing username parameter";
+ failed = TRUE;
+ }
+ }
+
+ if (request->nonce_count == NULL)
+ request->nonce_count = p_strdup(request->pool, "00000001");
+ if (request->qop_value == NULL)
+ request->qop_value = p_strdup(request->pool, "auth");
+
+ return !failed;
+}
+
+static void credentials_callback(enum passdb_result result,
+ const unsigned char *credentials, size_t size,
+ struct auth_request *auth_request)
+{
+ struct digest_auth_request *request =
+ (struct digest_auth_request *)auth_request;
+
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ if (!verify_credentials(request, credentials, size)) {
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ auth_request_success(auth_request, request->rspauth,
+ strlen(request->rspauth));
+ break;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ auth_request_internal_failure(auth_request);
+ break;
+ default:
+ auth_request_fail(auth_request);
+ break;
+ }
+}
+
+static void
+mech_digest_md5_auth_continue(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ struct digest_auth_request *request =
+ (struct digest_auth_request *)auth_request;
+ const char *username, *error;
+
+ if (parse_digest_response(request, data, data_size, &error)) {
+ if (auth_request->fields.realm != NULL &&
+ strchr(request->username, '@') == NULL) {
+ username = t_strconcat(request->username, "@",
+ auth_request->fields.realm, NULL);
+ auth_request->domain_is_realm = TRUE;
+ } else {
+ username = request->username;
+ }
+
+ if (auth_request_set_username(auth_request, username, &error) &&
+ (request->authzid == NULL ||
+ auth_request_set_login_username(auth_request,
+ request->authzid,
+ &error))) {
+ auth_request_lookup_credentials(auth_request,
+ "DIGEST-MD5", credentials_callback);
+ return;
+ }
+ }
+
+ if (error != NULL)
+ e_info(auth_request->mech_event, "%s", error);
+
+ auth_request_fail(auth_request);
+}
+
+static void
+mech_digest_md5_auth_initial(struct auth_request *auth_request,
+ const unsigned char *data ATTR_UNUSED,
+ size_t data_size ATTR_UNUSED)
+{
+ struct digest_auth_request *request =
+ (struct digest_auth_request *)auth_request;
+ string_t *challenge;
+
+ /* FIXME: there's no support for subsequent authentication */
+
+ challenge = get_digest_challenge(request);
+ auth_request_handler_reply_continue(auth_request, str_data(challenge),
+ str_len(challenge));
+}
+
+static struct auth_request *mech_digest_md5_auth_new(void)
+{
+ struct digest_auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"digest_md5_auth_request", 2048);
+ request = p_new(pool, struct digest_auth_request, 1);
+ request->pool = pool;
+ request->qop = QOP_AUTH;
+
+ request->auth_request.pool = pool;
+ return &request->auth_request;
+}
+
+const struct mech_module mech_digest_md5 = {
+ "DIGEST-MD5",
+
+ .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE |
+ MECH_SEC_MUTUAL_AUTH,
+ .passdb_need = MECH_PASSDB_NEED_LOOKUP_CREDENTIALS,
+
+ mech_digest_md5_auth_new,
+ mech_digest_md5_auth_initial,
+ mech_digest_md5_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-dovecot-token.c b/src/auth/mech-dovecot-token.c
new file mode 100644
index 0000000..408ef6e
--- /dev/null
+++ b/src/auth/mech-dovecot-token.c
@@ -0,0 +1,92 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+/* Used internally by Dovecot processes to authenticate against each others
+ (e.g. imap to imap-urlauth). See auth-token.c */
+
+#include "auth-common.h"
+#include "mech.h"
+#include "safe-memset.h"
+#include "auth-token.h"
+
+static void
+mech_dovecot_token_auth_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ const char *session_id, *username, *pid, *service, *error;
+ char *auth_token;
+ size_t i, len;
+ int count;
+
+ /* service \0 pid \0 username \0 session_id \0 auth_token */
+ service = (const char *) data;
+ session_id = username = pid = auth_token = NULL;
+ count = 0;
+ for (i = 0; i < data_size; i++) {
+ if (data[i] == '\0') {
+ count++; i++;
+ if (count == 1)
+ pid = (const char *)data + i;
+ else if (count == 2)
+ username = (const char *)data + i;
+ else if (count == 3)
+ session_id = (const char *)data + i;
+ else if (count == 4) {
+ len = data_size - i;
+ auth_token = p_strndup(unsafe_data_stack_pool,
+ data+i, len);
+ }
+ else
+ break;
+ }
+ }
+
+ if (count != 4) {
+ /* invalid input */
+ e_info(request->mech_event, "invalid input");
+ auth_request_fail(request);
+ } else if (!auth_request_set_username(request, username, &error)) {
+ /* invalid username */
+ e_info(request->mech_event, "%s", error);
+ auth_request_fail(request);
+ } else {
+ const char *valid_token =
+ auth_token_get(service, pid, request->fields.user,
+ session_id);
+
+ if (auth_token != NULL &&
+ str_equals_timing_almost_safe(auth_token, valid_token)) {
+ request->passdb_success = TRUE;
+ auth_request_set_field(request, "userdb_client_service", service, "");
+ auth_request_success(request, NULL, 0);
+ } else {
+ auth_request_fail(request);
+ }
+ }
+
+ /* make sure it's cleared */
+ if (auth_token != NULL)
+ safe_memset(auth_token, 0, strlen(auth_token));
+}
+
+static struct auth_request *mech_dovecot_token_auth_new(void)
+{
+ struct auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"dovecot_token_auth_request", 512);
+ request = p_new(pool, struct auth_request, 1);
+ request->pool = pool;
+ return request;
+}
+
+const struct mech_module mech_dovecot_token = {
+ "DOVECOT-TOKEN",
+
+ .flags = MECH_SEC_PRIVATE | MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_NOTHING,
+
+ mech_dovecot_token_auth_new,
+ mech_generic_auth_initial,
+ mech_dovecot_token_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-external.c b/src/auth/mech-external.c
new file mode 100644
index 0000000..39809b4
--- /dev/null
+++ b/src/auth/mech-external.c
@@ -0,0 +1,64 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+#include "mech.h"
+#include "mech-plain-common.h"
+
+static void
+mech_external_auth_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ const char *authzid, *error;
+
+ authzid = t_strndup(data, data_size);
+ if (request->fields.user == NULL) {
+ e_info(request->mech_event,
+ "username not known");
+ auth_request_fail(request);
+ return;
+ }
+
+ /* this call is done simply to put the username through translation
+ settings */
+ if (!auth_request_set_username(request, "", &error)) {
+ e_info(request->mech_event,
+ "Invalid username");
+ auth_request_fail(request);
+ return;
+ }
+
+ if (*authzid != '\0' &&
+ !auth_request_set_login_username(request, authzid, &error)) {
+ /* invalid login username */
+ e_info(request->mech_event,
+ "login user: %s", error);
+ auth_request_fail(request);
+ } else {
+ auth_request_verify_plain(request, "",
+ plain_verify_callback);
+ }
+}
+
+static struct auth_request *mech_external_auth_new(void)
+{
+ struct auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"external_auth_request", 2048);
+ request = p_new(pool, struct auth_request, 1);
+ request->pool = pool;
+ return request;
+}
+
+const struct mech_module mech_external = {
+ "EXTERNAL",
+
+ .flags = 0,
+ .passdb_need = MECH_PASSDB_NEED_VERIFY_PLAIN,
+
+ mech_external_auth_new,
+ mech_generic_auth_initial,
+ mech_external_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-gssapi.c b/src/auth/mech-gssapi.c
new file mode 100644
index 0000000..5ed8edb
--- /dev/null
+++ b/src/auth/mech-gssapi.c
@@ -0,0 +1,797 @@
+/*
+ * GSSAPI Module
+ *
+ * Copyright (c) 2005 Jelmer Vernooij <jelmer@samba.org>
+ *
+ * Related standards:
+ * - draft-ietf-sasl-gssapi-03
+ * - RFC2222
+ *
+ * Some parts inspired by an older patch from Colin Walters
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "auth-common.h"
+#include "env-util.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "hex-binary.h"
+#include "safe-memset.h"
+#include "mech.h"
+#include "passdb.h"
+
+
+#if defined(BUILTIN_GSSAPI) || defined(PLUGIN_BUILD)
+
+#ifndef HAVE___GSS_USEROK
+# define USE_KRB5_USEROK
+# include <krb5.h>
+#endif
+
+#ifdef HAVE_GSSAPI_GSSAPI_H
+# include <gssapi/gssapi.h>
+#elif defined (HAVE_GSSAPI_H)
+# include <gssapi.h>
+#endif
+
+#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H
+# include <gssapi/gssapi_krb5.h>
+#elif defined (HAVE_GSSAPI_KRB5_H)
+# include <gssapi_krb5.h>
+#else
+# undef USE_KRB5_USEROK
+#endif
+
+#ifdef HAVE_GSSAPI_GSSAPI_EXT_H
+# include <gssapi/gssapi_ext.h>
+#endif
+
+#define krb5_boolean2bool(X) ((X) != 0)
+
+/* Non-zero flags defined in RFC 2222 */
+enum sasl_gssapi_qop {
+ SASL_GSSAPI_QOP_UNSPECIFIED = 0x00,
+ SASL_GSSAPI_QOP_AUTH_ONLY = 0x01,
+ SASL_GSSAPI_QOP_AUTH_INT = 0x02,
+ SASL_GSSAPI_QOP_AUTH_CONF = 0x04
+};
+
+struct gssapi_auth_request {
+ struct auth_request auth_request;
+ gss_ctx_id_t gss_ctx;
+ gss_cred_id_t service_cred;
+
+ enum {
+ GSS_STATE_SEC_CONTEXT,
+ GSS_STATE_WRAP,
+ GSS_STATE_UNWRAP
+ } sasl_gssapi_state;
+
+ gss_name_t authn_name;
+ gss_name_t authz_name;
+
+ pool_t pool;
+};
+
+static bool gssapi_initialized = FALSE;
+
+static gss_OID_desc mech_gssapi_krb5_oid =
+ { 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+
+static int
+mech_gssapi_wrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf);
+
+static void mech_gssapi_log_error(struct auth_request *request,
+ OM_uint32 status_value, int status_type,
+ const char *description)
+{
+ OM_uint32 message_context = 0;
+ OM_uint32 minor_status;
+ gss_buffer_desc status_string;
+
+ do {
+ (void)gss_display_status(&minor_status, status_value,
+ status_type, GSS_C_NO_OID,
+ &message_context, &status_string);
+
+ e_info(request->mech_event,
+ "While %s: %s", description,
+ str_sanitize(status_string.value, SIZE_MAX));
+
+ (void)gss_release_buffer(&minor_status, &status_string);
+ } while (message_context != 0);
+}
+
+static void mech_gssapi_initialize(const struct auth_settings *set)
+{
+ const char *path = set->krb5_keytab;
+
+ if (*path != '\0') {
+ /* environment may be used by Kerberos 5 library directly */
+ env_put("KRB5_KTNAME", path);
+#ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY
+ gsskrb5_register_acceptor_identity(path);
+#elif defined (HAVE_KRB5_GSS_REGISTER_ACCEPTOR_IDENTITY)
+ krb5_gss_register_acceptor_identity(path);
+#endif
+ }
+}
+
+static struct auth_request *mech_gssapi_auth_new(void)
+{
+ struct gssapi_auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"gssapi_auth_request", 2048);
+ request = p_new(pool, struct gssapi_auth_request, 1);
+ request->pool = pool;
+
+ request->gss_ctx = GSS_C_NO_CONTEXT;
+
+ request->auth_request.pool = pool;
+ return &request->auth_request;
+}
+
+static OM_uint32
+obtain_service_credentials(struct auth_request *request, gss_cred_id_t *ret_r)
+{
+ OM_uint32 major_status, minor_status;
+ string_t *principal_name;
+ gss_buffer_desc inbuf;
+ gss_name_t gss_principal;
+ const char *service_name;
+
+ if (!gssapi_initialized) {
+ gssapi_initialized = TRUE;
+ mech_gssapi_initialize(request->set);
+ }
+
+ if (strcmp(request->set->gssapi_hostname, "$ALL") == 0) {
+ e_debug(request->mech_event,
+ "Using all keytab entries");
+ *ret_r = GSS_C_NO_CREDENTIAL;
+ return GSS_S_COMPLETE;
+ }
+
+ if (strcasecmp(request->fields.service, "POP3") == 0) {
+ /* The standard POP3 service name with GSSAPI is called
+ just "pop". */
+ service_name = "pop";
+ } else {
+ service_name = t_str_lcase(request->fields.service);
+ }
+
+ principal_name = t_str_new(128);
+ str_append(principal_name, service_name);
+ str_append_c(principal_name, '@');
+ str_append(principal_name, request->set->gssapi_hostname);
+
+ e_debug(request->mech_event,
+ "Obtaining credentials for %s", str_c(principal_name));
+
+ inbuf.length = str_len(principal_name);
+ inbuf.value = str_c_modifiable(principal_name);
+
+ major_status = gss_import_name(&minor_status, &inbuf,
+ GSS_C_NT_HOSTBASED_SERVICE,
+ &gss_principal);
+ str_free(&principal_name);
+
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+ "importing principal name");
+ return major_status;
+ }
+
+ major_status = gss_acquire_cred(&minor_status, gss_principal, 0,
+ GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
+ ret_r, NULL, NULL);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+ "acquiring service credentials");
+ mech_gssapi_log_error(request, minor_status, GSS_C_MECH_CODE,
+ "acquiring service credentials");
+ return major_status;
+ }
+
+ gss_release_name(&minor_status, &gss_principal);
+ return major_status;
+}
+
+static gss_name_t
+import_name(struct auth_request *request, void *str, size_t len)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc name_buf;
+ gss_name_t name;
+
+ name_buf.value = str;
+ name_buf.length = len;
+ major_status = gss_import_name(&minor_status, &name_buf,
+ GSS_C_NO_OID, &name);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+ "gss_import_name");
+ return GSS_C_NO_NAME;
+ }
+ return name;
+}
+
+static gss_name_t
+duplicate_name(struct auth_request *request, gss_name_t old)
+{
+ OM_uint32 major_status, minor_status;
+ gss_name_t new;
+
+ major_status = gss_duplicate_name(&minor_status, old, &new);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE,
+ "gss_duplicate_name");
+ return GSS_C_NO_NAME;
+ }
+ return new;
+}
+
+static bool data_has_nuls(const void *data, size_t len)
+{
+ const unsigned char *c = data;
+ size_t i;
+
+ /* apparently all names end with NUL? */
+ if (len > 0 && c[len-1] == '\0')
+ len--;
+
+ for (i = 0; i < len; i++) {
+ if (c[i] == '\0')
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int get_display_name(struct auth_request *auth_request, gss_name_t name,
+ gss_OID *name_type_r, const char **display_name_r)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc buf;
+
+ major_status = gss_display_name(&minor_status, name,
+ &buf, name_type_r);
+ if (major_status != GSS_S_COMPLETE) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE, "gss_display_name");
+ return -1;
+ }
+ if (data_has_nuls(buf.value, buf.length)) {
+ e_info(auth_request->mech_event,
+ "authn_name has NULs");
+ return -1;
+ }
+ *display_name_r = t_strndup(buf.value, buf.length);
+ (void)gss_release_buffer(&minor_status, &buf);
+ return 0;
+}
+
+static bool mech_gssapi_oid_cmp(const gss_OID_desc *oid1,
+ const gss_OID_desc *oid2)
+{
+ return oid1->length == oid2->length &&
+ mem_equals_timing_safe(oid1->elements, oid2->elements, oid1->length);
+}
+
+static int
+mech_gssapi_sec_context(struct gssapi_auth_request *request,
+ gss_buffer_desc inbuf)
+{
+ struct auth_request *auth_request = &request->auth_request;
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc output_token;
+ gss_OID name_type;
+ gss_OID mech_type;
+ const char *username, *error;
+ int ret = 0;
+
+ major_status = gss_accept_sec_context (
+ &minor_status,
+ &request->gss_ctx,
+ request->service_cred,
+ &inbuf,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &request->authn_name,
+ &mech_type,
+ &output_token,
+ NULL, /* ret_flags */
+ NULL, /* time_rec */
+ NULL /* delegated_cred_handle */
+ );
+
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE,
+ "processing incoming data");
+ mech_gssapi_log_error(auth_request, minor_status,
+ GSS_C_MECH_CODE,
+ "processing incoming data");
+ return -1;
+ }
+
+ switch (major_status) {
+ case GSS_S_COMPLETE:
+ if (!mech_gssapi_oid_cmp(mech_type, &mech_gssapi_krb5_oid)) {
+ e_info(auth_request->mech_event,
+ "GSSAPI mechanism not Kerberos5");
+ ret = -1;
+ } else if (get_display_name(auth_request, request->authn_name,
+ &name_type, &username) < 0)
+ ret = -1;
+ else if (!auth_request_set_username(auth_request, username,
+ &error)) {
+ e_info(auth_request->mech_event,
+ "authn_name: %s", error);
+ ret = -1;
+ } else {
+ request->sasl_gssapi_state = GSS_STATE_WRAP;
+ e_debug(auth_request->mech_event,
+ "security context state completed.");
+ }
+ break;
+ case GSS_S_CONTINUE_NEEDED:
+ e_debug(auth_request->mech_event,
+ "Processed incoming packet correctly, "
+ "waiting for another.");
+ break;
+ default:
+ e_error(auth_request->mech_event,
+ "Received unexpected major status %d", major_status);
+ break;
+ }
+
+ if (ret == 0) {
+ if (output_token.length > 0) {
+ auth_request_handler_reply_continue(auth_request,
+ output_token.value,
+ output_token.length);
+ } else {
+ /* If there is no output token, go straight to wrap,
+ which is expecting an empty input token. */
+ ret = mech_gssapi_wrap(request, output_token);
+ }
+ }
+ (void)gss_release_buffer(&minor_status, &output_token);
+ return ret;
+}
+
+static int
+mech_gssapi_wrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc outbuf;
+ unsigned char ret[4];
+
+ /* The client's return data should be empty here */
+
+ /* Only authentication, no integrity or confidentiality
+ protection (yet?) */
+ ret[0] = (SASL_GSSAPI_QOP_UNSPECIFIED |
+ SASL_GSSAPI_QOP_AUTH_ONLY);
+ ret[1] = 0xFF;
+ ret[2] = 0xFF;
+ ret[3] = 0xFF;
+
+ inbuf.length = 4;
+ inbuf.value = ret;
+
+ major_status = gss_wrap(&minor_status, request->gss_ctx, 0,
+ GSS_C_QOP_DEFAULT, &inbuf, NULL, &outbuf);
+
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(&request->auth_request, major_status,
+ GSS_C_GSS_CODE, "sending security layer negotiation");
+ mech_gssapi_log_error(&request->auth_request, minor_status,
+ GSS_C_MECH_CODE, "sending security layer negotiation");
+ return -1;
+ }
+
+ e_debug(request->auth_request.mech_event,
+ "Negotiated security layer");
+
+ auth_request_handler_reply_continue(&request->auth_request,
+ outbuf.value, outbuf.length);
+
+ (void)gss_release_buffer(&minor_status, &outbuf);
+ request->sasl_gssapi_state = GSS_STATE_UNWRAP;
+ return 0;
+}
+
+#ifdef USE_KRB5_USEROK
+static bool
+k5_principal_is_authorized(struct auth_request *request, const char *name)
+{
+ const char *value, *const *authorized_names, *const *tmp;
+
+ value = auth_fields_find(request->fields.extra_fields, "k5principals");
+ if (value == NULL)
+ return FALSE;
+
+ authorized_names = t_strsplit_spaces(value, ",");
+ for (tmp = authorized_names; *tmp != NULL; tmp++) {
+ if (strcmp(*tmp, name) == 0) {
+ e_debug(request->mech_event,
+ "authorized by k5principals field: %s", name);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool
+mech_gssapi_krb5_userok(struct gssapi_auth_request *request,
+ gss_name_t name, const char *login_user,
+ bool check_name_type)
+{
+ krb5_context ctx;
+ krb5_principal princ;
+ krb5_error_code krb5_err;
+ gss_OID name_type;
+ const char *princ_display_name;
+ bool authorized = FALSE;
+
+ /* Parse out the principal's username */
+ if (get_display_name(&request->auth_request, name, &name_type,
+ &princ_display_name) < 0)
+ return FALSE;
+
+ if (!mech_gssapi_oid_cmp(name_type, GSS_KRB5_NT_PRINCIPAL_NAME) &&
+ check_name_type) {
+ e_info(request->auth_request.mech_event,
+ "OID not kerberos principal name");
+ return FALSE;
+ }
+
+ /* Init a krb5 context and parse the principal username */
+ krb5_err = krb5_init_context(&ctx);
+ if (krb5_err != 0) {
+ e_error(request->auth_request.mech_event,
+ "krb5_init_context() failed: %d", (int)krb5_err);
+ return FALSE;
+ }
+ krb5_err = krb5_parse_name(ctx, princ_display_name, &princ);
+ if (krb5_err != 0) {
+ /* writing the error string would be better, but we probably
+ rarely get here and there doesn't seem to be a standard
+ way of getting it */
+ e_info(request->auth_request.mech_event,
+ "krb5_parse_name() failed: %d",
+ (int)krb5_err);
+ } else {
+ /* See if the principal is in the list of authorized
+ * principals for the user */
+ authorized = k5_principal_is_authorized(&request->auth_request,
+ princ_display_name);
+
+ /* See if the principal is authorized to act as the
+ specified (UNIX) user */
+ if (!authorized) {
+ authorized = krb5_boolean2bool(krb5_kuserok(ctx, princ, login_user));
+ }
+
+ krb5_free_principal(ctx, princ);
+ }
+ krb5_free_context(ctx);
+ return authorized;
+}
+#endif
+
+static int
+mech_gssapi_userok(struct gssapi_auth_request *request, const char *login_user)
+{
+ struct auth_request *auth_request = &request->auth_request;
+ OM_uint32 major_status, minor_status;
+ int equal_authn_authz;
+#ifdef HAVE___GSS_USEROK
+ int login_ok;
+#endif
+
+ /* if authn and authz names equal, don't bother checking further. */
+ major_status = gss_compare_name(&minor_status,
+ request->authn_name,
+ request->authz_name,
+ &equal_authn_authz);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE,
+ "gss_compare_name failed");
+ return -1;
+ }
+
+ if (equal_authn_authz != 0)
+ return 0;
+
+ /* handle cross-realm authentication */
+#ifdef HAVE___GSS_USEROK
+ /* Solaris */
+ major_status = __gss_userok(&minor_status, request->authn_name,
+ login_user, &login_ok);
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE, "__gss_userok failed");
+ return -1;
+ }
+
+ if (login_ok == 0) {
+ e_info(auth_request->mech_event,
+ "User not authorized to log in as %s", login_user);
+ return -1;
+ }
+ return 0;
+#elif defined(USE_KRB5_USEROK)
+ if (!mech_gssapi_krb5_userok(request, request->authn_name,
+ login_user, TRUE)) {
+ e_info(auth_request->mech_event,
+ "User not authorized to log in as %s", login_user);
+ return -1;
+ }
+
+ return 0;
+#else
+ e_info(auth_request->mech_event,
+ "Cross-realm authentication not supported "
+ "(authn_name=%s, authz_name=%s)",
+ request->auth_request.fields.original_username, login_user);
+ return -1;
+#endif
+}
+
+static void
+gssapi_credentials_callback(enum passdb_result result,
+ const unsigned char *credentials ATTR_UNUSED,
+ size_t size ATTR_UNUSED,
+ struct auth_request *request)
+{
+ struct gssapi_auth_request *gssapi_request =
+ (struct gssapi_auth_request *)request;
+
+ /* We don't care much whether the lookup succeeded or not because GSSAPI
+ * does not strictly require a passdb. But if a passdb is configured,
+ * now the k5principals field will have been filled in. */
+ switch (result) {
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ auth_request_internal_failure(request);
+ return;
+ case PASSDB_RESULT_USER_DISABLED:
+ case PASSDB_RESULT_PASS_EXPIRED:
+ /* user is explicitly disabled, don't allow it to log in */
+ auth_request_fail(request);
+ return;
+ case PASSDB_RESULT_NEXT:
+ case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+ case PASSDB_RESULT_USER_UNKNOWN:
+ case PASSDB_RESULT_PASSWORD_MISMATCH:
+ case PASSDB_RESULT_OK:
+ break;
+ }
+
+ if (mech_gssapi_userok(gssapi_request, request->fields.user) == 0)
+ auth_request_success(request, NULL, 0);
+ else
+ auth_request_fail(request);
+}
+
+static int
+mech_gssapi_unwrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf)
+{
+ struct auth_request *auth_request = &request->auth_request;
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc outbuf;
+ const char *login_user, *error;
+ unsigned char *name;
+ size_t name_len;
+
+ major_status = gss_unwrap(&minor_status, request->gss_ctx,
+ &inbuf, &outbuf, NULL, NULL);
+
+ if (GSS_ERROR(major_status) != 0) {
+ mech_gssapi_log_error(auth_request, major_status,
+ GSS_C_GSS_CODE,
+ "final negotiation: gss_unwrap");
+ return -1;
+ }
+
+ /* outbuf[0] contains bitmask for selected security layer,
+ outbuf[1..3] contains maximum output_message size */
+ if (outbuf.length < 4) {
+ e_error(auth_request->mech_event,
+ "Invalid response length");
+ (void)gss_release_buffer(&minor_status, &outbuf);
+ return -1;
+ }
+
+ if (outbuf.length > 4) {
+ name = (unsigned char *)outbuf.value + 4;
+ name_len = outbuf.length - 4;
+
+ if (data_has_nuls(name, name_len)) {
+ e_info(auth_request->mech_event,
+ "authz_name has NULs");
+ (void)gss_release_buffer(&minor_status, &outbuf);
+ return -1;
+ }
+
+ login_user = p_strndup(auth_request->pool, name, name_len);
+ request->authz_name = import_name(auth_request, name, name_len);
+ } else {
+ request->authz_name = duplicate_name(auth_request,
+ request->authn_name);
+ if (get_display_name(auth_request, request->authz_name,
+ NULL, &login_user) < 0) {
+ (void)gss_release_buffer(&minor_status, &outbuf);
+ return -1;
+ }
+ }
+
+ if (request->authz_name == GSS_C_NO_NAME) {
+ e_info(auth_request->mech_event,
+ "no authz_name");
+ (void)gss_release_buffer(&minor_status, &outbuf);
+ return -1;
+ }
+
+ /* Set username early, so that the credential lookup is for the
+ * authorizing user. This means the username in subsequent log
+ * messages will be the authorization name, not the authentication
+ * name, which may mean that future log messages should be adjusted
+ * to log the right thing. */
+ if (!auth_request_set_username(auth_request, login_user, &error)) {
+ e_info(auth_request->mech_event,
+ "authz_name: %s", error);
+ (void)gss_release_buffer(&minor_status, &outbuf);
+ return -1;
+ }
+
+ /* Continue in callback once auth_request is populated with passdb
+ information. */
+ auth_request->passdb_success = TRUE; /* default to success */
+ auth_request_lookup_credentials(&request->auth_request, "",
+ gssapi_credentials_callback);
+ (void)gss_release_buffer(&minor_status, &outbuf);
+ return 0;
+}
+
+static void
+mech_gssapi_auth_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ struct gssapi_auth_request *gssapi_request =
+ (struct gssapi_auth_request *)request;
+ gss_buffer_desc inbuf;
+ int ret = -1;
+
+ inbuf.value = (void *)data;
+ inbuf.length = data_size;
+
+ switch (gssapi_request->sasl_gssapi_state) {
+ case GSS_STATE_SEC_CONTEXT:
+ ret = mech_gssapi_sec_context(gssapi_request, inbuf);
+ break;
+ case GSS_STATE_WRAP:
+ ret = mech_gssapi_wrap(gssapi_request, inbuf);
+ break;
+ case GSS_STATE_UNWRAP:
+ ret = mech_gssapi_unwrap(gssapi_request, inbuf);
+ break;
+ default:
+ i_unreached();
+ }
+ if (ret < 0)
+ auth_request_fail(request);
+}
+
+static void
+mech_gssapi_auth_initial(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ struct gssapi_auth_request *gssapi_request =
+ (struct gssapi_auth_request *)request;
+ OM_uint32 major_status;
+
+ major_status =
+ obtain_service_credentials(request,
+ &gssapi_request->service_cred);
+
+ if (GSS_ERROR(major_status) != 0) {
+ auth_request_internal_failure(request);
+ return;
+ }
+ gssapi_request->authn_name = GSS_C_NO_NAME;
+ gssapi_request->authz_name = GSS_C_NO_NAME;
+
+ gssapi_request->sasl_gssapi_state = GSS_STATE_SEC_CONTEXT;
+
+ if (data_size == 0) {
+ /* The client should go first */
+ auth_request_handler_reply_continue(request, NULL, 0);
+ } else {
+ mech_gssapi_auth_continue(request, data, data_size);
+ }
+}
+
+static void
+mech_gssapi_auth_free(struct auth_request *request)
+{
+ struct gssapi_auth_request *gssapi_request =
+ (struct gssapi_auth_request *)request;
+ OM_uint32 minor_status;
+
+ if (gssapi_request->gss_ctx != GSS_C_NO_CONTEXT) {
+ (void)gss_delete_sec_context(&minor_status,
+ &gssapi_request->gss_ctx,
+ GSS_C_NO_BUFFER);
+ }
+
+ (void)gss_release_cred(&minor_status, &gssapi_request->service_cred);
+ if (gssapi_request->authn_name != GSS_C_NO_NAME) {
+ (void)gss_release_name(&minor_status,
+ &gssapi_request->authn_name);
+ }
+ if (gssapi_request->authz_name != GSS_C_NO_NAME) {
+ (void)gss_release_name(&minor_status,
+ &gssapi_request->authz_name);
+ }
+ pool_unref(&request->pool);
+}
+
+const struct mech_module mech_gssapi = {
+ "GSSAPI",
+
+ .flags = MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_NOTHING,
+
+ mech_gssapi_auth_new,
+ mech_gssapi_auth_initial,
+ mech_gssapi_auth_continue,
+ mech_gssapi_auth_free
+};
+
+/* MTI Kerberos v1.5+ and Heimdal v0.7+ supports SPNEGO for Kerberos tickets
+ internally. Nothing else needs to be done here. Note however that this does
+ not support SPNEGO when the only available credential is NTLM.. */
+const struct mech_module mech_gssapi_spnego = {
+ "GSS-SPNEGO",
+
+ .flags = MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_NOTHING,
+
+ mech_gssapi_auth_new,
+ mech_gssapi_auth_initial,
+ mech_gssapi_auth_continue,
+ mech_gssapi_auth_free
+};
+
+#ifndef BUILTIN_GSSAPI
+void mech_gssapi_init(void);
+void mech_gssapi_deinit(void);
+
+void mech_gssapi_init(void)
+{
+ mech_register_module(&mech_gssapi);
+#ifdef HAVE_GSSAPI_SPNEGO
+ /* load if we already didn't load it using winbind */
+ if (mech_module_find(mech_gssapi_spnego.mech_name) == NULL)
+ mech_register_module(&mech_gssapi_spnego);
+#endif
+}
+
+void mech_gssapi_deinit(void)
+{
+#ifdef HAVE_GSSAPI_SPNEGO
+ const struct mech_module *mech;
+
+ mech = mech_module_find(mech_gssapi_spnego.mech_name);
+ if (mech != NULL && mech->auth_new == mech_gssapi_auth_new)
+ mech_unregister_module(&mech_gssapi_spnego);
+#endif
+ mech_unregister_module(&mech_gssapi);
+}
+#endif
+
+#endif
diff --git a/src/auth/mech-login.c b/src/auth/mech-login.c
new file mode 100644
index 0000000..6f27433
--- /dev/null
+++ b/src/auth/mech-login.c
@@ -0,0 +1,76 @@
+/*
+ * LOGIN authentication mechanism.
+ *
+ * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "auth-common.h"
+#include "mech.h"
+#include "passdb.h"
+#include "safe-memset.h"
+#include "mech-plain-common.h"
+
+
+static void
+mech_login_auth_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ static const char prompt2[] = "Password:";
+ const char *username, *error;
+
+ if (request->fields.user == NULL) {
+ username = t_strndup(data, data_size);
+
+ if (!auth_request_set_username(request, username, &error)) {
+ e_info(request->mech_event, "%s", error);
+ auth_request_fail(request);
+ return;
+ }
+
+ auth_request_handler_reply_continue(request, prompt2,
+ strlen(prompt2));
+ } else {
+ char *pass = p_strndup(unsafe_data_stack_pool, data, data_size);
+ auth_request_verify_plain(request, pass, plain_verify_callback);
+ safe_memset(pass, 0, strlen(pass));
+ }
+}
+
+static void
+mech_login_auth_initial(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ static const char prompt1[] = "Username:";
+
+ if (data_size == 0) {
+ auth_request_handler_reply_continue(request, prompt1,
+ strlen(prompt1));
+ } else {
+ mech_login_auth_continue(request, data, data_size);
+ }
+}
+
+static struct auth_request *mech_login_auth_new(void)
+{
+ struct auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"login_auth_request", 2048);
+ request = p_new(pool, struct auth_request, 1);
+ request->pool = pool;
+ return request;
+}
+
+const struct mech_module mech_login = {
+ "LOGIN",
+
+ .flags = MECH_SEC_PLAINTEXT,
+ .passdb_need = MECH_PASSDB_NEED_VERIFY_PLAIN,
+
+ mech_login_auth_new,
+ mech_login_auth_initial,
+ mech_login_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-oauth2.c b/src/auth/mech-oauth2.c
new file mode 100644
index 0000000..dae5632
--- /dev/null
+++ b/src/auth/mech-oauth2.c
@@ -0,0 +1,339 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "mech.h"
+#include "passdb.h"
+#include "oauth2.h"
+#include "json-parser.h"
+#include <ctype.h>
+
+struct oauth2_auth_request {
+ struct auth_request auth;
+ bool failed;
+};
+
+static bool oauth2_find_oidc_url(struct auth_request *req, const char **url_r)
+{
+ struct auth_passdb *db = req->passdb;
+ if (req->openid_config_url != NULL) {
+ *url_r = req->openid_config_url;
+ return TRUE;
+ }
+
+ /* keep looking until you get a value */
+ for (; db != NULL; db = db->next) {
+ if (strcmp(db->passdb->iface.name, "oauth2") == 0) {
+ const char *url =
+ passdb_oauth2_get_oidc_url(req->passdb->passdb);
+ if (url == NULL || *url == '\0')
+ continue;
+ *url_r = url;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* RFC5801 based unescaping */
+static bool oauth2_unescape_username(const char *in, const char **username_r)
+{
+ string_t *out;
+ out = t_str_new(64);
+ for (; *in != '\0'; in++) {
+ if (in[0] == ',')
+ return FALSE;
+ if (in[0] == '=') {
+ if (in[1] == '2' && in[2] == 'C')
+ str_append_c(out, ',');
+ else if (in[1] == '3' && in[2] == 'D')
+ str_append_c(out, '=');
+ else
+ return FALSE;
+ in += 2;
+ } else {
+ str_append_c(out, *in);
+ }
+ }
+ *username_r = str_c(out);
+ return TRUE;
+}
+
+static void oauth2_verify_callback(enum passdb_result result,
+ const char *const *error_fields,
+ struct auth_request *request)
+{
+ const char *oidc_url;
+
+ i_assert(result == PASSDB_RESULT_OK || error_fields != NULL);
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ auth_request_success(request, "", 0);
+ break;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ request->internal_failure = TRUE;
+ /* fall through */
+ default:
+ /* we could get new token after this */
+ if (request->mech_password != NULL)
+ request->mech_password = NULL;
+ string_t *error = t_str_new(64);
+ str_append_c(error, '{');
+ for (unsigned int i = 0; error_fields[i] != NULL; i += 2) {
+ i_assert(error_fields[i+1] != NULL);
+ if (i > 0)
+ str_append_c(error, ',');
+ str_append_c(error, '"');
+ json_append_escaped(error, error_fields[i]);
+ str_append(error, "\":\"");
+ json_append_escaped(error, error_fields[i+1]);
+ str_append_c(error, '"');
+ }
+ /* FIXME: HORRIBLE HACK - REMOVE ME!!!
+ It is because the mech has not been implemented properly
+ that we need to pass the config url in this strange way.
+
+ This **must** be removed from here and db-oauth2 once the
+ validation result et al is handled here.
+ */
+ if (oauth2_find_oidc_url(request, &oidc_url)) {
+ if (str_len(error) > 0)
+ str_append_c(error, ',');
+ str_printfa(error, "\"openid-configuration\":\"");
+ json_append_escaped(error, oidc_url);
+ str_append_c(error, '"');
+ }
+ str_append_c(error, '}');
+ auth_request_fail_with_reply(request, str_data(error), str_len(error));
+ break;
+ }
+}
+
+static void mech_oauth2_verify_token(struct auth_request *request,
+ const char *token,
+ enum passdb_result result,
+ verify_plain_callback_t callback)
+{
+ i_assert(token != NULL);
+ if (result != PASSDB_RESULT_OK) {
+ request->passdb_result = result;
+ request->failed = TRUE;
+ }
+ auth_request_verify_plain(request, token, callback);
+}
+
+static void
+xoauth2_verify_callback(enum passdb_result result, struct auth_request *request)
+{
+ const char *const error_fields[] = {
+ "status", "401",
+ "schemes", "bearer",
+ "scope", "mail",
+ NULL
+ };
+ oauth2_verify_callback(result, error_fields, request);
+}
+
+static void
+oauthbearer_verify_callback(enum passdb_result result, struct auth_request *request)
+{
+ const char *error_fields[] = {
+ "status", "invalid_token",
+ NULL
+ };
+ oauth2_verify_callback(result, error_fields, request);
+}
+
+/* Input syntax:
+ user=Username^Aauth=Bearer token^A^A
+*/
+static void
+mech_xoauth2_auth_continue(struct auth_request *request,
+ const unsigned char *data,
+ size_t data_size)
+{
+ /* split the data from ^A */
+ bool user_given = FALSE;
+ const char *error;
+ const char *token = NULL;
+ const char *const *ptr;
+ const char *username;
+ const char *const *fields =
+ t_strsplit(t_strndup(data, data_size), "\x01");
+ for(ptr = fields; *ptr != NULL; ptr++) {
+ if (str_begins(*ptr, "user=")) {
+ /* xoauth2 does not require unescaping because the data
+ format does not contain anything to escape */
+ username = (*ptr)+5;
+ user_given = TRUE;
+ } else if (str_begins(*ptr, "auth=")) {
+ const char *value = (*ptr)+5;
+ if (strncasecmp(value, "bearer ", 7) == 0 &&
+ oauth2_valid_token(value+7)) {
+ token = value+7;
+ } else {
+ e_info(request->mech_event,
+ "Invalid continued data");
+ xoauth2_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ return;
+ }
+ }
+ /* do not fail on unexpected fields */
+ }
+
+ if (user_given && !auth_request_set_username(request, username, &error)) {
+ e_info(request->mech_event,
+ "%s", error);
+ xoauth2_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request);
+ return;
+ }
+
+ if (user_given && token != NULL)
+ mech_oauth2_verify_token(request, token, PASSDB_RESULT_OK,
+ xoauth2_verify_callback);
+ else {
+ e_info(request->mech_event, "Username or token missing");
+ xoauth2_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request);
+ }
+}
+
+/* Input syntax for data:
+ gs2flag,a=username,^Afield=...^Afield=...^Aauth=Bearer token^A^A
+*/
+static void
+mech_oauthbearer_auth_continue(struct auth_request *request,
+ const unsigned char *data,
+ size_t data_size)
+{
+ bool user_given = FALSE;
+ const char *error;
+ const char *username;
+ const char *const *ptr;
+ /* split the data from ^A */
+ const char **fields =
+ t_strsplit(t_strndup(data, data_size), "\x01");
+ const char *token = NULL;
+ /* ensure initial field is OK */
+ if (*fields == NULL || *(fields[0]) == '\0') {
+ e_info(request->mech_event,
+ "Invalid continued data");
+ oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ return;
+ }
+
+ /* the first field is specified by RFC5801 as gs2-header */
+ for(ptr = t_strsplit_spaces(fields[0], ","); *ptr != NULL; ptr++) {
+ switch(*ptr[0]) {
+ case 'f':
+ e_info(request->mech_event,
+ "Client requested non-standard mechanism");
+ oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ return;
+ case 'p':
+ /* channel binding is not supported */
+ e_info(request->mech_event,
+ "Client requested and used channel-binding");
+ oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ return;
+ case 'n':
+ case 'y':
+ /* we don't need to use channel-binding */
+ continue;
+ case 'a': /* authzid */
+ if ((*ptr)[1] != '=' ||
+ !oauth2_unescape_username((*ptr)+2, &username)) {
+ e_info(request->mech_event,
+ "Invalid username escaping");
+ oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ return;
+ } else {
+ user_given = TRUE;
+ }
+ break;
+ default:
+ e_info(request->mech_event,
+ "Invalid gs2-header in request");
+ oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ return;
+ }
+ }
+
+ for(ptr = fields; *ptr != NULL; ptr++) {
+ if (str_begins(*ptr, "auth=")) {
+ const char *value = (*ptr)+5;
+ if (strncasecmp(value, "bearer ", 7) == 0 &&
+ oauth2_valid_token(value+7)) {
+ token = value+7;
+ } else {
+ e_info(request->mech_event,
+ "Invalid continued data");
+ oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ return;
+ }
+ }
+ /* do not fail on unexpected fields */
+ }
+ if (user_given && !auth_request_set_username(request, username, &error)) {
+ e_info(request->mech_event,
+ "%s", error);
+ oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ return;
+ }
+ if (user_given && token != NULL)
+ mech_oauth2_verify_token(request, token, PASSDB_RESULT_OK,
+ oauthbearer_verify_callback);
+ else {
+ e_info(request->mech_event, "Missing username or token");
+ oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
+ request);
+ }
+}
+
+static struct auth_request *mech_oauth2_auth_new(void)
+{
+ struct oauth2_auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"oauth2_auth_request", 2048);
+ request = p_new(pool, struct oauth2_auth_request, 1);
+ request->auth.pool = pool;
+ return &request->auth;
+}
+
+const struct mech_module mech_oauthbearer = {
+ "OAUTHBEARER",
+
+ /* while this does not transfer plaintext password,
+ the token is still considered as password */
+ .flags = MECH_SEC_PLAINTEXT,
+ .passdb_need = 0,
+
+ mech_oauth2_auth_new,
+ mech_generic_auth_initial,
+ mech_oauthbearer_auth_continue,
+ mech_generic_auth_free
+};
+
+const struct mech_module mech_xoauth2 = {
+ "XOAUTH2",
+
+ .flags = MECH_SEC_PLAINTEXT,
+ .passdb_need = 0,
+
+ mech_oauth2_auth_new,
+ mech_generic_auth_initial,
+ mech_xoauth2_auth_continue,
+ mech_generic_auth_free
+};
+
+
diff --git a/src/auth/mech-otp-common.c b/src/auth/mech-otp-common.c
new file mode 100644
index 0000000..753fcbb
--- /dev/null
+++ b/src/auth/mech-otp-common.c
@@ -0,0 +1,71 @@
+/*
+ * Common code for OTP authentication mechanisms.
+ *
+ * Copyright (c) 2006 Andrey Panin <pazke@donpac.ru>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "auth-common.h"
+#include "hash.h"
+#include "mech.h"
+
+#include "otp.h"
+#include "mech-otp-common.h"
+
+static HASH_TABLE(char *, struct auth_request *) otp_lock_table;
+
+void otp_lock_init(void)
+{
+ if (hash_table_is_created(otp_lock_table))
+ return;
+
+ hash_table_create(&otp_lock_table, default_pool, 128,
+ strcase_hash, strcasecmp);
+}
+
+bool otp_try_lock(struct auth_request *auth_request)
+{
+ if (hash_table_lookup(otp_lock_table, auth_request->fields.user) != NULL)
+ return FALSE;
+
+ hash_table_insert(otp_lock_table, auth_request->fields.user, auth_request);
+ return TRUE;
+}
+
+void otp_unlock(struct auth_request *auth_request)
+{
+ struct otp_auth_request *request =
+ (struct otp_auth_request *)auth_request;
+
+ if (!request->lock)
+ return;
+
+ hash_table_remove(otp_lock_table, auth_request->fields.user);
+ request->lock = FALSE;
+}
+
+void otp_set_credentials_callback(bool success,
+ struct auth_request *auth_request)
+{
+ if (success)
+ auth_request_success(auth_request, "", 0);
+ else {
+ auth_request_internal_failure(auth_request);
+ otp_unlock(auth_request);
+ }
+
+ otp_unlock(auth_request);
+}
+
+void mech_otp_auth_free(struct auth_request *auth_request)
+{
+ otp_unlock(auth_request);
+
+ pool_unref(&auth_request->pool);
+}
+
+void mech_otp_deinit(void)
+{
+ hash_table_destroy(&otp_lock_table);
+}
diff --git a/src/auth/mech-otp-common.h b/src/auth/mech-otp-common.h
new file mode 100644
index 0000000..37a6551
--- /dev/null
+++ b/src/auth/mech-otp-common.h
@@ -0,0 +1,23 @@
+#ifndef MECH_OTP_COMMON_H
+#define MECH_OTP_COMMON_H
+
+struct otp_auth_request {
+ struct auth_request auth_request;
+
+ pool_t pool;
+
+ bool lock;
+
+ struct otp_state state;
+};
+
+void otp_lock_init(void);
+bool otp_try_lock(struct auth_request *auth_request);
+void otp_unlock(struct auth_request *auth_request);
+
+void otp_set_credentials_callback(bool success,
+ struct auth_request *auth_request);
+void mech_otp_auth_free(struct auth_request *auth_request);
+void mech_otp_deinit(void);
+
+#endif
diff --git a/src/auth/mech-otp.c b/src/auth/mech-otp.c
new file mode 100644
index 0000000..58f3db1
--- /dev/null
+++ b/src/auth/mech-otp.c
@@ -0,0 +1,240 @@
+/*
+ * One-Time-Password (RFC 2444) authentication mechanism.
+ *
+ * Copyright (c) 2006 Andrey Panin <pazke@donpac.ru>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "auth-common.h"
+#include "safe-memset.h"
+#include "hash.h"
+#include "mech.h"
+#include "passdb.h"
+#include "hex-binary.h"
+#include "otp.h"
+#include "mech-otp-common.h"
+
+static void
+otp_send_challenge(struct auth_request *auth_request,
+ const unsigned char *credentials, size_t size)
+{
+ struct otp_auth_request *request =
+ (struct otp_auth_request *)auth_request;
+ const char *answer;
+
+ if (otp_parse_dbentry(t_strndup(credentials, size),
+ &request->state) != 0) {
+ e_error(request->auth_request.mech_event,
+ "invalid OTP data in passdb");
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ if (--request->state.seq < 1) {
+ e_error(request->auth_request.mech_event,
+ "sequence number < 1");
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ request->lock = otp_try_lock(auth_request);
+ if (!request->lock) {
+ e_error(request->auth_request.mech_event,
+ "user is locked, race attack?");
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ answer = p_strdup_printf(request->pool, "otp-%s %u %s ext",
+ digest_name(request->state.algo),
+ request->state.seq, request->state.seed);
+
+ auth_request_handler_reply_continue(auth_request, answer,
+ strlen(answer));
+}
+
+static void
+otp_credentials_callback(enum passdb_result result,
+ const unsigned char *credentials, size_t size,
+ struct auth_request *auth_request)
+{
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ otp_send_challenge(auth_request, credentials, size);
+ break;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ auth_request_internal_failure(auth_request);
+ break;
+ default:
+ auth_request_fail(auth_request);
+ break;
+ }
+}
+
+static void
+mech_otp_auth_phase1(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ struct otp_auth_request *request =
+ (struct otp_auth_request *)auth_request;
+ const char *authenid, *error;
+ size_t i, count;
+
+ /* authorization ID \0 authentication ID
+ FIXME: we'll ignore authorization ID for now. */
+ authenid = NULL;
+
+ count = 0;
+ for (i = 0; i < data_size; i++) {
+ if (data[i] == '\0') {
+ if (++count == 1)
+ authenid = (const char *) data + i + 1;
+ }
+ }
+
+ if (count != 1) {
+ e_error(request->auth_request.mech_event,
+ "invalid input");
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ if (!auth_request_set_username(auth_request, authenid, &error)) {
+ e_info(auth_request->mech_event, "%s", error);
+ auth_request_fail(auth_request);
+ return;
+ }
+
+ auth_request_lookup_credentials(auth_request, "OTP",
+ otp_credentials_callback);
+}
+
+static void mech_otp_verify(struct auth_request *auth_request,
+ const char *data, bool hex)
+{
+ struct otp_auth_request *request =
+ (struct otp_auth_request *)auth_request;
+ struct otp_state *state = &request->state;
+ unsigned char hash[OTP_HASH_SIZE], cur_hash[OTP_HASH_SIZE];
+ int ret;
+
+ ret = otp_parse_response(data, hash, hex);
+ if (ret < 0) {
+ e_error(request->auth_request.mech_event,
+ "invalid response");
+ auth_request_fail(auth_request);
+ otp_unlock(auth_request);
+ return;
+ }
+
+ otp_next_hash(state->algo, hash, cur_hash);
+
+ ret = memcmp(cur_hash, state->hash, OTP_HASH_SIZE);
+ if (ret != 0) {
+ auth_request_fail(auth_request);
+ otp_unlock(auth_request);
+ return;
+ }
+
+ memcpy(state->hash, hash, sizeof(state->hash));
+
+ auth_request_set_credentials(auth_request, "OTP",
+ otp_print_dbentry(state),
+ otp_set_credentials_callback);
+}
+
+static void mech_otp_verify_init(struct auth_request *auth_request,
+ const char *data, bool hex)
+{
+ struct otp_auth_request *request =
+ (struct otp_auth_request *)auth_request;
+ struct otp_state new_state;
+ unsigned char hash[OTP_HASH_SIZE], cur_hash[OTP_HASH_SIZE];
+ const char *error;
+ int ret;
+
+ ret = otp_parse_init_response(data, &new_state, cur_hash, hex, &error);
+ if (ret < 0) {
+ e_error(request->auth_request.mech_event,
+ "invalid init response, %s", error);
+ auth_request_fail(auth_request);
+ otp_unlock(auth_request);
+ return;
+ }
+
+ otp_next_hash(request->state.algo, cur_hash, hash);
+
+ ret = memcmp(hash, request->state.hash, OTP_HASH_SIZE);
+ if (ret != 0) {
+ auth_request_fail(auth_request);
+ otp_unlock(auth_request);
+ return;
+ }
+
+ auth_request_set_credentials(auth_request, "OTP",
+ otp_print_dbentry(&new_state),
+ otp_set_credentials_callback);
+}
+
+static void
+mech_otp_auth_phase2(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ const char *str = t_strndup(data, data_size);
+
+ if (str_begins(str, "hex:")) {
+ mech_otp_verify(auth_request, str + 4, TRUE);
+ } else if (str_begins(str, "word:")) {
+ mech_otp_verify(auth_request, str + 5, FALSE);
+ } else if (str_begins(str, "init-hex:")) {
+ mech_otp_verify_init(auth_request, str + 9, TRUE);
+ } else if (str_begins(str, "init-word:")) {
+ mech_otp_verify_init(auth_request, str + 10, FALSE);
+ } else {
+ e_error(auth_request->mech_event,
+ "unsupported response type");
+ auth_request_fail(auth_request);
+ otp_unlock(auth_request);
+ }
+}
+
+static void
+mech_otp_auth_continue(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ if (auth_request->fields.user == NULL) {
+ mech_otp_auth_phase1(auth_request, data, data_size);
+ } else {
+ mech_otp_auth_phase2(auth_request, data, data_size);
+ }
+}
+
+static struct auth_request *mech_otp_auth_new(void)
+{
+ struct otp_auth_request *request;
+ pool_t pool;
+
+ otp_lock_init();
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"otp_auth_request", 2048);
+ request = p_new(pool, struct otp_auth_request, 1);
+ request->pool = pool;
+ request->lock = FALSE;
+
+ request->auth_request.refcount = 1;
+ request->auth_request.pool = pool;
+ return &request->auth_request;
+}
+
+const struct mech_module mech_otp = {
+ "OTP",
+
+ .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE | MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_SET_CREDENTIALS,
+
+ mech_otp_auth_new,
+ mech_generic_auth_initial,
+ mech_otp_auth_continue,
+ mech_otp_auth_free
+};
diff --git a/src/auth/mech-plain-common.c b/src/auth/mech-plain-common.c
new file mode 100644
index 0000000..2d947b9
--- /dev/null
+++ b/src/auth/mech-plain-common.c
@@ -0,0 +1,22 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "mech.h"
+#include "passdb.h"
+#include "mech-plain-common.h"
+
+void plain_verify_callback(enum passdb_result result,
+ struct auth_request *request)
+{
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ auth_request_success(request, "", 0);
+ break;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ auth_request_internal_failure(request);
+ break;
+ default:
+ auth_request_fail(request);
+ break;
+ }
+}
diff --git a/src/auth/mech-plain-common.h b/src/auth/mech-plain-common.h
new file mode 100644
index 0000000..1dc339c
--- /dev/null
+++ b/src/auth/mech-plain-common.h
@@ -0,0 +1,7 @@
+#ifndef MECH_PLAIN_COMMON_H
+#define MECH_PLAIN_COMMON_H
+
+void plain_verify_callback(enum passdb_result result,
+ struct auth_request *request);
+
+#endif
diff --git a/src/auth/mech-plain.c b/src/auth/mech-plain.c
new file mode 100644
index 0000000..3bb715f
--- /dev/null
+++ b/src/auth/mech-plain.c
@@ -0,0 +1,88 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "safe-memset.h"
+#include "mech.h"
+#include "passdb.h"
+#include "mech-plain-common.h"
+
+static void
+mech_plain_auth_continue(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ const char *authid, *authenid, *error;
+ char *pass;
+ size_t i, len;
+ int count;
+
+ /* authorization ID \0 authentication ID \0 pass. */
+ authid = (const char *) data;
+ authenid = NULL; pass = NULL;
+
+ count = 0;
+ for (i = 0; i < data_size; i++) {
+ if (data[i] == '\0') {
+ if (++count == 1)
+ authenid = (const char *) data + i+1;
+ else if (count == 2) {
+ i++;
+ len = data_size - i;
+ pass = p_strndup(unsafe_data_stack_pool,
+ data+i, len);
+ }
+ else
+ break;
+ }
+ }
+
+ if (count == 2 && authenid != NULL && strcmp(authid, authenid) == 0) {
+ /* the login username isn't different */
+ authid = "";
+ }
+
+ if (count != 2) {
+ /* invalid input */
+ e_info(request->mech_event, "invalid input");
+ auth_request_fail(request);
+ } else if (!auth_request_set_username(request, authenid, &error)) {
+ /* invalid username */
+ e_info(request->mech_event, "%s", error);
+ auth_request_fail(request);
+ } else if (*authid != '\0' &&
+ !auth_request_set_login_username(request, authid, &error)) {
+ /* invalid login username */
+ e_info(request->mech_event,
+ "login user: %s", error);
+ auth_request_fail(request);
+ } else {
+ auth_request_verify_plain(request, pass,
+ plain_verify_callback);
+ }
+
+ /* make sure it's cleared */
+ if (pass != NULL)
+ safe_memset(pass, 0, strlen(pass));
+}
+
+static struct auth_request *mech_plain_auth_new(void)
+{
+ struct auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"plain_auth_request", 2048);
+ request = p_new(pool, struct auth_request, 1);
+ request->pool = pool;
+ return request;
+}
+
+const struct mech_module mech_plain = {
+ "PLAIN",
+
+ .flags = MECH_SEC_PLAINTEXT | MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_VERIFY_PLAIN,
+
+ mech_plain_auth_new,
+ mech_generic_auth_initial,
+ mech_plain_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-scram.c b/src/auth/mech-scram.c
new file mode 100644
index 0000000..a90d0d1
--- /dev/null
+++ b/src/auth/mech-scram.c
@@ -0,0 +1,550 @@
+/*
+ * SCRAM-SHA-1 SASL authentication, see RFC-5802
+ *
+ * Copyright (c) 2011-2016 Florian Zeitz <florob@babelmonkeys.de>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include <limits.h>
+
+#include "auth-common.h"
+#include "base64.h"
+#include "buffer.h"
+#include "hmac.h"
+#include "sha1.h"
+#include "sha2.h"
+#include "randgen.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "strnum.h"
+#include "password-scheme.h"
+#include "mech.h"
+#include "mech-scram.h"
+
+/* s-nonce length */
+#define SCRAM_SERVER_NONCE_LEN 64
+
+struct scram_auth_request {
+ struct auth_request auth_request;
+
+ pool_t pool;
+
+ const struct hash_method *hash_method;
+ const char *password_scheme;
+
+ /* sent: */
+ const char *server_first_message;
+ const char *snonce;
+
+ /* received: */
+ const char *gs2_header;
+ const char *cnonce;
+ const char *client_first_message_bare;
+ const char *client_final_message_without_proof;
+ buffer_t *proof;
+
+ /* stored */
+ unsigned char *stored_key;
+ unsigned char *server_key;
+};
+
+static const char *
+get_scram_server_first(struct scram_auth_request *request,
+ int iter, const char *salt)
+{
+ unsigned char snonce[SCRAM_SERVER_NONCE_LEN+1];
+ string_t *str;
+ size_t i;
+
+ /* RFC 5802, Section 7:
+
+ server-first-message =
+ [reserved-mext ","] nonce "," salt ","
+ iteration-count ["," extensions]
+
+ nonce = "r=" c-nonce [s-nonce]
+
+ salt = "s=" base64
+
+ iteration-count = "i=" posit-number
+ ;; A positive number.
+ */
+
+ random_fill(snonce, sizeof(snonce)-1);
+
+ /* make sure snonce is printable and does not contain ',' */
+ for (i = 0; i < sizeof(snonce)-1; i++) {
+ snonce[i] = (snonce[i] % ('~' - '!')) + '!';
+ if (snonce[i] == ',')
+ snonce[i] = '~';
+ }
+ snonce[sizeof(snonce)-1] = '\0';
+ request->snonce = p_strndup(request->pool, snonce, sizeof(snonce));
+
+ str = t_str_new(32 + strlen(request->cnonce) + sizeof(snonce) +
+ strlen(salt));
+ str_printfa(str, "r=%s%s,s=%s,i=%d", request->cnonce, request->snonce,
+ salt, iter);
+ return str_c(str);
+}
+
+static const char *get_scram_server_final(struct scram_auth_request *request)
+{
+ const struct hash_method *hmethod = request->hash_method;
+ struct hmac_context ctx;
+ const char *auth_message;
+ unsigned char server_signature[hmethod->digest_size];
+ string_t *str;
+
+ /* RFC 5802, Section 3:
+
+ AuthMessage := client-first-message-bare + "," +
+ server-first-message + "," +
+ client-final-message-without-proof
+ ServerSignature := HMAC(ServerKey, AuthMessage)
+ */
+ auth_message = t_strconcat(request->client_first_message_bare, ",",
+ request->server_first_message, ",",
+ request->client_final_message_without_proof, NULL);
+
+ hmac_init(&ctx, request->server_key, hmethod->digest_size, hmethod);
+ hmac_update(&ctx, auth_message, strlen(auth_message));
+ hmac_final(&ctx, server_signature);
+
+ /* RFC 5802, Section 7:
+
+ server-final-message = (server-error / verifier)
+ ["," extensions]
+
+ verifier = "v=" base64
+ ;; base-64 encoded ServerSignature.
+
+ */
+ str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(sizeof(server_signature)));
+ str_append(str, "v=");
+ base64_encode(server_signature, sizeof(server_signature), str);
+
+ return str_c(str);
+}
+
+static const char *scram_unescape_username(const char *in)
+{
+ string_t *out;
+
+ /* RFC 5802, Section 5.1:
+
+ The characters ',' or '=' in usernames are sent as '=2C' and '=3D'
+ respectively. If the server receives a username that contains '='
+ not followed by either '2C' or '3D', then the server MUST fail the
+ authentication.
+ */
+
+ out = t_str_new(64);
+ for (; *in != '\0'; in++) {
+ i_assert(in[0] != ','); /* strsplit should have caught this */
+
+ if (in[0] == '=') {
+ if (in[1] == '2' && in[2] == 'C')
+ str_append_c(out, ',');
+ else if (in[1] == '3' && in[2] == 'D')
+ str_append_c(out, '=');
+ else
+ return NULL;
+ in += 2;
+ } else {
+ str_append_c(out, *in);
+ }
+ }
+ return str_c(out);
+}
+
+static bool
+parse_scram_client_first(struct scram_auth_request *request,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ const char *login_username = NULL;
+ const char *data_cstr, *p;
+ const char *gs2_header, *gs2_cbind_flag, *authzid;
+ const char *cfm_bare, *username, *nonce;
+ const char *const *fields;
+
+ data_cstr = gs2_header = t_strndup(data, size);
+
+ /* RFC 5802, Section 7:
+
+ client-first-message = gs2-header client-first-message-bare
+ gs2-header = gs2-cbind-flag "," [ authzid ] ","
+
+ client-first-message-bare = [reserved-mext ","]
+ username "," nonce ["," extensions]
+
+ extensions = attr-val *("," attr-val)
+ ;; All extensions are optional,
+ ;; i.e., unrecognized attributes
+ ;; not defined in this document
+ ;; MUST be ignored.
+ attr-val = ALPHA "=" value
+ */
+ p = strchr(data_cstr, ',');
+ if (p == NULL) {
+ *error_r = "Invalid initial client message: "
+ "Missing first ',' in GS2 header";
+ return FALSE;
+ }
+ gs2_cbind_flag = t_strdup_until(data_cstr, p);
+ data_cstr = p + 1;
+
+ p = strchr(data_cstr, ',');
+ if (p == NULL) {
+ *error_r = "Invalid initial client message: "
+ "Missing second ',' in GS2 header";
+ return FALSE;
+ }
+ authzid = t_strdup_until(data_cstr, p);
+ gs2_header = t_strdup_until(gs2_header, p + 1);
+ cfm_bare = p + 1;
+
+ fields = t_strsplit(cfm_bare, ",");
+ if (str_array_length(fields) < 2) {
+ *error_r = "Invalid initial client message: "
+ "Missing nonce field";
+ return FALSE;
+ }
+ username = fields[0];
+ nonce = fields[1];
+
+ /* gs2-cbind-flag = ("p=" cb-name) / "n" / "y"
+ */
+ switch (gs2_cbind_flag[0]) {
+ case 'p':
+ *error_r = "Channel binding not supported";
+ return FALSE;
+ case 'y':
+ case 'n':
+ break;
+ default:
+ *error_r = "Invalid GS2 header";
+ return FALSE;
+ }
+
+ /* authzid = "a=" saslname
+ ;; Protocol specific.
+ */
+ if (authzid[0] == '\0')
+ ;
+ else if (authzid[0] == 'a' && authzid[1] == '=') {
+ /* Unescape authzid */
+ login_username = scram_unescape_username(authzid + 2);
+
+ if (login_username == NULL) {
+ *error_r = "authzid escaping is invalid";
+ return FALSE;
+ }
+ } else {
+ *error_r = "Invalid authzid field";
+ return FALSE;
+ }
+
+ /* reserved-mext = "m=" 1*(value-char)
+ */
+ if (username[0] == 'm') {
+ *error_r = "Mandatory extension(s) not supported";
+ return FALSE;
+ }
+ /* username = "n=" saslname
+ */
+ if (username[0] == 'n' && username[1] == '=') {
+ /* Unescape username */
+ username = scram_unescape_username(username + 2);
+ if (username == NULL) {
+ *error_r = "Username escaping is invalid";
+ return FALSE;
+ }
+ if (!auth_request_set_username(&request->auth_request,
+ username, error_r))
+ return FALSE;
+ } else {
+ *error_r = "Invalid username field";
+ return FALSE;
+ }
+ if (login_username != NULL) {
+ if (!auth_request_set_login_username(&request->auth_request,
+ login_username, error_r))
+ return FALSE;
+ }
+
+ /* nonce = "r=" c-nonce [s-nonce] */
+ if (nonce[0] == 'r' && nonce[1] == '=')
+ request->cnonce = p_strdup(request->pool, nonce+2);
+ else {
+ *error_r = "Invalid client nonce";
+ return FALSE;
+ }
+
+ request->gs2_header = p_strdup(request->pool, gs2_header);
+ request->client_first_message_bare = p_strdup(request->pool, cfm_bare);
+ return TRUE;
+}
+
+static bool verify_credentials(struct scram_auth_request *request)
+{
+ const struct hash_method *hmethod = request->hash_method;
+ struct hmac_context ctx;
+ const char *auth_message;
+ unsigned char client_key[hmethod->digest_size];
+ unsigned char client_signature[hmethod->digest_size];
+ unsigned char stored_key[hmethod->digest_size];
+ size_t i;
+
+ /* RFC 5802, Section 3:
+
+ AuthMessage := client-first-message-bare + "," +
+ server-first-message + "," +
+ client-final-message-without-proof
+ ClientSignature := HMAC(StoredKey, AuthMessage)
+ */
+ auth_message = t_strconcat(request->client_first_message_bare, ",",
+ request->server_first_message, ",",
+ request->client_final_message_without_proof, NULL);
+
+ hmac_init(&ctx, request->stored_key, hmethod->digest_size, hmethod);
+ hmac_update(&ctx, auth_message, strlen(auth_message));
+ hmac_final(&ctx, client_signature);
+
+ /* ClientProof := ClientKey XOR ClientSignature */
+ const unsigned char *proof_data = request->proof->data;
+ for (i = 0; i < sizeof(client_signature); i++)
+ client_key[i] = proof_data[i] ^ client_signature[i];
+
+ /* StoredKey := H(ClientKey) */
+ hash_method_get_digest(hmethod, client_key, sizeof(client_key),
+ stored_key);
+
+ safe_memset(client_key, 0, sizeof(client_key));
+ safe_memset(client_signature, 0, sizeof(client_signature));
+
+ return mem_equals_timing_safe(stored_key, request->stored_key,
+ sizeof(stored_key));
+}
+
+static void
+credentials_callback(enum passdb_result result,
+ const unsigned char *credentials, size_t size,
+ struct auth_request *auth_request)
+{
+ struct scram_auth_request *request =
+ (struct scram_auth_request *)auth_request;
+ const char *salt, *error;
+ unsigned int iter_count;
+
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ if (scram_scheme_parse(request->hash_method,
+ request->password_scheme,
+ credentials, size, &iter_count, &salt,
+ request->stored_key, request->server_key,
+ &error) < 0) {
+ e_info(auth_request->mech_event,
+ "%s", error);
+ auth_request_fail(auth_request);
+ break;
+ }
+
+ request->server_first_message = p_strdup(request->pool,
+ get_scram_server_first(request, iter_count, salt));
+
+ auth_request_handler_reply_continue(auth_request,
+ request->server_first_message,
+ strlen(request->server_first_message));
+ break;
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ auth_request_internal_failure(auth_request);
+ break;
+ default:
+ auth_request_fail(auth_request);
+ break;
+ }
+}
+
+static bool
+parse_scram_client_final(struct scram_auth_request *request,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ const struct hash_method *hmethod = request->hash_method;
+ const char **fields, *cbind_input, *nonce_str;
+ unsigned int field_count;
+ string_t *str;
+
+ /* RFC 5802, Section 7:
+
+ client-final-message-without-proof =
+ channel-binding "," nonce [","
+ extensions]
+ client-final-message =
+ client-final-message-without-proof "," proof
+ */
+ fields = t_strsplit(t_strndup(data, size), ",");
+ field_count = str_array_length(fields);
+ if (field_count < 3) {
+ *error_r = "Invalid final client message";
+ return FALSE;
+ }
+
+ /* channel-binding = "c=" base64
+ ;; base64 encoding of cbind-input.
+
+ cbind-data = 1*OCTET
+ cbind-input = gs2-header [ cbind-data ]
+ ;; cbind-data MUST be present for
+ ;; gs2-cbind-flag of "p" and MUST be absent
+ ;; for "y" or "n".
+ */
+ cbind_input = request->gs2_header;
+ str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(strlen(cbind_input)));
+ str_append(str, "c=");
+ base64_encode(cbind_input, strlen(cbind_input), str);
+
+ if (strcmp(fields[0], str_c(str)) != 0) {
+ *error_r = "Invalid channel binding data";
+ return FALSE;
+ }
+
+ /* nonce = "r=" c-nonce [s-nonce]
+ ;; Second part provided by server.
+ c-nonce = printable
+ s-nonce = printable
+ */
+ nonce_str = t_strconcat("r=", request->cnonce, request->snonce, NULL);
+ if (strcmp(fields[1], nonce_str) != 0) {
+ *error_r = "Wrong nonce";
+ return FALSE;
+ }
+
+ /* proof = "p=" base64
+ */
+ if (fields[field_count-1][0] == 'p') {
+ size_t len = strlen(&fields[field_count-1][2]);
+
+ request->proof = buffer_create_dynamic(request->pool,
+ MAX_BASE64_DECODED_SIZE(len));
+ if (base64_decode(&fields[field_count-1][2], len, NULL,
+ request->proof) < 0) {
+ *error_r = "Invalid base64 encoding";
+ return FALSE;
+ }
+ if (request->proof->used != hmethod->digest_size) {
+ *error_r = "Invalid ClientProof length";
+ return FALSE;
+ }
+ } else {
+ *error_r = "Invalid ClientProof";
+ return FALSE;
+ }
+
+ (void)str_array_remove(fields, fields[field_count-1]);
+ request->client_final_message_without_proof =
+ p_strdup(request->pool, t_strarray_join(fields, ","));
+
+ return TRUE;
+}
+
+void mech_scram_auth_continue(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ struct scram_auth_request *request =
+ (struct scram_auth_request *)auth_request;
+ const char *error = NULL;
+ const char *server_final_message;
+ size_t len;
+
+ if (request->client_first_message_bare == NULL) {
+ /* Received client-first-message */
+ if (parse_scram_client_first(request, data,
+ data_size, &error)) {
+ auth_request_lookup_credentials(
+ &request->auth_request,
+ request->password_scheme,
+ credentials_callback);
+ return;
+ }
+ } else {
+ /* Received client-final-message */
+ if (parse_scram_client_final(request, data, data_size,
+ &error)) {
+ if (!verify_credentials(request)) {
+ e_info(auth_request->mech_event,
+ AUTH_LOG_MSG_PASSWORD_MISMATCH);
+ } else {
+ server_final_message =
+ get_scram_server_final(request);
+ len = strlen(server_final_message);
+ auth_request_success(auth_request,
+ server_final_message, len);
+ return;
+ }
+ }
+ }
+
+ if (error != NULL)
+ e_info(auth_request->mech_event, "%s", error);
+ auth_request_fail(auth_request);
+}
+
+struct auth_request *
+mech_scram_auth_new(const struct hash_method *hash_method,
+ const char *password_scheme)
+{
+ struct scram_auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"scram_auth_request", 2048);
+ request = p_new(pool, struct scram_auth_request, 1);
+ request->pool = pool;
+
+ request->hash_method = hash_method;
+ request->password_scheme = password_scheme;
+
+ request->stored_key = p_malloc(pool, hash_method->digest_size);
+ request->server_key = p_malloc(pool, hash_method->digest_size);
+
+ request->auth_request.pool = pool;
+ return &request->auth_request;
+}
+
+static struct auth_request *mech_scram_sha1_auth_new(void)
+{
+ return mech_scram_auth_new(&hash_method_sha1, "SCRAM-SHA-1");
+}
+
+static struct auth_request *mech_scram_sha256_auth_new(void)
+{
+ return mech_scram_auth_new(&hash_method_sha256, "SCRAM-SHA-256");
+}
+
+const struct mech_module mech_scram_sha1 = {
+ "SCRAM-SHA-1",
+
+ .flags = MECH_SEC_MUTUAL_AUTH,
+ .passdb_need = MECH_PASSDB_NEED_LOOKUP_CREDENTIALS,
+
+ mech_scram_sha1_auth_new,
+ mech_generic_auth_initial,
+ mech_scram_auth_continue,
+ mech_generic_auth_free
+};
+
+const struct mech_module mech_scram_sha256 = {
+ "SCRAM-SHA-256",
+
+ .flags = MECH_SEC_MUTUAL_AUTH,
+ .passdb_need = MECH_PASSDB_NEED_LOOKUP_CREDENTIALS,
+
+ mech_scram_sha256_auth_new,
+ mech_generic_auth_initial,
+ mech_scram_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech-scram.h b/src/auth/mech-scram.h
new file mode 100644
index 0000000..0275aa7
--- /dev/null
+++ b/src/auth/mech-scram.h
@@ -0,0 +1,10 @@
+#ifndef MECH_SCRAM_H
+#define MECH_SCRAM_H
+
+struct auth_request *
+mech_scram_auth_new(const struct hash_method *hash_method,
+ const char *password_scheme);
+void mech_scram_auth_continue(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size);
+
+#endif
diff --git a/src/auth/mech-winbind.c b/src/auth/mech-winbind.c
new file mode 100644
index 0000000..d2525db
--- /dev/null
+++ b/src/auth/mech-winbind.c
@@ -0,0 +1,364 @@
+/*
+ * NTLM and Negotiate authentication mechanisms,
+ * using Samba winbind daemon
+ *
+ * Copyright (c) 2007 Dmitry Butskoy <dmitry@butskoy.name>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "auth-common.h"
+#include "lib-signals.h"
+#include "mech.h"
+#include "str.h"
+#include "buffer.h"
+#include "base64.h"
+#include "execv-const.h"
+#include "istream.h"
+#include "ostream.h"
+
+#include <unistd.h>
+#include <sys/wait.h>
+
+#define DEFAULT_WINBIND_HELPER_PATH "/usr/bin/ntlm_auth"
+
+enum helper_result {
+ HR_OK = 0, /* OK or continue */
+ HR_FAIL = -1, /* authentication failed */
+ HR_RESTART = -2 /* FAIL + try to restart helper */
+};
+
+struct winbind_helper {
+ const char *param;
+ pid_t pid;
+
+ struct istream *in_pipe;
+ struct ostream *out_pipe;
+};
+
+struct winbind_auth_request {
+ struct auth_request auth_request;
+
+ struct winbind_helper *winbind;
+ bool continued;
+};
+
+static struct winbind_helper winbind_ntlm_context = {
+ "--helper-protocol=squid-2.5-ntlmssp", -1, NULL, NULL
+};
+static struct winbind_helper winbind_spnego_context = {
+ "--helper-protocol=gss-spnego", -1, NULL, NULL
+};
+
+static bool sigchld_handler_set = FALSE;
+
+static void winbind_helper_disconnect(struct winbind_helper *winbind)
+{
+ i_stream_destroy(&winbind->in_pipe);
+ o_stream_destroy(&winbind->out_pipe);
+}
+
+static void winbind_wait_pid(struct winbind_helper *winbind)
+{
+ int status, ret;
+
+ if (winbind->pid == -1)
+ return;
+
+ /* FIXME: use child-wait.h API */
+ if ((ret = waitpid(winbind->pid, &status, WNOHANG)) <= 0) {
+ if (ret < 0 && errno != ECHILD && errno != EINTR)
+ i_error("waitpid() failed: %m");
+ return;
+ }
+
+ if (WIFSIGNALED(status)) {
+ i_error("winbind: ntlm_auth died with signal %d",
+ WTERMSIG(status));
+ } else if (WIFEXITED(status)) {
+ i_error("winbind: ntlm_auth exited with exit code %d",
+ WEXITSTATUS(status));
+ } else {
+ /* shouldn't happen */
+ i_error("winbind: ntlm_auth exited with status %d",
+ status);
+ }
+ winbind->pid = -1;
+}
+
+static void sigchld_handler(const siginfo_t *si ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+ winbind_wait_pid(&winbind_ntlm_context);
+ winbind_wait_pid(&winbind_spnego_context);
+}
+
+static void
+winbind_helper_connect(const struct auth_settings *set,
+ struct winbind_helper *winbind,
+ struct event *event)
+{
+ int infd[2], outfd[2];
+ pid_t pid;
+
+ if (winbind->in_pipe != NULL || winbind->pid != -1)
+ return;
+
+ if (pipe(infd) < 0) {
+ e_error(event, "pipe() failed: %m");
+ return;
+ }
+ if (pipe(outfd) < 0) {
+ i_close_fd(&infd[0]); i_close_fd(&infd[1]);
+ return;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ e_error(event, "fork() failed: %m");
+ i_close_fd(&infd[0]); i_close_fd(&infd[1]);
+ i_close_fd(&outfd[0]); i_close_fd(&outfd[1]);
+ return;
+ }
+
+ if (pid == 0) {
+ /* child */
+ const char *args[3];
+
+ i_close_fd(&infd[0]);
+ i_close_fd(&outfd[1]);
+
+ if (dup2(outfd[0], STDIN_FILENO) < 0 ||
+ dup2(infd[1], STDOUT_FILENO) < 0)
+ i_fatal("dup2() failed: %m");
+
+ args[0] = set->winbind_helper_path;
+ args[1] = winbind->param;
+ args[2] = NULL;
+ execv_const(args[0], args);
+ }
+
+ /* parent */
+ i_close_fd(&infd[1]);
+ i_close_fd(&outfd[0]);
+
+ winbind->pid = pid;
+ winbind->in_pipe =
+ i_stream_create_fd_autoclose(&infd[0], AUTH_CLIENT_MAX_LINE_LENGTH);
+ winbind->out_pipe =
+ o_stream_create_fd_autoclose(&outfd[1], SIZE_MAX);
+
+ if (!sigchld_handler_set) {
+ sigchld_handler_set = TRUE;
+ lib_signals_set_handler(SIGCHLD, LIBSIG_FLAGS_SAFE,
+ sigchld_handler, NULL);
+ }
+}
+
+static enum helper_result
+do_auth_continue(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ struct winbind_auth_request *request =
+ (struct winbind_auth_request *)auth_request;
+ struct istream *in_pipe = request->winbind->in_pipe;
+ string_t *str;
+ char *answer;
+ const char **token;
+ bool gss_spnego = request->winbind == &winbind_spnego_context;
+
+ if (request->winbind->in_pipe == NULL)
+ return HR_RESTART;
+
+ str = t_str_new(MAX_BASE64_ENCODED_SIZE(data_size + 1) + 4);
+ str_printfa(str, "%s ", request->continued ? "KK" : "YR");
+ base64_encode(data, data_size, str);
+ str_append_c(str, '\n');
+
+ if (o_stream_send(request->winbind->out_pipe,
+ str_data(str), str_len(str)) < 0 ||
+ o_stream_flush(request->winbind->out_pipe) < 0) {
+ e_error(auth_request->mech_event,
+ "write(out_pipe) failed: %s",
+ o_stream_get_error(request->winbind->out_pipe));
+ return HR_RESTART;
+ }
+ request->continued = FALSE;
+
+ while ((answer = i_stream_read_next_line(in_pipe)) == NULL) {
+ if (in_pipe->stream_errno != 0 || in_pipe->eof)
+ break;
+ }
+ if (answer == NULL) {
+ if (in_pipe->stream_errno != 0) {
+ e_error(auth_request->mech_event,
+ "read(in_pipe) failed: %m");
+ } else {
+ e_error(auth_request->mech_event,
+ "read(in_pipe) failed: "
+ "unexpected end of file");
+ }
+ return HR_RESTART;
+ }
+
+ token = t_strsplit_spaces(answer, " ");
+ if (token[0] == NULL ||
+ (token[1] == NULL && strcmp(token[0], "BH") != 0) ||
+ (gss_spnego && (token[1] == NULL || token[2] == NULL))) {
+ e_error(auth_request->mech_event,
+ "Invalid input from helper: %s", answer);
+ return HR_RESTART;
+ }
+
+ /*
+ * NTLM:
+ * The child's reply contains 2 parts:
+ * - The code: TT, AF or NA
+ * - The argument:
+ * For TT it's the blob to send to the client, coded in base64
+ * For AF it's user or DOMAIN\user
+ * For NA it's the NT error code
+ *
+ * GSS-SPNEGO:
+ * The child's reply contains 3 parts:
+ * - The code: TT, AF or NA
+ * - The blob to send to the client, coded in base64
+ * - The argument:
+ * For TT it's a dummy '*'
+ * For AF it's DOMAIN\user
+ * For NA it's the NT error code
+ */
+
+ if (strcmp(token[0], "TT") == 0) {
+ buffer_t *buf;
+
+ i_assert(token[1] != NULL);
+ buf = t_base64_decode_str(token[1]);
+ auth_request_handler_reply_continue(auth_request, buf->data,
+ buf->used);
+ request->continued = TRUE;
+ return HR_OK;
+ } else if (strcmp(token[0], "NA") == 0) {
+ const char *error = gss_spnego ? token[2] : token[1];
+
+ e_info(auth_request->mech_event,
+ "user not authenticated: %s", error);
+ return HR_FAIL;
+ } else if (strcmp(token[0], "AF") == 0) {
+ const char *user, *p, *error;
+
+ user = t_strarray_join(gss_spnego ? token+2 : token+1, " ");
+ i_assert(user != NULL);
+
+ p = strchr(user, '\\');
+ if (p != NULL) {
+ /* change "DOMAIN\user" to uniform style
+ "user@DOMAIN" */
+ user = t_strconcat(p+1, "@",
+ t_strdup_until(user, p), NULL);
+ }
+
+ if (!auth_request_set_username(auth_request, user, &error)) {
+ e_info(auth_request->mech_event,
+ "%s", error);
+ return HR_FAIL;
+ }
+
+ request->auth_request.passdb_success = TRUE;
+ if (gss_spnego && strcmp(token[1], "*") != 0) {
+ buffer_t *buf;
+
+ buf = t_base64_decode_str(token[1]);
+ auth_request_success(&request->auth_request,
+ buf->data, buf->used);
+ } else {
+ auth_request_success(&request->auth_request, "", 0);
+ }
+ return HR_OK;
+ } else if (strcmp(token[0], "BH") == 0) {
+ e_info(auth_request->mech_event,
+ "ntlm_auth reports broken helper: %s",
+ token[1] != NULL ? token[1] : "");
+ return HR_RESTART;
+ } else {
+ e_error(auth_request->mech_event,
+ "Invalid input from helper: %s", answer);
+ return HR_RESTART;
+ }
+}
+
+static void
+mech_winbind_auth_initial(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ struct winbind_auth_request *request =
+ (struct winbind_auth_request *)auth_request;
+
+ winbind_helper_connect(auth_request->set, request->winbind,
+ auth_request->event);
+ mech_generic_auth_initial(auth_request, data, data_size);
+}
+
+static void
+mech_winbind_auth_continue(struct auth_request *auth_request,
+ const unsigned char *data, size_t data_size)
+{
+ struct winbind_auth_request *request =
+ (struct winbind_auth_request *)auth_request;
+ enum helper_result res;
+
+ res = do_auth_continue(auth_request, data, data_size);
+ if (res != HR_OK) {
+ if (res == HR_RESTART)
+ winbind_helper_disconnect(request->winbind);
+ auth_request_fail(auth_request);
+ }
+}
+
+static struct auth_request *do_auth_new(struct winbind_helper *winbind)
+{
+ struct winbind_auth_request *request;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"winbind_auth_request", 2048);
+ request = p_new(pool, struct winbind_auth_request, 1);
+ request->auth_request.pool = pool;
+
+ request->winbind = winbind;
+ return &request->auth_request;
+}
+
+static struct auth_request *mech_winbind_ntlm_auth_new(void)
+{
+ return do_auth_new(&winbind_ntlm_context);
+}
+
+static struct auth_request *mech_winbind_spnego_auth_new(void)
+{
+ return do_auth_new(&winbind_spnego_context);
+}
+
+const struct mech_module mech_winbind_ntlm = {
+ "NTLM",
+
+ .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE |
+ MECH_SEC_ALLOW_NULS,
+ .passdb_need = MECH_PASSDB_NEED_NOTHING,
+
+ mech_winbind_ntlm_auth_new,
+ mech_winbind_auth_initial,
+ mech_winbind_auth_continue,
+ mech_generic_auth_free
+};
+
+const struct mech_module mech_winbind_spnego = {
+ "GSS-SPNEGO",
+
+ .flags = 0,
+ .passdb_need = MECH_PASSDB_NEED_NOTHING,
+
+ mech_winbind_spnego_auth_new,
+ mech_winbind_auth_initial,
+ mech_winbind_auth_continue,
+ mech_generic_auth_free
+};
diff --git a/src/auth/mech.c b/src/auth/mech.c
new file mode 100644
index 0000000..477f27b
--- /dev/null
+++ b/src/auth/mech.c
@@ -0,0 +1,245 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "ioloop.h"
+#include "mech.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "passdb.h"
+
+#include <ctype.h>
+
+static struct mech_module_list *mech_modules;
+
+void mech_register_module(const struct mech_module *module)
+{
+ struct mech_module_list *list;
+ i_assert(strcmp(module->mech_name, t_str_ucase(module->mech_name)) == 0);
+
+ list = i_new(struct mech_module_list, 1);
+ list->module = *module;
+
+ list->next = mech_modules;
+ mech_modules = list;
+}
+
+void mech_unregister_module(const struct mech_module *module)
+{
+ struct mech_module_list **pos, *list;
+
+ for (pos = &mech_modules; *pos != NULL; pos = &(*pos)->next) {
+ if (strcmp((*pos)->module.mech_name, module->mech_name) == 0) {
+ list = *pos;
+ *pos = (*pos)->next;
+ i_free(list);
+ break;
+ }
+ }
+}
+
+const struct mech_module *mech_module_find(const char *name)
+{
+ struct mech_module_list *list;
+ name = t_str_ucase(name);
+
+ for (list = mech_modules; list != NULL; list = list->next) {
+ if (strcmp(list->module.mech_name, name) == 0)
+ return &list->module;
+ }
+ return NULL;
+}
+
+void mech_generic_auth_initial(struct auth_request *request,
+ const unsigned char *data, size_t data_size)
+{
+ if (data == NULL) {
+ auth_request_handler_reply_continue(request, uchar_empty_ptr, 0);
+ } else {
+ /* initial reply given, even if it was 0 bytes */
+ request->mech->auth_continue(request, data, data_size);
+ }
+}
+
+void mech_generic_auth_free(struct auth_request *request)
+{
+ pool_unref(&request->pool);
+}
+
+extern const struct mech_module mech_plain;
+extern const struct mech_module mech_login;
+extern const struct mech_module mech_apop;
+extern const struct mech_module mech_cram_md5;
+extern const struct mech_module mech_digest_md5;
+extern const struct mech_module mech_external;
+extern const struct mech_module mech_otp;
+extern const struct mech_module mech_scram_sha1;
+extern const struct mech_module mech_scram_sha256;
+extern const struct mech_module mech_anonymous;
+#ifdef HAVE_GSSAPI
+extern const struct mech_module mech_gssapi;
+#endif
+#ifdef HAVE_GSSAPI_SPNEGO
+extern const struct mech_module mech_gssapi_spnego;
+#endif
+extern const struct mech_module mech_winbind_ntlm;
+extern const struct mech_module mech_winbind_spnego;
+extern const struct mech_module mech_oauthbearer;
+extern const struct mech_module mech_xoauth2;
+
+static void mech_register_add(struct mechanisms_register *reg,
+ const struct mech_module *mech)
+{
+ struct mech_module_list *list;
+
+ list = p_new(reg->pool, struct mech_module_list, 1);
+ list->module = *mech;
+
+ str_printfa(reg->handshake, "MECH\t%s", mech->mech_name);
+ if ((mech->flags & MECH_SEC_PRIVATE) != 0)
+ str_append(reg->handshake, "\tprivate");
+ if ((mech->flags & MECH_SEC_ANONYMOUS) != 0)
+ str_append(reg->handshake, "\tanonymous");
+ if ((mech->flags & MECH_SEC_PLAINTEXT) != 0)
+ str_append(reg->handshake, "\tplaintext");
+ if ((mech->flags & MECH_SEC_DICTIONARY) != 0)
+ str_append(reg->handshake, "\tdictionary");
+ if ((mech->flags & MECH_SEC_ACTIVE) != 0)
+ str_append(reg->handshake, "\tactive");
+ if ((mech->flags & MECH_SEC_FORWARD_SECRECY) != 0)
+ str_append(reg->handshake, "\tforward-secrecy");
+ if ((mech->flags & MECH_SEC_MUTUAL_AUTH) != 0)
+ str_append(reg->handshake, "\tmutual-auth");
+ str_append_c(reg->handshake, '\n');
+
+ list->next = reg->modules;
+ reg->modules = list;
+}
+
+static const char *mech_get_plugin_name(const char *name)
+{
+ string_t *str = t_str_new(32);
+
+ str_append(str, "mech_");
+ for (; *name != '\0'; name++) {
+ if (*name == '-')
+ str_append_c(str, '_');
+ else
+ str_append_c(str, i_tolower(*name));
+ }
+ return str_c(str);
+}
+
+struct mechanisms_register *
+mech_register_init(const struct auth_settings *set)
+{
+ struct mechanisms_register *reg;
+ const struct mech_module *mech;
+ const char *const *mechanisms;
+ pool_t pool;
+
+ pool = pool_alloconly_create("mechanisms register", 1024);
+ reg = p_new(pool, struct mechanisms_register, 1);
+ reg->pool = pool;
+ reg->set = set;
+ reg->handshake = str_new(pool, 512);
+
+ mechanisms = t_strsplit_spaces(set->mechanisms, " ");
+ for (; *mechanisms != NULL; mechanisms++) {
+ const char *name = t_str_ucase(*mechanisms);
+
+ if (strcmp(name, "ANONYMOUS") == 0) {
+ if (*set->anonymous_username == '\0') {
+ i_fatal("ANONYMOUS listed in mechanisms, "
+ "but anonymous_username not set");
+ }
+ }
+ mech = mech_module_find(name);
+ if (mech == NULL) {
+ /* maybe it's a plugin. try to load it. */
+ auth_module_load(mech_get_plugin_name(name));
+ mech = mech_module_find(name);
+ }
+ if (mech == NULL)
+ i_fatal("Unknown authentication mechanism '%s'", name);
+ mech_register_add(reg, mech);
+ }
+
+ if (reg->modules == NULL)
+ i_fatal("No authentication mechanisms configured");
+ return reg;
+}
+
+void mech_register_deinit(struct mechanisms_register **_reg)
+{
+ struct mechanisms_register *reg = *_reg;
+
+ *_reg = NULL;
+ pool_unref(&reg->pool);
+}
+
+const struct mech_module *
+mech_register_find(const struct mechanisms_register *reg, const char *name)
+{
+ const struct mech_module_list *list;
+ name = t_str_ucase(name);
+
+ for (list = reg->modules; list != NULL; list = list->next) {
+ if (strcmp(list->module.mech_name, name) == 0)
+ return &list->module;
+ }
+ return NULL;
+}
+
+void mech_init(const struct auth_settings *set)
+{
+ mech_register_module(&mech_plain);
+ mech_register_module(&mech_login);
+ mech_register_module(&mech_apop);
+ mech_register_module(&mech_cram_md5);
+ mech_register_module(&mech_digest_md5);
+ mech_register_module(&mech_external);
+ if (set->use_winbind) {
+ mech_register_module(&mech_winbind_ntlm);
+ mech_register_module(&mech_winbind_spnego);
+ } else {
+#if defined(HAVE_GSSAPI_SPNEGO) && defined(BUILTIN_GSSAPI)
+ mech_register_module(&mech_gssapi_spnego);
+#endif
+ }
+ mech_register_module(&mech_otp);
+ mech_register_module(&mech_scram_sha1);
+ mech_register_module(&mech_scram_sha256);
+ mech_register_module(&mech_anonymous);
+#ifdef BUILTIN_GSSAPI
+ mech_register_module(&mech_gssapi);
+#endif
+ mech_register_module(&mech_oauthbearer);
+ mech_register_module(&mech_xoauth2);
+}
+
+void mech_deinit(const struct auth_settings *set)
+{
+ mech_unregister_module(&mech_plain);
+ mech_unregister_module(&mech_login);
+ mech_unregister_module(&mech_apop);
+ mech_unregister_module(&mech_cram_md5);
+ mech_unregister_module(&mech_digest_md5);
+ mech_unregister_module(&mech_external);
+ if (set->use_winbind) {
+ mech_unregister_module(&mech_winbind_ntlm);
+ mech_unregister_module(&mech_winbind_spnego);
+ } else {
+#if defined(HAVE_GSSAPI_SPNEGO) && defined(BUILTIN_GSSAPI)
+ mech_unregister_module(&mech_gssapi_spnego);
+#endif
+ }
+ mech_unregister_module(&mech_otp);
+ mech_unregister_module(&mech_scram_sha1);
+ mech_unregister_module(&mech_scram_sha256);
+ mech_unregister_module(&mech_anonymous);
+#ifdef BUILTIN_GSSAPI
+ mech_unregister_module(&mech_gssapi);
+#endif
+ mech_unregister_module(&mech_oauthbearer);
+ mech_unregister_module(&mech_xoauth2);
+}
diff --git a/src/auth/mech.h b/src/auth/mech.h
new file mode 100644
index 0000000..4a9f593
--- /dev/null
+++ b/src/auth/mech.h
@@ -0,0 +1,77 @@
+#ifndef MECH_H
+#define MECH_H
+
+#include "auth-client-interface.h"
+
+struct auth_settings;
+struct auth_request;
+
+#include "auth-request.h"
+#include "auth-request-handler.h"
+
+/* Used only for string sanitization. */
+#define MAX_MECH_NAME_LEN 64
+
+enum mech_passdb_need {
+ /* Mechanism doesn't need a passdb at all */
+ MECH_PASSDB_NEED_NOTHING = 0,
+ /* Mechanism just needs to verify a given plaintext password */
+ MECH_PASSDB_NEED_VERIFY_PLAIN,
+ /* Mechanism needs to verify a given challenge+response combination,
+ i.e. there is only a single response from client.
+ (Currently implemented the same as _LOOKUP_CREDENTIALS) */
+ MECH_PASSDB_NEED_VERIFY_RESPONSE,
+ /* Mechanism needs to look up credentials with appropriate scheme */
+ MECH_PASSDB_NEED_LOOKUP_CREDENTIALS,
+ /* Mechanism needs to look up credentials and also modify them */
+ MECH_PASSDB_NEED_SET_CREDENTIALS
+};
+
+struct mech_module {
+ const char *mech_name;
+
+ enum mech_security_flags flags;
+ enum mech_passdb_need passdb_need;
+
+ struct auth_request *(*auth_new)(void);
+ void (*auth_initial)(struct auth_request *request,
+ const unsigned char *data, size_t data_size);
+ void (*auth_continue)(struct auth_request *request,
+ const unsigned char *data, size_t data_size);
+ void (*auth_free)(struct auth_request *request);
+};
+
+struct mech_module_list {
+ struct mech_module_list *next;
+
+ struct mech_module module;
+};
+
+struct mechanisms_register {
+ pool_t pool;
+ const struct auth_settings *set;
+
+ struct mech_module_list *modules;
+ buffer_t *handshake;
+};
+
+extern const struct mech_module mech_dovecot_token;
+
+void mech_register_module(const struct mech_module *module);
+void mech_unregister_module(const struct mech_module *module);
+const struct mech_module *mech_module_find(const char *name);
+
+void mech_generic_auth_initial(struct auth_request *request,
+ const unsigned char *data, size_t data_size);
+void mech_generic_auth_free(struct auth_request *request);
+
+struct mechanisms_register *
+mech_register_init(const struct auth_settings *set);
+void mech_register_deinit(struct mechanisms_register **reg);
+const struct mech_module *
+mech_register_find(const struct mechanisms_register *reg, const char *name);
+
+void mech_init(const struct auth_settings *set);
+void mech_deinit(const struct auth_settings *set);
+
+#endif
diff --git a/src/auth/mycrypt.c b/src/auth/mycrypt.c
new file mode 100644
index 0000000..0bd00bc
--- /dev/null
+++ b/src/auth/mycrypt.c
@@ -0,0 +1,26 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#define _XOPEN_SOURCE 4
+#define _XOPEN_SOURCE_EXTENDED 1 /* 1 needed for AIX */
+#ifndef _AIX
+# define _XOPEN_VERSION 4 /* breaks AIX */
+#endif
+#define _XPG4_2
+#ifdef CRYPT_USE_XPG6
+# define _XPG6 /* Some Solaris versions require this, some break with this */
+#endif
+#include <unistd.h>
+#ifdef HAVE_CRYPT_H
+# include <crypt.h>
+#endif
+
+#include "mycrypt.h"
+
+char *mycrypt(const char *key, const char *salt)
+{
+ return crypt(key, salt);
+}
diff --git a/src/auth/mycrypt.h b/src/auth/mycrypt.h
new file mode 100644
index 0000000..550adfc
--- /dev/null
+++ b/src/auth/mycrypt.h
@@ -0,0 +1,8 @@
+#ifndef MYCRYPT_H
+#define MYCRYPT_H
+
+/* A simple wrapper to crypt(). Problem with it is that it requires
+ _XOPEN_SOURCE define which breaks other things. */
+char *mycrypt(const char *key, const char *salt);
+
+#endif
diff --git a/src/auth/passdb-blocking.c b/src/auth/passdb-blocking.c
new file mode 100644
index 0000000..6b7030c
--- /dev/null
+++ b/src/auth/passdb-blocking.c
@@ -0,0 +1,173 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "str.h"
+#include "strescape.h"
+#include "auth-worker-server.h"
+#include "password-scheme.h"
+#include "passdb.h"
+#include "passdb-blocking.h"
+
+
+static void
+auth_worker_reply_parse_args(struct auth_request *request,
+ const char *const *args)
+{
+ if (**args != '\0')
+ request->passdb_password = p_strdup(request->pool, *args);
+ args++;
+
+ if (*args != NULL)
+ auth_request_set_fields(request, args, NULL);
+}
+
+enum passdb_result
+passdb_blocking_auth_worker_reply_parse(struct auth_request *request, const char *reply)
+{
+ enum passdb_result ret;
+ const char *const *args;
+
+ args = t_strsplit_tabescaped(reply);
+
+ if (strcmp(*args, "OK") == 0 && args[1] != NULL && args[2] != NULL) {
+ /* OK \t user \t password [\t extra] */
+ if (args[1][0] != '\0')
+ auth_request_set_field(request, "user", args[1], NULL);
+ auth_worker_reply_parse_args(request, args + 2);
+ return PASSDB_RESULT_OK;
+ }
+
+ if (strcmp(*args, "NEXT") == 0 && args[1] != NULL) {
+ /* NEXT \t user [\t extra] */
+ if (args[1][0] != '\0')
+ auth_request_set_field(request, "user", args[1], NULL);
+ auth_worker_reply_parse_args(request, args + 1);
+ return PASSDB_RESULT_NEXT;
+ }
+
+ if (strcmp(*args, "FAIL") == 0 && args[1] != NULL) {
+ int result;
+ /* FAIL \t result [\t user \t password [\t extra]] */
+ if (str_to_int(args[1], &result) < 0) {
+ /* shouldn't happen */
+ } else {
+ ret = (enum passdb_result)result;
+ if (ret == PASSDB_RESULT_OK) {
+ /* shouldn't happen */
+ } else if (args[2] == NULL) {
+ /* internal failure most likely */
+ return ret;
+ } else if (args[3] != NULL) {
+ if (*args[2] != '\0') {
+ auth_request_set_field(request, "user",
+ args[2], NULL);
+ }
+ auth_worker_reply_parse_args(request, args + 3);
+ return ret;
+ }
+ }
+ }
+
+ e_error(authdb_event(request),
+ "Received invalid reply from worker: %s", reply);
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+}
+
+static bool
+verify_plain_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+ const char *reply, void *context)
+{
+ struct auth_request *request = context;
+ enum passdb_result result;
+
+ result = passdb_blocking_auth_worker_reply_parse(request, reply);
+ auth_request_verify_plain_callback(result, request);
+ auth_request_unref(&request);
+ return TRUE;
+}
+
+void passdb_blocking_verify_plain(struct auth_request *request)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ str_printfa(str, "PASSV\t%u\t", request->passdb->passdb->id);
+ str_append_tabescaped(str, request->mech_password);
+ str_append_c(str, '\t');
+ auth_request_export(request, str);
+
+ auth_request_ref(request);
+ auth_worker_call(request->pool, request->fields.user, str_c(str),
+ verify_plain_callback, request);
+}
+
+static bool
+lookup_credentials_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+ const char *reply, void *context)
+{
+ struct auth_request *request = context;
+ enum passdb_result result;
+ const char *password = NULL, *scheme = NULL;
+
+ result = passdb_blocking_auth_worker_reply_parse(request, reply);
+ if (result == PASSDB_RESULT_OK && request->passdb_password != NULL) {
+ password = request->passdb_password;
+ scheme = password_get_scheme(&password);
+ if (scheme == NULL) {
+ e_error(authdb_event(request),
+ "Received reply from worker without "
+ "password scheme");
+ result = PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+ }
+
+ passdb_handle_credentials(result, password, scheme,
+ auth_request_lookup_credentials_callback,
+ request);
+ auth_request_unref(&request);
+ return TRUE;
+}
+
+void passdb_blocking_lookup_credentials(struct auth_request *request)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ str_printfa(str, "PASSL\t%u\t", request->passdb->passdb->id);
+ str_append_tabescaped(str, request->wanted_credentials_scheme);
+ str_append_c(str, '\t');
+ auth_request_export(request, str);
+
+ auth_request_ref(request);
+ auth_worker_call(request->pool, request->fields.user, str_c(str),
+ lookup_credentials_callback, request);
+}
+
+static bool
+set_credentials_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+ const char *reply, void *context)
+{
+ struct auth_request *request = context;
+ bool success;
+
+ success = strcmp(reply, "OK") == 0 || str_begins(reply, "OK\t");
+ request->private_callback.set_credentials(success, request);
+ auth_request_unref(&request);
+ return TRUE;
+}
+
+void passdb_blocking_set_credentials(struct auth_request *request,
+ const char *new_credentials)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ str_printfa(str, "SETCRED\t%u\t", request->passdb->passdb->id);
+ str_append_tabescaped(str, new_credentials);
+ str_append_c(str, '\t');
+ auth_request_export(request, str);
+
+ auth_request_ref(request);
+ auth_worker_call(request->pool, request->fields.user, str_c(str),
+ set_credentials_callback, request);
+}
diff --git a/src/auth/passdb-blocking.h b/src/auth/passdb-blocking.h
new file mode 100644
index 0000000..3963998
--- /dev/null
+++ b/src/auth/passdb-blocking.h
@@ -0,0 +1,11 @@
+#ifndef PASSDB_BLOCKING_H
+#define PASSDB_BLOCKING_H
+
+enum passdb_result
+passdb_blocking_auth_worker_reply_parse(struct auth_request *request, const char *reply);
+void passdb_blocking_verify_plain(struct auth_request *request);
+void passdb_blocking_lookup_credentials(struct auth_request *request);
+void passdb_blocking_set_credentials(struct auth_request *request,
+ const char *new_credentials);
+
+#endif
diff --git a/src/auth/passdb-bsdauth.c b/src/auth/passdb-bsdauth.c
new file mode 100644
index 0000000..36469c6
--- /dev/null
+++ b/src/auth/passdb-bsdauth.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#ifdef PASSDB_BSDAUTH
+
+#include "safe-memset.h"
+#include "auth-cache.h"
+#include "ipwd.h"
+#include "mycrypt.h"
+
+#include <login_cap.h>
+#include <bsd_auth.h>
+
+static void
+bsdauth_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct passwd pw;
+ const char *type;
+ int result;
+
+ e_debug(authdb_event(request), "lookup");
+
+ switch (i_getpwnam(request->fields.user, &pw)) {
+ case -1:
+ e_error(authdb_event(request),
+ "getpwnam() failed: %m");
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
+ return;
+ case 0:
+ auth_request_log_unknown_user(request, AUTH_SUBSYS_DB);
+ callback(PASSDB_RESULT_USER_UNKNOWN, request);
+ return;
+ }
+
+ /* check if the password is valid */
+ type = t_strdup_printf("auth-%s", request->fields.service);
+ result = auth_userokay(request->fields.user, NULL,
+ t_strdup_noconst(type),
+ t_strdup_noconst(password));
+
+ /* clear the passwords from memory */
+ safe_memset(pw.pw_passwd, 0, strlen(pw.pw_passwd));
+
+ if (result == 0) {
+ auth_request_log_password_mismatch(request, AUTH_SUBSYS_DB);
+ callback(PASSDB_RESULT_PASSWORD_MISMATCH, request);
+ return;
+ }
+
+ /* make sure we're using the username exactly as it's in the database */
+ auth_request_set_field(request, "user", pw.pw_name, NULL);
+
+ callback(PASSDB_RESULT_OK, request);
+}
+
+static struct passdb_module *
+bsdauth_preinit(pool_t pool, const char *args)
+{
+ struct passdb_module *module;
+
+ module = p_new(pool, struct passdb_module, 1);
+ module->default_pass_scheme = "PLAIN"; /* same reason as PAM */
+ module->blocking = TRUE;
+
+ if (strcmp(args, "blocking=no") == 0)
+ module->blocking = FALSE;
+ else if (str_begins(args, "cache_key="))
+ module->default_cache_key = auth_cache_parse_key(pool, args + 10);
+ else if (*args != '\0')
+ i_fatal("passdb bsdauth: Unknown setting: %s", args);
+ return module;
+}
+
+static void bsdauth_deinit(struct passdb_module *module ATTR_UNUSED)
+{
+ endpwent();
+}
+
+struct passdb_module_interface passdb_bsdauth = {
+ "bsdauth",
+
+ bsdauth_preinit,
+ NULL,
+ bsdauth_deinit,
+
+ bsdauth_verify_plain,
+ NULL,
+ NULL
+};
+#else
+struct passdb_module_interface passdb_bsdauth = {
+ .name = "bsdauth"
+};
+#endif
diff --git a/src/auth/passdb-cache.c b/src/auth/passdb-cache.c
new file mode 100644
index 0000000..1fe3b07
--- /dev/null
+++ b/src/auth/passdb-cache.c
@@ -0,0 +1,214 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "str.h"
+#include "strescape.h"
+#include "restrict-process-size.h"
+#include "auth-request-stats.h"
+#include "auth-worker-server.h"
+#include "password-scheme.h"
+#include "passdb.h"
+#include "passdb-cache.h"
+#include "passdb-blocking.h"
+
+struct auth_cache *passdb_cache = NULL;
+
+static void
+passdb_cache_log_hit(struct auth_request *request, const char *value)
+{
+ const char *p;
+
+ if (!request->set->debug_passwords &&
+ *value != '\0' && *value != '\t') {
+ /* hide the password */
+ p = strchr(value, '\t');
+ value = t_strconcat(PASSWORD_HIDDEN_STR, p, NULL);
+ }
+ e_debug(authdb_event(request), "cache hit: %s", value);
+}
+
+static bool
+passdb_cache_lookup(struct auth_request *request, const char *key,
+ bool use_expired, struct auth_cache_node **node_r,
+ const char **value_r, bool *neg_expired_r)
+{
+ struct auth_stats *stats = auth_request_stats_get(request);
+ const char *value;
+ bool expired;
+
+ request->passdb_cache_result = AUTH_REQUEST_CACHE_MISS;
+
+ /* value = password \t ... */
+ value = auth_cache_lookup(passdb_cache, request, key, node_r,
+ &expired, neg_expired_r);
+ if (value == NULL || (expired && !use_expired)) {
+ stats->auth_cache_miss_count++;
+ e_debug(authdb_event(request),
+ value == NULL ? "cache miss" :
+ "cache expired");
+ return FALSE;
+ }
+ stats->auth_cache_hit_count++;
+ passdb_cache_log_hit(request, value);
+ request->passdb_cache_result = AUTH_REQUEST_CACHE_HIT;
+
+ *value_r = value;
+ return TRUE;
+}
+
+static bool
+passdb_cache_verify_plain_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+ const char *reply, void *context)
+{
+ struct auth_request *request = context;
+ enum passdb_result result;
+
+ result = passdb_blocking_auth_worker_reply_parse(request, reply);
+ if (result != PASSDB_RESULT_OK)
+ auth_fields_rollback(request->fields.extra_fields);
+ auth_request_verify_plain_callback_finish(result, request);
+ auth_request_unref(&request);
+ return TRUE;
+}
+
+bool passdb_cache_verify_plain(struct auth_request *request, const char *key,
+ const char *password,
+ enum passdb_result *result_r, bool use_expired)
+{
+ const char *value, *cached_pw, *scheme, *const *list;
+ struct auth_cache_node *node;
+ enum passdb_result ret;
+ bool neg_expired;
+
+ if (passdb_cache == NULL || key == NULL)
+ return FALSE;
+
+ if (!passdb_cache_lookup(request, key, use_expired,
+ &node, &value, &neg_expired))
+ return FALSE;
+
+ if (*value == '\0') {
+ /* negative cache entry */
+ auth_request_log_unknown_user(request, AUTH_SUBSYS_DB);
+ *result_r = PASSDB_RESULT_USER_UNKNOWN;
+ auth_request_verify_plain_callback_finish(*result_r, request);
+ return TRUE;
+ }
+
+ list = t_strsplit_tabescaped(value);
+
+ cached_pw = list[0];
+ if (*cached_pw == '\0') {
+ /* NULL password */
+ e_info(authdb_event(request),
+ "Cached NULL password access");
+ ret = PASSDB_RESULT_OK;
+ } else if (request->set->cache_verify_password_with_worker) {
+ string_t *str;
+
+ str = t_str_new(128);
+ str_printfa(str, "PASSW\t%u\t", request->passdb->passdb->id);
+ str_append_tabescaped(str, password);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, cached_pw);
+ str_append_c(str, '\t');
+ auth_request_export(request, str);
+
+ e_debug(authdb_event(request), "cache: "
+ "validating password on worker");
+ auth_request_ref(request);
+ /* Save the extra fields already here, and take a snapshot.
+ If verification fails, roll back fields. */
+ auth_request_set_fields(request, list + 1, NULL);
+ auth_fields_snapshot(request->fields.extra_fields);
+ auth_worker_call(request->pool, request->fields.user, str_c(str),
+ passdb_cache_verify_plain_callback, request);
+ return TRUE;
+ } else {
+ scheme = password_get_scheme(&cached_pw);
+ i_assert(scheme != NULL);
+
+ ret = auth_request_password_verify_log(request, password, cached_pw,
+ scheme, AUTH_SUBSYS_DB,
+ !(node->last_success || neg_expired));
+
+ if (ret == PASSDB_RESULT_PASSWORD_MISMATCH &&
+ (node->last_success || neg_expired)) {
+ /* a) the last authentication was successful. assume
+ that the password was changed and cache is expired.
+ b) negative TTL reached, use it for password
+ mismatches too. */
+ node->last_success = FALSE;
+ return FALSE;
+ }
+ }
+ node->last_success = ret == PASSDB_RESULT_OK;
+
+ /* save the extra_fields only after we know we're using the
+ cached data */
+ auth_request_set_fields(request, list + 1, NULL);
+
+ *result_r = ret;
+
+ auth_request_verify_plain_callback_finish(*result_r, request);
+ return TRUE;
+}
+
+bool passdb_cache_lookup_credentials(struct auth_request *request,
+ const char *key, const char **password_r,
+ const char **scheme_r,
+ enum passdb_result *result_r,
+ bool use_expired)
+{
+ const char *value, *const *list;
+ struct auth_cache_node *node;
+ bool neg_expired;
+
+ if (passdb_cache == NULL)
+ return FALSE;
+
+ if (!passdb_cache_lookup(request, key, use_expired,
+ &node, &value, &neg_expired))
+ return FALSE;
+
+ if (*value == '\0') {
+ /* negative cache entry */
+ *result_r = PASSDB_RESULT_USER_UNKNOWN;
+ *password_r = NULL;
+ *scheme_r = NULL;
+ return TRUE;
+ }
+
+ list = t_strsplit_tabescaped(value);
+ auth_request_set_fields(request, list + 1, NULL);
+
+ *result_r = PASSDB_RESULT_OK;
+ *password_r = *list[0] == '\0' ? NULL : list[0];
+ *scheme_r = password_get_scheme(password_r);
+ i_assert(*scheme_r != NULL || *password_r == NULL);
+ return TRUE;
+}
+
+void passdb_cache_init(const struct auth_settings *set)
+{
+ rlim_t limit;
+
+ if (set->cache_size == 0 || set->cache_ttl == 0)
+ return;
+
+ if (restrict_get_process_size(&limit) == 0 &&
+ set->cache_size > (uoff_t)limit) {
+ i_warning("auth_cache_size (%"PRIuUOFF_T"M) is higher than "
+ "process VSZ limit (%"PRIuUOFF_T"M)",
+ set->cache_size/1024/1024,
+ (uoff_t)(limit/1024/1024));
+ }
+ passdb_cache = auth_cache_new(set->cache_size, set->cache_ttl,
+ set->cache_negative_ttl);
+}
+
+void passdb_cache_deinit(void)
+{
+ if (passdb_cache != NULL)
+ auth_cache_free(&passdb_cache);
+}
diff --git a/src/auth/passdb-cache.h b/src/auth/passdb-cache.h
new file mode 100644
index 0000000..1b9ff08
--- /dev/null
+++ b/src/auth/passdb-cache.h
@@ -0,0 +1,21 @@
+#ifndef PASSDB_CACHE_H
+#define PASSDB_CACHE_H
+
+#include "auth-cache.h"
+#include "passdb.h"
+
+extern struct auth_cache *passdb_cache;
+
+bool passdb_cache_verify_plain(struct auth_request *request, const char *key,
+ const char *password,
+ enum passdb_result *result_r, bool use_expired);
+bool passdb_cache_lookup_credentials(struct auth_request *request,
+ const char *key, const char **password_r,
+ const char **scheme_r,
+ enum passdb_result *result_r,
+ bool use_expired);
+
+void passdb_cache_init(const struct auth_settings *set);
+void passdb_cache_deinit(void);
+
+#endif
diff --git a/src/auth/passdb-checkpassword.c b/src/auth/passdb-checkpassword.c
new file mode 100644
index 0000000..101c7f0
--- /dev/null
+++ b/src/auth/passdb-checkpassword.c
@@ -0,0 +1,153 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#ifdef PASSDB_CHECKPASSWORD
+
+#include "password-scheme.h"
+#include "db-checkpassword.h"
+
+struct checkpassword_passdb_module {
+ struct passdb_module module;
+ struct db_checkpassword *db;
+};
+
+static void
+auth_checkpassword_callback(struct auth_request *request,
+ enum db_checkpassword_status status,
+ const char *const *extra_fields,
+ verify_plain_callback_t *callback)
+{
+ const char *scheme, *crypted_pass = NULL;
+ unsigned int i;
+
+ switch (status) {
+ case DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE:
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
+ return;
+ case DB_CHECKPASSWORD_STATUS_FAILURE:
+ callback(PASSDB_RESULT_PASSWORD_MISMATCH, request);
+ return;
+ case DB_CHECKPASSWORD_STATUS_OK:
+ break;
+ }
+ for (i = 0; extra_fields[i] != NULL; i++) {
+ if (str_begins(extra_fields[i], "password="))
+ crypted_pass = extra_fields[i]+9;
+ else if (extra_fields[i][0] != '\0') {
+ auth_request_set_field_keyvalue(request,
+ extra_fields[i], NULL);
+ }
+ }
+ if (crypted_pass != NULL) {
+ /* for cache */
+ scheme = password_get_scheme(&crypted_pass);
+ if (scheme != NULL) {
+ auth_request_set_field(request, "password",
+ crypted_pass, scheme);
+ } else {
+ e_error(authdb_event(request),
+ "password field returned without {scheme} prefix");
+ }
+ }
+ callback(PASSDB_RESULT_OK, request);
+}
+
+static void
+checkpassword_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct passdb_module *_module = request->passdb->passdb;
+ struct checkpassword_passdb_module *module =
+ (struct checkpassword_passdb_module *)_module;
+
+ db_checkpassword_call(module->db, request, password,
+ auth_checkpassword_callback, callback);
+}
+
+static void
+credentials_checkpassword_callback(struct auth_request *request,
+ enum db_checkpassword_status status,
+ const char *const *extra_fields,
+ lookup_credentials_callback_t *callback)
+{
+ const char *scheme, *crypted_pass = NULL;
+ unsigned int i;
+
+ switch (status) {
+ case DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE:
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, NULL, 0, request);
+ return;
+ case DB_CHECKPASSWORD_STATUS_FAILURE:
+ callback(PASSDB_RESULT_USER_UNKNOWN, NULL, 0, request);
+ return;
+ case DB_CHECKPASSWORD_STATUS_OK:
+ break;
+ }
+ for (i = 0; extra_fields[i] != NULL; i++) {
+ if (str_begins(extra_fields[i], "password="))
+ crypted_pass = extra_fields[i]+9;
+ else if (extra_fields[i][0] != '\0') {
+ auth_request_set_field_keyvalue(request,
+ extra_fields[i], NULL);
+ }
+ }
+ scheme = password_get_scheme(&crypted_pass);
+ if (scheme == NULL)
+ scheme = request->wanted_credentials_scheme;
+
+ passdb_handle_credentials(PASSDB_RESULT_OK, crypted_pass, scheme,
+ callback, request);
+}
+
+static void
+checkpassword_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ struct passdb_module *_module = request->passdb->passdb;
+ struct checkpassword_passdb_module *module =
+ (struct checkpassword_passdb_module *)_module;
+
+ db_checkpassword_call(module->db, request, NULL,
+ credentials_checkpassword_callback, callback);
+}
+
+static struct passdb_module *
+checkpassword_preinit(pool_t pool, const char *args)
+{
+ struct checkpassword_passdb_module *module;
+ const char *checkpassword_path = args;
+ const char *checkpassword_reply_path =
+ PKG_LIBEXECDIR"/checkpassword-reply";
+
+ module = p_new(pool, struct checkpassword_passdb_module, 1);
+ module->db = db_checkpassword_init(checkpassword_path,
+ checkpassword_reply_path);
+ return &module->module;
+}
+
+static void checkpassword_deinit(struct passdb_module *_module)
+{
+ struct checkpassword_passdb_module *module =
+ (struct checkpassword_passdb_module *)_module;
+
+ db_checkpassword_deinit(&module->db);
+}
+
+struct passdb_module_interface passdb_checkpassword = {
+ "checkpassword",
+
+ checkpassword_preinit,
+ NULL,
+ checkpassword_deinit,
+
+ checkpassword_verify_plain,
+ checkpassword_lookup_credentials,
+ NULL
+};
+#else
+struct passdb_module_interface passdb_checkpassword = {
+ .name = "checkpassword"
+};
+#endif
diff --git a/src/auth/passdb-dict.c b/src/auth/passdb-dict.c
new file mode 100644
index 0000000..31eea93
--- /dev/null
+++ b/src/auth/passdb-dict.c
@@ -0,0 +1,184 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#include "array.h"
+#include "str.h"
+#include "var-expand.h"
+#include "dict.h"
+#include "password-scheme.h"
+#include "auth-cache.h"
+#include "db-dict.h"
+
+#include <string.h>
+
+struct dict_passdb_module {
+ struct passdb_module module;
+
+ struct dict_connection *conn;
+};
+
+struct passdb_dict_request {
+ struct auth_request *auth_request;
+ union {
+ verify_plain_callback_t *verify_plain;
+ lookup_credentials_callback_t *lookup_credentials;
+ } callback;
+};
+
+static int
+dict_query_save_results(struct auth_request *auth_request,
+ struct dict_connection *conn,
+ struct db_dict_value_iter *iter)
+{
+ const char *key, *value, *error;
+
+ while (db_dict_value_iter_next(iter, &key, &value)) {
+ if (value != NULL) {
+ auth_request_set_field(auth_request, key, value,
+ conn->set.default_pass_scheme);
+ }
+ }
+ if (db_dict_value_iter_deinit(&iter, &error) < 0) {
+ e_error(authdb_event(auth_request), "%s", error);
+ return -1;
+ }
+ return 0;
+}
+
+static enum passdb_result
+passdb_dict_lookup_key(struct auth_request *auth_request,
+ struct dict_passdb_module *module)
+{
+ struct db_dict_value_iter *iter;
+ int ret;
+
+ ret = db_dict_value_iter_init(module->conn, auth_request,
+ &module->conn->set.passdb_fields,
+ &module->conn->set.parsed_passdb_objects,
+ &iter);
+ if (ret < 0)
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ else if (ret == 0) {
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ return PASSDB_RESULT_USER_UNKNOWN;
+ } else {
+ if (dict_query_save_results(auth_request, module->conn, iter) < 0)
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+
+ if (auth_request->passdb_password == NULL &&
+ !auth_fields_exists(auth_request->fields.extra_fields,
+ "nopassword")) {
+ return auth_request_password_missing(auth_request);
+ } else {
+ return PASSDB_RESULT_OK;
+ }
+ }
+}
+
+static void passdb_dict_lookup_pass(struct passdb_dict_request *dict_request)
+{
+ struct auth_request *auth_request = dict_request->auth_request;
+ struct passdb_module *_module = auth_request->passdb->passdb;
+ struct dict_passdb_module *module =
+ (struct dict_passdb_module *)_module;
+ const char *password = NULL, *scheme = NULL;
+ enum passdb_result passdb_result;
+
+ if (array_count(&module->conn->set.passdb_fields) == 0 &&
+ array_count(&module->conn->set.parsed_passdb_objects) == 0) {
+ e_error(authdb_event(auth_request),
+ "No passdb_objects or passdb_fields specified");
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ } else {
+ passdb_result = passdb_dict_lookup_key(auth_request, module);
+ }
+
+ if (passdb_result == PASSDB_RESULT_OK) {
+ /* passdb_password may change on the way,
+ so we'll need to strdup. */
+ password = t_strdup(auth_request->passdb_password);
+ scheme = password_get_scheme(&password);
+ /* auth_request_set_field() sets scheme */
+ i_assert(password == NULL || scheme != NULL);
+ }
+
+ if (auth_request->wanted_credentials_scheme != NULL) {
+ passdb_handle_credentials(passdb_result, password, scheme,
+ dict_request->callback.lookup_credentials,
+ auth_request);
+ } else {
+ if (password != NULL) {
+ passdb_result =
+ auth_request_password_verify(auth_request,
+ auth_request->mech_password,
+ password, scheme, AUTH_SUBSYS_DB);
+ }
+
+ dict_request->callback.verify_plain(passdb_result,
+ auth_request);
+ }
+}
+
+static void dict_verify_plain(struct auth_request *request,
+ const char *password ATTR_UNUSED,
+ verify_plain_callback_t *callback)
+{
+ struct passdb_dict_request *dict_request;
+
+ dict_request = p_new(request->pool, struct passdb_dict_request, 1);
+ dict_request->auth_request = request;
+ dict_request->callback.verify_plain = callback;
+
+ passdb_dict_lookup_pass(dict_request);
+}
+
+static void dict_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ struct passdb_dict_request *dict_request;
+
+ dict_request = p_new(request->pool, struct passdb_dict_request, 1);
+ dict_request->auth_request = request;
+ dict_request->callback.lookup_credentials = callback;
+
+ passdb_dict_lookup_pass(dict_request);
+}
+
+static struct passdb_module *
+passdb_dict_preinit(pool_t pool, const char *args)
+{
+ struct dict_passdb_module *module;
+ struct dict_connection *conn;
+
+ module = p_new(pool, struct dict_passdb_module, 1);
+ module->conn = conn = db_dict_init(args);
+
+ module->module.blocking = TRUE;
+ module->module.default_cache_key = auth_cache_parse_key(pool,
+ db_dict_parse_cache_key(&conn->set.keys, &conn->set.passdb_fields,
+ &conn->set.parsed_passdb_objects));
+ module->module.default_pass_scheme = conn->set.default_pass_scheme;
+ return &module->module;
+}
+
+static void passdb_dict_deinit(struct passdb_module *_module)
+{
+ struct dict_passdb_module *module =
+ (struct dict_passdb_module *)_module;
+
+ db_dict_unref(&module->conn);
+}
+
+struct passdb_module_interface passdb_dict = {
+ "dict",
+
+ passdb_dict_preinit,
+ NULL,
+ passdb_dict_deinit,
+
+ dict_verify_plain,
+ dict_lookup_credentials,
+ NULL
+};
diff --git a/src/auth/passdb-imap.c b/src/auth/passdb-imap.c
new file mode 100644
index 0000000..9965732
--- /dev/null
+++ b/src/auth/passdb-imap.c
@@ -0,0 +1,241 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "ioloop.h"
+#include "passdb.h"
+#include "str.h"
+#include "imap-resp-code.h"
+#include "imapc-client.h"
+
+#define IMAP_DEFAULT_PORT 143
+#define IMAPS_DEFAULT_PORT 993
+#define DNS_CLIENT_SOCKET_NAME "dns-client"
+
+struct imap_passdb_module {
+ struct passdb_module module;
+ struct imapc_client_settings set;
+ bool set_have_vars;
+};
+
+struct imap_auth_request {
+ struct imapc_client *client;
+ struct auth_request *auth_request;
+ verify_plain_callback_t *verify_callback;
+ struct timeout *to_free;
+};
+
+static enum passdb_result
+passdb_imap_get_failure_result(const struct imapc_command_reply *reply)
+{
+ const char *key = reply->resp_text_key;
+
+ if (key == NULL)
+ return PASSDB_RESULT_PASSWORD_MISMATCH;
+
+ if (strcasecmp(key, IMAP_RESP_CODE_AUTHFAILED) == 0 ||
+ strcasecmp(key, IMAP_RESP_CODE_AUTHZFAILED) == 0)
+ return PASSDB_RESULT_PASSWORD_MISMATCH;
+ if (strcasecmp(key, IMAP_RESP_CODE_EXPIRED) == 0)
+ return PASSDB_RESULT_PASS_EXPIRED;
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+}
+
+static void passdb_imap_login_free(struct imap_auth_request *request)
+{
+ timeout_remove(&request->to_free);
+ imapc_client_deinit(&request->client);
+ auth_request_unref(&request->auth_request);
+}
+
+static void
+passdb_imap_login_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imap_auth_request *request = context;
+ enum passdb_result result = PASSDB_RESULT_INTERNAL_FAILURE;
+
+ switch (reply->state) {
+ case IMAPC_COMMAND_STATE_OK:
+ result = PASSDB_RESULT_OK;
+ break;
+ case IMAPC_COMMAND_STATE_NO:
+ result = passdb_imap_get_failure_result(reply);
+ e_info(authdb_event(request->auth_request),
+ "%s", reply->text_full);
+ break;
+ case IMAPC_COMMAND_STATE_AUTH_FAILED:
+ case IMAPC_COMMAND_STATE_BAD:
+ case IMAPC_COMMAND_STATE_DISCONNECTED:
+ e_error(authdb_event(request->auth_request),
+ "%s", reply->text_full);
+ break;
+ }
+ request->verify_callback(result, request->auth_request);
+ /* imapc_client can't be freed in this callback, so do it in a
+ separate callback. FIXME: remove this once imapc supports proper
+ refcounting. */
+ request->to_free = timeout_add_short(0, passdb_imap_login_free, request);
+}
+
+static void
+passdb_imap_verify_plain(struct auth_request *auth_request,
+ const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct passdb_module *_module = auth_request->passdb->passdb;
+ struct imap_passdb_module *module =
+ (struct imap_passdb_module *)_module;
+ struct imap_auth_request *request;
+ struct imapc_client_settings set;
+ const char *error;
+ string_t *str;
+
+ set = module->set;
+ set.debug = event_want_debug(auth_request->event);
+ set.dns_client_socket_path =
+ t_strconcat(auth_request->set->base_dir, "/",
+ DNS_CLIENT_SOCKET_NAME, NULL);
+ set.password = password;
+ set.max_idle_time = IMAPC_DEFAULT_MAX_IDLE_TIME;
+ if (set.ssl_set.ca_dir == NULL)
+ set.ssl_set.ca_dir = auth_request->set->ssl_client_ca_dir;
+ if (set.ssl_set.ca_file == NULL)
+ set.ssl_set.ca_file = auth_request->set->ssl_client_ca_file;
+
+ if (module->set_have_vars) {
+ str = t_str_new(128);
+ if (auth_request_var_expand(str, set.username, auth_request,
+ NULL, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand username=%s: %s",
+ set.username, error);
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ }
+ set.username = t_strdup(str_c(str));
+
+ str_truncate(str, 0);
+ if (auth_request_var_expand(str, set.host, auth_request,
+ NULL, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand host=%s: %s",
+ set.host, error);
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ }
+ set.host = t_strdup(str_c(str));
+ }
+ e_debug(authdb_event(auth_request),
+ "lookup host=%s port=%d", set.host, set.port);
+
+ request = p_new(auth_request->pool, struct imap_auth_request, 1);
+ request->client = imapc_client_init(&set, authdb_event(auth_request));
+ request->auth_request = auth_request;
+ request->verify_callback = callback;
+
+ auth_request_ref(auth_request);
+ imapc_client_set_login_callback(request->client, passdb_imap_login_callback, request);
+ imapc_client_login(request->client);
+}
+
+static struct passdb_module *
+passdb_imap_preinit(pool_t pool, const char *args)
+{
+ struct imap_passdb_module *module;
+ char **tmp;
+ const char *key, *value;
+ bool port_set = FALSE;
+
+ module = p_new(pool, struct imap_passdb_module, 1);
+ module->module.default_pass_scheme = "PLAIN";
+ module->set.port = IMAP_DEFAULT_PORT;
+ module->set.ssl_mode = IMAPC_CLIENT_SSL_MODE_NONE;
+ module->set.username = "%u";
+ module->set.rawlog_dir = "";
+
+ for (tmp = p_strsplit(pool, args, " "); *tmp != NULL; tmp++) {
+ key = *tmp;
+ value = strchr(key, '=');
+ if (value == NULL)
+ value = "";
+ else
+ key = t_strdup_until(key, value++);
+ if (strcmp(key, "host") == 0)
+ module->set.host = value;
+ else if (strcmp(key, "port") == 0) {
+ if (net_str2port(value, &module->set.port) < 0)
+ i_fatal("passdb imap: Invalid port: %s", value);
+ port_set = TRUE;
+ } else if (strcmp(key, "username") == 0)
+ module->set.username = value;
+ else if (strcmp(key, "ssl_ca_dir") == 0)
+ module->set.ssl_set.ca_dir = value;
+ else if (strcmp(key, "ssl_ca_file") == 0)
+ module->set.ssl_set.ca_file = value;
+ else if (strcmp(key, "rawlog_dir") == 0)
+ module->set.rawlog_dir = value;
+ else if (strcmp(key, "ssl") == 0) {
+ if (strcmp(value, "imaps") == 0) {
+ if (!port_set)
+ module->set.port = IMAPS_DEFAULT_PORT;
+ module->set.ssl_mode =
+ IMAPC_CLIENT_SSL_MODE_IMMEDIATE;
+ } else if (strcmp(value, "starttls") == 0) {
+ module->set.ssl_mode =
+ IMAPC_CLIENT_SSL_MODE_STARTTLS;
+ } else {
+ i_fatal("passdb imap: Invalid ssl mode: %s",
+ value);
+ }
+ } else if (strcmp(key, "allow_invalid_cert") == 0) {
+ if (strcmp(value, "yes") == 0) {
+ module->set.ssl_set.allow_invalid_cert = TRUE;
+ } else if (strcmp(value, "no") == 0) {
+ module->set.ssl_set.allow_invalid_cert = FALSE;
+ } else {
+ i_fatal("passdb imap: Invalid allow_invalid_cert value: %s",
+ value);
+ }
+ } else {
+ i_fatal("passdb imap: Unknown parameter: %s", key);
+ }
+ }
+
+ if (!module->set.ssl_set.allow_invalid_cert && module->set.ssl_mode != IMAPC_CLIENT_SSL_MODE_NONE) {
+ if (module->set.ssl_set.ca_dir == NULL && module->set.ssl_set.ca_file == NULL)
+ i_fatal("passdb imap: Cannot verify certificate without ssl_ca_dir or ssl_ca_file setting");
+ }
+
+ if (module->set.host == NULL)
+ i_fatal("passdb imap: Missing host parameter");
+
+ module->set_have_vars =
+ strchr(module->set.username, '%') != NULL ||
+ strchr(module->set.host, '%') != NULL;
+ return &module->module;
+}
+
+static struct passdb_module_interface passdb_imap_plugin = {
+ "imap",
+
+ passdb_imap_preinit,
+ NULL,
+ NULL,
+
+ passdb_imap_verify_plain,
+ NULL,
+ NULL
+};
+
+void authdb_imap_init(void);
+void authdb_imap_deinit(void);
+
+void authdb_imap_init(void)
+{
+ passdb_register_module(&passdb_imap_plugin);
+
+}
+void authdb_imap_deinit(void)
+{
+ passdb_unregister_module(&passdb_imap_plugin);
+}
diff --git a/src/auth/passdb-ldap.c b/src/auth/passdb-ldap.c
new file mode 100644
index 0000000..1aa4b28
--- /dev/null
+++ b/src/auth/passdb-ldap.c
@@ -0,0 +1,517 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#if defined(PASSDB_LDAP) && (defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD))
+
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "password-scheme.h"
+#include "auth-cache.h"
+#include "db-ldap.h"
+
+#include <ldap.h>
+
+struct ldap_passdb_module {
+ struct passdb_module module;
+
+ struct ldap_connection *conn;
+};
+
+struct passdb_ldap_request {
+ union {
+ struct ldap_request ldap;
+ struct ldap_request_search search;
+ struct ldap_request_bind bind;
+ } request;
+ const char *dn;
+
+ union {
+ verify_plain_callback_t *verify_plain;
+ lookup_credentials_callback_t *lookup_credentials;
+ } callback;
+
+ unsigned int entries;
+ bool require_password;
+};
+
+static void
+ldap_query_save_result(struct ldap_connection *conn,
+ struct auth_request *auth_request,
+ struct ldap_request_search *ldap_request,
+ LDAPMessage *res)
+{
+ struct db_ldap_result_iterate_context *ldap_iter;
+ const char *name, *const *values;
+
+ ldap_iter = db_ldap_result_iterate_init(conn, ldap_request, res, FALSE);
+ while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) {
+ if (values[0] == NULL) {
+ auth_request_set_null_field(auth_request, name);
+ continue;
+ }
+ if (values[1] != NULL) {
+ e_warning(authdb_event(auth_request),
+ "Multiple values found for '%s', "
+ "using value '%s'", name, values[0]);
+ }
+ auth_request_set_field(auth_request, name, values[0],
+ conn->set.default_pass_scheme);
+ }
+ db_ldap_result_iterate_deinit(&ldap_iter);
+}
+
+static void
+ldap_lookup_finish(struct auth_request *auth_request,
+ struct passdb_ldap_request *ldap_request,
+ LDAPMessage *res)
+{
+ enum passdb_result passdb_result;
+ const char *password = NULL, *scheme;
+
+ if (res == NULL) {
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ } else if (ldap_request->entries == 0) {
+ passdb_result = PASSDB_RESULT_USER_UNKNOWN;
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ } else if (ldap_request->entries > 1) {
+ e_error(authdb_event(auth_request),
+ "pass_filter matched multiple objects, aborting");
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ } else if (auth_request->passdb_password == NULL &&
+ ldap_request->require_password &&
+ !auth_fields_exists(auth_request->fields.extra_fields, "nopassword")) {
+ passdb_result = auth_request_password_missing(auth_request);
+ } else {
+ /* passdb_password may change on the way,
+ so we'll need to strdup. */
+ password = t_strdup(auth_request->passdb_password);
+ passdb_result = PASSDB_RESULT_OK;
+ }
+
+ scheme = password_get_scheme(&password);
+ /* auth_request_set_field() sets scheme */
+ i_assert(password == NULL || scheme != NULL);
+
+ if (auth_request->wanted_credentials_scheme != NULL) {
+ passdb_handle_credentials(passdb_result, password, scheme,
+ ldap_request->callback.lookup_credentials,
+ auth_request);
+ } else {
+ if (password != NULL) {
+ passdb_result =
+ auth_request_password_verify(auth_request,
+ auth_request->mech_password,
+ password, scheme, AUTH_SUBSYS_DB);
+ }
+
+ ldap_request->callback.verify_plain(passdb_result,
+ auth_request);
+ }
+}
+
+static void
+ldap_lookup_pass_callback(struct ldap_connection *conn,
+ struct ldap_request *request, LDAPMessage *res)
+{
+ struct passdb_ldap_request *ldap_request =
+ (struct passdb_ldap_request *)request;
+ struct auth_request *auth_request = request->auth_request;
+
+ if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) {
+ ldap_lookup_finish(auth_request, ldap_request, res);
+ auth_request_unref(&auth_request);
+ return;
+ }
+
+ if (ldap_request->entries++ == 0) {
+ /* first entry */
+ ldap_query_save_result(conn, auth_request,
+ &ldap_request->request.search, res);
+ }
+}
+
+static void
+ldap_auth_bind_callback(struct ldap_connection *conn,
+ struct ldap_request *ldap_request, LDAPMessage *res)
+{
+ struct passdb_ldap_request *passdb_ldap_request =
+ (struct passdb_ldap_request *)ldap_request;
+ struct auth_request *auth_request = ldap_request->auth_request;
+ enum passdb_result passdb_result;
+ int ret;
+
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+
+ if (res != NULL) {
+ ret = ldap_result2error(conn->ld, res, 0);
+ if (ret == LDAP_SUCCESS)
+ passdb_result = PASSDB_RESULT_OK;
+ else if (ret == LDAP_INVALID_CREDENTIALS) {
+ auth_request_log_login_failure(auth_request,
+ AUTH_SUBSYS_DB,
+ AUTH_LOG_MSG_PASSWORD_MISMATCH" (for LDAP bind)");
+ passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH;
+ } else if (ret == LDAP_NO_SUCH_OBJECT) {
+ passdb_result = PASSDB_RESULT_USER_UNKNOWN;
+ auth_request_log_unknown_user(auth_request,
+ AUTH_SUBSYS_DB);
+ } else {
+ e_error(authdb_event(auth_request),
+ "ldap_bind() failed: %s",
+ ldap_err2string(ret));
+ }
+ }
+
+ passdb_ldap_request->callback.
+ verify_plain(passdb_result, auth_request);
+ auth_request_unref(&auth_request);
+}
+
+static void ldap_auth_bind(struct ldap_connection *conn,
+ struct ldap_request_bind *brequest)
+{
+ struct passdb_ldap_request *passdb_ldap_request =
+ (struct passdb_ldap_request *)brequest;
+ struct auth_request *auth_request = brequest->request.auth_request;
+
+ if (*auth_request->mech_password == '\0') {
+ /* Assume that empty password fails. This is especially
+ important with Windows 2003 AD, which always returns success
+ with empty passwords. */
+ e_info(authdb_event(auth_request),
+ "Login attempt with empty password");
+ passdb_ldap_request->callback.
+ verify_plain(PASSDB_RESULT_PASSWORD_MISMATCH,
+ auth_request);
+ return;
+ }
+
+ brequest->request.callback = ldap_auth_bind_callback;
+ db_ldap_request(conn, &brequest->request);
+}
+
+static void passdb_ldap_request_fail(struct passdb_ldap_request *request,
+ enum passdb_result passdb_result)
+{
+ struct auth_request *auth_request = request->request.ldap.auth_request;
+
+ if (auth_request->wanted_credentials_scheme != NULL) {
+ request->callback.lookup_credentials(passdb_result, NULL, 0,
+ auth_request);
+ } else {
+ request->callback.verify_plain(passdb_result, auth_request);
+ }
+ auth_request_unref(&auth_request);
+}
+
+static void
+ldap_bind_lookup_dn_fail(struct auth_request *auth_request,
+ struct passdb_ldap_request *request,
+ LDAPMessage *res)
+{
+ enum passdb_result passdb_result;
+
+ if (res == NULL)
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ else if (request->entries == 0) {
+ passdb_result = PASSDB_RESULT_USER_UNKNOWN;
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ } else {
+ i_assert(request->entries > 1);
+ e_error(authdb_event(auth_request),
+ "pass_filter matched multiple objects, aborting");
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ passdb_ldap_request_fail(request, passdb_result);
+}
+
+static void ldap_bind_lookup_dn_callback(struct ldap_connection *conn,
+ struct ldap_request *ldap_request,
+ LDAPMessage *res)
+{
+ struct passdb_ldap_request *passdb_ldap_request =
+ (struct passdb_ldap_request *)ldap_request;
+ struct auth_request *auth_request = ldap_request->auth_request;
+ struct passdb_ldap_request *brequest;
+ char *dn;
+
+ if (res != NULL && ldap_msgtype(res) == LDAP_RES_SEARCH_ENTRY) {
+ if (passdb_ldap_request->entries++ > 0) {
+ /* too many replies */
+ return;
+ }
+
+ /* first entry */
+ ldap_query_save_result(conn, auth_request,
+ &passdb_ldap_request->request.search, res);
+
+ /* save dn */
+ dn = ldap_get_dn(conn->ld, res);
+ passdb_ldap_request->dn = p_strdup(auth_request->pool, dn);
+ ldap_memfree(dn);
+ } else if (res == NULL || passdb_ldap_request->entries != 1) {
+ /* failure */
+ ldap_bind_lookup_dn_fail(auth_request, passdb_ldap_request, res);
+ } else if (auth_request->fields.skip_password_check) {
+ /* we've already verified that the password matched -
+ we just wanted to get any extra fields */
+ passdb_ldap_request->callback.
+ verify_plain(PASSDB_RESULT_OK, auth_request);
+ auth_request_unref(&auth_request);
+ } else {
+ /* create a new bind request */
+ brequest = p_new(auth_request->pool,
+ struct passdb_ldap_request, 1);
+ brequest->dn = passdb_ldap_request->dn;
+ brequest->callback = passdb_ldap_request->callback;
+ brequest->request.bind.dn = brequest->dn;
+ brequest->request.bind.request.type = LDAP_REQUEST_TYPE_BIND;
+ brequest->request.bind.request.auth_request = auth_request;
+
+ ldap_auth_bind(conn, &brequest->request.bind);
+ }
+}
+
+static void ldap_lookup_pass(struct auth_request *auth_request,
+ struct passdb_ldap_request *request,
+ bool require_password)
+{
+ struct passdb_module *_module = auth_request->passdb->passdb;
+ struct ldap_passdb_module *module =
+ (struct ldap_passdb_module *)_module;
+ struct ldap_connection *conn = module->conn;
+ struct ldap_request_search *srequest = &request->request.search;
+ const char **attr_names = (const char **)conn->pass_attr_names;
+ const char *error;
+ string_t *str;
+
+ request->require_password = require_password;
+ srequest->request.type = LDAP_REQUEST_TYPE_SEARCH;
+
+ str = t_str_new(512);
+ if (auth_request_var_expand(str, conn->set.base, auth_request,
+ ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand base=%s: %s", conn->set.base, error);
+ passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE);
+ return;
+ }
+ srequest->base = p_strdup(auth_request->pool, str_c(str));
+
+ str_truncate(str, 0);
+ if (auth_request_var_expand(str, conn->set.pass_filter,
+ auth_request, ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand pass_filter=%s: %s",
+ conn->set.pass_filter, error);
+ passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE);
+ return;
+ }
+ srequest->filter = p_strdup(auth_request->pool, str_c(str));
+ srequest->attr_map = &conn->pass_attr_map;
+ srequest->attributes = conn->pass_attr_names;
+
+ e_debug(authdb_event(auth_request), "pass search: "
+ "base=%s scope=%s filter=%s fields=%s",
+ srequest->base, conn->set.scope,
+ srequest->filter, attr_names == NULL ? "(all)" :
+ t_strarray_join(attr_names, ","));
+
+ srequest->request.callback = ldap_lookup_pass_callback;
+ db_ldap_request(conn, &srequest->request);
+}
+
+static void ldap_bind_lookup_dn(struct auth_request *auth_request,
+ struct passdb_ldap_request *request)
+{
+ struct passdb_module *_module = auth_request->passdb->passdb;
+ struct ldap_passdb_module *module =
+ (struct ldap_passdb_module *)_module;
+ struct ldap_connection *conn = module->conn;
+ struct ldap_request_search *srequest = &request->request.search;
+ const char *error;
+ string_t *str;
+
+ srequest->request.type = LDAP_REQUEST_TYPE_SEARCH;
+
+ str = t_str_new(512);
+ if (auth_request_var_expand(str, conn->set.base, auth_request,
+ ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand base=%s: %s", conn->set.base, error);
+ passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE);
+ return;
+ }
+ srequest->base = p_strdup(auth_request->pool, str_c(str));
+
+ str_truncate(str, 0);
+ if (auth_request_var_expand(str, conn->set.pass_filter,
+ auth_request, ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand pass_filter=%s: %s",
+ conn->set.pass_filter, error);
+ passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE);
+ return;
+ }
+ srequest->filter = p_strdup(auth_request->pool, str_c(str));
+
+ /* we don't need the attributes to perform authentication, but they
+ may contain some extra parameters. if a password is returned,
+ it's just ignored. */
+ srequest->attr_map = &conn->pass_attr_map;
+ srequest->attributes = conn->pass_attr_names;
+
+ e_debug(authdb_event(auth_request),
+ "bind search: base=%s filter=%s",
+ srequest->base, srequest->filter);
+
+ srequest->request.callback = ldap_bind_lookup_dn_callback;
+ db_ldap_request(conn, &srequest->request);
+}
+
+static void
+ldap_verify_plain_auth_bind_userdn(struct auth_request *auth_request,
+ struct passdb_ldap_request *request)
+{
+ struct passdb_module *_module = auth_request->passdb->passdb;
+ struct ldap_passdb_module *module =
+ (struct ldap_passdb_module *)_module;
+ struct ldap_connection *conn = module->conn;
+ struct ldap_request_bind *brequest = &request->request.bind;
+ string_t *dn;
+ const char *error;
+
+ brequest->request.type = LDAP_REQUEST_TYPE_BIND;
+
+ dn = t_str_new(512);
+ if (auth_request_var_expand(dn, conn->set.auth_bind_userdn,
+ auth_request, ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand auth_bind_userdn=%s: %s",
+ conn->set.auth_bind_userdn, error);
+ passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE);
+ return;
+ }
+
+ brequest->dn = p_strdup(auth_request->pool, str_c(dn));
+ ldap_auth_bind(conn, brequest);
+}
+
+static void
+ldap_verify_plain(struct auth_request *request,
+ const char *password ATTR_UNUSED,
+ verify_plain_callback_t *callback)
+{
+ struct passdb_module *_module = request->passdb->passdb;
+ struct ldap_passdb_module *module =
+ (struct ldap_passdb_module *)_module;
+ struct ldap_connection *conn = module->conn;
+ struct passdb_ldap_request *ldap_request;
+
+ /* reconnect if needed. this is also done by db_ldap_search(), but
+ with auth binds we'll have to do it ourself */
+ if (db_ldap_connect(conn)< 0) {
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
+ return;
+ }
+
+ ldap_request = p_new(request->pool, struct passdb_ldap_request, 1);
+ ldap_request->callback.verify_plain = callback;
+
+ auth_request_ref(request);
+ ldap_request->request.ldap.auth_request = request;
+
+ if (!conn->set.auth_bind)
+ ldap_lookup_pass(request, ldap_request, TRUE);
+ else if (conn->set.auth_bind_userdn == NULL)
+ ldap_bind_lookup_dn(request, ldap_request);
+ else
+ ldap_verify_plain_auth_bind_userdn(request, ldap_request);
+}
+
+static void ldap_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ struct passdb_module *_module = request->passdb->passdb;
+ struct ldap_passdb_module *module =
+ (struct ldap_passdb_module *)_module;
+ struct passdb_ldap_request *ldap_request;
+ bool require_password;
+
+ ldap_request = p_new(request->pool, struct passdb_ldap_request, 1);
+ ldap_request->callback.lookup_credentials = callback;
+
+ auth_request_ref(request);
+ ldap_request->request.ldap.auth_request = request;
+
+ /* with auth_bind=yes we don't necessarily have a password.
+ this will fail actual password credentials lookups, but it's fine
+ for passdb lookups done by lmtp/doveadm */
+ require_password = !module->conn->set.auth_bind;
+ ldap_lookup_pass(request, ldap_request, require_password);
+}
+
+static struct passdb_module *
+passdb_ldap_preinit(pool_t pool, const char *args)
+{
+ struct ldap_passdb_module *module;
+ struct ldap_connection *conn;
+
+ module = p_new(pool, struct ldap_passdb_module, 1);
+ module->conn = conn = db_ldap_init(args, FALSE);
+ p_array_init(&conn->pass_attr_map, pool, 16);
+ db_ldap_set_attrs(conn, conn->set.pass_attrs, &conn->pass_attr_names,
+ &conn->pass_attr_map,
+ conn->set.auth_bind ? "password" : NULL);
+ module->module.blocking = conn->set.blocking;
+ module->module.default_cache_key =
+ auth_cache_parse_key(pool,
+ t_strconcat(conn->set.base,
+ conn->set.pass_attrs,
+ conn->set.pass_filter, NULL));
+ module->module.default_pass_scheme = conn->set.default_pass_scheme;
+ return &module->module;
+}
+
+static void passdb_ldap_init(struct passdb_module *_module)
+{
+ struct ldap_passdb_module *module =
+ (struct ldap_passdb_module *)_module;
+
+ if (!module->module.blocking || worker)
+ db_ldap_connect_delayed(module->conn);
+}
+
+static void passdb_ldap_deinit(struct passdb_module *_module)
+{
+ struct ldap_passdb_module *module =
+ (struct ldap_passdb_module *)_module;
+
+ db_ldap_unref(&module->conn);
+}
+
+#ifndef PLUGIN_BUILD
+struct passdb_module_interface passdb_ldap =
+#else
+struct passdb_module_interface passdb_ldap_plugin =
+#endif
+{
+ "ldap",
+
+ passdb_ldap_preinit,
+ passdb_ldap_init,
+ passdb_ldap_deinit,
+
+ ldap_verify_plain,
+ ldap_lookup_credentials,
+ NULL
+};
+#else
+struct passdb_module_interface passdb_ldap = {
+ .name = "ldap"
+};
+#endif
diff --git a/src/auth/passdb-lua.c b/src/auth/passdb-lua.c
new file mode 100644
index 0000000..13665c4
--- /dev/null
+++ b/src/auth/passdb-lua.c
@@ -0,0 +1,193 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+#include "auth-cache.h"
+
+#if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD)
+
+#include "db-lua.h"
+
+struct dlua_passdb_module {
+ struct passdb_module module;
+ struct dlua_script *script;
+ const char *file;
+ bool has_password_verify;
+};
+
+static enum passdb_result
+passdb_lua_verify_password(struct dlua_passdb_module *module,
+ struct auth_request *request, const char *password)
+{
+ const char *error = NULL;
+ enum passdb_result result =
+ auth_lua_call_password_verify(module->script, request,
+ password, &error);
+ if (result == PASSDB_RESULT_PASSWORD_MISMATCH) {
+ auth_request_log_password_mismatch(request, AUTH_SUBSYS_DB);
+ } else if (result == PASSDB_RESULT_INTERNAL_FAILURE && error != NULL) {
+ e_error(authdb_event(request), "passdb-lua: %s",
+ error);
+ }
+ return result;
+}
+
+static enum passdb_result
+passdb_lua_lookup(struct auth_request *request,
+ const char **scheme_r, const char **password_r)
+{
+ const char *error = NULL;
+ enum passdb_result result;
+ struct dlua_passdb_module *module =
+ (struct dlua_passdb_module *)request->passdb->passdb;
+
+ *scheme_r = *password_r = NULL;
+
+ result = auth_lua_call_passdb_lookup(module->script, request, scheme_r,
+ password_r, &error);
+
+ if (result == PASSDB_RESULT_INTERNAL_FAILURE && error != NULL) {
+ e_error(authdb_event(request), "db-lua: %s", error);
+ } else if (result != PASSDB_RESULT_OK) {
+ /* skip next bit */
+ } else if (!auth_fields_exists(request->fields.extra_fields, "nopassword")) {
+ if (*password_r == NULL || **password_r == '\0') {
+ result = auth_request_password_missing(request);
+ } else {
+ if (*scheme_r == NULL)
+ *scheme_r = request->passdb->passdb->default_pass_scheme;
+ auth_request_set_field(request, "password",
+ *password_r, *scheme_r);
+ }
+ } else if (*password_r != NULL && **password_r != '\0') {
+ e_info(authdb_event(request),
+ "nopassword given and password is not empty");
+ result = PASSDB_RESULT_PASSWORD_MISMATCH;
+ }
+ return result;
+}
+
+static void
+passdb_lua_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ const char *lua_password, *lua_scheme;
+ enum passdb_result result =
+ passdb_lua_lookup(request, &lua_scheme, &lua_password);
+
+ passdb_handle_credentials(result, lua_password, lua_scheme, callback, request);
+}
+
+static void
+passdb_lua_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct dlua_passdb_module *module =
+ (struct dlua_passdb_module *)request->passdb->passdb;
+ const char *lua_password, *lua_scheme;
+ enum passdb_result result;
+
+ if (module->has_password_verify) {
+ result = passdb_lua_verify_password(module, request, password);
+ } else {
+ result = passdb_lua_lookup(request, &lua_scheme, &lua_password);
+ if (result == PASSDB_RESULT_OK) {
+ if (lua_scheme == NULL)
+ lua_scheme = "PLAIN";
+ result = auth_request_password_verify(request, password,
+ lua_password,
+ lua_scheme,
+ AUTH_SUBSYS_DB);
+ }
+ }
+ callback(result, request);
+}
+
+static struct passdb_module *
+passdb_lua_preinit(pool_t pool, const char *args)
+{
+ const char *cache_key = "%u";
+ const char *scheme = "PLAIN";
+ struct dlua_passdb_module *module;
+ bool blocking = TRUE;
+
+ module = p_new(pool, struct dlua_passdb_module, 1);
+ const char *const *fields = t_strsplit_spaces(args, " ");
+ while(*fields != NULL) {
+ if (str_begins(*fields, "file=")) {
+ module->file = p_strdup(pool, (*fields)+5);
+ } else if (str_begins(*fields, "blocking=")) {
+ const char *value = (*fields)+9;
+ if (strcmp(value, "yes") == 0) {
+ blocking = TRUE;
+ } else if (strcmp(value, "no") == 0) {
+ blocking = FALSE;
+ } else {
+ i_fatal("Invalid value %s. "
+ "Field blocking must be yes or no",
+ value);
+ }
+ } else if (str_begins(*fields, "cache_key=")) {
+ if (*((*fields)+10) != '\0')
+ cache_key = (*fields)+10;
+ else /* explicitly disable auth caching for lua */
+ cache_key = NULL;
+ } else if (str_begins(*fields, "scheme=")) {
+ scheme = p_strdup(pool, (*fields)+7);
+ } else {
+ i_fatal("Unsupported parameter %s", *fields);
+ }
+ fields++;
+ }
+
+ if (module->file == NULL)
+ i_fatal("passdb-lua: Missing mandatory file= parameter");
+
+ module->module.blocking = blocking;
+ module->module.default_cache_key =
+ auth_cache_parse_key(pool, cache_key);
+ module->module.default_pass_scheme = scheme;
+ return &module->module;
+}
+
+static void passdb_lua_init(struct passdb_module *_module)
+{
+ struct dlua_passdb_module *module =
+ (struct dlua_passdb_module *)_module;
+ const char *error;
+
+ if (dlua_script_create_file(module->file, &module->script, auth_event, &error) < 0 ||
+ auth_lua_script_init(module->script, &error) < 0)
+ i_fatal("passdb-lua: initialization failed: %s", error);
+ module->has_password_verify =
+ dlua_script_has_function(module->script, AUTH_LUA_PASSWORD_VERIFY);
+}
+
+static void passdb_lua_deinit(struct passdb_module *_module)
+{
+ struct dlua_passdb_module *module =
+ (struct dlua_passdb_module *)_module;
+ dlua_script_unref(&module->script);
+}
+
+#ifndef PLUGIN_BUILD
+struct passdb_module_interface passdb_lua =
+#else
+struct passdb_module_interface passdb_lua_plugin =
+#endif
+{
+ "lua",
+
+ passdb_lua_preinit,
+ passdb_lua_init,
+ passdb_lua_deinit,
+
+ passdb_lua_verify_plain,
+ passdb_lua_lookup_credentials,
+ NULL
+};
+#else
+struct passdb_module_interface passdb_lua = {
+ .name = "lua"
+};
+#endif
diff --git a/src/auth/passdb-oauth2.c b/src/auth/passdb-oauth2.c
new file mode 100644
index 0000000..e52a73f
--- /dev/null
+++ b/src/auth/passdb-oauth2.c
@@ -0,0 +1,90 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+#include "db-oauth2.h"
+
+struct oauth2_passdb_module {
+ struct passdb_module module;
+ struct db_oauth2 *db;
+};
+
+static void
+oauth2_verify_plain_continue(struct db_oauth2_request *req,
+ enum passdb_result result, const char *error,
+ struct auth_request *request)
+{
+ if (result == PASSDB_RESULT_INTERNAL_FAILURE)
+ e_error(authdb_event(request), "oauth2 failed: %s",
+ error);
+ else if (result != PASSDB_RESULT_OK)
+ e_info(authdb_event(request), "oauth2 failed: %s",
+ error);
+ else {
+ auth_request_set_field(request, "token", req->token, "PLAIN");
+ }
+ req->verify_callback(result, request);
+ auth_request_unref(&request);
+}
+
+static void
+oauth2_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct oauth2_passdb_module *module =
+ (struct oauth2_passdb_module *)request->passdb->passdb;
+ struct db_oauth2_request *req =
+ p_new(request->pool, struct db_oauth2_request, 1);
+ req->pool = request->pool;
+ req->verify_callback = callback;
+
+ auth_request_ref(request);
+
+ db_oauth2_lookup(module->db, req, password, request, oauth2_verify_plain_continue, request);
+}
+
+static struct passdb_module *
+oauth2_preinit(pool_t pool, const char *args)
+{
+ struct oauth2_passdb_module *module;
+
+ module = p_new(pool, struct oauth2_passdb_module, 1);
+ module->db = db_oauth2_init(args);
+ module->module.default_pass_scheme = "PLAIN";
+
+ if (db_oauth2_uses_password_grant(module->db)) {
+ module->module.default_cache_key = "%u";
+ } else {
+ module->module.default_cache_key = "%u%w";
+ }
+
+ return &module->module;
+}
+
+static void oauth2_deinit(struct passdb_module *passdb)
+{
+ struct oauth2_passdb_module *module = (struct oauth2_passdb_module *)passdb;
+ db_oauth2_unref(&module->db);
+}
+
+/* FIXME: Remove when oauth2 mech is fixed */
+const char *passdb_oauth2_get_oidc_url(struct passdb_module *passdb)
+{
+ struct oauth2_passdb_module *module =
+ container_of(passdb, struct oauth2_passdb_module, module);
+ if (module->db != NULL)
+ return db_oauth2_get_openid_configuration_url(module->db);
+ return NULL;
+}
+
+struct passdb_module_interface passdb_oauth2 = {
+ "oauth2",
+
+ oauth2_preinit,
+ NULL,
+ oauth2_deinit,
+
+ oauth2_verify_plain,
+ NULL,
+ NULL
+};
diff --git a/src/auth/passdb-pam.c b/src/auth/passdb-pam.c
new file mode 100644
index 0000000..6a1032d
--- /dev/null
+++ b/src/auth/passdb-pam.c
@@ -0,0 +1,401 @@
+/*
+ Based on auth_pam.c from popa3d by Solar Designer <solar@openwall.com>.
+
+ You're allowed to do whatever you like with this software (including
+ re-distribution in source and/or binary form, with or without
+ modification), provided that credit is given where it is due and any
+ modified versions are marked as such. There's absolutely no warranty.
+*/
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#ifdef PASSDB_PAM
+
+#include "lib-signals.h"
+#include "str.h"
+#include "net.h"
+#include "safe-memset.h"
+#include "auth-cache.h"
+
+#include <sys/stat.h>
+
+#ifdef HAVE_SECURITY_PAM_APPL_H
+# include <security/pam_appl.h>
+#elif defined(HAVE_PAM_PAM_APPL_H)
+# include <pam/pam_appl.h>
+#endif
+
+#if defined(sun) || defined(__sun__) || defined(_HPUX_SOURCE)
+# define pam_const
+#else
+# define pam_const const
+#endif
+
+typedef pam_const void *pam_item_t;
+
+#define PASSDB_PAM_DEFAULT_MAX_REQUESTS 100
+
+struct pam_passdb_module {
+ struct passdb_module module;
+
+ const char *service_name, *pam_cache_key;
+ unsigned int requests_left;
+
+ bool pam_setcred:1;
+ bool pam_session:1;
+ bool failure_show_msg:1;
+};
+
+struct pam_conv_context {
+ struct auth_request *request;
+ const char *pass;
+ const char *failure_msg;
+};
+
+static int
+pam_userpass_conv(int num_msg, pam_const struct pam_message **msg,
+ struct pam_response **resp_r, void *appdata_ptr)
+{
+ /* @UNSAFE */
+ struct pam_conv_context *ctx = appdata_ptr;
+ struct passdb_module *_passdb = ctx->request->passdb->passdb;
+ struct pam_passdb_module *passdb = (struct pam_passdb_module *)_passdb;
+ struct pam_response *resp;
+ char *string;
+ int i;
+
+ *resp_r = NULL;
+
+ resp = calloc(num_msg, sizeof(struct pam_response));
+ if (resp == NULL)
+ i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
+
+ for (i = 0; i < num_msg; i++) {
+ e_debug(authdb_event(ctx->request),
+ "#%d/%d style=%d msg=%s", i+1, num_msg,
+ msg[i]->msg_style,
+ msg[i]->msg != NULL ? msg[i]->msg : "");
+ switch (msg[i]->msg_style) {
+ case PAM_PROMPT_ECHO_ON:
+ /* Assume we're asking for user. We might not ever
+ get here because PAM already knows the user. */
+ string = strdup(ctx->request->fields.user);
+ if (string == NULL)
+ i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
+ break;
+ case PAM_PROMPT_ECHO_OFF:
+ /* Assume we're asking for password */
+ if (passdb->failure_show_msg)
+ ctx->failure_msg = t_strdup(msg[i]->msg);
+ string = strdup(ctx->pass);
+ if (string == NULL)
+ i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
+ break;
+ case PAM_ERROR_MSG:
+ case PAM_TEXT_INFO:
+ string = NULL;
+ break;
+ default:
+ while (--i >= 0) {
+ if (resp[i].resp != NULL) {
+ safe_memset(resp[i].resp, 0,
+ strlen(resp[i].resp));
+ free(resp[i].resp);
+ }
+ }
+
+ free(resp);
+ return PAM_CONV_ERR;
+ }
+
+ resp[i].resp_retcode = PAM_SUCCESS;
+ resp[i].resp = string;
+ }
+
+ *resp_r = resp;
+ return PAM_SUCCESS;
+}
+
+static const char *
+pam_get_missing_service_file_path(const char *service ATTR_UNUSED)
+{
+#ifdef SUNPAM
+ /* Uses /etc/pam.conf - we're not going to parse that */
+ return NULL;
+#else
+ static bool service_checked = FALSE;
+ const char *path;
+ struct stat st;
+
+ if (service_checked) {
+ /* check and complain only once */
+ return NULL;
+ }
+ service_checked = TRUE;
+
+ path = t_strdup_printf("/etc/pam.d/%s", service);
+ if (stat(path, &st) < 0 && errno == ENOENT) {
+ /* looks like it's missing. but before assuming that the system
+ even uses /etc/pam.d, make sure that it exists. */
+ if (stat("/etc/pam.d", &st) == 0)
+ return path;
+ }
+ /* exists or is unknown */
+ return NULL;
+#endif
+}
+
+static int try_pam_auth(struct auth_request *request, pam_handle_t *pamh,
+ const char *service)
+{
+ struct passdb_module *_module = request->passdb->passdb;
+ struct pam_passdb_module *module = (struct pam_passdb_module *)_module;
+ const char *path, *str;
+ pam_item_t item;
+ int status;
+
+ if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
+ path = pam_get_missing_service_file_path(service);
+ switch (status) {
+ case PAM_USER_UNKNOWN:
+ str = "unknown user";
+ break;
+ default:
+ str = t_strconcat("pam_authenticate() failed: ",
+ pam_strerror(pamh, status), NULL);
+ break;
+ }
+ if (path != NULL) {
+ /* log this as error, since it probably is */
+ str = t_strdup_printf("%s (%s missing?)", str, path);
+ e_error(authdb_event(request), "%s", str);
+ } else if (status == PAM_AUTH_ERR) {
+ str = t_strconcat(str, " ("AUTH_LOG_MSG_PASSWORD_MISMATCH"?)", NULL);
+ if (request->set->debug_passwords) {
+ str = t_strconcat(str, " (given password: ",
+ request->mech_password,
+ ")", NULL);
+ }
+ e_info(authdb_event(request), "%s", str);
+ } else {
+ if (status == PAM_USER_UNKNOWN)
+ auth_request_log_unknown_user(request, AUTH_SUBSYS_DB);
+ else {
+ e_info(authdb_event(request),
+ "%s", str);
+ }
+ }
+ return status;
+ }
+
+#ifdef HAVE_PAM_SETCRED
+ if (module->pam_setcred) {
+ if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) !=
+ PAM_SUCCESS) {
+ e_error(authdb_event(request),
+ "pam_setcred() failed: %s",
+ pam_strerror(pamh, status));
+ return status;
+ }
+ }
+#endif
+
+ if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
+ e_error(authdb_event(request),
+ "pam_acct_mgmt() failed: %s",
+ pam_strerror(pamh, status));
+ return status;
+ }
+
+ if (module->pam_session) {
+ if ((status = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
+ e_error(authdb_event(request),
+ "pam_open_session() failed: %s",
+ pam_strerror(pamh, status));
+ return status;
+ }
+
+ if ((status = pam_close_session(pamh, 0)) != PAM_SUCCESS) {
+ e_error(authdb_event(request),
+ "pam_close_session() failed: %s",
+ pam_strerror(pamh, status));
+ return status;
+ }
+ }
+
+ status = pam_get_item(pamh, PAM_USER, &item);
+ if (status != PAM_SUCCESS) {
+ e_error(authdb_event(request),
+ "pam_get_item(PAM_USER) failed: %s",
+ pam_strerror(pamh, status));
+ return status;
+ }
+ auth_request_set_field(request, "user", item, NULL);
+ return PAM_SUCCESS;
+}
+
+static void set_pam_items(struct auth_request *request, pam_handle_t *pamh)
+{
+ const char *host;
+
+ /* These shouldn't fail, and we don't really care if they do. */
+ host = net_ip2addr(&request->fields.remote_ip);
+ if (host[0] != '\0')
+ (void)pam_set_item(pamh, PAM_RHOST, host);
+ (void)pam_set_item(pamh, PAM_RUSER, request->fields.user);
+ /* TTY is needed by eg. pam_access module */
+ (void)pam_set_item(pamh, PAM_TTY, "dovecot");
+}
+
+static enum passdb_result
+pam_verify_plain_call(struct auth_request *request, const char *service,
+ const char *password)
+{
+ pam_handle_t *pamh;
+ struct pam_conv_context ctx;
+ struct pam_conv conv;
+ enum passdb_result result;
+ int status, status2;
+
+ conv.conv = pam_userpass_conv;
+ conv.appdata_ptr = &ctx;
+
+ i_zero(&ctx);
+ ctx.request = request;
+ ctx.pass = password;
+
+ status = pam_start(service, request->fields.user, &conv, &pamh);
+ if (status != PAM_SUCCESS) {
+ e_error(authdb_event(request),
+ "pam_start() failed: %s",
+ pam_strerror(pamh, status));
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ set_pam_items(request, pamh);
+ status = try_pam_auth(request, pamh, service);
+ if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) {
+ e_error(authdb_event(request),
+ "pam_end() failed: %s",
+ pam_strerror(pamh, status2));
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ switch (status) {
+ case PAM_SUCCESS:
+ result = PASSDB_RESULT_OK;
+ break;
+ case PAM_USER_UNKNOWN:
+ result = PASSDB_RESULT_USER_UNKNOWN;
+ break;
+ case PAM_NEW_AUTHTOK_REQD:
+ case PAM_ACCT_EXPIRED:
+ result = PASSDB_RESULT_PASS_EXPIRED;
+ break;
+ default:
+ result = PASSDB_RESULT_PASSWORD_MISMATCH;
+ break;
+ }
+
+ if (result != PASSDB_RESULT_OK && ctx.failure_msg != NULL) {
+ auth_request_set_field(request, "reason",
+ ctx.failure_msg, NULL);
+ }
+ return result;
+}
+
+static void
+pam_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct passdb_module *_module = request->passdb->passdb;
+ struct pam_passdb_module *module = (struct pam_passdb_module *)_module;
+ enum passdb_result result;
+ const char *service, *error;
+
+ if (module->requests_left > 0) {
+ if (--module->requests_left == 0)
+ worker_restart_request = TRUE;
+ }
+
+ if (t_auth_request_var_expand(module->service_name, request, NULL,
+ &service, &error) <= 0) {
+ e_debug(authdb_event(request),
+ "Failed to expand service %s: %s",
+ module->service_name, error);
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
+ return;
+ }
+
+ e_debug(authdb_event(request),
+ "lookup service=%s", service);
+
+ result = pam_verify_plain_call(request, service, password);
+ callback(result, request);
+}
+
+static struct passdb_module *
+pam_preinit(pool_t pool, const char *args)
+{
+ struct pam_passdb_module *module;
+ const char *const *t_args;
+ int i;
+
+ module = p_new(pool, struct pam_passdb_module, 1);
+ module->service_name = "dovecot";
+ /* we're caching the password by using directly the plaintext password
+ given by the auth mechanism */
+ module->module.default_pass_scheme = "PLAIN";
+ module->module.blocking = TRUE;
+ module->requests_left = PASSDB_PAM_DEFAULT_MAX_REQUESTS;
+
+ t_args = t_strsplit_spaces(args, " ");
+ for(i = 0; t_args[i] != NULL; i++) {
+ /* -session for backwards compatibility */
+ if (strcmp(t_args[i], "-session") == 0 ||
+ strcmp(t_args[i], "session=yes") == 0)
+ module->pam_session = TRUE;
+ else if (strcmp(t_args[i], "setcred=yes") == 0)
+ module->pam_setcred = TRUE;
+ else if (str_begins(t_args[i], "cache_key=")) {
+ module->module.default_cache_key =
+ auth_cache_parse_key(pool, t_args[i] + 10);
+ } else if (strcmp(t_args[i], "blocking=yes") == 0) {
+ /* ignore, for backwards compatibility */
+ } else if (strcmp(t_args[i], "failure_show_msg=yes") == 0) {
+ module->failure_show_msg = TRUE;
+ } else if (strcmp(t_args[i], "*") == 0) {
+ /* for backwards compatibility */
+ module->service_name = "%Ls";
+ } else if (str_begins(t_args[i], "max_requests=")) {
+ if (str_to_uint(t_args[i] + 13,
+ &module->requests_left) < 0) {
+ i_error("pam: Invalid requests_left value: %s",
+ t_args[i] + 13);
+ }
+ } else if (t_args[i+1] == NULL) {
+ module->service_name = p_strdup(pool, t_args[i]);
+ } else {
+ i_fatal("pam: Unknown setting: %s", t_args[i]);
+ }
+ }
+ return &module->module;
+}
+
+struct passdb_module_interface passdb_pam = {
+ "pam",
+
+ pam_preinit,
+ NULL,
+ NULL,
+
+ pam_verify_plain,
+ NULL,
+ NULL
+};
+#else
+struct passdb_module_interface passdb_pam = {
+ .name = "pam"
+};
+#endif
diff --git a/src/auth/passdb-passwd-file.c b/src/auth/passdb-passwd-file.c
new file mode 100644
index 0000000..1f45fab
--- /dev/null
+++ b/src/auth/passdb-passwd-file.c
@@ -0,0 +1,210 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#ifdef PASSDB_PASSWD_FILE
+
+#include "str.h"
+#include "auth-cache.h"
+#include "password-scheme.h"
+#include "db-passwd-file.h"
+
+struct passwd_file_passdb_module {
+ struct passdb_module module;
+
+ struct db_passwd_file *pwf;
+ const char *username_format;
+};
+
+static int
+passwd_file_add_extra_fields(struct auth_request *request, char *const *fields)
+{
+ string_t *str = t_str_new(512);
+ const struct var_expand_table *table;
+ const char *key, *value, *error;
+ unsigned int i;
+
+ table = auth_request_get_var_expand_table(request, NULL);
+
+ for (i = 0; fields[i] != NULL; i++) {
+ value = strchr(fields[i], '=');
+ if (value != NULL) {
+ key = t_strdup_until(fields[i], value);
+ str_truncate(str, 0);
+ if (auth_request_var_expand_with_table(str, value + 1,
+ request, table, NULL, &error) <= 0) {
+ e_error(authdb_event(request),
+ "Failed to expand extra field %s: %s",
+ fields[i], error);
+ return -1;
+ }
+ value = str_c(str);
+ } else {
+ key = fields[i];
+ value = "";
+ }
+ auth_request_set_field(request, key, value, NULL);
+ }
+ return 0;
+}
+
+static int passwd_file_save_results(struct auth_request *request,
+ const struct passwd_user *pu,
+ const char **crypted_pass_r,
+ const char **scheme_r)
+{
+ *crypted_pass_r = pu->password != NULL ? pu->password : "";
+ *scheme_r = password_get_scheme(crypted_pass_r);
+ if (*scheme_r == NULL)
+ *scheme_r = request->passdb->passdb->default_pass_scheme;
+
+ /* save the password so cache can use it */
+ auth_request_set_field(request, "password",
+ *crypted_pass_r, *scheme_r);
+
+ if (pu->extra_fields != NULL) {
+ if (passwd_file_add_extra_fields(request, pu->extra_fields) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void
+passwd_file_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct passdb_module *_module = request->passdb->passdb;
+ struct passwd_file_passdb_module *module =
+ (struct passwd_file_passdb_module *)_module;
+ struct passwd_user *pu;
+ const char *scheme, *crypted_pass;
+ enum passdb_result result;
+ int ret;
+
+ ret = db_passwd_file_lookup(module->pwf, request,
+ module->username_format, &pu);
+ if (ret <= 0) {
+ callback(ret < 0 ? PASSDB_RESULT_INTERNAL_FAILURE :
+ PASSDB_RESULT_USER_UNKNOWN, request);
+ return;
+ }
+
+ if (passwd_file_save_results(request, pu, &crypted_pass, &scheme) < 0) {
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
+ return;
+ }
+
+ result = auth_request_password_verify(request, password, crypted_pass,
+ scheme, AUTH_SUBSYS_DB);
+
+ callback(result, request);
+}
+
+static void
+passwd_file_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ struct passdb_module *_module = request->passdb->passdb;
+ struct passwd_file_passdb_module *module =
+ (struct passwd_file_passdb_module *)_module;
+ struct passwd_user *pu;
+ const char *crypted_pass, *scheme;
+ int ret;
+
+ ret = db_passwd_file_lookup(module->pwf, request,
+ module->username_format, &pu);
+ if (ret <= 0) {
+ callback(ret < 0 ? PASSDB_RESULT_INTERNAL_FAILURE :
+ PASSDB_RESULT_USER_UNKNOWN, NULL, 0, request);
+ return;
+ }
+
+ if (passwd_file_save_results(request, pu, &crypted_pass, &scheme) < 0) {
+ callback(PASSDB_RESULT_INTERNAL_FAILURE, NULL, 0, request);
+ return;
+ }
+
+ passdb_handle_credentials(PASSDB_RESULT_OK, crypted_pass, scheme,
+ callback, request);
+}
+
+static struct passdb_module *
+passwd_file_preinit(pool_t pool, const char *args)
+{
+ struct passwd_file_passdb_module *module;
+ const char *scheme = PASSWD_FILE_DEFAULT_SCHEME;
+ const char *format = PASSWD_FILE_DEFAULT_USERNAME_FORMAT;
+ const char *key, *value;
+
+ while (*args != '\0') {
+ if (*args == '/')
+ break;
+
+ key = args;
+ value = strchr(key, '=');
+ if (value == NULL) {
+ value = "";
+ args = strchr(key, ' ');
+ } else {
+ key = t_strdup_until(key, value);
+ args = strchr(++value, ' ');
+ if (args != NULL)
+ value = t_strdup_until(value, args);
+ }
+ if (args == NULL)
+ args = "";
+ else
+ args++;
+
+ if (strcmp(key, "scheme") == 0)
+ scheme = p_strdup(pool, value);
+ else if (strcmp(key, "username_format") == 0)
+ format = p_strdup(pool, value);
+ else
+ i_fatal("passdb passwd-file: Unknown setting: %s", key);
+ }
+
+ if (*args == '\0')
+ i_fatal("passdb passwd-file: Missing args");
+
+ module = p_new(pool, struct passwd_file_passdb_module, 1);
+ module->pwf = db_passwd_file_init(args, FALSE,
+ global_auth_settings->debug);
+ module->username_format = format;
+ module->module.default_pass_scheme = scheme;
+ return &module->module;
+}
+
+static void passwd_file_init(struct passdb_module *_module)
+{
+ struct passwd_file_passdb_module *module =
+ (struct passwd_file_passdb_module *)_module;
+
+ db_passwd_file_parse(module->pwf);
+}
+
+static void passwd_file_deinit(struct passdb_module *_module)
+{
+ struct passwd_file_passdb_module *module =
+ (struct passwd_file_passdb_module *)_module;
+
+ db_passwd_file_unref(&module->pwf);
+}
+
+struct passdb_module_interface passdb_passwd_file = {
+ "passwd-file",
+
+ passwd_file_preinit,
+ passwd_file_init,
+ passwd_file_deinit,
+
+ passwd_file_verify_plain,
+ passwd_file_lookup_credentials,
+ NULL
+};
+#else
+struct passdb_module_interface passdb_passwd_file = {
+ .name = "passwd-file"
+};
+#endif
diff --git a/src/auth/passdb-passwd.c b/src/auth/passdb-passwd.c
new file mode 100644
index 0000000..89c54eb
--- /dev/null
+++ b/src/auth/passdb-passwd.c
@@ -0,0 +1,127 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#ifdef PASSDB_PASSWD
+
+#include "safe-memset.h"
+#include "ipwd.h"
+
+#define PASSWD_CACHE_KEY "%u"
+#define PASSWD_PASS_SCHEME "CRYPT"
+
+static enum passdb_result
+passwd_lookup(struct auth_request *request, struct passwd *pw_r)
+{
+ e_debug(authdb_event(request), "lookup");
+
+ switch (i_getpwnam(request->fields.user, pw_r)) {
+ case -1:
+ e_error(authdb_event(request),
+ "getpwnam() failed: %m");
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ case 0:
+ auth_request_log_unknown_user(request, AUTH_SUBSYS_DB);
+ return PASSDB_RESULT_USER_UNKNOWN;
+ }
+
+ if (!IS_VALID_PASSWD(pw_r->pw_passwd)) {
+ e_info(authdb_event(request),
+ "invalid password field '%s'", pw_r->pw_passwd);
+ return PASSDB_RESULT_USER_DISABLED;
+ }
+
+ /* save the password so cache can use it */
+ auth_request_set_field(request, "password", pw_r->pw_passwd,
+ PASSWD_PASS_SCHEME);
+ return PASSDB_RESULT_OK;
+}
+
+static void
+passwd_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct passwd pw;
+ enum passdb_result res;
+
+ res = passwd_lookup(request, &pw);
+ if (res != PASSDB_RESULT_OK) {
+ callback(res, request);
+ return;
+ }
+ /* check if the password is valid */
+ res = auth_request_password_verify(request, password, pw.pw_passwd,
+ PASSWD_PASS_SCHEME, AUTH_SUBSYS_DB);
+
+ /* clear the passwords from memory */
+ safe_memset(pw.pw_passwd, 0, strlen(pw.pw_passwd));
+
+ if (res != PASSDB_RESULT_OK) {
+ callback(res, request);
+ return;
+ }
+
+ /* make sure we're using the username exactly as it's in the database */
+ auth_request_set_field(request, "user", pw.pw_name, NULL);
+
+ callback(res, request);
+}
+
+static void
+passwd_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ struct passwd pw;
+ enum passdb_result res;
+
+ res = passwd_lookup(request, &pw);
+ if (res != PASSDB_RESULT_OK) {
+ callback(res, NULL, 0, request);
+ return;
+ }
+ /* make sure we're using the username exactly as it's in the database */
+ auth_request_set_field(request, "user", pw.pw_name, NULL);
+ passdb_handle_credentials(PASSDB_RESULT_OK, pw.pw_passwd,
+ PASSWD_PASS_SCHEME, callback, request);
+}
+
+static struct passdb_module *
+passwd_preinit(pool_t pool, const char *args)
+{
+ struct passdb_module *module;
+
+ module = p_new(pool, struct passdb_module, 1);
+ module->blocking = TRUE;
+ if (strcmp(args, "blocking=no") == 0)
+ module->blocking = FALSE;
+ else if (*args != '\0')
+ i_fatal("passdb passwd: Unknown setting: %s", args);
+
+ module->default_cache_key = PASSWD_CACHE_KEY;
+ module->default_pass_scheme = PASSWD_PASS_SCHEME;
+ return module;
+}
+
+static void passwd_deinit(struct passdb_module *module ATTR_UNUSED)
+{
+ endpwent();
+}
+
+struct passdb_module_interface passdb_passwd = {
+ "passwd",
+
+ passwd_preinit,
+ NULL,
+ passwd_deinit,
+
+ passwd_verify_plain,
+ passwd_lookup_credentials,
+ NULL
+};
+
+#else
+struct passdb_module_interface passdb_passwd = {
+ .name = "passwd"
+};
+#endif
diff --git a/src/auth/passdb-shadow.c b/src/auth/passdb-shadow.c
new file mode 100644
index 0000000..e312b45
--- /dev/null
+++ b/src/auth/passdb-shadow.c
@@ -0,0 +1,125 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#ifdef PASSDB_SHADOW
+
+#include "safe-memset.h"
+
+#include <shadow.h>
+
+#define SHADOW_CACHE_KEY "%u"
+#define SHADOW_PASS_SCHEME "CRYPT"
+
+static enum passdb_result
+shadow_lookup(struct auth_request *request, struct spwd **spw_r)
+{
+ e_debug(authdb_event(request), "lookup");
+
+ *spw_r = getspnam(request->fields.user);
+ if (*spw_r == NULL) {
+ auth_request_log_unknown_user(request, AUTH_SUBSYS_DB);
+ return PASSDB_RESULT_USER_UNKNOWN;
+ }
+
+ if (!IS_VALID_PASSWD((*spw_r)->sp_pwdp)) {
+ e_info(authdb_event(request),
+ "invalid password field");
+ return PASSDB_RESULT_USER_DISABLED;
+ }
+
+ /* save the password so cache can use it */
+ auth_request_set_field(request, "password", (*spw_r)->sp_pwdp,
+ SHADOW_PASS_SCHEME);
+ return PASSDB_RESULT_OK;
+}
+
+static void
+shadow_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ struct spwd *spw;
+ enum passdb_result res;
+ int ret;
+
+ res = shadow_lookup(request, &spw);
+ if (res != PASSDB_RESULT_OK) {
+ callback(res, request);
+ return;
+ }
+
+ /* check if the password is valid */
+ ret = auth_request_password_verify(request, password, spw->sp_pwdp,
+ SHADOW_PASS_SCHEME, AUTH_SUBSYS_DB);
+
+ /* clear the passwords from memory */
+ safe_memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp));
+
+ if (ret <= 0) {
+ callback(PASSDB_RESULT_PASSWORD_MISMATCH, request);
+ return;
+ }
+
+ /* make sure we're using the username exactly as it's in the database */
+ auth_request_set_field(request, "user", spw->sp_namp, NULL);
+
+ callback(PASSDB_RESULT_OK, request);
+}
+
+static void
+shadow_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ struct spwd *spw;
+ enum passdb_result res;
+
+ res = shadow_lookup(request, &spw);
+ if (res != PASSDB_RESULT_OK) {
+ callback(res, NULL, 0, request);
+ return;
+ }
+ /* make sure we're using the username exactly as it's in the database */
+ auth_request_set_field(request, "user", spw->sp_namp, NULL);
+ passdb_handle_credentials(PASSDB_RESULT_OK, spw->sp_pwdp,
+ SHADOW_PASS_SCHEME, callback, request);
+}
+
+static struct passdb_module *
+shadow_preinit(pool_t pool, const char *args)
+{
+ struct passdb_module *module;
+
+ module = p_new(pool, struct passdb_module, 1);
+ module->blocking = TRUE;
+ if (strcmp(args, "blocking=no") == 0)
+ module->blocking = FALSE;
+ else if (*args != '\0')
+ i_fatal("passdb shadow: Unknown setting: %s", args);
+
+ module->default_cache_key = SHADOW_CACHE_KEY;
+ module->default_pass_scheme = SHADOW_PASS_SCHEME;
+ return module;
+}
+
+static void shadow_deinit(struct passdb_module *module ATTR_UNUSED)
+{
+ endspent();
+}
+
+struct passdb_module_interface passdb_shadow = {
+ "shadow",
+
+ shadow_preinit,
+ NULL,
+ shadow_deinit,
+
+ shadow_verify_plain,
+ shadow_lookup_credentials,
+ NULL
+};
+#else
+struct passdb_module_interface passdb_shadow = {
+ .name = "shadow"
+};
+#endif
diff --git a/src/auth/passdb-sql.c b/src/auth/passdb-sql.c
new file mode 100644
index 0000000..0b993fb
--- /dev/null
+++ b/src/auth/passdb-sql.c
@@ -0,0 +1,310 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+
+#ifdef PASSDB_SQL
+
+#include "safe-memset.h"
+#include "password-scheme.h"
+#include "auth-cache.h"
+#include "db-sql.h"
+
+#include <string.h>
+
+struct sql_passdb_module {
+ struct passdb_module module;
+
+ struct db_sql_connection *conn;
+};
+
+struct passdb_sql_request {
+ struct auth_request *auth_request;
+ union {
+ verify_plain_callback_t *verify_plain;
+ lookup_credentials_callback_t *lookup_credentials;
+ set_credentials_callback_t *set_credentials;
+ } callback;
+};
+
+static void sql_query_save_results(struct sql_result *result,
+ struct passdb_sql_request *sql_request)
+{
+ struct auth_request *auth_request = sql_request->auth_request;
+ struct passdb_module *_module = auth_request->passdb->passdb;
+ struct sql_passdb_module *module = (struct sql_passdb_module *)_module;
+ unsigned int i, fields_count;
+ const char *name, *value;
+
+ fields_count = sql_result_get_fields_count(result);
+ for (i = 0; i < fields_count; i++) {
+ name = sql_result_get_field_name(result, i);
+ value = sql_result_get_field_value(result, i);
+
+ if (*name == '\0')
+ ;
+ else if (value == NULL)
+ auth_request_set_null_field(auth_request, name);
+ else {
+ auth_request_set_field(auth_request, name, value,
+ module->conn->set.default_pass_scheme);
+ }
+ }
+}
+
+static void sql_query_callback(struct sql_result *result,
+ struct passdb_sql_request *sql_request)
+{
+ struct auth_request *auth_request = sql_request->auth_request;
+ struct passdb_module *_module = auth_request->passdb->passdb;
+ struct sql_passdb_module *module = (struct sql_passdb_module *)_module;
+ enum passdb_result passdb_result;
+ const char *password, *scheme;
+ int ret;
+
+ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+ password = NULL;
+
+ ret = sql_result_next_row(result);
+ if (ret >= 0)
+ db_sql_success(module->conn);
+ if (ret < 0) {
+ if (!module->conn->default_password_query) {
+ e_error(authdb_event(auth_request),
+ "Password query failed: %s",
+ sql_result_get_error(result));
+ } else {
+ e_error(authdb_event(auth_request),
+ "Password query failed: %s "
+ "(using built-in default password_query: %s)",
+ sql_result_get_error(result),
+ module->conn->set.password_query);
+ }
+ } else if (ret == 0) {
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ passdb_result = PASSDB_RESULT_USER_UNKNOWN;
+ } else {
+ sql_query_save_results(result, sql_request);
+
+ /* Note that we really want to check if the password field is
+ found. Just checking if password is set isn't enough,
+ because with proxies we might want to return NULL as
+ password. */
+ if (sql_result_find_field(result, "password") < 0 &&
+ sql_result_find_field(result, "password_noscheme") < 0) {
+ e_error(authdb_event(auth_request),
+ "Password query must return a field named "
+ "'password'");
+ } else if (sql_result_next_row(result) > 0) {
+ e_error(authdb_event(auth_request),
+ "Password query returned multiple matches");
+ } else if (auth_request->passdb_password == NULL &&
+ !auth_fields_exists(auth_request->fields.extra_fields,
+ "nopassword")) {
+ passdb_result = auth_request_password_missing(auth_request);
+ } else {
+ /* passdb_password may change on the way,
+ so we'll need to strdup. */
+ password = t_strdup(auth_request->passdb_password);
+ passdb_result = PASSDB_RESULT_OK;
+ }
+ }
+
+ scheme = password_get_scheme(&password);
+ /* auth_request_set_field() sets scheme */
+ i_assert(password == NULL || scheme != NULL);
+
+ if (auth_request->wanted_credentials_scheme != NULL) {
+ passdb_handle_credentials(passdb_result, password, scheme,
+ sql_request->callback.lookup_credentials,
+ auth_request);
+ auth_request_unref(&auth_request);
+ return;
+ }
+
+ /* verify plain */
+ if (password == NULL) {
+ sql_request->callback.verify_plain(passdb_result, auth_request);
+ auth_request_unref(&auth_request);
+ return;
+ }
+
+ passdb_result = auth_request_password_verify(auth_request,
+ auth_request->mech_password,
+ password, scheme, AUTH_SUBSYS_DB);
+
+ sql_request->callback.verify_plain(passdb_result, auth_request);
+ auth_request_unref(&auth_request);
+}
+
+static const char *
+passdb_sql_escape(const char *str, const struct auth_request *auth_request)
+{
+ struct passdb_module *_module = auth_request->passdb->passdb;
+ struct sql_passdb_module *module = (struct sql_passdb_module *)_module;
+
+ return sql_escape_string(module->conn->db, str);
+}
+
+static void sql_lookup_pass(struct passdb_sql_request *sql_request)
+{
+ struct passdb_module *_module =
+ sql_request->auth_request->passdb->passdb;
+ struct sql_passdb_module *module = (struct sql_passdb_module *)_module;
+ const char *query, *error;
+
+ if (t_auth_request_var_expand(module->conn->set.password_query,
+ sql_request->auth_request,
+ passdb_sql_escape, &query, &error) <= 0) {
+ e_debug(authdb_event(sql_request->auth_request),
+ "Failed to expand password_query=%s: %s",
+ module->conn->set.password_query, error);
+ sql_request->callback.verify_plain(PASSDB_RESULT_INTERNAL_FAILURE,
+ sql_request->auth_request);
+ return;
+ }
+
+ e_debug(authdb_event(sql_request->auth_request),
+ "query: %s", query);
+
+ auth_request_ref(sql_request->auth_request);
+ sql_query(module->conn->db, query,
+ sql_query_callback, sql_request);
+}
+
+static void sql_verify_plain(struct auth_request *request,
+ const char *password ATTR_UNUSED,
+ verify_plain_callback_t *callback)
+{
+ struct passdb_sql_request *sql_request;
+
+ sql_request = p_new(request->pool, struct passdb_sql_request, 1);
+ sql_request->auth_request = request;
+ sql_request->callback.verify_plain = callback;
+
+ sql_lookup_pass(sql_request);
+}
+
+static void sql_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ struct passdb_sql_request *sql_request;
+
+ sql_request = p_new(request->pool, struct passdb_sql_request, 1);
+ sql_request->auth_request = request;
+ sql_request->callback.lookup_credentials = callback;
+
+ sql_lookup_pass(sql_request);
+}
+
+static void sql_set_credentials_callback(const struct sql_commit_result *sql_result,
+ struct passdb_sql_request *sql_request)
+{
+ struct passdb_module *_module =
+ sql_request->auth_request->passdb->passdb;
+ struct sql_passdb_module *module = (struct sql_passdb_module *)_module;
+
+ if (sql_result->error != NULL) {
+ if (!module->conn->default_update_query) {
+ auth_request_log_error(sql_request->auth_request,
+ AUTH_SUBSYS_DB,
+ "Set credentials query failed: %s", sql_result->error);
+ } else {
+ auth_request_log_error(sql_request->auth_request,
+ AUTH_SUBSYS_DB,
+ "Set credentials query failed: %s"
+ "(using built-in default update_query: %s)",
+ sql_result->error, module->conn->set.update_query);
+ }
+ }
+
+ sql_request->callback.
+ set_credentials(sql_result->error == NULL, sql_request->auth_request);
+ i_free(sql_request);
+}
+
+static void sql_set_credentials(struct auth_request *request,
+ const char *new_credentials,
+ set_credentials_callback_t *callback)
+{
+ struct sql_passdb_module *module =
+ (struct sql_passdb_module *) request->passdb->passdb;
+ struct sql_transaction_context *transaction;
+ struct passdb_sql_request *sql_request;
+ const char *query, *error;
+
+ request->mech_password = p_strdup(request->pool, new_credentials);
+
+ if (t_auth_request_var_expand(module->conn->set.update_query,
+ request, passdb_sql_escape,
+ &query, &error) <= 0) {
+ e_error(authdb_event(request),
+ "Failed to expand update_query=%s: %s",
+ module->conn->set.update_query, error);
+ callback(FALSE, request);
+ return;
+ }
+
+ sql_request = i_new(struct passdb_sql_request, 1);
+ sql_request->auth_request = request;
+ sql_request->callback.set_credentials = callback;
+
+ transaction = sql_transaction_begin(module->conn->db);
+ sql_update(transaction, query);
+ sql_transaction_commit(&transaction,
+ sql_set_credentials_callback, sql_request);
+}
+
+static struct passdb_module *
+passdb_sql_preinit(pool_t pool, const char *args)
+{
+ struct sql_passdb_module *module;
+ struct db_sql_connection *conn;
+
+ module = p_new(pool, struct sql_passdb_module, 1);
+ module->conn = conn = db_sql_init(args, FALSE);
+
+ module->module.default_cache_key =
+ auth_cache_parse_key(pool, conn->set.password_query);
+ module->module.default_pass_scheme = conn->set.default_pass_scheme;
+ return &module->module;
+}
+
+static void passdb_sql_init(struct passdb_module *_module)
+{
+ struct sql_passdb_module *module =
+ (struct sql_passdb_module *)_module;
+ enum sql_db_flags flags;
+
+ flags = sql_get_flags(module->conn->db);
+ module->module.blocking = (flags & SQL_DB_FLAG_BLOCKING) != 0;
+
+ if (!module->module.blocking || worker)
+ db_sql_connect(module->conn);
+ db_sql_check_userdb_warning(module->conn);
+}
+
+static void passdb_sql_deinit(struct passdb_module *_module)
+{
+ struct sql_passdb_module *module =
+ (struct sql_passdb_module *)_module;
+
+ db_sql_unref(&module->conn);
+}
+
+struct passdb_module_interface passdb_sql = {
+ "sql",
+
+ passdb_sql_preinit,
+ passdb_sql_init,
+ passdb_sql_deinit,
+
+ sql_verify_plain,
+ sql_lookup_credentials,
+ sql_set_credentials
+};
+#else
+struct passdb_module_interface passdb_sql = {
+ .name = "sql"
+};
+#endif
diff --git a/src/auth/passdb-static.c b/src/auth/passdb-static.c
new file mode 100644
index 0000000..2b99fab
--- /dev/null
+++ b/src/auth/passdb-static.c
@@ -0,0 +1,113 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "passdb.h"
+#include "passdb-template.h"
+#include "password-scheme.h"
+
+struct static_passdb_module {
+ struct passdb_module module;
+ struct passdb_template *tmpl;
+ const char *static_password_tmpl;
+};
+
+static enum passdb_result
+static_save_fields(struct auth_request *request, const char **password_r,
+ const char **scheme_r)
+{
+ struct static_passdb_module *module =
+ (struct static_passdb_module *)request->passdb->passdb;
+ const char *error;
+
+ *password_r = NULL;
+ *scheme_r = NULL;
+
+ e_debug(authdb_event(request), "lookup");
+ if (passdb_template_export(module->tmpl, request, &error) < 0) {
+ e_error(authdb_event(request),
+ "Failed to expand template: %s", error);
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+
+ if (module->static_password_tmpl != NULL) {
+ if (t_auth_request_var_expand(module->static_password_tmpl,
+ request, NULL, password_r, &error) <= 0) {
+ e_error(authdb_event(request),
+ "Failed to expand password=%s: %s",
+ module->static_password_tmpl, error);
+ return PASSDB_RESULT_INTERNAL_FAILURE;
+ }
+ } else if (auth_fields_exists(request->fields.extra_fields, "nopassword")) {
+ *password_r = "";
+ } else {
+ return auth_request_password_missing(request);
+ }
+
+ *scheme_r = password_get_scheme(password_r);
+
+ if (*scheme_r == NULL)
+ *scheme_r = STATIC_PASS_SCHEME;
+
+ auth_request_set_field(request, "password",
+ *password_r, *scheme_r);
+
+ return PASSDB_RESULT_OK;
+}
+
+static void
+static_verify_plain(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback)
+{
+ enum passdb_result result;
+ const char *static_password;
+ const char *static_scheme;
+
+ result = static_save_fields(request, &static_password, &static_scheme);
+ if (result != PASSDB_RESULT_OK) {
+ callback(result, request);
+ return;
+ }
+
+ result = auth_request_password_verify(request, password, static_password,
+ static_scheme, AUTH_SUBSYS_DB);
+ callback(result, request);
+}
+
+static void
+static_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ enum passdb_result result;
+ const char *static_password;
+ const char *static_scheme;
+
+ result = static_save_fields(request, &static_password, &static_scheme);
+ passdb_handle_credentials(result, static_password,
+ static_scheme, callback, request);
+}
+
+static struct passdb_module *
+static_preinit(pool_t pool, const char *args)
+{
+ struct static_passdb_module *module;
+ const char *value;
+
+ module = p_new(pool, struct static_passdb_module, 1);
+ module->tmpl = passdb_template_build(pool, args);
+
+ if (passdb_template_remove(module->tmpl, "password", &value))
+ module->static_password_tmpl = value;
+ return &module->module;
+}
+
+struct passdb_module_interface passdb_static = {
+ "static",
+
+ static_preinit,
+ NULL,
+ NULL,
+
+ static_verify_plain,
+ static_lookup_credentials,
+ NULL
+};
diff --git a/src/auth/passdb-template.c b/src/auth/passdb-template.c
new file mode 100644
index 0000000..3bb7f54
--- /dev/null
+++ b/src/auth/passdb-template.c
@@ -0,0 +1,102 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "str.h"
+#include "passdb.h"
+#include "passdb-template.h"
+
+struct passdb_template {
+ ARRAY(const char *) args;
+};
+
+struct passdb_template *passdb_template_build(pool_t pool, const char *args)
+{
+ struct passdb_template *tmpl;
+ const char *const *tmp, *key, *value;
+
+ tmpl = p_new(pool, struct passdb_template, 1);
+
+ tmp = t_strsplit_spaces(args, " ");
+ p_array_init(&tmpl->args, pool, str_array_length(tmp));
+
+ for (; *tmp != NULL; tmp++) {
+ value = strchr(*tmp, '=');
+ if (value == NULL)
+ key = *tmp;
+ else
+ key = t_strdup_until(*tmp, value++);
+
+ if (*key == '\0')
+ i_fatal("Invalid passdb template %s - key must not be empty",
+ args);
+
+ key = p_strdup(pool, key);
+ value = p_strdup(pool, value);
+ array_push_back(&tmpl->args, &key);
+ array_push_back(&tmpl->args, &value);
+ }
+ return tmpl;
+}
+
+int passdb_template_export(struct passdb_template *tmpl,
+ struct auth_request *auth_request,
+ const char **error_r)
+{
+ const struct var_expand_table *table;
+ string_t *str;
+ const char *const *args, *value;
+ unsigned int i, count;
+
+ if (passdb_template_is_empty(tmpl))
+ return 0;
+
+ str = t_str_new(256);
+ table = auth_request_get_var_expand_table(auth_request, NULL);
+
+ args = array_get(&tmpl->args, &count);
+ i_assert((count % 2) == 0);
+ for (i = 0; i < count; i += 2) {
+ if (args[i+1] == NULL)
+ value = "";
+ else {
+ str_truncate(str, 0);
+ if (auth_request_var_expand_with_table(str, args[i+1],
+ auth_request, table, NULL, error_r) <= 0)
+ return -1;
+ value = str_c(str);
+ }
+ auth_request_set_field(auth_request, args[i], value,
+ STATIC_PASS_SCHEME);
+ }
+ return 0;
+}
+
+bool passdb_template_remove(struct passdb_template *tmpl,
+ const char *key, const char **value_r)
+{
+ const char *const *args;
+ unsigned int i, count;
+
+ args = array_get(&tmpl->args, &count);
+ i_assert((count % 2) == 0);
+ for (i = 0; i < count; i += 2) {
+ if (strcmp(args[i], key) == 0) {
+ *value_r = args[i+1];
+ array_delete(&tmpl->args, i, 2);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+bool passdb_template_is_empty(struct passdb_template *tmpl)
+{
+ return array_count(&tmpl->args) == 0;
+}
+
+const char *const *passdb_template_get_args(struct passdb_template *tmpl, unsigned int *count_r)
+{
+ return array_get(&tmpl->args, count_r);
+}
+
diff --git a/src/auth/passdb-template.h b/src/auth/passdb-template.h
new file mode 100644
index 0000000..b681c80
--- /dev/null
+++ b/src/auth/passdb-template.h
@@ -0,0 +1,16 @@
+#ifndef PASSDB_TEMPLATE_H
+#define PASSDB_TEMPLATE_H
+
+#define STATIC_PASS_SCHEME "PLAIN"
+
+struct passdb_template *passdb_template_build(pool_t pool, const char *args);
+int passdb_template_export(struct passdb_template *tmpl,
+ struct auth_request *auth_request,
+ const char **error_r);
+bool passdb_template_remove(struct passdb_template *tmpl,
+ const char *key, const char **value_r);
+bool passdb_template_is_empty(struct passdb_template *tmpl);
+
+const char *const *passdb_template_get_args(struct passdb_template *tmpl, unsigned int *count_r);
+
+#endif
diff --git a/src/auth/passdb.c b/src/auth/passdb.c
new file mode 100644
index 0000000..9bc2b87
--- /dev/null
+++ b/src/auth/passdb.c
@@ -0,0 +1,351 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "password-scheme.h"
+#include "auth-worker-server.h"
+#include "passdb.h"
+
+static ARRAY(struct passdb_module_interface *) passdb_interfaces;
+static ARRAY(struct passdb_module *) passdb_modules;
+
+static const struct passdb_module_interface passdb_iface_deinit = {
+ .name = "deinit"
+};
+
+static struct passdb_module_interface *passdb_interface_find(const char *name)
+{
+ struct passdb_module_interface *iface;
+
+ array_foreach_elem(&passdb_interfaces, iface) {
+ if (strcmp(iface->name, name) == 0)
+ return iface;
+ }
+ return NULL;
+}
+
+void passdb_register_module(struct passdb_module_interface *iface)
+{
+ struct passdb_module_interface *old_iface;
+
+ old_iface = passdb_interface_find(iface->name);
+ if (old_iface != NULL && old_iface->verify_plain == NULL) {
+ /* replacing a "support not compiled in" passdb */
+ passdb_unregister_module(old_iface);
+ } else if (old_iface != NULL) {
+ i_panic("passdb_register_module(%s): Already registered",
+ iface->name);
+ }
+ array_push_back(&passdb_interfaces, &iface);
+}
+
+void passdb_unregister_module(struct passdb_module_interface *iface)
+{
+ struct passdb_module_interface *const *ifaces;
+ unsigned int idx;
+
+ array_foreach(&passdb_interfaces, ifaces) {
+ if (*ifaces == iface) {
+ idx = array_foreach_idx(&passdb_interfaces, ifaces);
+ array_delete(&passdb_interfaces, idx, 1);
+ return;
+ }
+ }
+ i_panic("passdb_unregister_module(%s): Not registered", iface->name);
+}
+
+bool passdb_get_credentials(struct auth_request *auth_request,
+ const char *input, const char *input_scheme,
+ const unsigned char **credentials_r, size_t *size_r)
+{
+ const char *wanted_scheme = auth_request->wanted_credentials_scheme;
+ const char *plaintext, *error;
+ int ret;
+ struct password_generate_params pwd_gen_params;
+
+ if (auth_request->prefer_plain_credentials &&
+ password_scheme_is_alias(input_scheme, "PLAIN")) {
+ /* we've a plaintext scheme and we prefer to get it instead
+ of converting it to the fallback scheme */
+ wanted_scheme = "";
+ }
+
+ ret = password_decode(input, input_scheme,
+ credentials_r, size_r, &error);
+ if (ret <= 0) {
+ if (ret < 0) {
+ e_error(authdb_event(auth_request),
+ "Password data is not valid for scheme %s: %s",
+ input_scheme, error);
+ } else {
+ e_error(authdb_event(auth_request),
+ "Unknown scheme %s", input_scheme);
+ }
+ return FALSE;
+ }
+
+ if (*wanted_scheme == '\0') {
+ /* anything goes. change the wanted_credentials_scheme to what
+ we actually got, so blocking passdbs work. */
+ auth_request->wanted_credentials_scheme =
+ p_strdup(auth_request->pool, t_strcut(input_scheme, '.'));
+ return TRUE;
+ }
+
+ if (!password_scheme_is_alias(input_scheme, wanted_scheme)) {
+ if (!password_scheme_is_alias(input_scheme, "PLAIN")) {
+ const char *error = t_strdup_printf(
+ "Requested %s scheme, but we have only %s",
+ wanted_scheme, input_scheme);
+ if (auth_request->set->debug_passwords) {
+ error = t_strdup_printf("%s (input: %s)",
+ error, input);
+ }
+ e_info(authdb_event(auth_request),
+ "%s", error);
+ return FALSE;
+ }
+
+ /* we can generate anything out of plaintext passwords */
+ plaintext = t_strndup(*credentials_r, *size_r);
+ i_zero(&pwd_gen_params);
+ pwd_gen_params.user = auth_request->fields.original_username;
+ if (!auth_request->domain_is_realm &&
+ strchr(pwd_gen_params.user, '@') != NULL) {
+ /* domain must not be used as realm. add the @realm. */
+ pwd_gen_params.user = t_strconcat(pwd_gen_params.user, "@",
+ auth_request->fields.realm, NULL);
+ }
+ if (auth_request->set->debug_passwords) {
+ e_debug(authdb_event(auth_request),
+ "Generating %s from user '%s', password '%s'",
+ wanted_scheme, pwd_gen_params.user, plaintext);
+ }
+ if (!password_generate(plaintext, &pwd_gen_params,
+ wanted_scheme, credentials_r, size_r)) {
+ e_error(authdb_event(auth_request),
+ "Requested unknown scheme %s", wanted_scheme);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+void passdb_handle_credentials(enum passdb_result result,
+ const char *password, const char *scheme,
+ lookup_credentials_callback_t *callback,
+ struct auth_request *auth_request)
+{
+ const unsigned char *credentials = NULL;
+ size_t size = 0;
+
+ if (result != PASSDB_RESULT_OK) {
+ callback(result, NULL, 0, auth_request);
+ return;
+ } else if (auth_fields_exists(auth_request->fields.extra_fields,
+ "noauthenticate")) {
+ callback(PASSDB_RESULT_NEXT, NULL, 0, auth_request);
+ return;
+ }
+
+ if (password != NULL) {
+ if (!passdb_get_credentials(auth_request, password, scheme,
+ &credentials, &size))
+ result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE;
+ } else if (*auth_request->wanted_credentials_scheme == '\0') {
+ /* We're doing a passdb lookup (not authenticating).
+ Pass through a NULL password without an error. */
+ } else if (auth_request->fields.delayed_credentials != NULL) {
+ /* We already have valid credentials from an earlier
+ passdb lookup. auth_request_lookup_credentials_finish()
+ will use them. */
+ } else {
+ e_info(authdb_event(auth_request),
+ "Requested %s scheme, but we have a NULL password",
+ auth_request->wanted_credentials_scheme);
+ result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE;
+ }
+
+ callback(result, credentials, size, auth_request);
+}
+
+static struct passdb_module *
+passdb_find(const char *driver, const char *args, unsigned int *idx_r)
+{
+ struct passdb_module *const *passdbs;
+ unsigned int i, count;
+
+ passdbs = array_get(&passdb_modules, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(passdbs[i]->iface.name, driver) == 0 &&
+ strcmp(passdbs[i]->args, args) == 0) {
+ *idx_r = i;
+ return passdbs[i];
+ }
+ }
+ return NULL;
+}
+
+struct passdb_module *
+passdb_preinit(pool_t pool, const struct auth_passdb_settings *set)
+{
+ static unsigned int auth_passdb_id = 0;
+ struct passdb_module_interface *iface;
+ struct passdb_module *passdb;
+ unsigned int idx;
+
+ iface = passdb_interface_find(set->driver);
+ if (iface == NULL || iface->verify_plain == NULL) {
+ /* maybe it's a plugin. try to load it. */
+ auth_module_load(t_strconcat("authdb_", set->driver, NULL));
+ iface = passdb_interface_find(set->driver);
+ }
+ if (iface == NULL)
+ i_fatal("Unknown passdb driver '%s'", set->driver);
+ if (iface->verify_plain == NULL) {
+ i_fatal("Support not compiled in for passdb driver '%s'",
+ set->driver);
+ }
+ if (iface->preinit == NULL && iface->init == NULL &&
+ *set->args != '\0') {
+ i_fatal("passdb %s: No args are supported: %s",
+ set->driver, set->args);
+ }
+
+ passdb = passdb_find(set->driver, set->args, &idx);
+ if (passdb != NULL)
+ return passdb;
+
+ if (iface->preinit == NULL)
+ passdb = p_new(pool, struct passdb_module, 1);
+ else
+ passdb = iface->preinit(pool, set->args);
+ passdb->id = ++auth_passdb_id;
+ passdb->iface = *iface;
+ passdb->args = p_strdup(pool, set->args);
+ if (*set->mechanisms == '\0') {
+ passdb->mechanisms = NULL;
+ } else if (strcasecmp(set->mechanisms, "none") == 0) {
+ passdb->mechanisms = (const char *const[]){NULL};
+ } else {
+ passdb->mechanisms = (const char* const*)p_strsplit_spaces(pool, set->mechanisms, " ,");
+ }
+
+ if (*set->username_filter == '\0') {
+ passdb->username_filter = NULL;
+ } else {
+ passdb->username_filter = (const char* const*)p_strsplit_spaces(pool, set->username_filter, " ,");
+ }
+ array_push_back(&passdb_modules, &passdb);
+ return passdb;
+}
+
+void passdb_init(struct passdb_module *passdb)
+{
+ if (passdb->iface.init != NULL && passdb->init_refcount == 0)
+ passdb->iface.init(passdb);
+ passdb->init_refcount++;
+}
+
+void passdb_deinit(struct passdb_module *passdb)
+{
+ unsigned int idx;
+
+ i_assert(passdb->init_refcount > 0);
+
+ if (--passdb->init_refcount > 0)
+ return;
+
+ if (passdb_find(passdb->iface.name, passdb->args, &idx) == NULL)
+ i_unreached();
+ array_delete(&passdb_modules, idx, 1);
+
+ if (passdb->iface.deinit != NULL)
+ passdb->iface.deinit(passdb);
+
+ /* make sure passdb isn't accessed again */
+ passdb->iface = passdb_iface_deinit;
+}
+
+void passdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN])
+{
+ struct md5_context ctx;
+ struct passdb_module *const *passdbs;
+ unsigned int i, count;
+
+ md5_init(&ctx);
+ passdbs = array_get(&passdb_modules, &count);
+ for (i = 0; i < count; i++) {
+ md5_update(&ctx, &passdbs[i]->id, sizeof(passdbs[i]->id));
+ md5_update(&ctx, passdbs[i]->iface.name,
+ strlen(passdbs[i]->iface.name));
+ md5_update(&ctx, passdbs[i]->args, strlen(passdbs[i]->args));
+ }
+ md5_final(&ctx, md5);
+}
+
+const char *
+passdb_result_to_string(enum passdb_result result)
+{
+ switch (result) {
+ case PASSDB_RESULT_INTERNAL_FAILURE:
+ return "internal_failure";
+ case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+ return "scheme_not_available";
+ case PASSDB_RESULT_USER_UNKNOWN:
+ return "user_unknown";
+ case PASSDB_RESULT_USER_DISABLED:
+ return "user_disabled";
+ case PASSDB_RESULT_PASS_EXPIRED:
+ return "pass_expired";
+ case PASSDB_RESULT_NEXT:
+ return "next";
+ case PASSDB_RESULT_PASSWORD_MISMATCH:
+ return "password_mismatch";
+ case PASSDB_RESULT_OK:
+ return "ok";
+ }
+ i_unreached();
+}
+
+extern struct passdb_module_interface passdb_passwd;
+extern struct passdb_module_interface passdb_bsdauth;
+extern struct passdb_module_interface passdb_dict;
+#ifdef HAVE_LUA
+extern struct passdb_module_interface passdb_lua;
+#endif
+extern struct passdb_module_interface passdb_shadow;
+extern struct passdb_module_interface passdb_passwd_file;
+extern struct passdb_module_interface passdb_pam;
+extern struct passdb_module_interface passdb_checkpassword;
+extern struct passdb_module_interface passdb_ldap;
+extern struct passdb_module_interface passdb_sql;
+extern struct passdb_module_interface passdb_static;
+extern struct passdb_module_interface passdb_oauth2;
+
+void passdbs_init(void)
+{
+ i_array_init(&passdb_interfaces, 16);
+ i_array_init(&passdb_modules, 16);
+ passdb_register_module(&passdb_passwd);
+ passdb_register_module(&passdb_bsdauth);
+ passdb_register_module(&passdb_dict);
+#ifdef HAVE_LUA
+ passdb_register_module(&passdb_lua);
+#endif
+ passdb_register_module(&passdb_passwd_file);
+ passdb_register_module(&passdb_pam);
+ passdb_register_module(&passdb_checkpassword);
+ passdb_register_module(&passdb_shadow);
+ passdb_register_module(&passdb_ldap);
+ passdb_register_module(&passdb_sql);
+ passdb_register_module(&passdb_static);
+ passdb_register_module(&passdb_oauth2);
+}
+
+void passdbs_deinit(void)
+{
+ array_free(&passdb_modules);
+ array_free(&passdb_interfaces);
+}
diff --git a/src/auth/passdb.h b/src/auth/passdb.h
new file mode 100644
index 0000000..f9b33ea
--- /dev/null
+++ b/src/auth/passdb.h
@@ -0,0 +1,123 @@
+#ifndef PASSDB_H
+#define PASSDB_H
+
+#include "md5.h"
+
+#define IS_VALID_PASSWD(pass) \
+ ((pass)[0] != '\0' && (pass)[0] != '*' && (pass)[0] != '!')
+
+struct auth_request;
+struct auth_passdb_settings;
+
+enum passdb_result {
+ PASSDB_RESULT_INTERNAL_FAILURE = -1,
+ PASSDB_RESULT_SCHEME_NOT_AVAILABLE = -2,
+
+ PASSDB_RESULT_USER_UNKNOWN = -3,
+ PASSDB_RESULT_USER_DISABLED = -4,
+ PASSDB_RESULT_PASS_EXPIRED = -5,
+ PASSDB_RESULT_NEXT = -6,
+
+ PASSDB_RESULT_PASSWORD_MISMATCH = 0,
+ PASSDB_RESULT_OK = 1
+};
+
+typedef void verify_plain_callback_t(enum passdb_result result,
+ struct auth_request *request);
+typedef void verify_plain_continue_callback_t(struct auth_request *request,
+ verify_plain_callback_t *callback);
+typedef void lookup_credentials_callback_t(enum passdb_result result,
+ const unsigned char *credentials,
+ size_t size,
+ struct auth_request *request);
+typedef void set_credentials_callback_t(bool success,
+ struct auth_request *request);
+
+struct passdb_module_interface {
+ const char *name;
+
+ struct passdb_module *(*preinit)(pool_t pool, const char *args);
+ void (*init)(struct passdb_module *module);
+ void (*deinit)(struct passdb_module *module);
+
+ /* Check if plaintext password matches */
+ void (*verify_plain)(struct auth_request *request, const char *password,
+ verify_plain_callback_t *callback);
+
+ /* Return authentication credentials, set in
+ auth_request->credentials. */
+ void (*lookup_credentials)(struct auth_request *request,
+ lookup_credentials_callback_t *callback);
+
+ /* Update credentials */
+ void (*set_credentials)(struct auth_request *request,
+ const char *new_credentials,
+ set_credentials_callback_t *callback);
+};
+
+struct passdb_module {
+ const char *args;
+ /* The default caching key for this module, or NULL if caching isn't
+ wanted. This is updated by settings in auth_passdb. */
+ const char *default_cache_key;
+ /* Default password scheme for this module.
+ If default_cache_key is set, must not be NULL. */
+ const char *default_pass_scheme;
+ /* Supported authentication mechanisms, NULL is all, [NULL] is none*/
+ const char *const *mechanisms;
+ /* Username filter, NULL is no filter */
+ const char *const *username_filter;
+
+ /* If blocking is set to TRUE, use child processes to access
+ this passdb. */
+ bool blocking;
+ /* id is used by blocking passdb to identify the passdb */
+ unsigned int id;
+
+ /* number of time init() has been called */
+ int init_refcount;
+
+ /* WARNING: avoid adding anything here that isn't based on args.
+ if you do, you need to change passdb.c:passdb_find() also to avoid
+ accidentally merging wrong passdbs. */
+
+ struct passdb_module_interface iface;
+};
+
+const char *passdb_result_to_string(enum passdb_result result);
+
+/* Try to get credentials in wanted scheme (request->credentials_scheme) from
+ given input. Returns FALSE if this wasn't possible (unknown scheme,
+ conversion not possible or invalid credentials).
+
+ If wanted scheme is "", the credentials are returned as-is without any
+ checks. This is useful mostly just to see if there exist any credentials
+ at all. */
+bool passdb_get_credentials(struct auth_request *auth_request,
+ const char *input, const char *input_scheme,
+ const unsigned char **credentials_r,
+ size_t *size_r);
+
+void passdb_handle_credentials(enum passdb_result result,
+ const char *password, const char *scheme,
+ lookup_credentials_callback_t *callback,
+ struct auth_request *auth_request);
+
+struct passdb_module *
+passdb_preinit(pool_t pool, const struct auth_passdb_settings *set);
+void passdb_init(struct passdb_module *passdb);
+void passdb_deinit(struct passdb_module *passdb);
+
+void passdb_register_module(struct passdb_module_interface *iface);
+void passdb_unregister_module(struct passdb_module_interface *iface);
+
+void passdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN]);
+
+void passdbs_init(void);
+void passdbs_deinit(void);
+
+const char *passdb_oauth2_get_oidc_url(struct passdb_module *passdb);
+
+#include "auth-request.h"
+
+#endif
diff --git a/src/auth/password-scheme-crypt.c b/src/auth/password-scheme-crypt.c
new file mode 100644
index 0000000..34febad
--- /dev/null
+++ b/src/auth/password-scheme-crypt.c
@@ -0,0 +1,196 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mycrypt.h"
+#include "password-scheme.h"
+#include "crypt-blowfish.h"
+#include "randgen.h"
+
+/* Lengths and limits for some crypt() algorithms. */
+#define CRYPT_BLF_ROUNDS_DEFAULT 5
+#define CRYPT_BLF_ROUNDS_MIN 4
+#define CRYPT_BLF_ROUNDS_MAX 31
+#define CRYPT_BLF_SALT_LEN 16 /* raw salt */
+#define CRYPT_BLF_PREFIX_LEN (7+22+1) /* $2.$nn$ + salt */
+#define CRYPT_BLF_BUFFER_LEN 128
+#define CRYPT_BLF_PREFIX "$2y"
+#define CRYPT_SHA2_ROUNDS_DEFAULT 5000
+#define CRYPT_SHA2_ROUNDS_MIN 1000
+#define CRYPT_SHA2_ROUNDS_MAX 999999999
+#define CRYPT_SHA2_SALT_LEN 16
+
+static void
+crypt_generate_des(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+#define CRYPT_SALT_LEN 2
+ const char *password, *salt;
+
+ salt = password_generate_salt(CRYPT_SALT_LEN);
+ password = t_strdup(mycrypt(plaintext, salt));
+ *raw_password_r = (const unsigned char *)password;
+ *size_r = strlen(password);
+}
+
+static void
+crypt_generate_blowfish(const char *plaintext, const struct password_generate_params *params,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ char salt[CRYPT_BLF_SALT_LEN];
+ char password[CRYPT_BLF_BUFFER_LEN];
+ char magic_salt[CRYPT_BLF_PREFIX_LEN];
+ unsigned int rounds = params->rounds;
+
+ if (rounds == 0)
+ rounds = CRYPT_BLF_ROUNDS_DEFAULT;
+ else if (rounds < CRYPT_BLF_ROUNDS_MIN)
+ rounds = CRYPT_BLF_ROUNDS_MIN;
+ else if (rounds > CRYPT_BLF_ROUNDS_MAX)
+ rounds = CRYPT_BLF_ROUNDS_MAX;
+
+ random_fill(salt, CRYPT_BLF_SALT_LEN);
+ if (crypt_gensalt_blowfish_rn(CRYPT_BLF_PREFIX, rounds,
+ salt, CRYPT_BLF_SALT_LEN,
+ magic_salt, CRYPT_BLF_PREFIX_LEN) == NULL)
+ i_fatal("crypt_gensalt_blowfish_rn failed: %m");
+
+ if (crypt_blowfish_rn(plaintext, magic_salt, password,
+ CRYPT_BLF_BUFFER_LEN) == NULL)
+ i_fatal("crypt_blowfish_rn failed: %m");
+
+ *raw_password_r = (const unsigned char *)t_strdup(password);
+ *size_r = strlen(password);
+}
+
+static int
+crypt_verify_blowfish(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ const char *password;
+ const char *salt;
+ char crypted[CRYPT_BLF_BUFFER_LEN];
+
+ if (size == 0) {
+ /* the default mycrypt() handler would return match */
+ return 0;
+ }
+ password = t_strndup(raw_password, size);
+
+ if (size < CRYPT_BLF_PREFIX_LEN ||
+ !str_begins(password, "$2") ||
+ password[2] < 'a' || password[2] > 'z' ||
+ password[3] != '$') {
+ *error_r = "Password is not blowfish password";
+ return -1;
+ }
+
+ salt = t_strndup(password, CRYPT_BLF_PREFIX_LEN);
+ if (crypt_blowfish_rn(plaintext, salt, crypted, CRYPT_BLF_BUFFER_LEN) == NULL) {
+ /* really shouldn't happen unless the system is broken */
+ *error_r = t_strdup_printf("crypt_blowfish_rn failed: %m");
+ return -1;
+ }
+
+ return strcmp(crypted, password) == 0 ? 1 : 0;
+}
+
+static void
+crypt_generate_sha256(const char *plaintext, const struct password_generate_params *params,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ const char *password, *salt, *magic_salt;
+ unsigned int rounds = params->rounds;
+
+ if (rounds == 0)
+ rounds = CRYPT_SHA2_ROUNDS_DEFAULT;
+ else if (rounds < CRYPT_SHA2_ROUNDS_MIN)
+ rounds = CRYPT_SHA2_ROUNDS_MIN;
+ else if (rounds > CRYPT_SHA2_ROUNDS_MAX)
+ rounds = CRYPT_SHA2_ROUNDS_MAX;
+
+ salt = password_generate_salt(CRYPT_SHA2_SALT_LEN);
+ if (rounds == CRYPT_SHA2_ROUNDS_DEFAULT)
+ magic_salt = t_strdup_printf("$5$%s", salt);
+ else
+ magic_salt = t_strdup_printf("$5$rounds=%u$%s", rounds, salt);
+ password = t_strdup(mycrypt(plaintext, magic_salt));
+ *raw_password_r = (const unsigned char *)password;
+ *size_r = strlen(password);
+}
+
+static void
+crypt_generate_sha512(const char *plaintext, const struct password_generate_params *params,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ const char *password, *salt, *magic_salt;
+ unsigned int rounds = params->rounds;
+
+ if (rounds == 0)
+ rounds = CRYPT_SHA2_ROUNDS_DEFAULT;
+ else if (rounds < CRYPT_SHA2_ROUNDS_MIN)
+ rounds = CRYPT_SHA2_ROUNDS_MIN;
+ else if (rounds > CRYPT_SHA2_ROUNDS_MAX)
+ rounds = CRYPT_SHA2_ROUNDS_MAX;
+
+ salt = password_generate_salt(CRYPT_SHA2_SALT_LEN);
+ if (rounds == CRYPT_SHA2_ROUNDS_DEFAULT)
+ magic_salt = t_strdup_printf("$6$%s", salt);
+ else
+ magic_salt = t_strdup_printf("$6$rounds=%u$%s", rounds, salt);
+ password = t_strdup(mycrypt(plaintext, magic_salt));
+ *raw_password_r = (const unsigned char *)password;
+ *size_r = strlen(password);
+}
+
+/* keep in sync with the crypt_schemes struct below */
+static const struct {
+ const char *key;
+ const char *salt;
+ const char *expected;
+} sample[] = {
+ { "08/15!test~4711", "JB", "JBOZ0DgmtucwE" },
+ { "08/15!test~4711", "$5$rounds=1000$0123456789abcdef",
+ "$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt"
+ "9McEgrbFMKi9qrb1jehe7hn4" },
+ { "08/15!test~4711", "$6$rounds=1000$0123456789abcdef",
+ "$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTq"
+ "vhJoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1" }
+};
+
+/* keep in sync with the sample struct above */
+static const struct password_scheme crypt_schemes[] = {
+ { "DES-CRYPT", PW_ENCODING_NONE, 0, crypt_verify,
+ crypt_generate_des },
+ { "SHA256-CRYPT", PW_ENCODING_NONE, 0, crypt_verify,
+ crypt_generate_sha256 },
+ { "SHA512-CRYPT", PW_ENCODING_NONE, 0, crypt_verify,
+ crypt_generate_sha512 }
+};
+
+static const struct password_scheme blf_crypt_scheme = {
+ "BLF-CRYPT", PW_ENCODING_NONE, 0, crypt_verify_blowfish,
+ crypt_generate_blowfish
+};
+
+static const struct password_scheme default_crypt_scheme = {
+ "CRYPT", PW_ENCODING_NONE, 0, crypt_verify,
+ crypt_generate_blowfish
+};
+
+void password_scheme_register_crypt(void)
+{
+ unsigned int i;
+ const char *crypted;
+
+ i_assert(N_ELEMENTS(crypt_schemes) == N_ELEMENTS(sample));
+
+ for (i = 0; i < N_ELEMENTS(crypt_schemes); i++) {
+ crypted = mycrypt(sample[i].key, sample[i].salt);
+ if (crypted != NULL &&
+ (strcmp(crypted, sample[i].expected) == 0))
+ password_scheme_register(&crypt_schemes[i]);
+ }
+ password_scheme_register(&blf_crypt_scheme);
+ password_scheme_register(&default_crypt_scheme);
+}
diff --git a/src/auth/password-scheme-md5crypt.c b/src/auth/password-scheme-md5crypt.c
new file mode 100644
index 0000000..a75e3b2
--- /dev/null
+++ b/src/auth/password-scheme-md5crypt.c
@@ -0,0 +1,147 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * Ported from FreeBSD to Linux, only minimal changes. --marekm
+ */
+
+/*
+ * Adapted from shadow-19990607 by Tudor Bosman, tudorb@jm.nu
+ */
+
+#include "lib.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "md5.h"
+#include "password-scheme.h"
+
+static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static char magic[] = "$1$"; /*
+ * This string is magic for
+ * this algorithm. Having
+ * it this way, we can get
+ * get better later on
+ */
+
+static void
+to64(string_t *str, unsigned long v, int n)
+{
+ while (--n >= 0) {
+ str_append_c(str, itoa64[v&0x3f]);
+ v >>= 6;
+ }
+}
+
+/*
+ * UNIX password
+ *
+ * Use MD5 for what it is best at...
+ */
+
+const char *password_generate_md5_crypt(const char *pw, const char *salt)
+{
+ const char *sp,*ep;
+ unsigned char final[MD5_RESULTLEN];
+ int sl,pl,i,j;
+ struct md5_context ctx,ctx1;
+ unsigned long l;
+ string_t *passwd;
+ size_t pw_len = strlen(pw);
+
+ /* Refine the Salt first */
+ sp = salt;
+
+ /* If it starts with the magic string, then skip that */
+ if (strncmp(sp, magic, sizeof(magic)-1) == 0)
+ sp += sizeof(magic)-1;
+
+ /* It stops at the first '$', max 8 chars */
+ for(ep=sp;*ep != '\0' && *ep != '$' && ep < (sp+8);ep++)
+ continue;
+
+ /* get the length of the true salt */
+ sl = ep - sp;
+
+ md5_init(&ctx);
+
+ /* The password first, since that is what is most unknown */
+ md5_update(&ctx,pw,pw_len);
+
+ /* Then our magic string */
+ md5_update(&ctx,magic,sizeof(magic)-1);
+
+ /* Then the raw salt */
+ md5_update(&ctx,sp,sl);
+
+ /* Then just as many characters of the MD5(pw,salt,pw) */
+ md5_init(&ctx1);
+ md5_update(&ctx1,pw,pw_len);
+ md5_update(&ctx1,sp,sl);
+ md5_update(&ctx1,pw,pw_len);
+ md5_final(&ctx1,final);
+ for(pl = pw_len; pl > 0; pl -= MD5_RESULTLEN)
+ md5_update(&ctx,final,pl>MD5_RESULTLEN ? MD5_RESULTLEN : pl);
+
+ /* Don't leave anything around in vm they could use. */
+ safe_memset(final, 0, sizeof(final));
+
+ /* Then something really weird... */
+ for (j=0,i = pw_len; i != 0; i >>= 1)
+ if ((i&1) != 0)
+ md5_update(&ctx, final+j, 1);
+ else
+ md5_update(&ctx, pw+j, 1);
+
+ /* Now make the output string */
+ passwd = t_str_new(sl + 64);
+ str_append(passwd, magic);
+ str_append_data(passwd, sp, sl);
+ str_append_c(passwd, '$');
+
+ md5_final(&ctx,final);
+
+ /*
+ * and now, just to make sure things don't run too fast
+ * On a 60 Mhz Pentium this takes 34 msec, so you would
+ * need 30 seconds to build a 1000 entry dictionary...
+ */
+ for(i=0;i<1000;i++) {
+ md5_init(&ctx1);
+ if((i & 1) != 0)
+ md5_update(&ctx1,pw,pw_len);
+ else
+ md5_update(&ctx1,final,MD5_RESULTLEN);
+
+ if((i % 3) != 0)
+ md5_update(&ctx1,sp,sl);
+
+ if((i % 7) != 0)
+ md5_update(&ctx1,pw,pw_len);
+
+ if((i & 1) != 0)
+ md5_update(&ctx1,final,MD5_RESULTLEN);
+ else
+ md5_update(&ctx1,pw,pw_len);
+ md5_final(&ctx1,final);
+ }
+
+ l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(passwd,l,4);
+ l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(passwd,l,4);
+ l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(passwd,l,4);
+ l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(passwd,l,4);
+ l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(passwd,l,4);
+ l = final[11] ; to64(passwd,l,2);
+
+ /* Don't leave anything around in vm they could use. */
+ safe_memset(final, 0, sizeof(final));
+
+ return str_c(passwd);
+}
diff --git a/src/auth/password-scheme-otp.c b/src/auth/password-scheme-otp.c
new file mode 100644
index 0000000..ad7951b
--- /dev/null
+++ b/src/auth/password-scheme-otp.c
@@ -0,0 +1,40 @@
+/*
+ * OTP password scheme.
+ *
+ * Copyright (c) 2006 Andrey Panin <pazke@donpac.ru>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "lib.h"
+#include "hex-binary.h"
+#include "password-scheme.h"
+#include "randgen.h"
+#include "otp.h"
+
+int password_generate_otp(const char *pw, const char *state_data,
+ unsigned int algo, const char **result_r)
+{
+ struct otp_state state;
+
+ if (state_data != NULL) {
+ if (otp_parse_dbentry(state_data, &state) != 0)
+ return -1;
+ } else {
+ /* Generate new OTP credentials from plaintext */
+ unsigned char random_data[OTP_MAX_SEED_LEN / 2];
+ const char *random_hex;
+
+ random_fill(random_data, sizeof(random_data));
+ random_hex = binary_to_hex(random_data, sizeof(random_data));
+ if (i_strocpy(state.seed, random_hex, sizeof(state.seed)) < 0)
+ i_unreached();
+
+ state.seq = 1024;
+ state.algo = algo;
+ }
+
+ otp_hash(state.algo, state.seed, pw, state.seq, state.hash);
+ *result_r = otp_print_dbentry(&state);
+ return 0;
+}
diff --git a/src/auth/password-scheme-pbkdf2.c b/src/auth/password-scheme-pbkdf2.c
new file mode 100644
index 0000000..1e8eb3b
--- /dev/null
+++ b/src/auth/password-scheme-pbkdf2.c
@@ -0,0 +1,82 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "password-scheme.h"
+#include "hex-binary.h"
+#include "hash-method.h"
+#include "pkcs5.h"
+
+#define PBKDF2_KEY_SIZE_SHA1 20
+
+#define PBKDF2_GENERATE_SALT_LEN 16
+#define PBKDF2_ROUNDS_DEFAULT 5000
+
+static void
+pbkdf_run(const char *plaintext, const char *salt,
+ unsigned int rounds, unsigned char key_r[PBKDF2_KEY_SIZE_SHA1])
+{
+ memset(key_r, 0, PBKDF2_KEY_SIZE_SHA1);
+ buffer_t buf;
+ buffer_create_from_data(&buf, key_r, PBKDF2_KEY_SIZE_SHA1);
+
+ pkcs5_pbkdf(PKCS5_PBKDF2, hash_method_lookup("sha1"),
+ (const unsigned char *)plaintext, strlen(plaintext),
+ (const unsigned char *)salt, strlen(salt),
+ rounds, PBKDF2_KEY_SIZE_SHA1, &buf);
+}
+
+void pbkdf2_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char key[PBKDF2_KEY_SIZE_SHA1];
+ const char *salt;
+ string_t *str = t_str_new(64);
+ unsigned int rounds = params->rounds;
+
+ if (rounds == 0)
+ rounds = PBKDF2_ROUNDS_DEFAULT;
+ salt = password_generate_salt(PBKDF2_GENERATE_SALT_LEN);
+ pbkdf_run(plaintext, salt, rounds, key);
+
+ str_printfa(str, "$1$%s$%u$", salt, rounds);
+ binary_to_hex_append(str, key, sizeof(key));
+
+ *raw_password_r = str_data(str);
+ *size_r = str_len(str);
+}
+
+int pbkdf2_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ const char *const *fields;
+ const char *salt;
+ unsigned int rounds;
+ unsigned char key1[PBKDF2_KEY_SIZE_SHA1], key2[PBKDF2_KEY_SIZE_SHA1];
+ buffer_t buf;
+
+ /* $1$salt$rounds$hash */
+ if (size < 3 || memcmp(raw_password, "$1$", 3) != 0) {
+ *error_r = "Invalid PBKDF2 passdb entry prefix";
+ return -1;
+ }
+
+ fields = t_strsplit(t_strndup(raw_password + 3, size - 3), "$");
+ salt = fields[0];
+ if (str_array_length(fields) != 3 ||
+ str_to_uint(fields[1], &rounds) < 0) {
+ *error_r = "Invalid PBKDF2 passdb entry format";
+ return -1;
+ }
+ buffer_create_from_data(&buf, key1, sizeof(key1));
+ if (strlen(fields[2]) != sizeof(key1)*2 ||
+ hex_to_binary(fields[2], &buf) < 0) {
+ *error_r = "PBKDF2 hash not 160bit hex-encoded";
+ return -1;
+ }
+
+ pbkdf_run(plaintext, salt, rounds, key2);
+ return mem_equals_timing_safe(key1, key2, sizeof(key1)) ? 1 : 0;
+}
diff --git a/src/auth/password-scheme-scram.c b/src/auth/password-scheme-scram.c
new file mode 100644
index 0000000..a395074
--- /dev/null
+++ b/src/auth/password-scheme-scram.c
@@ -0,0 +1,224 @@
+/*
+ * SCRAM-SHA-1 SASL authentication, see RFC-5802
+ *
+ * Copyright (c) 2012 Florian Zeitz <florob@babelmonkeys.de>
+ *
+ * This software is released under the MIT license.
+ */
+
+
+#include "lib.h"
+#include "safe-memset.h"
+#include "base64.h"
+#include "buffer.h"
+#include "hmac.h"
+#include "randgen.h"
+#include "hash-method.h"
+#include "sha1.h"
+#include "sha2.h"
+#include "str.h"
+#include "password-scheme.h"
+
+/* SCRAM allowed iteration count range. RFC says it SHOULD be at least 4096 */
+#define SCRAM_MIN_ITERATE_COUNT 4096
+#define SCRAM_MAX_ITERATE_COUNT INT_MAX
+
+#define SCRAM_DEFAULT_ITERATE_COUNT 4096
+
+static void
+Hi(const struct hash_method *hmethod, const unsigned char *str, size_t str_size,
+ const unsigned char *salt, size_t salt_size, unsigned int i,
+ unsigned char *result)
+{
+ struct hmac_context ctx;
+ unsigned char U[hmethod->digest_size];
+ unsigned int j, k;
+
+ /* Calculate U1 */
+ hmac_init(&ctx, str, str_size, hmethod);
+ hmac_update(&ctx, salt, salt_size);
+ hmac_update(&ctx, "\0\0\0\1", 4);
+ hmac_final(&ctx, U);
+
+ memcpy(result, U, hmethod->digest_size);
+
+ /* Calculate U2 to Ui and Hi */
+ for (j = 2; j <= i; j++) {
+ hmac_init(&ctx, str, str_size, hmethod);
+ hmac_update(&ctx, U, sizeof(U));
+ hmac_final(&ctx, U);
+ for (k = 0; k < hmethod->digest_size; k++)
+ result[k] ^= U[k];
+ }
+}
+
+int scram_scheme_parse(const struct hash_method *hmethod, const char *name,
+ const unsigned char *credentials, size_t size,
+ unsigned int *iter_count_r, const char **salt_r,
+ unsigned char stored_key_r[],
+ unsigned char server_key_r[], const char **error_r)
+{
+ const char *const *fields;
+ buffer_t *buf;
+
+ /* password string format: iter,salt,stored_key,server_key */
+ fields = t_strsplit(t_strndup(credentials, size), ",");
+
+ if (str_array_length(fields) != 4) {
+ *error_r = t_strdup_printf(
+ "Invalid %s passdb entry format", name);
+ return -1;
+ }
+ if (str_to_uint(fields[0], iter_count_r) < 0 ||
+ *iter_count_r < SCRAM_MIN_ITERATE_COUNT ||
+ *iter_count_r > SCRAM_MAX_ITERATE_COUNT) {
+ *error_r = t_strdup_printf(
+ "Invalid %s iteration count in passdb", name);
+ return -1;
+ }
+ *salt_r = fields[1];
+
+ buf = t_buffer_create(hmethod->digest_size);
+ if (base64_decode(fields[2], strlen(fields[2]), NULL, buf) < 0 ||
+ buf->used != hmethod->digest_size) {
+ *error_r = t_strdup_printf(
+ "Invalid %s StoredKey in passdb", name);
+ return -1;
+ }
+ memcpy(stored_key_r, buf->data, hmethod->digest_size);
+
+ buffer_set_used_size(buf, 0);
+ if (base64_decode(fields[3], strlen(fields[3]), NULL, buf) < 0 ||
+ buf->used != hmethod->digest_size) {
+ *error_r = t_strdup_printf(
+ "Invalid %s ServerKey in passdb", name);
+ return -1;
+ }
+ memcpy(server_key_r, buf->data, hmethod->digest_size);
+ return 0;
+}
+
+int scram_verify(const struct hash_method *hmethod, const char *scheme_name,
+ const char *plaintext, const unsigned char *raw_password,
+ size_t size, const char **error_r)
+{
+ struct hmac_context ctx;
+ const char *salt_base64;
+ unsigned int iter_count;
+ const unsigned char *salt;
+ size_t salt_len;
+ unsigned char salted_password[hmethod->digest_size];
+ unsigned char client_key[hmethod->digest_size];
+ unsigned char stored_key[hmethod->digest_size];
+ unsigned char calculated_stored_key[hmethod->digest_size];
+ unsigned char server_key[hmethod->digest_size];
+ int ret;
+
+ if (scram_scheme_parse(hmethod, scheme_name, raw_password, size,
+ &iter_count, &salt_base64,
+ stored_key, server_key, error_r) < 0)
+ return -1;
+
+ salt = buffer_get_data(t_base64_decode_str(salt_base64), &salt_len);
+
+ /* FIXME: credentials should be SASLprepped UTF8 data here */
+ Hi(hmethod, (const unsigned char *)plaintext, strlen(plaintext),
+ salt, salt_len, iter_count, salted_password);
+
+ /* Calculate ClientKey */
+ hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod);
+ hmac_update(&ctx, "Client Key", 10);
+ hmac_final(&ctx, client_key);
+
+ /* Calculate StoredKey */
+ hash_method_get_digest(hmethod, client_key, sizeof(client_key),
+ calculated_stored_key);
+ ret = mem_equals_timing_safe(stored_key, calculated_stored_key,
+ sizeof(stored_key)) ? 1 : 0;
+
+ safe_memset(salted_password, 0, sizeof(salted_password));
+ safe_memset(client_key, 0, sizeof(client_key));
+ safe_memset(stored_key, 0, sizeof(stored_key));
+
+ return ret;
+}
+
+void scram_generate(const struct hash_method *hmethod, const char *plaintext,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ string_t *str;
+ struct hmac_context ctx;
+ unsigned char salt[16];
+ unsigned char salted_password[hmethod->digest_size];
+ unsigned char client_key[hmethod->digest_size];
+ unsigned char server_key[hmethod->digest_size];
+ unsigned char stored_key[hmethod->digest_size];
+
+ random_fill(salt, sizeof(salt));
+
+ str = t_str_new(MAX_BASE64_ENCODED_SIZE(sizeof(salt)));
+ str_printfa(str, "%d,", SCRAM_DEFAULT_ITERATE_COUNT);
+ base64_encode(salt, sizeof(salt), str);
+
+ /* FIXME: credentials should be SASLprepped UTF8 data here */
+ Hi(hmethod, (const unsigned char *)plaintext, strlen(plaintext), salt,
+ sizeof(salt), SCRAM_DEFAULT_ITERATE_COUNT, salted_password);
+
+ /* Calculate ClientKey */
+ hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod);
+ hmac_update(&ctx, "Client Key", 10);
+ hmac_final(&ctx, client_key);
+
+ /* Calculate StoredKey */
+ hash_method_get_digest(hmethod, client_key, sizeof(client_key),
+ stored_key);
+ str_append_c(str, ',');
+ base64_encode(stored_key, sizeof(stored_key), str);
+
+ /* Calculate ServerKey */
+ hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod);
+ hmac_update(&ctx, "Server Key", 10);
+ hmac_final(&ctx, server_key);
+ str_append_c(str, ',');
+ base64_encode(server_key, sizeof(server_key), str);
+
+ safe_memset(salted_password, 0, sizeof(salted_password));
+ safe_memset(client_key, 0, sizeof(client_key));
+ safe_memset(server_key, 0, sizeof(server_key));
+ safe_memset(stored_key, 0, sizeof(stored_key));
+
+ *raw_password_r = (const unsigned char *)str_c(str);
+ *size_r = str_len(str);
+}
+
+int scram_sha1_verify(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ return scram_verify(&hash_method_sha1, "SCRAM-SHA-1", plaintext,
+ raw_password, size, error_r);
+}
+
+void scram_sha1_generate(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ scram_generate(&hash_method_sha1, plaintext, raw_password_r, size_r);
+}
+
+int scram_sha256_verify(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ return scram_verify(&hash_method_sha256, "SCRAM-SHA-256", plaintext,
+ raw_password, size, error_r);
+}
+
+void scram_sha256_generate(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ scram_generate(&hash_method_sha256, plaintext, raw_password_r, size_r);
+}
diff --git a/src/auth/password-scheme-sodium.c b/src/auth/password-scheme-sodium.c
new file mode 100644
index 0000000..3e2f6bd
--- /dev/null
+++ b/src/auth/password-scheme-sodium.c
@@ -0,0 +1,92 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "password-scheme.h"
+
+#ifdef HAVE_LIBSODIUM
+#include <sodium.h>
+
+static void
+generate_argon2i(const char *plaintext, const struct password_generate_params *params,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned long long rounds = params->rounds;
+ size_t memlimit;
+ char result[crypto_pwhash_STRBYTES];
+
+ if (rounds == 0)
+ rounds = crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE;
+
+ if (rounds >= crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE)
+ memlimit = crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE;
+ else if (rounds >= crypto_pwhash_argon2i_OPSLIMIT_MODERATE)
+ memlimit = crypto_pwhash_argon2i_MEMLIMIT_MODERATE;
+ else
+ memlimit = crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE;
+
+ if (crypto_pwhash_argon2i_str(result, plaintext, strlen(plaintext), rounds, memlimit) < 0)
+ i_fatal("crypto_pwhash_argon2i_str failed: %m");
+ *raw_password_r = (const unsigned char*)t_strdup(result);
+ *size_r = strlen(result);
+}
+
+#ifdef crypto_pwhash_ALG_ARGON2ID13
+static void
+generate_argon2id(const char *plaintext, const struct password_generate_params *params,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned long long rounds = params->rounds;
+ size_t memlimit;
+ char result[crypto_pwhash_argon2id_STRBYTES];
+ i_zero(&result);
+
+ if (rounds == 0)
+ rounds = crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE;
+
+ if (rounds >= crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE)
+ memlimit = crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE;
+ else if (rounds >= crypto_pwhash_argon2id_OPSLIMIT_MODERATE)
+ memlimit = crypto_pwhash_argon2id_MEMLIMIT_MODERATE;
+ else
+ memlimit = crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE;
+
+ /* XXX: Bug in sodium-1.0.13, it expects rounds to be 3 */
+ if (rounds < 3)
+ rounds = 3;
+
+ if (crypto_pwhash_argon2id_str(result, plaintext, strlen(plaintext), rounds, memlimit) < 0)
+ i_fatal("crypto_pwhash_argon2id_str failed: %m");
+ *raw_password_r = (const unsigned char*)t_strdup(result);
+ *size_r = strlen(result);
+}
+#endif
+
+static int
+verify_argon2(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r ATTR_UNUSED)
+{
+ const char *passwd = t_strndup(raw_password, size);
+ if (crypto_pwhash_str_verify(passwd, plaintext, strlen(plaintext)) < 0)
+ return 0;
+ return 1;
+}
+
+
+static const struct password_scheme sodium_schemes[] = {
+ { "ARGON2I", PW_ENCODING_NONE, 0, verify_argon2,
+ generate_argon2i },
+#ifdef crypto_pwhash_ALG_ARGON2ID13
+ { "ARGON2ID", PW_ENCODING_NONE, 0, verify_argon2,
+ generate_argon2id },
+#endif
+};
+
+void password_scheme_register_sodium(void)
+{
+ if (sodium_init() != 0)
+ i_fatal("sodium_init() failed");
+ for(size_t i = 0; i < N_ELEMENTS(sodium_schemes); i++)
+ password_scheme_register(&sodium_schemes[i]);
+}
+#endif
diff --git a/src/auth/password-scheme.c b/src/auth/password-scheme.c
new file mode 100644
index 0000000..c572cd3
--- /dev/null
+++ b/src/auth/password-scheme.c
@@ -0,0 +1,822 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "base64.h"
+#include "hex-binary.h"
+#include "md4.h"
+#include "md5.h"
+#include "hmac.h"
+#include "hmac-cram-md5.h"
+#include "mycrypt.h"
+#include "randgen.h"
+#include "sha1.h"
+#include "sha2.h"
+#include "otp.h"
+#include "str.h"
+#include "password-scheme.h"
+
+static const char salt_chars[] =
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static HASH_TABLE(const char*, const struct password_scheme *) password_schemes;
+
+static const struct password_scheme *
+password_scheme_lookup_name(const char *name)
+{
+ return hash_table_lookup(password_schemes, name);
+}
+
+/* Lookup scheme and encoding by given name. The encoding is taken from
+ ".base64", ".b64" or ".hex" suffix if it exists, otherwise the default
+ encoding is used. */
+static const struct password_scheme *
+password_scheme_lookup(const char *name, enum password_encoding *encoding_r)
+{
+ const struct password_scheme *scheme;
+ const char *encoding = NULL;
+
+ *encoding_r = PW_ENCODING_NONE;
+ if ((encoding = strchr(name, '.')) != NULL) {
+ name = t_strdup_until(name, encoding);
+ encoding++;
+ }
+
+ scheme = password_scheme_lookup_name(name);
+ if (scheme == NULL)
+ return NULL;
+
+ if (encoding == NULL)
+ *encoding_r = scheme->default_encoding;
+ else if (strcasecmp(encoding, "b64") == 0 ||
+ strcasecmp(encoding, "base64") == 0)
+ *encoding_r = PW_ENCODING_BASE64;
+ else if (strcasecmp(encoding, "hex") == 0)
+ *encoding_r = PW_ENCODING_HEX;
+ else {
+ /* unknown encoding. treat as invalid scheme. */
+ return NULL;
+ }
+ return scheme;
+}
+
+int password_verify(const char *plaintext,
+ const struct password_generate_params *params,
+ const char *scheme, const unsigned char *raw_password,
+ size_t size, const char **error_r)
+{
+ const struct password_scheme *s;
+ enum password_encoding encoding;
+ const unsigned char *generated;
+ size_t generated_size;
+ int ret;
+
+ s = password_scheme_lookup(scheme, &encoding);
+ if (s == NULL) {
+ *error_r = "Unknown password scheme";
+ return -1;
+ }
+
+ if (s->password_verify != NULL) {
+ ret = s->password_verify(plaintext, params, raw_password, size,
+ error_r);
+ } else {
+ /* generic verification handler: generate the password and
+ compare it to the one in database */
+ s->password_generate(plaintext, params,
+ &generated, &generated_size);
+ ret = size != generated_size ? 0 :
+ mem_equals_timing_safe(generated, raw_password, size) ? 1 : 0;
+ }
+
+ if (ret == 0)
+ *error_r = AUTH_LOG_MSG_PASSWORD_MISMATCH;
+ return ret;
+}
+
+const char *password_get_scheme(const char **password)
+{
+ const char *p, *scheme;
+
+ if (*password == NULL)
+ return NULL;
+
+ if (str_begins(*password, "$1$")) {
+ /* $1$<salt>$<password>[$<ignored>] */
+ p = strchr(*password + 3, '$');
+ if (p != NULL) {
+ /* stop at next '$' after password */
+ p = strchr(p+1, '$');
+ if (p != NULL)
+ *password = t_strdup_until(*password, p);
+ return "MD5-CRYPT";
+ }
+ }
+
+ if (**password != '{')
+ return NULL;
+
+ p = strchr(*password, '}');
+ if (p == NULL)
+ return NULL;
+
+ scheme = t_strdup_until(*password + 1, p);
+ *password = p + 1;
+ return scheme;
+}
+
+int password_decode(const char *password, const char *scheme,
+ const unsigned char **raw_password_r, size_t *size_r,
+ const char **error_r)
+{
+ const struct password_scheme *s;
+ enum password_encoding encoding;
+ buffer_t *buf;
+ size_t len;
+ bool guessed_encoding;
+
+ *error_r = NULL;
+
+ s = password_scheme_lookup(scheme, &encoding);
+ if (s == NULL) {
+ *error_r = "Unknown scheme";
+ return 0;
+ }
+
+ len = strlen(password);
+ if (encoding != PW_ENCODING_NONE && s->raw_password_len != 0 &&
+ strchr(scheme, '.') == NULL) {
+ /* encoding not specified. we can guess quite well between
+ base64 and hex encodings. the only problem is distinguishing
+ 2 character strings, but there shouldn't be any that short
+ raw_password_lens. */
+ encoding = len == s->raw_password_len * 2 ?
+ PW_ENCODING_HEX : PW_ENCODING_BASE64;
+ guessed_encoding = TRUE;
+ } else {
+ guessed_encoding = FALSE;
+ }
+
+ switch (encoding) {
+ case PW_ENCODING_NONE:
+ *raw_password_r = (const unsigned char *)password;
+ *size_r = len;
+ break;
+ case PW_ENCODING_HEX:
+ buf = t_buffer_create(len / 2 + 1);
+ if (hex_to_binary(password, buf) == 0) {
+ *raw_password_r = buf->data;
+ *size_r = buf->used;
+ break;
+ }
+ if (!guessed_encoding) {
+ *error_r = "Input isn't valid HEX encoded data";
+ return -1;
+ }
+ /* check if it's base64-encoded after all. some input lengths
+ produce matching hex and base64 encoded lengths. */
+ /* fall through */
+ case PW_ENCODING_BASE64:
+ buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(len));
+ if (base64_decode(password, len, NULL, buf) < 0) {
+ *error_r = "Input isn't valid base64 encoded data";
+ return -1;
+ }
+
+ *raw_password_r = buf->data;
+ *size_r = buf->used;
+ break;
+ }
+ if (s->raw_password_len != *size_r && s->raw_password_len != 0) {
+ /* password has invalid length */
+ *error_r = t_strdup_printf(
+ "Input length isn't valid (%u instead of %u)",
+ (unsigned int)*size_r, s->raw_password_len);
+ return -1;
+ }
+ return 1;
+}
+
+bool password_generate(const char *plaintext, const struct password_generate_params *params,
+ const char *scheme,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ const struct password_scheme *s;
+ enum password_encoding encoding;
+
+ s = password_scheme_lookup(scheme, &encoding);
+ if (s == NULL)
+ return FALSE;
+
+ s->password_generate(plaintext, params, raw_password_r, size_r);
+ return TRUE;
+}
+
+bool password_generate_encoded(const char *plaintext, const struct password_generate_params *params,
+ const char *scheme, const char **password_r)
+{
+ const struct password_scheme *s;
+ const unsigned char *raw_password;
+ enum password_encoding encoding;
+ string_t *str;
+ size_t size;
+
+ s = password_scheme_lookup(scheme, &encoding);
+ if (s == NULL)
+ return FALSE;
+
+ s->password_generate(plaintext, params, &raw_password, &size);
+ switch (encoding) {
+ case PW_ENCODING_NONE:
+ *password_r = t_strndup(raw_password, size);
+ break;
+ case PW_ENCODING_BASE64:
+ str = t_str_new(MAX_BASE64_ENCODED_SIZE(size) + 1);
+ base64_encode(raw_password, size, str);
+ *password_r = str_c(str);
+ break;
+ case PW_ENCODING_HEX:
+ *password_r = binary_to_hex(raw_password, size);
+ break;
+ }
+ return TRUE;
+}
+
+const char *password_generate_salt(size_t len)
+{
+ char *salt;
+ salt = t_malloc_no0(len + 1);
+ for (size_t i = 0; i < len; i++)
+ salt[i] = salt_chars[i_rand_limit(sizeof(salt_chars) - 1)];
+ salt[len] = '\0';
+ return salt;
+}
+
+bool password_scheme_is_alias(const char *scheme1, const char *scheme2)
+{
+ const struct password_scheme *s1 = NULL, *s2 = NULL;
+
+ if (*scheme1 == '\0' || *scheme2 == '\0')
+ return FALSE;
+
+ scheme1 = t_strcut(scheme1, '.');
+ scheme2 = t_strcut(scheme2, '.');
+
+ if (strcasecmp(scheme1, scheme2) == 0)
+ return TRUE;
+
+ s1 = hash_table_lookup(password_schemes, scheme1);
+ s2 = hash_table_lookup(password_schemes, scheme2);
+
+ /* if they've the same generate function, they're equivalent */
+ return s1 != NULL && s2 != NULL &&
+ s1->password_generate == s2->password_generate;
+}
+
+const char *
+password_scheme_detect(const char *plain_password, const char *crypted_password,
+ const struct password_generate_params *params)
+{
+ struct hash_iterate_context *ctx;
+ const char *key;
+ const struct password_scheme *scheme;
+ const unsigned char *raw_password;
+ size_t raw_password_size;
+ const char *error;
+
+ ctx = hash_table_iterate_init(password_schemes);
+ while (hash_table_iterate(ctx, password_schemes, &key, &scheme)) {
+ if (password_decode(crypted_password, scheme->name,
+ &raw_password, &raw_password_size,
+ &error) <= 0)
+ continue;
+
+ if (password_verify(plain_password, params, scheme->name,
+ raw_password, raw_password_size,
+ &error) > 0)
+ break;
+ key = NULL;
+ }
+ hash_table_iterate_deinit(&ctx);
+ return key;
+}
+
+int crypt_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ const char *password, *crypted;
+
+ if (size > 4 && raw_password[0] == '$' && raw_password[1] == '2' &&
+ raw_password[3] == '$')
+ return password_verify(plaintext, params, "BLF-CRYPT",
+ raw_password, size, error_r);
+
+ if (size == 0) {
+ /* the default mycrypt() handler would return match */
+ return 0;
+ }
+
+ password = t_strndup(raw_password, size);
+ crypted = mycrypt(plaintext, password);
+ if (crypted == NULL) {
+ /* really shouldn't happen unless the system is broken */
+ *error_r = t_strdup_printf("crypt() failed: %m");
+ return -1;
+ }
+
+ return str_equals_timing_almost_safe(crypted, password) ? 1 : 0;
+}
+
+static int
+md5_verify(const char *plaintext, const struct password_generate_params *params,
+ const unsigned char *raw_password, size_t size, const char **error_r)
+{
+ const char *password, *str, *error;
+ const unsigned char *md5_password;
+ size_t md5_size;
+
+ password = t_strndup(raw_password, size);
+ if (str_begins(password, "$1$")) {
+ /* MD5-CRYPT */
+ str = password_generate_md5_crypt(plaintext, password);
+ return str_equals_timing_almost_safe(str, password) ? 1 : 0;
+ } else if (password_decode(password, "PLAIN-MD5",
+ &md5_password, &md5_size, &error) <= 0) {
+ *error_r = "Not a valid MD5-CRYPT or PLAIN-MD5 password";
+ return -1;
+ } else {
+ return password_verify(plaintext, params, "PLAIN-MD5",
+ md5_password, md5_size, error_r);
+ }
+}
+
+static int
+md5_crypt_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r ATTR_UNUSED)
+{
+ const char *password, *str;
+
+ password = t_strndup(raw_password, size);
+ str = password_generate_md5_crypt(plaintext, password);
+ return str_equals_timing_almost_safe(str, password) ? 1 : 0;
+}
+
+static void
+md5_crypt_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ const char *password;
+ const char *salt;
+
+ salt = password_generate_salt(8);
+
+ password = password_generate_md5_crypt(plaintext, salt);
+ *raw_password_r = (const unsigned char *)password;
+ *size_r = strlen(password);
+}
+
+static void
+sha1_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(SHA1_RESULTLEN);
+ sha1_get_digest(plaintext, strlen(plaintext), digest);
+
+ *raw_password_r = digest;
+ *size_r = SHA1_RESULTLEN;
+}
+
+static void
+sha256_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(SHA256_RESULTLEN);
+ sha256_get_digest(plaintext, strlen(plaintext), digest);
+
+ *raw_password_r = digest;
+ *size_r = SHA256_RESULTLEN;
+}
+
+static void
+sha512_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(SHA512_RESULTLEN);
+ sha512_get_digest(plaintext, strlen(plaintext), digest);
+
+ *raw_password_r = digest;
+ *size_r = SHA512_RESULTLEN;
+}
+
+static void
+ssha_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+#define SSHA_SALT_LEN 4
+ unsigned char *digest, *salt;
+ struct sha1_ctxt ctx;
+
+ digest = t_malloc_no0(SHA1_RESULTLEN + SSHA_SALT_LEN);
+ salt = digest + SHA1_RESULTLEN;
+ random_fill(salt, SSHA_SALT_LEN);
+
+ sha1_init(&ctx);
+ sha1_loop(&ctx, plaintext, strlen(plaintext));
+ sha1_loop(&ctx, salt, SSHA_SALT_LEN);
+ sha1_result(&ctx, digest);
+
+ *raw_password_r = digest;
+ *size_r = SHA1_RESULTLEN + SSHA_SALT_LEN;
+}
+
+static int ssha_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ unsigned char sha1_digest[SHA1_RESULTLEN];
+ struct sha1_ctxt ctx;
+
+ /* format: <SHA1 hash><salt> */
+ if (size <= SHA1_RESULTLEN) {
+ *error_r = "SSHA password is too short";
+ return -1;
+ }
+
+ sha1_init(&ctx);
+ sha1_loop(&ctx, plaintext, strlen(plaintext));
+ sha1_loop(&ctx, raw_password + SHA1_RESULTLEN, size - SHA1_RESULTLEN);
+ sha1_result(&ctx, sha1_digest);
+ return mem_equals_timing_safe(sha1_digest, raw_password, SHA1_RESULTLEN) ? 1 : 0;
+}
+
+static void
+ssha256_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+#define SSHA256_SALT_LEN 4
+ unsigned char *digest, *salt;
+ struct sha256_ctx ctx;
+
+ digest = t_malloc_no0(SHA256_RESULTLEN + SSHA256_SALT_LEN);
+ salt = digest + SHA256_RESULTLEN;
+ random_fill(salt, SSHA256_SALT_LEN);
+
+ sha256_init(&ctx);
+ sha256_loop(&ctx, plaintext, strlen(plaintext));
+ sha256_loop(&ctx, salt, SSHA256_SALT_LEN);
+ sha256_result(&ctx, digest);
+
+ *raw_password_r = digest;
+ *size_r = SHA256_RESULTLEN + SSHA256_SALT_LEN;
+}
+
+static int ssha256_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ unsigned char sha256_digest[SHA256_RESULTLEN];
+ struct sha256_ctx ctx;
+
+ /* format: <SHA256 hash><salt> */
+ if (size <= SHA256_RESULTLEN) {
+ *error_r = "SSHA256 password is too short";
+ return -1;
+ }
+
+ sha256_init(&ctx);
+ sha256_loop(&ctx, plaintext, strlen(plaintext));
+ sha256_loop(&ctx, raw_password + SHA256_RESULTLEN,
+ size - SHA256_RESULTLEN);
+ sha256_result(&ctx, sha256_digest);
+ return mem_equals_timing_safe(sha256_digest, raw_password,
+ SHA256_RESULTLEN) ? 1 : 0;
+}
+
+static void
+ssha512_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+#define SSHA512_SALT_LEN 4
+ unsigned char *digest, *salt;
+ struct sha512_ctx ctx;
+
+ digest = t_malloc_no0(SHA512_RESULTLEN + SSHA512_SALT_LEN);
+ salt = digest + SHA512_RESULTLEN;
+ random_fill(salt, SSHA512_SALT_LEN);
+
+ sha512_init(&ctx);
+ sha512_loop(&ctx, plaintext, strlen(plaintext));
+ sha512_loop(&ctx, salt, SSHA512_SALT_LEN);
+ sha512_result(&ctx, digest);
+
+ *raw_password_r = digest;
+ *size_r = SHA512_RESULTLEN + SSHA512_SALT_LEN;
+}
+
+static int ssha512_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ unsigned char sha512_digest[SHA512_RESULTLEN];
+ struct sha512_ctx ctx;
+
+ /* format: <SHA512 hash><salt> */
+ if (size <= SHA512_RESULTLEN) {
+ *error_r = "SSHA512 password is too short";
+ return -1;
+ }
+
+ sha512_init(&ctx);
+ sha512_loop(&ctx, plaintext, strlen(plaintext));
+ sha512_loop(&ctx, raw_password + SHA512_RESULTLEN,
+ size - SHA512_RESULTLEN);
+ sha512_result(&ctx, sha512_digest);
+ return mem_equals_timing_safe(sha512_digest, raw_password,
+ SHA512_RESULTLEN) ? 1 : 0;
+}
+
+static void
+smd5_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+#define SMD5_SALT_LEN 4
+ unsigned char *digest, *salt;
+ struct md5_context ctx;
+
+ digest = t_malloc_no0(MD5_RESULTLEN + SMD5_SALT_LEN);
+ salt = digest + MD5_RESULTLEN;
+ random_fill(salt, SMD5_SALT_LEN);
+
+ md5_init(&ctx);
+ md5_update(&ctx, plaintext, strlen(plaintext));
+ md5_update(&ctx, salt, SMD5_SALT_LEN);
+ md5_final(&ctx, digest);
+
+ *raw_password_r = digest;
+ *size_r = MD5_RESULTLEN + SMD5_SALT_LEN;
+}
+
+static int smd5_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ unsigned char md5_digest[MD5_RESULTLEN];
+ struct md5_context ctx;
+
+ /* format: <MD5 hash><salt> */
+ if (size <= MD5_RESULTLEN) {
+ *error_r = "SMD5 password is too short";
+ return -1;
+ }
+
+ md5_init(&ctx);
+ md5_update(&ctx, plaintext, strlen(plaintext));
+ md5_update(&ctx, raw_password + MD5_RESULTLEN, size - MD5_RESULTLEN);
+ md5_final(&ctx, md5_digest);
+ return mem_equals_timing_safe(md5_digest, raw_password, MD5_RESULTLEN) ? 1 : 0;
+}
+
+static void
+plain_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ *raw_password_r = (const unsigned char *)plaintext,
+ *size_r = strlen(plaintext);
+}
+
+static int
+plain_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r ATTR_UNUSED)
+{
+ size_t plaintext_len = strlen(plaintext);
+
+ if (plaintext_len != size)
+ return 0;
+ return mem_equals_timing_safe(plaintext, raw_password, size) ? 1 : 0;
+}
+
+static int
+plain_trunc_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ size_t i, plaintext_len, trunc_len = 0;
+
+ /* format: <length>-<password> */
+ for (i = 0; i < size; i++) {
+ if (raw_password[i] >= '0' && raw_password[i] <= '9')
+ trunc_len = trunc_len*10 + raw_password[i]-'0';
+ else
+ break;
+ }
+ if (i == size || raw_password[i] != '-') {
+ *error_r = "PLAIN-TRUNC missing length: prefix";
+ return -1;
+ }
+ i++;
+
+ plaintext_len = strlen(plaintext);
+ if (size-i == trunc_len && plaintext_len >= trunc_len) {
+ /* possibly truncated password. allow the given password as
+ long as the prefix matches. */
+ return mem_equals_timing_safe(raw_password+i, plaintext, trunc_len) ? 1 : 0;
+ }
+ return plaintext_len == size-i &&
+ mem_equals_timing_safe(raw_password+i, plaintext, plaintext_len) ? 1 : 0;
+}
+
+static void
+cram_md5_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ struct hmac_context ctx;
+ unsigned char *context_digest;
+
+ context_digest = t_malloc_no0(CRAM_MD5_CONTEXTLEN);
+ hmac_init(&ctx, (const unsigned char *)plaintext,
+ strlen(plaintext), &hash_method_md5);
+ hmac_md5_get_cram_context(&ctx, context_digest);
+
+ *raw_password_r = context_digest;
+ *size_r = CRAM_MD5_CONTEXTLEN;
+}
+
+static void
+digest_md5_generate(const char *plaintext, const struct password_generate_params *params,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ const char *realm, *str, *user;
+ unsigned char *digest;
+
+ if (params->user == NULL)
+ i_fatal("digest_md5_generate(): username not given");
+
+ user = params->user;
+
+
+ /* assume user@realm format for username. If user@domain is wanted
+ in the username, allow also user@domain@realm. */
+ realm = strrchr(user, '@');
+ if (realm != NULL) {
+ user = t_strdup_until(user, realm);
+ realm++;
+ } else {
+ realm = "";
+ }
+
+ /* user:realm:passwd */
+ digest = t_malloc_no0(MD5_RESULTLEN);
+ str = t_strdup_printf("%s:%s:%s", user, realm, plaintext);
+ md5_get_digest(str, strlen(str), digest);
+
+ *raw_password_r = digest;
+ *size_r = MD5_RESULTLEN;
+}
+
+static void
+plain_md4_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(MD4_RESULTLEN);
+ md4_get_digest(plaintext, strlen(plaintext), digest);
+
+ *raw_password_r = digest;
+ *size_r = MD4_RESULTLEN;
+}
+
+static void
+plain_md5_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(MD5_RESULTLEN);
+ md5_get_digest(plaintext, strlen(plaintext), digest);
+
+ *raw_password_r = digest;
+ *size_r = MD5_RESULTLEN;
+}
+
+static int otp_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ const char *password, *generated;
+
+ password = t_strndup(raw_password, size);
+ if (password_generate_otp(plaintext, password, UINT_MAX, &generated) < 0) {
+ *error_r = "Invalid OTP data in passdb";
+ return -1;
+ }
+
+ return strcasecmp(password, generated) == 0 ? 1 : 0;
+}
+
+static void
+otp_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ const char *password;
+
+ if (password_generate_otp(plaintext, NULL, OTP_HASH_SHA1, &password) < 0)
+ i_unreached();
+ *raw_password_r = (const unsigned char *)password;
+ *size_r = strlen(password);
+}
+
+static const struct password_scheme builtin_schemes[] = {
+ { "MD5", PW_ENCODING_NONE, 0, md5_verify, md5_crypt_generate },
+ { "MD5-CRYPT", PW_ENCODING_NONE, 0,
+ md5_crypt_verify, md5_crypt_generate },
+ { "SHA", PW_ENCODING_BASE64, SHA1_RESULTLEN, NULL, sha1_generate },
+ { "SHA1", PW_ENCODING_BASE64, SHA1_RESULTLEN, NULL, sha1_generate },
+ { "SHA256", PW_ENCODING_BASE64, SHA256_RESULTLEN,
+ NULL, sha256_generate },
+ { "SHA512", PW_ENCODING_BASE64, SHA512_RESULTLEN,
+ NULL, sha512_generate },
+ { "SMD5", PW_ENCODING_BASE64, 0, smd5_verify, smd5_generate },
+ { "SSHA", PW_ENCODING_BASE64, 0, ssha_verify, ssha_generate },
+ { "SSHA256", PW_ENCODING_BASE64, 0, ssha256_verify, ssha256_generate },
+ { "SSHA512", PW_ENCODING_BASE64, 0, ssha512_verify, ssha512_generate },
+ { "PLAIN", PW_ENCODING_NONE, 0, plain_verify, plain_generate },
+ { "CLEAR", PW_ENCODING_NONE, 0, plain_verify, plain_generate },
+ { "CLEARTEXT", PW_ENCODING_NONE, 0, plain_verify, plain_generate },
+ { "PLAIN-TRUNC", PW_ENCODING_NONE, 0, plain_trunc_verify, plain_generate },
+ { "CRAM-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN,
+ NULL, cram_md5_generate },
+ { "SCRAM-SHA-1", PW_ENCODING_NONE, 0, scram_sha1_verify,
+ scram_sha1_generate},
+ { "SCRAM-SHA-256", PW_ENCODING_NONE, 0, scram_sha256_verify,
+ scram_sha256_generate},
+ { "HMAC-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN,
+ NULL, cram_md5_generate },
+ { "DIGEST-MD5", PW_ENCODING_HEX, MD5_RESULTLEN,
+ NULL, digest_md5_generate },
+ { "PLAIN-MD4", PW_ENCODING_HEX, MD4_RESULTLEN,
+ NULL, plain_md4_generate },
+ { "PLAIN-MD5", PW_ENCODING_HEX, MD5_RESULTLEN,
+ NULL, plain_md5_generate },
+ { "LDAP-MD5", PW_ENCODING_BASE64, MD5_RESULTLEN,
+ NULL, plain_md5_generate },
+ { "OTP", PW_ENCODING_NONE, 0, otp_verify, otp_generate },
+ { "PBKDF2", PW_ENCODING_NONE, 0, pbkdf2_verify, pbkdf2_generate },
+};
+
+void password_scheme_register(const struct password_scheme *scheme)
+{
+ if (password_scheme_lookup_name(scheme->name) != NULL) {
+ i_panic("password_scheme_register(%s): Already registered",
+ scheme->name);
+ }
+ hash_table_insert(password_schemes, scheme->name, scheme);
+}
+
+void password_scheme_unregister(const struct password_scheme *scheme)
+{
+ if (!hash_table_try_remove(password_schemes, scheme->name))
+ i_panic("password_scheme_unregister(%s): Not registered", scheme->name);
+}
+
+void password_schemes_get(ARRAY_TYPE(password_scheme_p) *schemes_r)
+{
+ struct hash_iterate_context *ctx;
+ const char *key;
+ const struct password_scheme *scheme;
+ ctx = hash_table_iterate_init(password_schemes);
+ while(hash_table_iterate(ctx, password_schemes, &key, &scheme)) {
+ array_push_back(schemes_r, &scheme);
+ }
+ hash_table_iterate_deinit(&ctx);
+}
+
+void password_schemes_init(void)
+{
+ unsigned int i;
+
+ hash_table_create(&password_schemes, default_pool,
+ N_ELEMENTS(builtin_schemes)*2, strfastcase_hash,
+ strcasecmp);
+ for (i = 0; i < N_ELEMENTS(builtin_schemes); i++)
+ password_scheme_register(&builtin_schemes[i]);
+ password_scheme_register_crypt();
+#ifdef HAVE_LIBSODIUM
+ password_scheme_register_sodium();
+#endif
+}
+
+void password_schemes_deinit(void)
+{
+ hash_table_destroy(&password_schemes);
+}
diff --git a/src/auth/password-scheme.h b/src/auth/password-scheme.h
new file mode 100644
index 0000000..b3c3f56
--- /dev/null
+++ b/src/auth/password-scheme.h
@@ -0,0 +1,147 @@
+#ifndef PASSWORD_SCHEME_H
+#define PASSWORD_SCHEME_H
+
+#define AUTH_LOG_MSG_PASSWORD_MISMATCH "Password mismatch"
+
+struct hash_method;
+
+enum password_encoding {
+ PW_ENCODING_NONE,
+ PW_ENCODING_BASE64,
+ PW_ENCODING_HEX
+};
+
+struct password_generate_params {
+ const char *user;
+ unsigned int rounds;
+};
+
+struct password_scheme {
+ const char *name;
+ enum password_encoding default_encoding;
+ /* If non-zero, this is the expected raw password length.
+ It can be used to automatically detect encoding between
+ hex and base64 encoded passwords. */
+ unsigned int raw_password_len;
+
+ int (*password_verify)(const char *plaintext,
+ const struct password_generate_params *params,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r);
+ void (*password_generate)(const char *plaintext,
+ const struct password_generate_params *params,
+ const unsigned char **raw_password_r,
+ size_t *size_r);
+};
+ARRAY_DEFINE_TYPE(password_scheme_p, const struct password_scheme *);
+void password_schemes_get(ARRAY_TYPE(password_scheme_p) *schemes_r);
+
+extern unsigned int password_scheme_encryption_rounds;
+
+/* Returns 1 = matched, 0 = didn't match, -1 = unknown scheme or invalid
+ raw_password */
+int password_verify(const char *plaintext,
+ const struct password_generate_params *params,
+ const char *scheme,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r);
+
+/* Extracts scheme from password, or returns NULL if it isn't found.
+ If auth_request is given, it's used for debug logging. */
+const char *password_get_scheme(const char **password);
+
+/* Decode encoded (base64/hex) password to raw form. Returns 1 if ok,
+ 0 if scheme is unknown, -1 if password is invalid. */
+int password_decode(const char *password, const char *scheme,
+ const unsigned char **raw_password_r, size_t *size_r,
+ const char **error_r);
+
+/* Create password with wanted scheme out of plaintext password and username.
+ Potential base64/hex directives are ignored in scheme. Returns FALSE if
+ the scheme is unknown. */
+bool password_generate(const char *plaintext,
+ const struct password_generate_params *params,
+ const char *scheme,
+ const unsigned char **raw_password_r, size_t *size_r);
+/* Like above, but generate encoded passwords. If hex/base64 directive isn't
+ specified in the scheme, the default encoding for the scheme is used.
+ Returns FALSE if the scheme is unknown. */
+bool password_generate_encoded(const char *plaintext,
+ const struct password_generate_params *params,
+ const char *scheme, const char **password_r);
+
+/* Returns TRUE if schemes are equivalent. */
+bool password_scheme_is_alias(const char *scheme1, const char *scheme2);
+
+/* Try to detect in which scheme crypted password is. Returns the scheme name
+ or NULL if nothing was found. */
+const char *
+password_scheme_detect(const char *plain_password, const char *crypted_password,
+ const struct password_generate_params *params);
+
+void password_scheme_register(const struct password_scheme *scheme);
+void password_scheme_unregister(const struct password_scheme *scheme);
+
+void password_schemes_init(void);
+void password_schemes_deinit(void);
+
+/* some password schemes/algorithms supports a variable number of
+ encryption rounds. */
+void password_set_encryption_rounds(unsigned int rounds);
+
+/* INTERNAL: */
+const char *password_generate_salt(size_t len);
+const char *password_generate_md5_crypt(const char *pw, const char *salt);
+int password_generate_otp(const char *pw, const char *state_data,
+ unsigned int algo, const char **result_r)
+ ATTR_NULL(2);
+
+int crypt_verify(const char *plaintext,
+ const struct password_generate_params *params,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r);
+
+int scram_scheme_parse(const struct hash_method *hmethod, const char *name,
+ const unsigned char *credentials, size_t size,
+ unsigned int *iter_count_r, const char **salt_r,
+ unsigned char stored_key_r[],
+ unsigned char server_key_r[], const char **error_r);
+int scram_verify(const struct hash_method *hmethod, const char *scheme_name,
+ const char *plaintext, const unsigned char *raw_password,
+ size_t size, const char **error_r);
+void scram_generate(const struct hash_method *hmethod, const char *plaintext,
+ const unsigned char **raw_password_r, size_t *size_r);
+
+int scram_sha1_verify(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r ATTR_UNUSED);
+void scram_sha1_generate(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r);
+
+int scram_sha256_verify(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r);
+void scram_sha256_generate(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r);
+
+void pbkdf2_generate(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r);
+int pbkdf2_verify(const char *plaintext,
+ const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r);
+
+/* check which of the algorithms Blowfish, SHA-256 and SHA-512 are
+ supported by the used libc's/glibc's crypt() */
+void password_scheme_register_crypt(void);
+
+#ifdef HAVE_LIBSODIUM
+void password_scheme_register_sodium(void);
+#endif
+
+#endif
diff --git a/src/auth/test-auth-cache.c b/src/auth/test-auth-cache.c
new file mode 100644
index 0000000..ea71b1b
--- /dev/null
+++ b/src/auth/test-auth-cache.c
@@ -0,0 +1,82 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "auth-request.h"
+#include "auth-cache.h"
+#include "test-common.h"
+
+const struct var_expand_table
+auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT + 1] = {
+ /* these 3 must be in this order */
+ { 'u', NULL, "user" },
+ { 'n', NULL, "username" },
+ { 'd', NULL, "domain" },
+
+ { 'a', NULL, NULL },
+ { '\0', NULL, "longb" },
+ { 'c', NULL, "longc" },
+ { '\0', NULL, NULL }
+};
+
+struct var_expand_table *
+auth_request_get_var_expand_table_full(const struct auth_request *auth_request ATTR_UNUSED,
+ const char *username ATTR_UNUSED,
+ auth_request_escape_func_t *escape_func ATTR_UNUSED,
+ unsigned int *count ATTR_UNUSED)
+{
+ i_unreached();
+}
+
+int auth_request_var_expand_with_table(string_t *dest, const char *str,
+ const struct auth_request *auth_request ATTR_UNUSED,
+ const struct var_expand_table *table ATTR_UNUSED,
+ auth_request_escape_func_t *escape_func ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ return var_expand(dest, str, auth_request_var_expand_static_tab, error_r);
+}
+
+static void test_auth_cache_parse_key(void)
+{
+ static const struct {
+ const char *in, *out;
+ } tests[] = {
+ { "%n@%d", "%u" },
+ { "%{username}@%{domain}", "%u" },
+ { "%n%d%u", "%u" },
+ { "%n", "%n" },
+ { "%d", "%d" },
+ { "%a%b%u", "%u\t%a\t%b" },
+
+ { "foo%5.5Mabar", "%a" },
+ { "foo%5.5M{longb}bar", "%{longb}" },
+ { "foo%5.5Mcbar", "%c" },
+ { "foo%5.5M{longc}bar", "%c" },
+ { "%a%b", "%a\t%b" },
+ { "%a%{longb}%a", "%a\t%{longb}" },
+ { "%{longc}%c", "%c" },
+ { "%c%a%{longc}%c", "%a\t%c" },
+ { "%a%{env:foo}%{env:foo}%a", "%a\t%{env:foo}\t%{env:foo}" }
+ };
+ const char *cache_key;
+ unsigned int i;
+
+ test_begin("auth cache parse key");
+
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ cache_key = auth_cache_parse_key(pool_datastack_create(),
+ tests[i].in);
+ test_assert(strcmp(cache_key, tests[i].out) == 0);
+ }
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_auth_cache_parse_key,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/auth/test-auth-request-fields.c b/src/auth/test-auth-request-fields.c
new file mode 100644
index 0000000..e43a1bf
--- /dev/null
+++ b/src/auth/test-auth-request-fields.c
@@ -0,0 +1,147 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "test-auth.h"
+#include "str.h"
+#include "strescape.h"
+#include "auth-request.h"
+
+struct test_auth_request_field {
+ const char *internal_name;
+ const char *event_field;
+ const char *value;
+};
+
+static const struct test_auth_request_field auth_request_field_names[] = {
+ /* use the order in auth_request_export() */
+#define PREFIX "\t\r\n\001prefix-"
+ { "user", "user", PREFIX"testuser" },
+ { "service", "service", PREFIX"testservice" },
+ { "master-user", "master_user", PREFIX"testmasteruser" },
+ { "original-username", "original_user", PREFIX"testoriguser" },
+ { "requested-login-user", "login_user", PREFIX"testloginuser" },
+ { "lip", "local_ip", "255.254.253.252" },
+ { "rip", "remote_ip", "155.154.153.152" },
+ { "lport", "local_port", "12" },
+ { "rport", "remote_port", "13" },
+ { "real_lip", "real_local_ip", "1.2.3.4" },
+ { "real_rip", "real_remote_ip", "5.6.7.8" },
+ { "real_lport", "real_local_port", "14" },
+ { "real_rport", "real_remote_port", "15" },
+ { "local_name", "local_name", PREFIX"testlocalname" },
+ { "session", "session", PREFIX"testsession" },
+ { "secured", NULL, "" },
+ { "skip-password-check", NULL, "" },
+ { "delayed-credentials", NULL, "" },
+ { "valid-client-cert", NULL, "" },
+ { "no-penalty", NULL, "" },
+ { "successful", NULL, "" },
+ { "mech", "mechanism", "TOKEN" },
+ { "client_id", "client_id", PREFIX"testclientid" },
+ { "passdb_extrafield1", NULL, PREFIX"extravalue1" },
+ { "passdb_extrafield2", NULL, PREFIX"extravalue2" },
+ { "userdb_uextrafield1", NULL, PREFIX"userextravalue1" },
+ { "userdb_uextrafield2", NULL, PREFIX"userextravalue2" },
+};
+
+static struct auth_request *
+test_auth_request_init(const struct mech_module *mech)
+{
+ struct auth_request *request;
+ pool_t pool = pool_alloconly_create("test auth request", 1024);
+
+ request = p_new(pool, struct auth_request, 1);
+ request->pool = pool;
+ request->event = event_create(NULL);
+ request->mech = mech;
+ auth_request_fields_init(request);
+
+ /* fill out fields that are always exported */
+ request->fields.user = "user";
+ request->fields.original_username = "user";
+ request->fields.service = "service";
+ return request;
+}
+
+static void test_auth_request_deinit(struct auth_request *request)
+{
+ event_unref(&request->event);
+ pool_unref(&request->pool);
+}
+
+static void test_auth_request_fields_list(void)
+{
+ struct auth_request *request =
+ test_auth_request_init(&mech_dovecot_token);
+ string_t *exported = t_str_new(512);
+ for (unsigned int i = 0; i < N_ELEMENTS(auth_request_field_names); i++) {
+ const struct test_auth_request_field *test =
+ &auth_request_field_names[i];
+ test_assert_idx(auth_request_import(request,
+ test->internal_name, test->value), i);
+
+ str_append(exported, test->internal_name);
+ if (test->value[0] != '\0') {
+ str_append_c(exported, '=');
+ str_append_tabescaped(exported, test->value);
+ }
+ str_append_c(exported, '\t');
+
+ if (test->event_field != NULL) {
+ const char *value =
+ event_find_field_recursive_str(request->event, test->event_field);
+ test_assert_idx(null_strcmp(value, test->value) == 0, i);
+ }
+ }
+ str_truncate(exported, str_len(exported)-1);
+
+ string_t *exported2 = t_str_new(512);
+ auth_request_export(request, exported2);
+ test_assert_strcmp(str_c(exported), str_c(exported2));
+
+ test_auth_request_deinit(request);
+}
+
+static bool
+test_auth_request_export_cmp(struct auth_request *request,
+ const char *key, const char *value)
+{
+ string_t *exported = t_str_new(128);
+ str_append(exported, "user=user\tservice=service\toriginal-username=user\t");
+ str_append(exported, key);
+ if (value[0] != '\0') {
+ str_append_c(exported, '=');
+ str_append_tabescaped(exported, value);
+ }
+
+ string_t *exported2 = t_str_new(128);
+ auth_request_export(request, exported2);
+ test_assert_strcmp(str_c(exported), str_c(exported2));
+ return strcmp(str_c(exported), str_c(exported2)) == 0;
+
+}
+
+static void test_auth_request_fields_secured(void)
+{
+ struct auth_request *request = test_auth_request_init(NULL);
+
+ test_assert(auth_request_import(request, "secured", ""));
+ test_assert(test_auth_request_export_cmp(request, "secured", ""));
+ test_assert(null_strcmp(event_find_field_recursive_str(request->event, "transport"), "trusted") == 0);
+
+ test_assert(auth_request_import(request, "secured", "tls"));
+ test_assert(test_auth_request_export_cmp(request, "secured", "tls"));
+ test_assert(null_strcmp(event_find_field_recursive_str(request->event, "transport"), "TLS") == 0);
+
+ test_assert(auth_request_import(request, "secured", "blah"));
+ test_assert(test_auth_request_export_cmp(request, "secured", ""));
+ test_assert(null_strcmp(event_find_field_recursive_str(request->event, "transport"), "trusted") == 0);
+ test_auth_request_deinit(request);
+}
+
+void test_auth_request_fields(void)
+{
+ test_begin("auth request fields");
+ test_auth_request_fields_list();
+ test_auth_request_fields_secured();
+ test_end();
+}
diff --git a/src/auth/test-auth-request-var-expand.c b/src/auth/test-auth-request-var-expand.c
new file mode 100644
index 0000000..e54e2ba
--- /dev/null
+++ b/src/auth/test-auth-request-var-expand.c
@@ -0,0 +1,259 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "test-auth.h"
+#include "str.h"
+#include "auth.h"
+#include "passdb.h"
+#include "userdb.h"
+#include "auth-request.h"
+
+static struct passdb_module test_passdb = {
+ .id = 40
+};
+static struct userdb_module test_userdb = {
+ .id = 41
+};
+
+static struct auth_passdb test_auth_passdb = {
+ .passdb = &test_passdb
+};
+static struct auth_userdb test_auth_userdb = {
+ .userdb = &test_userdb
+};
+
+static struct auth_request default_test_request = {
+ .fields = {
+ .user = "-user@+domain1@+domain2",
+ .service = "-service",
+ .local_ip = { .family = AF_INET },
+ .remote_ip = { .family = AF_INET },
+ .mech_name = "-mech",
+ .secured = AUTH_REQUEST_SECURED,
+ .local_port = 21,
+ .remote_port = 210,
+ .valid_client_cert = TRUE,
+ .requested_login_user = "-loginuser@+logindomain1@+logindomain2",
+ .session_id = "-session",
+ .real_local_ip = { .family = AF_INET },
+ .real_remote_ip = { .family = AF_INET },
+ .real_local_port = 200,
+ .real_remote_port = 201,
+ .master_user = "-masteruser@-masterdomain1@-masterdomain2",
+ .original_username = "-origuser@-origdomain1@-origdomain2",
+ },
+ .client_pid = 54321,
+ .mech_password = "-password",
+
+ .session_pid = 5000,
+
+ .passdb = &test_auth_passdb,
+ .userdb = &test_auth_userdb
+};
+
+static struct auth_request test_request;
+static struct auth_request empty_test_request = { .fields = { .user = "" } };
+
+static const char *
+test_escape(const char *string, const struct auth_request *request)
+{
+ char *dest;
+ unsigned int i;
+
+ test_assert(request == &test_request);
+
+ dest = t_strdup_noconst(string);
+ for (i = 0; dest[i] != '\0'; i++) {
+ if (dest[i] == '-')
+ dest[i] = '+';
+ }
+ return dest;
+}
+
+static bool test_empty_request(string_t *str, const char *input)
+{
+ const struct var_expand_table *tab =
+ auth_request_get_var_expand_table(&empty_test_request, NULL);
+ const char *error;
+
+ str_truncate(str, 0);
+ test_assert(var_expand(str, input, tab, &error) == 1);
+ return strspn(str_c(str), "\n0") == str_len(str);
+}
+
+static void test_auth_request_var_expand_shortlong(void)
+{
+ static const char *test_input_short =
+ "%u\n%n\n%d\n%s\n%h\n%l\n%r\n%l\n%r\n%p\n%w\n%m\n%c\n"
+ "%a\n%b\n%a\n%b\n%k\n";
+ static const char *test_input_long =
+ "%{user}\n%{username}\n%{domain}\n%{service}\n%{home}\n"
+ "%{lip}\n%{rip}\n%{local_ip}\n%{remote_ip}\n"
+ "%{pid}\n%{password}\n%{mech}\n%{secured}\n"
+ "%{lport}\n%{rport}\n%{local_port}\n%{remote_port}\n%{cert}\n";
+ static const char *test_output =
+ /* %{home} is intentionally always expanding to empty */
+ "+user@+domain1@+domain2\n+user\n+domain1@+domain2\n+service\n\n"
+ "7.91.205.21\n73.150.2.210\n7.91.205.21\n73.150.2.210\n"
+ "54321\n+password\n+mech\nsecured\n"
+ "21\n210\n21\n210\nvalid\n";
+ const struct var_expand_table *tab;
+ string_t *str = t_str_new(256);
+ const char *error;
+
+ test_begin("auth request var expand short and long");
+
+ tab = auth_request_get_var_expand_table(&test_request, test_escape);
+ test_assert(var_expand(str, test_input_short, tab, &error) == 1);
+ test_assert(strcmp(str_c(str), test_output) == 0);
+
+ str_truncate(str, 0);
+ test_assert(var_expand(str, test_input_long, tab, &error) == 1);
+ test_assert(strcmp(str_c(str), test_output) == 0);
+
+ /* test with empty input that it won't crash */
+ test_assert(test_empty_request(str, test_input_short));
+ test_assert(test_empty_request(str, test_input_long));
+
+ test_end();
+}
+
+static void test_auth_request_var_expand_flags(void)
+{
+ static const char *test_input = "%!\n%{secured}\n%{cert}\n";
+ string_t *str = t_str_new(10);
+ const char *error;
+
+ test_begin("auth request var expand flags");
+
+ test_request.userdb_lookup = FALSE;
+ test_request.fields.secured = AUTH_REQUEST_SECURED_NONE;
+ test_request.fields.valid_client_cert = FALSE;
+ test_assert(var_expand(str, test_input,
+ auth_request_get_var_expand_table(&test_request, test_escape),
+ &error) == 1);
+ test_assert(strcmp(str_c(str), "40\n\n\n") == 0);
+
+ test_request.userdb_lookup = TRUE;
+ test_request.fields.secured = AUTH_REQUEST_SECURED;
+ test_request.fields.valid_client_cert = TRUE;
+
+ str_truncate(str, 0);
+ test_assert(var_expand(str, test_input,
+ auth_request_get_var_expand_table(&test_request, test_escape),
+ &error) == 1);
+ test_assert(strcmp(str_c(str), "41\nsecured\nvalid\n") == 0);
+
+ test_assert(test_empty_request(str, test_input));
+ test_end();
+}
+
+static void test_auth_request_var_expand_long(void)
+{
+ static const char *test_input =
+ "%{login_user}\n%{login_username}\n%{login_domain}\n%{session}\n"
+ "%{real_lip}\n%{real_rip}\n%{real_lport}\n%{real_rport}\n"
+ "%{real_local_ip}\n%{real_remote_ip}\n"
+ "%{real_local_port}\n%{real_remote_port}\n"
+ "%{master_user}\n%{session_pid}\n"
+ "%{orig_user}\n%{orig_username}\n%{orig_domain}\n";
+ static const char *test_output =
+ "+loginuser@+logindomain1@+logindomain2\n+loginuser\n+logindomain1@+logindomain2\n+session\n"
+ "13.81.174.20\n13.81.174.21\n200\n201\n"
+ "13.81.174.20\n13.81.174.21\n"
+ "200\n201\n"
+ "+masteruser@+masterdomain1@+masterdomain2\n5000\n"
+ "+origuser@+origdomain1@+origdomain2\n+origuser\n+origdomain1@+origdomain2\n";
+ string_t *str = t_str_new(256);
+ const char *error;
+
+ test_begin("auth request var expand long-only");
+
+ test_assert(var_expand(str, test_input,
+ auth_request_get_var_expand_table(&test_request, test_escape),
+ &error) == 1);
+ test_assert(strcmp(str_c(str), test_output) == 0);
+
+ test_assert(test_empty_request(str, test_input));
+ test_end();
+}
+
+static void test_auth_request_var_expand_usernames(void)
+{
+ static const struct {
+ const char *username, *output;
+ } tests[] = {
+ { "-foo", "+foo\n\n\n\n+foo" },
+ { "-foo@-domain", "+foo\n+domain\n+domain\n+domain\n+foo@+domain" },
+ { "-foo@-domain1@-domain2", "+foo\n+domain1@+domain2\n+domain1\n+domain2\n+foo@+domain1@+domain2" }
+ };
+ static const char *test_input =
+ "%{username}\n%{domain}\n%{domain_first}\n%{domain_last}\n%{user}";
+ string_t *str = t_str_new(64);
+ const char *error;
+ unsigned int i;
+
+ test_begin("auth request var expand usernames");
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ test_request.fields.user = t_strdup_noconst(tests[i].username);
+ str_truncate(str, 0);
+ test_assert(var_expand(str, test_input,
+ auth_request_get_var_expand_table(&test_request, test_escape),
+ &error) == 1);
+ test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i);
+ }
+ test_request.fields.user = default_test_request.fields.user;
+ test_end();
+}
+
+static void test_auth_request_var_expand_funcs(void)
+{
+ pool_t pool;
+ const char *value, *error;
+
+ test_begin("auth request var expand funcs");
+
+ pool = pool_alloconly_create("test var expand funcs", 1024);
+ test_request.fields.extra_fields = auth_fields_init(pool);
+ test_request.fields.userdb_reply = auth_fields_init(pool);
+
+ auth_fields_add(test_request.fields.extra_fields, "pkey1", "-pval1", 0);
+ auth_fields_add(test_request.fields.extra_fields, "pkey2", "", 0);
+
+ auth_fields_add(test_request.fields.userdb_reply, "ukey1", "-uval1", 0);
+ auth_fields_add(test_request.fields.userdb_reply, "ukey2", "", 0);
+
+ test_assert(t_auth_request_var_expand(
+ "%{passdb:pkey1}\n%{passdb:pkey1:default1}\n"
+ "%{passdb:pkey2}\n%{passdb:pkey2:default2}\n"
+ "%{passdb:pkey3}\n%{passdb:pkey3:default3}\n"
+ "%{passdb:ukey1}\n%{passdb:ukey1:default4}\n",
+ &test_request, test_escape, &value, &error) == 1);
+ test_assert(strcmp(value, "+pval1\n+pval1\n\n\n\ndefault3\n\ndefault4\n") == 0);
+
+ test_assert(t_auth_request_var_expand(
+ "%{userdb:ukey1}\n%{userdb:ukey1:default1}\n"
+ "%{userdb:ukey2}\n%{userdb:ukey2:default2}\n"
+ "%{userdb:ukey3}\n%{userdb:ukey3:default3}\n"
+ "%{userdb:pkey1}\n%{userdb:pkey1:default4}\n",
+ &test_request, test_escape, &value, &error) == 1);
+ test_assert(strcmp(value, "+uval1\n+uval1\n\n\n\ndefault3\n\ndefault4\n") == 0);
+
+ pool_unref(&pool);
+ test_end();
+}
+
+void test_auth_request_var_expand(void)
+{
+ default_test_request.fields.local_ip.u.ip4.s_addr = htonl(123456789);
+ default_test_request.fields.remote_ip.u.ip4.s_addr = htonl(1234567890);
+ default_test_request.fields.real_local_ip.u.ip4.s_addr = htonl(223456788);
+ default_test_request.fields.real_remote_ip.u.ip4.s_addr = htonl(223456789);
+
+ test_request = default_test_request;
+
+ test_auth_request_var_expand_shortlong();
+ test_auth_request_var_expand_flags();
+ test_auth_request_var_expand_long();
+ test_auth_request_var_expand_usernames();
+ test_auth_request_var_expand_funcs();
+}
diff --git a/src/auth/test-auth.h b/src/auth/test-auth.h
new file mode 100644
index 0000000..4641fe3
--- /dev/null
+++ b/src/auth/test-auth.h
@@ -0,0 +1,25 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#ifndef TEST_AUTH_H
+#define TEST_AUTH_H 1
+
+#define AUTH_REQUEST_FIELDS_CONST
+
+#include "lib.h"
+#include "test-common.h"
+
+struct auth_passdb;
+
+extern struct auth_passdb_settings mock_passdb_set;
+
+void test_auth_request_var_expand(void);
+void test_auth_request_fields(void);
+void test_db_dict_parse_cache_key(void);
+void test_username_filter(void);
+void test_db_lua(void);
+struct auth_passdb *passdb_mock(void);
+void passdb_mock_mod_init(void);
+void passdb_mock_mod_deinit(void);
+
+#endif
+
diff --git a/src/auth/test-db-dict.c b/src/auth/test-db-dict.c
new file mode 100644
index 0000000..59b0e78
--- /dev/null
+++ b/src/auth/test-db-dict.c
@@ -0,0 +1,43 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "auth-settings.h"
+#include "test-auth.h"
+#include "array.h"
+#include "db-dict.h"
+
+void test_db_dict_parse_cache_key(void)
+{
+ struct db_dict_key keys[] = {
+ { "key0", "%d and %n", NULL, NULL, 0 },
+ { "key1", "%{foo}%r%{bar}", NULL, NULL, 0 },
+ { "key2", "%{test1}/path", NULL, NULL, 0 },
+ { "key3", "path2/%{test2}", NULL, NULL, 0 },
+ { "key4", "%{plop}", NULL, NULL, 0 },
+ { "key5", "%{unused}", NULL, NULL, 0 }
+ };
+ struct db_dict_field fields[] = {
+ { "name1", "hello %{dict:key0} %l and %{dict:key1}" },
+ { "name2", "%{dict:key2} also %{extra} plus" }
+ };
+ const struct db_dict_key *objects[] = {
+ &keys[3], &keys[4]
+ };
+ buffer_t keybuf, fieldbuf, objectbuf;
+ ARRAY_TYPE(db_dict_key) keyarr;
+ ARRAY_TYPE(db_dict_field) fieldarr;
+ ARRAY_TYPE(db_dict_key_p) objectarr;
+
+ test_begin("db dict parse cache key");
+
+ buffer_create_from_const_data(&keybuf, keys, sizeof(keys));
+ buffer_create_from_const_data(&fieldbuf, fields, sizeof(fields));
+ buffer_create_from_const_data(&objectbuf, objects, sizeof(objects));
+ array_create_from_buffer(&keyarr, &keybuf, sizeof(keys[0]));
+ array_create_from_buffer(&fieldarr, &fieldbuf, sizeof(fields[0]));
+ array_create_from_buffer(&objectarr, &objectbuf, sizeof(objects[0]));
+
+ test_assert(strcmp(db_dict_parse_cache_key(&keyarr, &fieldarr, &objectarr),
+ "\t%d and %n\t%l\t%{foo}%r%{bar}\t%{test1}/path\t%{extra}\tpath2/%{test2}\t%{plop}") == 0);
+ test_end();
+}
diff --git a/src/auth/test-libpassword.c b/src/auth/test-libpassword.c
new file mode 100644
index 0000000..114daf6
--- /dev/null
+++ b/src/auth/test-libpassword.c
@@ -0,0 +1,137 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "password-scheme.h"
+
+#ifdef HAVE_LIBSODIUM
+#include <sodium.h>
+#endif
+
+static struct {
+ const char *scheme_generated;
+ const char *scheme_detected;
+} known_non_aliases[] = {
+ { "MD5", "DES-CRYPT" },
+ { "MD5-CRYPT", "DES-CRYPT" },
+ { "ARGON2ID", "ARGON2I" },
+};
+
+/* some algorithms are detected as something other, because they are compatible
+ but not considered aliases by dovecot. treat those here to avoid false errors. */
+static bool schemes_are_known_non_alias(const char *generated, const char *detected)
+{
+ for(size_t i = 0; i < N_ELEMENTS(known_non_aliases); i++) {
+ if (strcmp(known_non_aliases[i].scheme_generated, generated) == 0 &&
+ strcmp(known_non_aliases[i].scheme_detected, detected) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+test_password_scheme(const char *scheme, const char *crypted,
+ const char *plaintext)
+{
+ struct password_generate_params params = {
+ .user = "testuser1",
+ .rounds = 0,
+ };
+ const unsigned char *raw_password;
+ size_t siz;
+ const char *error, *scheme2;
+
+ test_begin(t_strdup_printf("password scheme(%s)", scheme));
+
+ test_assert(strcmp(password_get_scheme(&crypted), scheme) == 0);
+ test_assert(password_decode(crypted, scheme, &raw_password, &siz, &error) == 1);
+ test_assert(password_verify(plaintext, &params, scheme, raw_password, siz, &error) == 1);
+
+ test_assert(password_generate_encoded(plaintext, &params, scheme, &crypted));
+ crypted = t_strdup_printf("{%s}%s", scheme, crypted);
+ test_assert(strcmp(password_get_scheme(&crypted), scheme) == 0);
+ test_assert(password_decode(crypted, scheme, &raw_password, &siz, &error) == 1);
+ test_assert(password_verify(plaintext, &params, scheme, raw_password, siz, &error) == 1);
+
+ scheme2 = password_scheme_detect(plaintext, crypted, &params);
+
+ test_assert(scheme2 != NULL &&
+ (password_scheme_is_alias(scheme, scheme2) ||
+ schemes_are_known_non_alias(scheme, scheme2)));
+
+ test_end();
+}
+
+static void test_password_failures(void)
+{
+ const char *scheme = "PLAIN";
+ const char *crypted = "{PLAIN}invalid";
+ const char *plaintext = "test";
+
+ struct password_generate_params params = {
+ .user = "testuser1",
+ .rounds = 0,
+ };
+ const unsigned char *raw_password;
+ size_t siz;
+ const char *error;
+
+ test_begin("password scheme failures");
+
+ /* wrong password */
+ test_assert(strcmp(password_get_scheme(&crypted), scheme) == 0);
+ test_assert(password_decode(crypted, scheme, &raw_password, &siz, &error) == 1);
+ test_assert(password_verify(plaintext, &params, scheme, raw_password, siz, &error) == 0);
+
+ /* unknown scheme */
+ crypted = "{INVALID}invalid";
+ scheme = password_get_scheme(&crypted);
+ test_assert(password_decode(crypted, scheme, &raw_password, &siz, &error) == 0);
+
+ /* crypt with empty value */
+ test_assert(password_verify(plaintext, &params, "CRYPT", NULL, 0, &error) == 0);
+
+ test_end();
+}
+
+static void test_password_schemes(void)
+{
+ test_password_scheme("PLAIN", "{PLAIN}test", "test");
+ test_password_scheme("CRYPT", "{CRYPT}//EsnG9FLTKjo", "test");
+ test_password_scheme("PLAIN-MD4", "{PLAIN-MD4}db346d691d7acc4dc2625db19f9e3f52", "test");
+ test_password_scheme("MD5", "{MD5}$1$wmyrgRuV$kImF6.9MAFQNHe23kq5vI/", "test");
+ test_password_scheme("SHA1", "{SHA1}qUqP5cyxm6YcTAhz05Hph5gvu9M=", "test");
+ test_password_scheme("SMD5", "{SMD5}JTu1KRwptKZJg/RLd+6Vn5GUd0M=", "test");
+ test_password_scheme("LDAP-MD5", "{LDAP-MD5}CY9rzUYh03PK3k6DJie09g==", "test");
+ test_password_scheme("SHA256", "{SHA256}n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=", "test");
+ test_password_scheme("SHA512", "{SHA512}7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==", "test");
+ test_password_scheme("SSHA", "{SSHA}H/zrDv8FXUu1JmwvVYijfrYEF34jVZcO", "test");
+ test_password_scheme("MD5-CRYPT", "{MD5-CRYPT}$1$GgvxyNz8$OjZhLh4P.gF1lxYEbLZ3e/", "test");
+ test_password_scheme("OTP", "{OTP}sha1 1024 ae6b49aa481f7233 f69fc7f98b8fbf54", "test");
+ test_password_scheme("PBKDF2", "{PBKDF2}$1$bUnT4Pl7yFtYX0KU$5000$50a83cafdc517b9f46519415e53c6a858908680a", "test");
+ test_password_scheme("CRAM-MD5", "{CRAM-MD5}e02d374fde0dc75a17a557039a3a5338c7743304777dccd376f332bee68d2cf6", "test");
+ test_password_scheme("DIGEST-MD5", "{DIGEST-MD5}77c1a8c437c9b08ba2f460fe5d58db5d", "test");
+ test_password_scheme("SCRAM-SHA-1", "{SCRAM-SHA-1}4096,GetyLXdBuHzf1FWf8SLz2Q==,NA/OqmF4hhrsrB9KR7po+dliTGM=,QBiURvQaE6H6qYTmeghDHLANBFQ=", "test");
+ test_password_scheme("SCRAM-SHA-256", "{SCRAM-SHA-256}4096,LfNGSFqiFykEZ1xDAYlnKQ==,"
+ "HACNf9CII7cMz3XjRy/Oh3Ae2LHApoDyNw74d3YtFws=,"
+ "AQH0j7Hf8J12g8eNBadvzlNB2am3PxgNwFCFd3RxEaw=",
+ "test");
+ test_password_scheme("BLF-CRYPT", "{BLF-CRYPT}$2y$05$11ipvo5dR6CwkzwmhwM26OXgzXwhV2PyPuLV.Qi31ILcRcThQpEiW", "test");
+#ifdef HAVE_LIBSODIUM
+ test_password_scheme("ARGON2I", "{ARGON2I}$argon2i$v=19$m=32768,t=4,p=1$f2iuP4aUeNMrgu34fhOkkg$1XSZZMWlIs0zmE+snlUIcLADO3GXbA2O/hsQmmc317k", "test");
+#ifdef crypto_pwhash_ALG_ARGON2ID13
+ test_password_scheme("ARGON2ID", "{ARGON2ID}$argon2id$v=19$m=65536,t=3,p=1$vBb99oJ12p3WAdYlaMHz1A$jtFOtbo/sYV9OSlTxDo/nVNq3uArHd5GJSEx0ty85Cc", "test");
+#endif
+#endif
+}
+
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_password_schemes,
+ test_password_failures,
+ NULL
+ };
+ password_schemes_init();
+ return test_run(test_functions);
+}
diff --git a/src/auth/test-lua.c b/src/auth/test-lua.c
new file mode 100644
index 0000000..c794c73
--- /dev/null
+++ b/src/auth/test-lua.c
@@ -0,0 +1,182 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "test-auth.h"
+
+#ifdef BUILTIN_LUA
+#include "istream.h"
+#include "auth-settings.h"
+#include "auth-request.h"
+#include "auth-fields.h"
+#include "db-lua.h"
+
+static struct auth_settings test_lua_auth_set = {
+ .master_user_separator = "",
+ .default_realm = "",
+ .username_format = "",
+};
+
+static struct auth_request *test_db_lua_auth_request_new(void)
+{
+ const char *error;
+ struct auth_request *req = auth_request_new_dummy(NULL);
+ req->set = global_auth_settings;
+ struct event *event = event_create(req->event);
+ array_push_back(&req->authdb_event, &event);
+ req->passdb = passdb_mock();
+ test_assert(auth_request_set_username(req, "testuser", &error));
+ return req;
+}
+
+static void test_db_lua_auth_verify(void)
+{
+ struct auth_request *req = test_db_lua_auth_request_new();
+
+ static const char *luascript =
+"function auth_password_verify(req, pass)\n"
+" req:log_debug(\"user \" .. req.user)\n"
+" if req:password_verify(\"{SHA256-CRYPT}$5$XtUywQCSjW0zAJgE$YjuPKQnsLuH4iE9kranZyy1lbil5IrRUfs7X6EyJyG1\", pass) then\n"
+" return dovecot.auth.PASSDB_RESULT_OK, {}\n"
+" end\n"
+"end\n";
+ const char *error = NULL;
+ struct dlua_script *script = NULL;
+
+ test_begin("auth db lua passdb_verify");
+
+ test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0);
+ if (script != NULL) {
+ test_assert(auth_lua_script_init(script, &error) == 0);
+ test_assert(auth_lua_call_password_verify(script, req, "password", &error) == 1);
+ dlua_script_unref(&script);
+ }
+ if (error != NULL) {
+ i_error("Test failed: %s", error);
+ }
+ i_free(req->passdb);
+
+ auth_request_passdb_lookup_end(req, PASSDB_RESULT_OK);
+ auth_request_unref(&req);
+
+ test_end();
+}
+
+static void test_db_lua_auth_lookup_numberish_value(void)
+{
+ const char *scheme,*pass;
+
+ struct auth_request *req = test_db_lua_auth_request_new();
+
+ static const char *luascript =
+"function auth_passdb_lookup(req)\n"
+" local fields = {}\n"
+" fields[\"user\"] = \"01234\"\n"
+" return dovecot.auth.PASSDB_RESULT_OK, fields\n"
+"end\n";
+ const char *error = NULL;
+ struct dlua_script *script = NULL;
+
+ test_begin("auth db lua passdb_lookup");
+
+ test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0);
+ if (script != NULL) {
+ test_assert(auth_lua_script_init(script, &error) == 0);
+ test_assert(auth_lua_call_passdb_lookup(script, req, &scheme, &pass, &error) == 1);
+ test_assert(strcmp(req->fields.user, "01234") == 0);
+ dlua_script_unref(&script);
+ }
+ if (error != NULL) {
+ i_error("Test failed: %s", error);
+ }
+ i_free(req->passdb);
+ auth_request_passdb_lookup_end(req, PASSDB_RESULT_OK);
+ auth_request_unref(&req);
+
+ test_end();
+}
+
+static void test_db_lua_auth_lookup(void)
+{
+ const char *scheme,*pass;
+
+ struct auth_request *req = test_db_lua_auth_request_new();
+
+ static const char *luascript =
+"function auth_passdb_lookup(req)\n"
+" req:log_debug(\"user \" .. req.user)\n"
+" return dovecot.auth.PASSDB_RESULT_OK, req:var_expand(\"password=pass\")\n"
+"end\n";
+ const char *error = NULL;
+ struct dlua_script *script = NULL;
+
+ test_begin("auth db lua passdb_lookup");
+
+ test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0);
+ if (script != NULL) {
+ test_assert(auth_lua_script_init(script, &error) == 0);
+ test_assert(auth_lua_call_passdb_lookup(script, req, &scheme, &pass, &error) == 1);
+ dlua_script_unref(&script);
+ test_assert_strcmp(pass, "pass");
+ }
+ if (error != NULL) {
+ i_error("Test failed: %s", error);
+ }
+ i_free(req->passdb);
+ auth_request_passdb_lookup_end(req, PASSDB_RESULT_OK);
+ auth_request_unref(&req);
+
+ test_end();
+}
+
+static void test_db_lua_auth_lookup_bad_keyname(void)
+{
+ const char *scheme,*pass;
+
+ struct auth_request *req = test_db_lua_auth_request_new();
+
+ static const char *luascript =
+"function auth_passdb_lookup(req)\n"
+" req:log_debug(\"user \" .. req.user)\n"
+" return dovecot.auth.PASSDB_RESULT_OK, {"
+"[\"\"]=\"1\","
+"[\"\\t\"]=\"2\","
+"[\"\\n\"]=\"3\","
+"[\"password\"]=\"pass\""
+"}\n"
+"end\n";
+ const char *error = NULL;
+ struct dlua_script *script = NULL;
+
+ test_begin("auth db lua bad keyname");
+
+ test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0);
+ if (script != NULL) {
+ test_assert(auth_lua_script_init(script, &error) == 0);
+ test_expect_error_string_n_times("db-lua: Field key", 3);
+ test_assert(auth_lua_call_passdb_lookup(script, req, &scheme, &pass, &error) == 1);
+ dlua_script_unref(&script);
+ test_assert_strcmp(pass, "pass");
+ const ARRAY_TYPE(auth_field) *fields =
+ auth_fields_export(req->fields.extra_fields);
+ test_assert(array_count(fields) == 0);
+ }
+ if (error != NULL) {
+ i_error("Test failed: %s", error);
+ }
+ i_free(req->passdb);
+ auth_request_passdb_lookup_end(req, PASSDB_RESULT_OK);
+ auth_request_unref(&req);
+
+ test_end();
+}
+
+void test_db_lua(void) {
+ memset(test_lua_auth_set.username_chars_map, 0xff,
+ sizeof(test_lua_auth_set.username_chars_map));
+ global_auth_settings = &test_lua_auth_set;
+ test_db_lua_auth_lookup();
+ test_db_lua_auth_lookup_numberish_value();
+ test_db_lua_auth_verify();
+ test_db_lua_auth_lookup_bad_keyname();
+}
+
+#endif
diff --git a/src/auth/test-main.c b/src/auth/test-main.c
new file mode 100644
index 0000000..762c34b
--- /dev/null
+++ b/src/auth/test-main.c
@@ -0,0 +1,39 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "auth-settings.h"
+#include "test-common.h"
+#include "test-auth.h"
+#include "password-scheme.h"
+#include "passdb.h"
+
+int main(int argc, const char *argv[])
+{
+ const char *match = "";
+ int ret;
+ static const struct named_test test_functions[] = {
+ TEST_NAMED(test_auth_request_var_expand)
+ TEST_NAMED(test_auth_request_fields)
+ TEST_NAMED(test_db_dict_parse_cache_key)
+ TEST_NAMED(test_username_filter)
+#if defined(BUILTIN_LUA)
+ TEST_NAMED(test_db_lua)
+#endif
+ { NULL, NULL }
+ };
+
+ password_schemes_init();
+ passdbs_init();
+ passdb_mock_mod_init();
+
+ if (argc > 2 && strcasecmp(argv[1], "--match") == 0)
+ match = argv[2];
+
+ ret = test_run_named(test_functions, match);
+
+ passdb_mock_mod_deinit();
+ password_schemes_deinit();
+ passdbs_deinit();
+
+ return ret;
+}
diff --git a/src/auth/test-mech.c b/src/auth/test-mech.c
new file mode 100644
index 0000000..8a2c7b3
--- /dev/null
+++ b/src/auth/test-mech.c
@@ -0,0 +1,412 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "test-auth.h"
+#include "auth.h"
+#include "str.h"
+#include "auth-common.h"
+#include "auth-request.h"
+#include "auth-request-handler-private.h"
+#include "auth-settings.h"
+#include "mech-digest-md5-private.h"
+#include "otp.h"
+#include "mech-otp-common.h"
+#include "settings-parser.h"
+#include "password-scheme.h"
+#include "auth-token.h"
+
+#include <unistd.h>
+#include <time.h>
+
+#define UCHAR_LEN(str) (const unsigned char *)(str), sizeof(str)-1
+
+extern const struct mech_module mech_anonymous;
+extern const struct mech_module mech_apop;
+extern const struct mech_module mech_cram_md5;
+extern const struct mech_module mech_digest_md5;
+extern const struct mech_module mech_dovecot_token;
+extern const struct mech_module mech_external;
+extern const struct mech_module mech_login;
+extern const struct mech_module mech_oauthbearer;
+extern const struct mech_module mech_otp;
+extern const struct mech_module mech_plain;
+extern const struct mech_module mech_scram_sha1;
+extern const struct mech_module mech_scram_sha256;
+extern const struct mech_module mech_xoauth2;
+
+static struct auth_settings set;
+static struct mechanisms_register *mech_reg;
+
+struct test_case {
+ const struct mech_module *mech;
+ const unsigned char *in;
+ size_t len;
+ const char *username;
+ const char *expect_error;
+ bool success;
+ bool set_username_before_test;
+ bool set_cert_username;
+};
+
+static void
+verify_plain_continue_mock_callback(struct auth_request *request,
+ verify_plain_callback_t *callback)
+{
+ request->passdb_success = TRUE;
+ callback(PASSDB_RESULT_OK, request);
+}
+
+static void
+request_handler_reply_mock_callback(struct auth_request *request,
+ enum auth_client_result result,
+ const void *auth_reply ATTR_UNUSED,
+ size_t reply_size ATTR_UNUSED)
+{
+ request->failed = result != AUTH_CLIENT_RESULT_SUCCESS;
+
+ if (request->passdb_result == PASSDB_RESULT_OK)
+ request->failed = FALSE;
+ else if (request->mech == &mech_otp) {
+ if (null_strcmp(request->fields.user, "otp_phase_2") == 0)
+ request->failed = FALSE;
+ } else if (request->mech == &mech_oauthbearer) {
+ }
+};
+
+static void
+request_handler_reply_continue_mock_callback(struct auth_request *request,
+ const void *reply,
+ size_t reply_size)
+{
+ request->context = p_strndup(request->pool, reply, reply_size);
+}
+
+static void
+auth_client_request_mock_callback(const char *reply ATTR_UNUSED,
+ struct auth_client_connection *conn ATTR_UNUSED)
+{
+}
+
+static void test_mechs_init(void)
+{
+ const char *const services[] = {NULL};
+ process_start_time = time(NULL);
+
+ /* Copy default settings */
+ set = *(const struct auth_settings *)auth_setting_parser_info.defaults;
+ global_auth_settings = &set;
+ global_auth_settings->base_dir = ".";
+ memset((&set)->username_chars_map, 1, sizeof((&set)->username_chars_map));
+ set.username_format = "";
+
+ t_array_init(&set.passdbs, 2);
+ struct auth_passdb_settings *mock_set = t_new(struct auth_passdb_settings, 1);
+ *mock_set = mock_passdb_set;
+ array_push_back(&set.passdbs, &mock_set);
+ mock_set = t_new(struct auth_passdb_settings, 1);
+ *mock_set = mock_passdb_set;
+ mock_set->master = TRUE;
+ array_push_back(&set.passdbs, &mock_set);
+ t_array_init(&set.userdbs, 1);
+
+ /* Disable stats */
+ set.stats = FALSE;
+
+ /* For tests of digest-md5. */
+ set.realms_arr = t_strsplit_spaces("example.com ", " ");
+ /* For tests of mech-anonymous. */
+ set.anonymous_username = "anonuser";
+
+ mech_init(global_auth_settings);
+ mech_reg = mech_register_init(global_auth_settings);
+ passdbs_init();
+ userdbs_init();
+ passdb_mock_mod_init();
+ password_schemes_init();
+
+ auths_preinit(&set, pool_datastack_create(), mech_reg, services);
+ auths_init();
+ auth_token_init();
+}
+
+static void test_mech_prepare_request(struct auth_request **request_r,
+ const struct mech_module *mech,
+ struct auth_request_handler *handler,
+ unsigned int running_test,
+ const struct test_case *test_case)
+{
+ global_auth_settings->ssl_username_from_cert = test_case->set_cert_username;
+ struct auth *auth = auth_default_service();
+
+ struct auth_request *request = auth_request_new(mech, NULL);
+ request->handler = handler;
+ request->id = running_test+1;
+ request->mech_password = NULL;
+ request->state = AUTH_REQUEST_STATE_NEW;
+ request->set = global_auth_settings;
+ request->connect_uid = running_test;
+ request->passdb = auth->passdbs;
+ request->userdb = auth->userdbs;
+ handler->refcount = 1;
+
+ auth_fields_add(request->fields.extra_fields, "nodelay", "", 0);
+ auth_request_ref(request);
+ auth_request_state_count[AUTH_REQUEST_STATE_NEW] = 1;
+
+ if (test_case->set_username_before_test || test_case->set_cert_username)
+ request->fields.user = p_strdup(request->pool, test_case->username);
+ if (test_case->set_cert_username)
+ request->fields.cert_username = TRUE;
+
+ *request_r = request;
+}
+
+static void test_mech_handle_challenge(struct auth_request *request,
+ const unsigned char *in,
+ size_t in_len,
+ unsigned int running_test,
+ bool expected_success)
+{
+ string_t *out = t_str_new(16);
+ str_append_data(out, in, in_len);
+ const char *challenge = request->context;
+ if (request->mech == &mech_login) {
+ /* We do not care about any specific password just give
+ * the username input as password also in case it's wanted. */
+ if (expected_success)
+ test_assert_strcmp_idx(challenge, "Password:", running_test);
+ else
+ test_assert_strcmp_idx(challenge, "Username:", running_test);
+ } else if (request->mech == &mech_cram_md5 && *in != '\0') {
+ str_truncate(out, 0);
+ str_append(out, "testuser b913a602c7eda7a495b4e6e7334d3890");
+ } else if (request->mech == &mech_digest_md5) {
+ struct digest_auth_request *digest_request =
+ (struct digest_auth_request *) request;
+ digest_request->nonce = "OA6MG9tEQGm2hh";
+ }
+ auth_request_continue(request, out->data, out->used);
+}
+
+static inline const unsigned char *
+test_mech_construct_apop_challenge(unsigned int connect_uid, size_t *len_r)
+{
+ string_t *apop_challenge = t_str_new(128);
+
+ str_printfa(apop_challenge,"<%lx.%lx.%"PRIxTIME_T".", (unsigned long)getpid(),
+ (unsigned long)connect_uid, process_start_time+10);
+ str_append_data(apop_challenge, "\0testuser\0responseoflen16-", 26);
+ *len_r = apop_challenge->used;
+ return apop_challenge->data;
+}
+
+static void test_mechs(void)
+{
+ static struct auth_request_handler handler = {
+ .callback = auth_client_request_mock_callback,
+ .reply_callback = request_handler_reply_mock_callback,
+ .reply_continue_callback = request_handler_reply_continue_mock_callback,
+ .verify_plain_continue_callback = verify_plain_continue_mock_callback,
+ };
+
+ static struct test_case tests[] = {
+ /* Expected to be successful */
+ {&mech_anonymous, UCHAR_LEN("\0any \0 bad \0 content"), "anonuser", NULL, TRUE, FALSE, FALSE},
+ {&mech_apop, NULL, 0, "testuser", NULL, TRUE, FALSE, FALSE},
+ {&mech_cram_md5, UCHAR_LEN("testuser b913a602c7eda7a495b4e6e7334d3890"), "testuser", NULL, TRUE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\""), "testuser@example.com", NULL,TRUE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "testuser@example.com", NULL, TRUE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("username=\"test\xc3\xbaser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "test\xc3\xbaser@example.com", NULL, TRUE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("username=\"test\xc3\xbaser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",charset=\"utf-8\",cipher=unsupported,nc=00000001,digest-uri=imap/server.com,response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "test\xc3\xbaser@example.com", NULL, TRUE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("username=\"testuser\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",charset=\"utf-8\",cipher=unsupported,nc=00000001,digest-uri=imap/server.com,response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "testuser@example.com", NULL, TRUE, FALSE, FALSE},
+ {&mech_external, UCHAR_LEN(""), "testuser", NULL, TRUE, TRUE, TRUE},
+ {&mech_dovecot_token, NULL, 0, "testuser", NULL, TRUE, FALSE, FALSE},
+ {&mech_login, UCHAR_LEN("testuser"), "testuser", NULL, TRUE, FALSE, FALSE},
+ {&mech_plain, UCHAR_LEN("\0testuser\0testpass"), "testuser", NULL, TRUE, FALSE, FALSE},
+ {&mech_plain, UCHAR_LEN("normaluser\0masteruser\0masterpass"), "masteruser", NULL, TRUE, FALSE, FALSE},
+ {&mech_plain, UCHAR_LEN("normaluser\0normaluser\0masterpass"), "normaluser", NULL, TRUE, FALSE, FALSE},
+ {&mech_otp, UCHAR_LEN("hex:5Bf0 75d9 959d 036f"), "otp_phase_2", NULL, TRUE, TRUE, FALSE},
+ {&mech_otp, UCHAR_LEN("word:BOND FOGY DRAB NE RISE MART"), "otp_phase_2", NULL, TRUE, TRUE, FALSE},
+ {&mech_otp, UCHAR_LEN("init-hex:f6bd 6b33 89b8 7203:md5 499 ke6118:23d1 b253 5ae0 2b7e"), "otp_phase_2", NULL, TRUE, TRUE, FALSE},
+ {&mech_otp, UCHAR_LEN("init-word:END KERN BALM NICK EROS WAVY:md5 499 ke1235:BABY FAIN OILY NIL TIDY DADE"), "otp_phase_2", NULL , TRUE, TRUE, FALSE},
+ {&mech_oauthbearer, UCHAR_LEN("n,a=testuser,p=cHJvb2Y=,f=nonstandart\x01host=server\x01port=143\x01""auth=Bearer vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==\x01\x01"), "testuser", NULL, FALSE, TRUE, FALSE},
+ {&mech_scram_sha1, UCHAR_LEN("n,,n=testuser,r=rOprNGfwEbeRWgbNEkqO"), "testuser", NULL, TRUE, FALSE, FALSE},
+ {&mech_scram_sha256, UCHAR_LEN("n,,n=testuser,r=rOprNGfwEbeRWgbNEkqO"), "testuser", NULL, TRUE, FALSE, FALSE},
+ {&mech_xoauth2, UCHAR_LEN("user=testuser\x01""auth=Bearer vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==\x01\x01"), "testuser", NULL, TRUE, FALSE, FALSE},
+
+ /* Below tests are expected to fail */
+ /* Empty input tests*/
+ {&mech_apop, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_cram_md5, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_dovecot_token, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_external, UCHAR_LEN(""), "testuser", NULL, FALSE, TRUE, FALSE},
+ {&mech_external, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_login, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_otp, UCHAR_LEN(""), NULL, "invalid input", FALSE, FALSE, FALSE},
+ {&mech_otp, UCHAR_LEN(""), "testuser", "invalid input", FALSE, FALSE, FALSE},
+ {&mech_plain, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_oauthbearer, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_xoauth2, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_scram_sha1, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_scram_sha256, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE},
+
+ /* Bad input tests*/
+ {&mech_apop, UCHAR_LEN("1.1.1\0test\0user\0response"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_apop, UCHAR_LEN("1.1.1\0testuser\0tooshort"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_apop, UCHAR_LEN("1.1.1\0testuser\0responseoflen16-"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_apop, UCHAR_LEN("1.1.1"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_otp, UCHAR_LEN("somebody\0testuser"), "testuser", "otp(testuser): unsupported response type", FALSE, TRUE, FALSE},
+ {&mech_cram_md5, UCHAR_LEN("testuser\0response"), "testuser", NULL, FALSE, FALSE, FALSE},
+ {&mech_plain, UCHAR_LEN("testuser\0"), "testuser", NULL, FALSE, FALSE, FALSE},
+
+ /* Covering most of the digest md5 parsing */
+ {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",cnonce=\"OA6MHXh6VqTrRk\",response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("realm=\"example.com\",cnonce=\"OA6MHXh6VqTrRk\",nonce=\"OA6MG9tEQGm2hh\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\", nonce=\"OA6MG9tEQGm2hh\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("qop=\"auth-int\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("qop=\"auth-int\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("qop=\"auth-conf\",\"cipher=rc4\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("cnonce=\"OA6MHXh6VqTrRk\",cnonce=\"OA6MHXh6VqTrRk\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("cnonce=\"\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("nonce=\"not matching\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("nc=00000001,nc=00000002"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("nc=NAN"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("nc=00000002"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("cipher=unsupported"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("digest-uri="), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("username=\"\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("username=\"a\",username=\"b\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("response=broken"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("maxbuf=32,maxbuf=1024"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("maxbuf=broken"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("authzid=\"somebody\",authzid=\"else\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("authzid=\"\""), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("charset=unsupported"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_digest_md5, UCHAR_LEN("qop=unsupported"), NULL, NULL, FALSE, FALSE, FALSE},
+
+ /* Too much nuls */
+ {&mech_dovecot_token, UCHAR_LEN("service\0pid\0fail\0se\0ssion_id\0deadbeef"), NULL , NULL, FALSE, FALSE, FALSE},
+ {&mech_login, UCHAR_LEN("test user\0user"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_oauthbearer, UCHAR_LEN("n,a==testuser,\x01""auth=Bearer token\x01\x01"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_oauthbearer, UCHAR_LEN("n,a=testuser,f=non-standard\x01""auth=Bearer token\x01\x01"), "testuser", NULL, FALSE, FALSE, FALSE},
+ {&mech_oauthbearer, UCHAR_LEN("n,a=testuser\x01""auth=token\x01\x01"), "testuser", NULL, FALSE, FALSE, FALSE},
+ {&mech_xoauth2, UCHAR_LEN("testuser\x01auth=Bearer token\x01\x01"), NULL, NULL, FALSE, FALSE, FALSE},
+ /* does not start with [B|b]earer */
+ {&mech_xoauth2, UCHAR_LEN("user=testuser\x01""auth=token\x01\x01"), "testuser", NULL, FALSE, FALSE, FALSE},
+ /* Too much nuls */
+ {&mech_plain, UCHAR_LEN("\0fa\0il\0ing\0withthis"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_plain, UCHAR_LEN("failingwiththis"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_plain, UCHAR_LEN("failing\0withthis"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_otp, UCHAR_LEN("someb\0ody\0testuser"), NULL, "invalid input", FALSE, FALSE, FALSE},
+ /* phase 2 */
+ {&mech_otp, UCHAR_LEN("someb\0ody\0testuser"), "testuser", "otp(testuser): unsupported response type", FALSE, TRUE, FALSE},
+ {&mech_scram_sha1, UCHAR_LEN("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_scram_sha1, UCHAR_LEN("iws0X8v3Bz2T0CJGbJQyF0X+HI4Ts=,,,,"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_scram_sha1, UCHAR_LEN("n,a=masteruser,,"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_scram_sha1, UCHAR_LEN("n,a==masteruser,,"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_scram_sha1, UCHAR_LEN("n,,m=testuser,,"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_scram_sha1, UCHAR_LEN("broken\0input"), NULL, NULL, FALSE, FALSE, FALSE},
+ {&mech_scram_sha256, UCHAR_LEN("broken\0input"), NULL, NULL, FALSE, FALSE, FALSE},
+ };
+
+ test_mechs_init();
+
+ string_t *d_token = t_str_new(32);
+ str_append_data(d_token, UCHAR_LEN("service\0pid\0testuser\0session\0"));
+ str_append(d_token, auth_token_get("service","pid","testuser","session"));
+
+ for (unsigned int running_test = 0; running_test < N_ELEMENTS(tests);
+ running_test++) T_BEGIN {
+ struct test_case *test_case = &tests[running_test];
+ const struct mech_module *mech = test_case->mech;
+ struct auth_request *request;
+ const char *testname = t_strdup_printf("auth mech %s %d/%zu",
+ mech->mech_name,
+ running_test+1,
+ N_ELEMENTS(tests));
+ test_begin(testname);
+
+ test_mech_prepare_request(&request, mech, &handler, running_test,
+ test_case);
+
+ if (mech == &mech_apop && test_case->in == NULL)
+ test_case->in =
+ test_mech_construct_apop_challenge(request->connect_uid,
+ &test_case->len);
+ if (mech == &mech_dovecot_token && test_case->in == NULL) {
+ test_case->in = d_token->data;
+ test_case->len = d_token->used;
+ }
+
+ if (test_case->expect_error != NULL)
+ test_expect_error_string(test_case->expect_error);
+
+ request->state = AUTH_REQUEST_STATE_NEW;
+ unsigned char *input_dup = test_case->len == 0 ? NULL :
+ i_memdup(test_case->in, test_case->len);
+ request->initial_response = input_dup;
+ request->initial_response_len = test_case->len;
+ auth_request_initial(request);
+
+ const char *challenge = request->context;
+
+ if (challenge != NULL) {
+ test_mech_handle_challenge(request, test_case->in,
+ test_case->len,
+ running_test,
+ test_case->success);
+ }
+
+ const char *username = request->fields.user;
+
+ if (request->fields.master_user != NULL)
+ username = request->fields.master_user;
+
+ if (!test_case->set_username_before_test && test_case->success) {
+ /* If the username was set by the test logic, do not
+ * compare it as it does not give any additional
+ * information */
+ test_assert_strcmp_idx(test_case->username, username,
+ running_test);
+ } else if (!test_case->set_username_before_test && !test_case->success) {
+ /* If the username is not set by the testlogic and we
+ * expect failure, verify that the mechanism failed by
+ * checking that the username is not set */
+ test_assert_idx(username == NULL, running_test);
+ }
+
+ if (test_case->success)
+ test_assert_idx(request->failed == FALSE, running_test);
+ else
+ test_assert_idx(request->failed == TRUE, running_test);
+
+ event_unref(&request->event);
+ event_unref(&request->mech_event);
+ i_free(input_dup);
+ mech->auth_free(request);
+
+ test_end();
+ } T_END;
+ mech_otp_deinit();
+ auths_deinit();
+ auth_token_deinit();
+ password_schemes_deinit();
+ passdb_mock_mod_deinit();
+ passdbs_deinit();
+ userdbs_deinit();
+ event_unref(&auth_event);
+ mech_deinit(global_auth_settings);
+ mech_register_deinit(&mech_reg);
+ auths_free();
+ i_unlink("auth-token-secret.dat");
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_mechs,
+ NULL
+ };
+
+ return test_run(test_functions);
+}
diff --git a/src/auth/test-mock.c b/src/auth/test-mock.c
new file mode 100644
index 0000000..9584912
--- /dev/null
+++ b/src/auth/test-mock.c
@@ -0,0 +1,109 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "test-auth.h"
+#include "auth-common.h"
+#include "passdb.h"
+
+struct auth_penalty *auth_penalty;
+time_t process_start_time;
+bool worker, worker_restart_request;
+static struct passdb_module *mock_passdb_mod = NULL;
+static pool_t mock_pool;
+
+void auth_module_load(const char *names ATTR_UNUSED)
+{
+}
+void auth_refresh_proctitle(void) {
+}
+
+static void passdb_mock_init(struct passdb_module *module ATTR_UNUSED)
+{
+}
+static void passdb_mock_deinit(struct passdb_module *module ATTR_UNUSED)
+{
+}
+static void passdb_mock_verify_plain(struct auth_request *request, const char *password ATTR_UNUSED,
+ verify_plain_callback_t *callback)
+{
+ callback(PASSDB_RESULT_OK, request);
+}
+
+static void passdb_mock_lookup_credentials(struct auth_request *request,
+ lookup_credentials_callback_t *callback)
+{
+ passdb_handle_credentials(PASSDB_RESULT_OK, "password", "PLAIN",
+ callback, request);
+}
+
+static struct passdb_module_interface mock_interface = {
+ .name = "mock",
+ .init = passdb_mock_init,
+ .deinit = passdb_mock_deinit,
+ .verify_plain = passdb_mock_verify_plain,
+ .lookup_credentials = passdb_mock_lookup_credentials,
+};
+
+struct auth_passdb_settings mock_passdb_set = {
+ .name = "mock",
+ .driver = "mock",
+ .args = "",
+ .default_fields = "",
+ .override_fields = "",
+ .mechanisms = "",
+ .username_filter = "",
+ .skip = "never",
+ .result_success = "return-ok",
+ .result_failure = "continue",
+ .result_internalfail = "continue",
+ .deny = FALSE,
+ .pass = FALSE,
+ .master = FALSE,
+ .auth_verbose = "default"
+};
+
+void passdb_mock_mod_init(void)
+{
+ if (mock_passdb_mod != NULL)
+ return;
+
+ mock_pool = pool_allocfree_create("auth mock");
+
+ passdb_register_module(&mock_interface);
+
+ struct auth_passdb_settings set = {
+ .name = "mock",
+ .driver = "mock",
+ .args = "",
+ .default_fields = "",
+ .override_fields = "",
+ .mechanisms = "",
+ .username_filter = "",
+
+ .skip = "never",
+ .result_success = "return-ok",
+ .result_failure = "continue",
+ .result_internalfail = "continue",
+
+ .deny = FALSE,
+ .pass = FALSE,
+ .master = FALSE,
+ .auth_verbose = "default"
+ };
+ mock_passdb_mod = passdb_preinit(mock_pool, &set);
+ passdb_init(mock_passdb_mod);
+}
+
+void passdb_mock_mod_deinit(void)
+{
+ passdb_deinit(mock_passdb_mod);
+ passdb_unregister_module(&mock_interface);
+ pool_unref(&mock_pool);
+}
+
+struct auth_passdb *passdb_mock(void)
+{
+ struct auth_passdb *ret = i_new(struct auth_passdb, 1);
+ ret->set = &mock_passdb_set;
+ ret->passdb = mock_passdb_mod;
+ return ret;
+}
diff --git a/src/auth/test-username-filter.c b/src/auth/test-username-filter.c
new file mode 100644
index 0000000..4a1c221
--- /dev/null
+++ b/src/auth/test-username-filter.c
@@ -0,0 +1,50 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "auth-settings.h"
+#include "test-auth.h"
+#include "auth-request.h"
+
+void test_username_filter(void)
+{
+ const struct {
+ const char *filter;
+ const char *input;
+ bool accepted;
+ } cases[] = {
+ { "", "", TRUE },
+ { "*", "", TRUE },
+ { "", "testuser1", TRUE },
+ { "*", "testuser1", TRUE },
+ { "!*", "testuser1", FALSE },
+ { "!*", "", FALSE },
+ { "*@*", "", FALSE },
+ { "*@*", "@", TRUE },
+ { "!*@*", "@", FALSE },
+ { "!*@*", "", TRUE },
+ { "*@*", "testuser1", FALSE },
+ { "!*@*", "testuser1", TRUE },
+ { "*@*", "testuser1@testdomain", TRUE },
+ { "!*@*", "testuser1@testdomain", FALSE },
+ { "*@testdomain *@testdomain2", "testuser1@testdomain", TRUE },
+ { "*@testdomain *@testdomain2", "testuser1@testdomain2", TRUE },
+ { "*@testdomain *@testdomain2", "testuser1@testdomain3", FALSE },
+ { "!testuser@testdomain *@testdomain", "testuser@testdomain", FALSE },
+ { "!testuser@testdomain *@testdomain", "testuser2@testdomain", TRUE },
+ { "*@testdomain !testuser@testdomain !testuser2@testdomain", "testuser@testdomain", FALSE },
+ { "*@testdomain !testuser@testdomain !testuser2@testdomain", "testuser3@testdomain", TRUE },
+ { "!testuser@testdomain !testuser2@testdomain", "testuser", TRUE },
+ { "!testuser@testdomain !testuser2@testdomain", "testuser@testdomain", FALSE },
+ { "!testuser@testdomain *@testdomain !testuser2@testdomain", "testuser3@testdomain", TRUE },
+ { "!testuser@testdomain *@testdomain !testuser2@testdomain", "testuser@testdomain", FALSE },
+ };
+
+ test_begin("test username_filter");
+
+ for(size_t i = 0; i < N_ELEMENTS(cases); i++) {
+ const char *const *filter = t_strsplit_spaces(cases[i].filter, " ,");
+ test_assert_idx(auth_request_username_accepted(filter, cases[i].input) == cases[i].accepted, i);
+ }
+
+ test_end();
+}
diff --git a/src/auth/userdb-blocking.c b/src/auth/userdb-blocking.c
new file mode 100644
index 0000000..9f928e7
--- /dev/null
+++ b/src/auth/userdb-blocking.c
@@ -0,0 +1,144 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "str.h"
+#include "auth-worker-server.h"
+#include "userdb.h"
+#include "userdb-blocking.h"
+
+
+struct blocking_userdb_iterate_context {
+ struct userdb_iterate_context ctx;
+ struct auth_worker_connection *conn;
+ bool next;
+ bool destroyed;
+};
+
+static bool user_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+ const char *reply, void *context)
+{
+ struct auth_request *request = context;
+ enum userdb_result result;
+ const char *username, *args;
+
+ if (str_begins(reply, "FAIL\t")) {
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+ args = reply + 5;
+ } else if (str_begins(reply, "NOTFOUND\t")) {
+ result = USERDB_RESULT_USER_UNKNOWN;
+ args = reply + 9;
+ } else if (str_begins(reply, "OK\t")) {
+ result = USERDB_RESULT_OK;
+ username = reply + 3;
+ args = strchr(username, '\t');
+ if (args == NULL)
+ args = "";
+ else
+ username = t_strdup_until(username, args++);
+ if (username[0] != '\0' &&
+ strcmp(request->fields.user, username) != 0) {
+ auth_request_set_username_forced(request, username);
+ request->user_changed_by_lookup = TRUE;
+ }
+ } else {
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+ e_error(authdb_event(request),
+ "BUG: auth-worker sent invalid user reply");
+ args = "";
+ }
+
+ if (*args != '\0') {
+ auth_fields_import(request->fields.userdb_reply, args, 0);
+ if (auth_fields_exists(request->fields.userdb_reply, "tempfail"))
+ request->userdb_lookup_tempfailed = TRUE;
+ }
+
+ auth_request_userdb_callback(result, request);
+ auth_request_unref(&request);
+ return TRUE;
+}
+
+void userdb_blocking_lookup(struct auth_request *request)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ str_printfa(str, "USER\t%u\t", request->userdb->userdb->id);
+ auth_request_export(request, str);
+
+ auth_request_ref(request);
+ auth_worker_call(request->pool, request->fields.user,
+ str_c(str), user_callback, request);
+}
+
+static bool iter_callback(struct auth_worker_connection *conn,
+ const char *reply, void *context)
+{
+ struct blocking_userdb_iterate_context *ctx = context;
+
+ ctx->conn = conn;
+
+ if (str_begins(reply, "*\t")) {
+ if (ctx->destroyed)
+ return TRUE;
+ ctx->next = FALSE;
+ ctx->ctx.callback(reply + 2, ctx->ctx.context);
+ return ctx->next || ctx->destroyed;
+ }
+
+ if (strcmp(reply, "OK") != 0)
+ ctx->ctx.failed = TRUE;
+ if (!ctx->destroyed)
+ ctx->ctx.callback(NULL, ctx->ctx.context);
+ auth_request_unref(&ctx->ctx.auth_request);
+ return TRUE;
+}
+
+struct userdb_iterate_context *
+userdb_blocking_iter_init(struct auth_request *request,
+ userdb_iter_callback_t *callback, void *context)
+{
+ struct blocking_userdb_iterate_context *ctx;
+ string_t *str;
+
+ str = t_str_new(128);
+ str_printfa(str, "LIST\t%u\t", request->userdb->userdb->id);
+ auth_request_export(request, str);
+
+ ctx = p_new(request->pool, struct blocking_userdb_iterate_context, 1);
+ ctx->ctx.auth_request = request;
+ ctx->ctx.callback = callback;
+ ctx->ctx.context = context;
+
+ auth_request_ref(request);
+ auth_worker_call(request->pool, "*",
+ str_c(str), iter_callback, ctx);
+ return &ctx->ctx;
+}
+
+void userdb_blocking_iter_next(struct userdb_iterate_context *_ctx)
+{
+ struct blocking_userdb_iterate_context *ctx =
+ (struct blocking_userdb_iterate_context *)_ctx;
+
+ i_assert(ctx->conn != NULL);
+
+ ctx->next = TRUE;
+ auth_worker_server_resume_input(ctx->conn);
+}
+
+int userdb_blocking_iter_deinit(struct userdb_iterate_context **_ctx)
+{
+ struct blocking_userdb_iterate_context *ctx =
+ (struct blocking_userdb_iterate_context *)*_ctx;
+ int ret = ctx->ctx.failed ? -1 : 0;
+
+ *_ctx = NULL;
+
+ /* iter_callback() may still be called */
+ ctx->destroyed = TRUE;
+
+ if (ctx->conn != NULL)
+ auth_worker_server_resume_input(ctx->conn);
+ return ret;
+}
diff --git a/src/auth/userdb-blocking.h b/src/auth/userdb-blocking.h
new file mode 100644
index 0000000..2bbf075
--- /dev/null
+++ b/src/auth/userdb-blocking.h
@@ -0,0 +1,12 @@
+#ifndef USERDB_BLOCKING_H
+#define USERDB_BLOCKING_H
+
+void userdb_blocking_lookup(struct auth_request *request);
+
+struct userdb_iterate_context *
+userdb_blocking_iter_init(struct auth_request *request,
+ userdb_iter_callback_t *callback, void *context);
+void userdb_blocking_iter_next(struct userdb_iterate_context *ctx);
+int userdb_blocking_iter_deinit(struct userdb_iterate_context **ctx);
+
+#endif
diff --git a/src/auth/userdb-checkpassword.c b/src/auth/userdb-checkpassword.c
new file mode 100644
index 0000000..aa6455e
--- /dev/null
+++ b/src/auth/userdb-checkpassword.c
@@ -0,0 +1,92 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#ifdef USERDB_CHECKPASSWORD
+
+#include "db-checkpassword.h"
+
+struct checkpassword_userdb_module {
+ struct userdb_module module;
+ struct db_checkpassword *db;
+};
+
+static void
+userdb_checkpassword_callback(struct auth_request *request,
+ enum db_checkpassword_status status,
+ const char *const *extra_fields,
+ userdb_callback_t *callback)
+{
+ unsigned int i;
+
+ switch (status) {
+ case DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE:
+ callback(USERDB_RESULT_INTERNAL_FAILURE, request);
+ break;
+ case DB_CHECKPASSWORD_STATUS_FAILURE:
+ callback(USERDB_RESULT_USER_UNKNOWN, request);
+ break;
+ case DB_CHECKPASSWORD_STATUS_OK:
+ for (i = 0; extra_fields[i] != NULL; i++) {
+ if (!str_begins(extra_fields[i], "userdb_"))
+ continue;
+ auth_request_set_field_keyvalue(request,
+ extra_fields[i], NULL);
+ }
+ callback(USERDB_RESULT_OK, request);
+ break;
+ }
+}
+
+static void
+checkpassword_lookup(struct auth_request *request, userdb_callback_t *callback)
+{
+ struct userdb_module *_module = request->userdb->userdb;
+ struct checkpassword_userdb_module *module =
+ (struct checkpassword_userdb_module *)_module;
+
+ db_checkpassword_call(module->db, request, NULL,
+ userdb_checkpassword_callback, callback);
+}
+
+static struct userdb_module *
+checkpassword_preinit(pool_t pool, const char *args)
+{
+ struct checkpassword_userdb_module *module;
+ const char *checkpassword_path = args;
+ const char *checkpassword_reply_path =
+ PKG_LIBEXECDIR"/checkpassword-reply";
+
+ module = p_new(pool, struct checkpassword_userdb_module, 1);
+ module->db = db_checkpassword_init(checkpassword_path,
+ checkpassword_reply_path);
+ return &module->module;
+}
+
+static void checkpassword_deinit(struct userdb_module *_module)
+{
+ struct checkpassword_userdb_module *module =
+ (struct checkpassword_userdb_module *)_module;
+
+ db_checkpassword_deinit(&module->db);
+}
+
+struct userdb_module_interface userdb_checkpassword = {
+ "checkpassword",
+
+ checkpassword_preinit,
+ NULL,
+ checkpassword_deinit,
+
+ checkpassword_lookup,
+
+ NULL,
+ NULL,
+ NULL
+};
+#else
+struct userdb_module_interface userdb_checkpassword = {
+ .name = "checkpassword"
+};
+#endif
diff --git a/src/auth/userdb-dict.c b/src/auth/userdb-dict.c
new file mode 100644
index 0000000..51e618e
--- /dev/null
+++ b/src/auth/userdb-dict.c
@@ -0,0 +1,205 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "auth-cache.h"
+#include "db-dict.h"
+
+#include <dict.h>
+
+struct dict_userdb_module {
+ struct userdb_module module;
+
+ struct dict_connection *conn;
+};
+
+struct dict_userdb_iterate_context {
+ struct userdb_iterate_context ctx;
+
+ userdb_callback_t *userdb_callback;
+ const char *key_prefix;
+ size_t key_prefix_len;
+ struct dict_iterate_context *iter;
+};
+
+static int
+dict_query_save_results(struct auth_request *auth_request,
+ struct db_dict_value_iter *iter)
+{
+ const char *key, *value, *error;
+
+ while (db_dict_value_iter_next(iter, &key, &value)) {
+ if (value != NULL)
+ auth_request_set_userdb_field(auth_request, key, value);
+ }
+ if (db_dict_value_iter_deinit(&iter, &error) < 0) {
+ e_error(authdb_event(auth_request), "%s", error);
+ return -1;
+ }
+ return 0;
+}
+
+static void userdb_dict_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct dict_userdb_module *module =
+ (struct dict_userdb_module *)_module;
+ struct db_dict_value_iter *iter;
+ enum userdb_result userdb_result;
+ int ret;
+
+ if (array_count(&module->conn->set.userdb_fields) == 0 &&
+ array_count(&module->conn->set.parsed_userdb_objects) == 0) {
+ e_error(authdb_event(auth_request),
+ "No userdb_objects or userdb_fields specified");
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ }
+
+ ret = db_dict_value_iter_init(module->conn, auth_request,
+ &module->conn->set.userdb_fields,
+ &module->conn->set.parsed_userdb_objects,
+ &iter);
+ if (ret < 0)
+ userdb_result = USERDB_RESULT_INTERNAL_FAILURE;
+ else if (ret == 0) {
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ userdb_result = USERDB_RESULT_USER_UNKNOWN;
+ } else {
+ if (dict_query_save_results(auth_request, iter) < 0)
+ userdb_result = USERDB_RESULT_INTERNAL_FAILURE;
+ else
+ userdb_result = USERDB_RESULT_OK;
+ }
+ callback(userdb_result, auth_request);
+}
+
+static struct userdb_iterate_context *
+userdb_dict_iterate_init(struct auth_request *auth_request,
+ userdb_iter_callback_t *callback, void *context)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct dict_userdb_module *module =
+ (struct dict_userdb_module *)_module;
+ struct dict_userdb_iterate_context *ctx;
+ string_t *path;
+ const char *error;
+
+ ctx = i_new(struct dict_userdb_iterate_context, 1);
+ ctx->ctx.auth_request = auth_request;
+ ctx->ctx.callback = callback;
+ ctx->ctx.context = context;
+ auth_request_ref(auth_request);
+
+ if (*module->conn->set.iterate_prefix == '\0') {
+ if (!module->conn->set.iterate_disable) {
+ e_error(authdb_event(auth_request),
+ "iterate: iterate_prefix not set");
+ ctx->ctx.failed = TRUE;
+ }
+ return &ctx->ctx;
+ }
+
+ path = t_str_new(128);
+ str_append(path, DICT_PATH_SHARED);
+ if (auth_request_var_expand(path, module->conn->set.iterate_prefix,
+ auth_request, NULL, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand iterate_prefix=%s: %s",
+ module->conn->set.iterate_prefix, error);
+ ctx->ctx.failed = TRUE;
+ return &ctx->ctx;
+ }
+ ctx->key_prefix = p_strdup(auth_request->pool, str_c(path));
+ ctx->key_prefix_len = strlen(ctx->key_prefix);
+
+ struct dict_op_settings set = {
+ .username = auth_request->fields.user,
+ };
+ ctx->iter = dict_iterate_init(module->conn->dict, &set, ctx->key_prefix, 0);
+ e_debug(authdb_event(auth_request),
+ "iterate: prefix=%s", ctx->key_prefix);
+ return &ctx->ctx;
+}
+
+static const char *
+userdb_dict_get_user(struct dict_userdb_iterate_context *ctx, const char *key)
+{
+ i_assert(strncmp(key, ctx->key_prefix, ctx->key_prefix_len) == 0);
+
+ return key + ctx->key_prefix_len;
+}
+
+static void userdb_dict_iterate_next(struct userdb_iterate_context *_ctx)
+{
+ struct dict_userdb_iterate_context *ctx =
+ (struct dict_userdb_iterate_context *)_ctx;
+ const char *key, *value;
+
+ if (ctx->iter != NULL && dict_iterate(ctx->iter, &key, &value))
+ _ctx->callback(userdb_dict_get_user(ctx, key), _ctx->context);
+ else
+ _ctx->callback(NULL, _ctx->context);
+}
+
+static int userdb_dict_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+ struct dict_userdb_iterate_context *ctx =
+ (struct dict_userdb_iterate_context *)_ctx;
+ const char *error;
+ int ret = _ctx->failed ? -1 : 0;
+
+ if (dict_iterate_deinit(&ctx->iter, &error) < 0) {
+ e_error(authdb_event(_ctx->auth_request),
+ "dict_iterate(%s) failed: %s",
+ ctx->key_prefix, error);
+ ret = -1;
+ }
+ auth_request_unref(&ctx->ctx.auth_request);
+ i_free(ctx);
+ return ret;
+}
+
+static struct userdb_module *
+userdb_dict_preinit(pool_t pool, const char *args)
+{
+ struct dict_userdb_module *module;
+ struct dict_connection *conn;
+
+ module = p_new(pool, struct dict_userdb_module, 1);
+ module->conn = conn = db_dict_init(args);
+
+ module->module.blocking = TRUE;
+ module->module.default_cache_key = auth_cache_parse_key(pool,
+ db_dict_parse_cache_key(&conn->set.keys, &conn->set.userdb_fields,
+ &conn->set.parsed_userdb_objects));
+ return &module->module;
+}
+
+static void userdb_dict_deinit(struct userdb_module *_module)
+{
+ struct dict_userdb_module *module =
+ (struct dict_userdb_module *)_module;
+
+ db_dict_unref(&module->conn);
+}
+
+struct userdb_module_interface userdb_dict =
+{
+ "dict",
+
+ userdb_dict_preinit,
+ NULL,
+ userdb_dict_deinit,
+
+ userdb_dict_lookup,
+
+ userdb_dict_iterate_init,
+ userdb_dict_iterate_next,
+ userdb_dict_iterate_deinit
+};
diff --git a/src/auth/userdb-ldap.c b/src/auth/userdb-ldap.c
new file mode 100644
index 0000000..ecbf09e
--- /dev/null
+++ b/src/auth/userdb-ldap.c
@@ -0,0 +1,343 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#if defined(USERDB_LDAP) && (defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD))
+
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "auth-cache.h"
+#include "db-ldap.h"
+
+#include <ldap.h>
+
+struct ldap_userdb_module {
+ struct userdb_module module;
+
+ struct ldap_connection *conn;
+};
+
+struct userdb_ldap_request {
+ struct ldap_request_search request;
+ userdb_callback_t *userdb_callback;
+ unsigned int entries;
+};
+
+struct userdb_iter_ldap_request {
+ struct ldap_request_search request;
+ struct ldap_userdb_iterate_context *ctx;
+ userdb_callback_t *userdb_callback;
+};
+
+struct ldap_userdb_iterate_context {
+ struct userdb_iterate_context ctx;
+ struct userdb_iter_ldap_request request;
+ pool_t pool;
+ struct ldap_connection *conn;
+ bool continued, in_callback, deinitialized;
+};
+
+static void
+ldap_query_get_result(struct ldap_connection *conn,
+ struct auth_request *auth_request,
+ struct ldap_request_search *ldap_request,
+ LDAPMessage *res)
+{
+ struct db_ldap_result_iterate_context *ldap_iter;
+ const char *name, *const *values;
+
+ ldap_iter = db_ldap_result_iterate_init(conn, ldap_request, res, TRUE);
+ while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) {
+ auth_request_set_userdb_field_values(auth_request,
+ name, values);
+ }
+ db_ldap_result_iterate_deinit(&ldap_iter);
+}
+
+static void
+userdb_ldap_lookup_finish(struct auth_request *auth_request,
+ struct userdb_ldap_request *urequest,
+ LDAPMessage *res)
+{
+ enum userdb_result result = USERDB_RESULT_INTERNAL_FAILURE;
+
+ if (res == NULL) {
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+ } else if (urequest->entries == 0) {
+ result = USERDB_RESULT_USER_UNKNOWN;
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ } else if (urequest->entries > 1) {
+ e_error(authdb_event(auth_request),
+ "user_filter matched multiple objects, aborting");
+ result = USERDB_RESULT_INTERNAL_FAILURE;
+ } else {
+ result = USERDB_RESULT_OK;
+ }
+
+ urequest->userdb_callback(result, auth_request);
+}
+
+static void userdb_ldap_lookup_callback(struct ldap_connection *conn,
+ struct ldap_request *request,
+ LDAPMessage *res)
+{
+ struct userdb_ldap_request *urequest =
+ (struct userdb_ldap_request *) request;
+ struct auth_request *auth_request =
+ urequest->request.request.auth_request;
+
+ if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) {
+ userdb_ldap_lookup_finish(auth_request, urequest, res);
+ auth_request_unref(&auth_request);
+ return;
+ }
+
+ if (urequest->entries++ == 0) {
+ /* first entry */
+ ldap_query_get_result(conn, auth_request,
+ &urequest->request, res);
+ }
+}
+
+static void userdb_ldap_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct ldap_userdb_module *module =
+ (struct ldap_userdb_module *)_module;
+ struct ldap_connection *conn = module->conn;
+ const char **attr_names = (const char **)conn->user_attr_names;
+ struct userdb_ldap_request *request;
+ const char *error;
+ string_t *str;
+
+ auth_request_ref(auth_request);
+ request = p_new(auth_request->pool, struct userdb_ldap_request, 1);
+ request->userdb_callback = callback;
+
+ str = t_str_new(512);
+ if (auth_request_var_expand(str, conn->set.base, auth_request,
+ ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand base=%s: %s", conn->set.base, error);
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ }
+ request->request.base = p_strdup(auth_request->pool, str_c(str));
+
+ str_truncate(str, 0);
+ if (auth_request_var_expand(str, conn->set.user_filter, auth_request,
+ ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand user_filter=%s: %s",
+ conn->set.user_filter, error);
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ }
+ request->request.filter = p_strdup(auth_request->pool, str_c(str));
+
+ request->request.attr_map = &conn->user_attr_map;
+ request->request.attributes = conn->user_attr_names;
+
+ e_debug(authdb_event(auth_request), "user search: "
+ "base=%s scope=%s filter=%s fields=%s",
+ request->request.base, conn->set.scope,
+ request->request.filter,
+ attr_names == NULL ? "(all)" :
+ t_strarray_join(attr_names, ","));
+
+ request->request.request.auth_request = auth_request;
+ request->request.request.callback = userdb_ldap_lookup_callback;
+ db_ldap_request(conn, &request->request.request);
+}
+
+static void userdb_ldap_iterate_callback(struct ldap_connection *conn,
+ struct ldap_request *request,
+ LDAPMessage *res)
+{
+ struct userdb_iter_ldap_request *urequest =
+ (struct userdb_iter_ldap_request *)request;
+ struct ldap_userdb_iterate_context *ctx = urequest->ctx;
+ struct db_ldap_result_iterate_context *ldap_iter;
+ const char *name, *const *values;
+
+ if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) {
+ if (res == NULL)
+ ctx->ctx.failed = TRUE;
+ if (!ctx->deinitialized)
+ ctx->ctx.callback(NULL, ctx->ctx.context);
+ auth_request_unref(&request->auth_request);
+ return;
+ }
+
+ if (ctx->deinitialized)
+ return;
+
+ /* the iteration can take a while. reset the request's create time so
+ it won't be aborted while it's still running */
+ request->create_time = ioloop_time;
+
+ ctx->in_callback = TRUE;
+ ldap_iter = db_ldap_result_iterate_init(conn, &urequest->request,
+ res, TRUE);
+ while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) {
+ if (strcmp(name, "user") != 0) {
+ e_warning(authdb_event(request->auth_request), "iterate: "
+ "Ignoring field not named 'user': %s", name);
+ continue;
+ }
+ for (; *values != NULL; values++) {
+ ctx->continued = FALSE;
+ ctx->ctx.callback(*values, ctx->ctx.context);
+ }
+ }
+ db_ldap_result_iterate_deinit(&ldap_iter);
+ if (!ctx->continued)
+ db_ldap_enable_input(conn, FALSE);
+ ctx->in_callback = FALSE;
+}
+
+static struct userdb_iterate_context *
+userdb_ldap_iterate_init(struct auth_request *auth_request,
+ userdb_iter_callback_t *callback, void *context)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct ldap_userdb_module *module =
+ (struct ldap_userdb_module *)_module;
+ struct ldap_connection *conn = module->conn;
+ struct ldap_userdb_iterate_context *ctx;
+ struct userdb_iter_ldap_request *request;
+ const char **attr_names = (const char **)conn->iterate_attr_names;
+ const char *error;
+ string_t *str;
+
+ ctx = p_new(auth_request->pool, struct ldap_userdb_iterate_context, 1);
+ ctx->ctx.auth_request = auth_request;
+ ctx->ctx.callback = callback;
+ ctx->ctx.context = context;
+ ctx->conn = conn;
+ request = &ctx->request;
+ request->ctx = ctx;
+
+ auth_request_ref(auth_request);
+ request->request.request.auth_request = auth_request;
+
+ str = t_str_new(512);
+ if (auth_request_var_expand(str, conn->set.base, auth_request,
+ ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand base=%s: %s", conn->set.base, error);
+ ctx->ctx.failed = TRUE;
+ }
+ request->request.base = p_strdup(auth_request->pool, str_c(str));
+
+ str_truncate(str, 0);
+ if (auth_request_var_expand(str, conn->set.iterate_filter,
+ auth_request, ldap_escape, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand iterate_filter=%s: %s",
+ conn->set.iterate_filter, error);
+ ctx->ctx.failed = TRUE;
+ }
+ request->request.filter = p_strdup(auth_request->pool, str_c(str));
+ request->request.attr_map = &conn->iterate_attr_map;
+ request->request.attributes = conn->iterate_attr_names;
+ request->request.multi_entry = TRUE;
+
+ e_debug(auth_request->event, "ldap: iterate: base=%s scope=%s filter=%s fields=%s",
+ request->request.base, conn->set.scope,
+ request->request.filter, attr_names == NULL ? "(all)" :
+ t_strarray_join(attr_names, ","));
+ request->request.request.callback = userdb_ldap_iterate_callback;
+ db_ldap_request(conn, &request->request.request);
+ return &ctx->ctx;
+}
+
+static void userdb_ldap_iterate_next(struct userdb_iterate_context *_ctx)
+{
+ struct ldap_userdb_iterate_context *ctx =
+ (struct ldap_userdb_iterate_context *)_ctx;
+
+ ctx->continued = TRUE;
+ if (!ctx->in_callback)
+ db_ldap_enable_input(ctx->conn, TRUE);
+}
+
+static int userdb_ldap_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+ struct ldap_userdb_iterate_context *ctx =
+ (struct ldap_userdb_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ db_ldap_enable_input(ctx->conn, TRUE);
+ ctx->deinitialized = TRUE;
+ return ret;
+}
+
+static struct userdb_module *
+userdb_ldap_preinit(pool_t pool, const char *args)
+{
+ struct ldap_userdb_module *module;
+ struct ldap_connection *conn;
+
+ module = p_new(pool, struct ldap_userdb_module, 1);
+ module->conn = conn = db_ldap_init(args, TRUE);
+ p_array_init(&conn->user_attr_map, pool, 16);
+ p_array_init(&conn->iterate_attr_map, pool, 16);
+
+ db_ldap_set_attrs(conn, conn->set.user_attrs, &conn->user_attr_names,
+ &conn->user_attr_map, NULL);
+ db_ldap_set_attrs(conn, conn->set.iterate_attrs,
+ &conn->iterate_attr_names,
+ &conn->iterate_attr_map, NULL);
+ module->module.blocking = conn->set.blocking;
+ module->module.default_cache_key =
+ auth_cache_parse_key(pool,
+ t_strconcat(conn->set.base,
+ conn->set.user_attrs,
+ conn->set.user_filter, NULL));
+ return &module->module;
+}
+
+static void userdb_ldap_init(struct userdb_module *_module)
+{
+ struct ldap_userdb_module *module =
+ (struct ldap_userdb_module *)_module;
+
+ if (!module->module.blocking || worker)
+ db_ldap_connect_delayed(module->conn);
+}
+
+static void userdb_ldap_deinit(struct userdb_module *_module)
+{
+ struct ldap_userdb_module *module =
+ (struct ldap_userdb_module *)_module;
+
+ db_ldap_unref(&module->conn);
+}
+
+#ifndef PLUGIN_BUILD
+struct userdb_module_interface userdb_ldap =
+#else
+struct userdb_module_interface userdb_ldap_plugin =
+#endif
+{
+ "ldap",
+
+ userdb_ldap_preinit,
+ userdb_ldap_init,
+ userdb_ldap_deinit,
+
+ userdb_ldap_lookup,
+
+ userdb_ldap_iterate_init,
+ userdb_ldap_iterate_next,
+ userdb_ldap_iterate_deinit
+};
+#else
+struct userdb_module_interface userdb_ldap = {
+ .name = "ldap"
+};
+#endif
diff --git a/src/auth/userdb-lua.c b/src/auth/userdb-lua.c
new file mode 100644
index 0000000..6b75c0d
--- /dev/null
+++ b/src/auth/userdb-lua.c
@@ -0,0 +1,139 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+#include "auth-cache.h"
+
+#if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD)
+
+#include "db-lua.h"
+
+struct dlua_userdb_module {
+ struct userdb_module module;
+ struct dlua_script *script;
+ const char *file;
+};
+
+static void userdb_lua_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct dlua_userdb_module *module =
+ (struct dlua_userdb_module *)_module;
+ const char *error;
+ enum userdb_result result =
+ auth_lua_call_userdb_lookup(module->script, auth_request, &error);
+ if (result == USERDB_RESULT_INTERNAL_FAILURE)
+ e_error(authdb_event(auth_request),
+ "userdb-lua: %s", error);
+ callback(result, auth_request);
+}
+
+static struct userdb_module *
+userdb_lua_preinit(pool_t pool, const char *args)
+{
+ struct dlua_userdb_module *module;
+ const char *cache_key = "%u";
+ bool blocking = TRUE;
+
+ module = p_new(pool, struct dlua_userdb_module, 1);
+ const char *const *fields = t_strsplit_spaces(args, " ");
+ while(*fields != NULL) {
+ if (str_begins(*fields, "file=")) {
+ module->file = p_strdup(pool, (*fields)+5);
+ } else if (str_begins(*fields, "blocking=")) {
+ const char *value = (*fields)+9;
+ if (strcmp(value, "yes") == 0) {
+ blocking = TRUE;
+ } else if (strcmp(value, "no") == 0) {
+ blocking = FALSE;
+ } else {
+ i_fatal("Invalid value %s. "
+ "Field blocking must be yes or no",
+ value);
+ }
+ } else if (str_begins(*fields, "cache_key=")) {
+ if (*((*fields)+10) != '\0')
+ cache_key = (*fields)+10;
+ else /* explicitly disable auth caching for lua */
+ cache_key = NULL;
+ } else {
+ i_fatal("Unsupported parameter %s", *fields);
+ }
+ fields++;
+ }
+
+ if (module->file == NULL)
+ i_fatal("userdb-lua: Missing mandatory file= parameter");
+
+ module->module.blocking = blocking;
+ if (cache_key != NULL) {
+ module->module.default_cache_key =
+ auth_cache_parse_key(pool, cache_key);
+ }
+ return &module->module;
+}
+
+static void userdb_lua_init(struct userdb_module *_module)
+{
+ struct dlua_userdb_module *module =
+ (struct dlua_userdb_module *)_module;
+ const char *error;
+
+ if (dlua_script_create_file(module->file, &module->script, auth_event, &error) < 0 ||
+ auth_lua_script_init(module->script, &error) < 0)
+ i_fatal("userdb-lua: initialization failed: %s", error);
+}
+
+static void userdb_lua_deinit(struct userdb_module *_module)
+{
+ struct dlua_userdb_module *module =
+ (struct dlua_userdb_module *)_module;
+ dlua_script_unref(&module->script);
+}
+
+static struct userdb_iterate_context *
+userdb_lua_iterate_init(struct auth_request *auth_request,
+ userdb_iter_callback_t *callback,
+ void *context)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct dlua_userdb_module *module =
+ (struct dlua_userdb_module *)_module;
+ return auth_lua_call_userdb_iterate_init(module->script, auth_request,
+ callback, context);
+}
+
+static void userdb_lua_iterate_next(struct userdb_iterate_context *ctx)
+{
+ auth_lua_userdb_iterate_next(ctx);
+}
+
+static int userdb_lua_iterate_deinit(struct userdb_iterate_context *ctx)
+{
+ return auth_lua_userdb_iterate_deinit(ctx);
+}
+
+#ifndef PLUGIN_BUILD
+struct userdb_module_interface userdb_lua =
+#else
+struct userdb_module_interface userdb_lua_plugin =
+#endif
+{
+ "lua",
+
+ userdb_lua_preinit,
+ userdb_lua_init,
+ userdb_lua_deinit,
+
+ userdb_lua_lookup,
+
+ userdb_lua_iterate_init,
+ userdb_lua_iterate_next,
+ userdb_lua_iterate_deinit
+};
+#else
+struct userdb_module_interface userdb_lua = {
+ .name = "lua"
+};
+#endif
diff --git a/src/auth/userdb-passwd-file.c b/src/auth/userdb-passwd-file.c
new file mode 100644
index 0000000..1e87138
--- /dev/null
+++ b/src/auth/userdb-passwd-file.c
@@ -0,0 +1,247 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#ifdef USERDB_PASSWD_FILE
+
+#include "istream.h"
+#include "str.h"
+#include "auth-cache.h"
+#include "db-passwd-file.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+struct passwd_file_userdb_iterate_context {
+ struct userdb_iterate_context ctx;
+ struct istream *input;
+ char *path;
+ bool skip_passdb_entries;
+};
+
+struct passwd_file_userdb_module {
+ struct userdb_module module;
+
+ struct db_passwd_file *pwf;
+ const char *username_format;
+};
+
+static int
+passwd_file_add_extra_fields(struct auth_request *request, char *const *fields)
+{
+ string_t *str = t_str_new(512);
+ const struct var_expand_table *table;
+ const char *key, *value, *error;
+ unsigned int i;
+
+ table = auth_request_get_var_expand_table(request, NULL);
+
+ for (i = 0; fields[i] != NULL; i++) {
+ if (!str_begins(fields[i], "userdb_"))
+ continue;
+
+ key = fields[i] + 7;
+ value = strchr(key, '=');
+ if (value != NULL) {
+ key = t_strdup_until(key, value);
+ str_truncate(str, 0);
+ if (auth_request_var_expand_with_table(str, value + 1,
+ request, table, NULL, &error) <= 0) {
+ e_error(authdb_event(request),
+ "Failed to expand extra field %s: %s",
+ fields[i], error);
+ return -1;
+ }
+ value = str_c(str);
+ } else {
+ value = "";
+ }
+ auth_request_set_userdb_field(request, key, value);
+ }
+ return 0;
+}
+
+static void passwd_file_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct passwd_file_userdb_module *module =
+ (struct passwd_file_userdb_module *)_module;
+ struct passwd_user *pu;
+ int ret;
+
+ ret = db_passwd_file_lookup(module->pwf, auth_request,
+ module->username_format, &pu);
+ if (ret <= 0 || pu->uid == 0) {
+ callback(ret < 0 ? USERDB_RESULT_INTERNAL_FAILURE :
+ USERDB_RESULT_USER_UNKNOWN, auth_request);
+ return;
+ }
+
+ if (pu->uid != (uid_t)-1) {
+ auth_request_set_userdb_field(auth_request, "uid",
+ dec2str(pu->uid));
+ }
+ if (pu->gid != (gid_t)-1) {
+ auth_request_set_userdb_field(auth_request, "gid",
+ dec2str(pu->gid));
+ }
+
+ if (pu->home != NULL)
+ auth_request_set_userdb_field(auth_request, "home", pu->home);
+
+ if (pu->extra_fields != NULL &&
+ passwd_file_add_extra_fields(auth_request, pu->extra_fields) < 0) {
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ }
+
+ callback(USERDB_RESULT_OK, auth_request);
+}
+
+static struct userdb_iterate_context *
+passwd_file_iterate_init(struct auth_request *auth_request,
+ userdb_iter_callback_t *callback, void *context)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct passwd_file_userdb_module *module =
+ (struct passwd_file_userdb_module *)_module;
+ struct passwd_file_userdb_iterate_context *ctx;
+ int fd;
+
+ ctx = i_new(struct passwd_file_userdb_iterate_context, 1);
+ ctx->ctx.auth_request = auth_request;
+ ctx->ctx.callback = callback;
+ ctx->ctx.context = context;
+ ctx->skip_passdb_entries = !module->pwf->userdb_warn_missing;
+ if (module->pwf->default_file == NULL) {
+ e_error(authdb_event(auth_request),
+ "passwd-file: User iteration isn't currently supported "
+ "with %%variable paths");
+ ctx->ctx.failed = TRUE;
+ return &ctx->ctx;
+ }
+ ctx->path = i_strdup(module->pwf->default_file->path);
+
+ /* for now we support only a single passwd-file */
+ fd = open(ctx->path, O_RDONLY);
+ if (fd == -1) {
+ e_error(authdb_event(auth_request),
+ "open(%s) failed: %m", ctx->path);
+ ctx->ctx.failed = TRUE;
+ } else {
+ ctx->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
+ }
+ return &ctx->ctx;
+}
+
+static void passwd_file_iterate_next(struct userdb_iterate_context *_ctx)
+{
+ struct passwd_file_userdb_iterate_context *ctx =
+ (struct passwd_file_userdb_iterate_context *)_ctx;
+ const char *line, *p;
+
+ if (ctx->input == NULL)
+ line = NULL;
+ else {
+ while ((line = i_stream_read_next_line(ctx->input)) != NULL) {
+ if (*line == '\0' || *line == ':' || *line == '#')
+ continue; /* no username or comment */
+ if (ctx->skip_passdb_entries &&
+ ((p = i_strchr_to_next(line, ':')) == NULL ||
+ strchr(p, ':') == NULL)) {
+ /* only passdb info */
+ continue;
+ }
+ break;
+ }
+ if (line == NULL && ctx->input->stream_errno != 0) {
+ e_error(authdb_event(_ctx->auth_request),
+ "read(%s) failed: %s", ctx->path,
+ i_stream_get_error(ctx->input));
+ _ctx->failed = TRUE;
+ }
+ }
+ if (line == NULL)
+ _ctx->callback(NULL, _ctx->context);
+ else T_BEGIN {
+ _ctx->callback(t_strcut(line, ':'), _ctx->context);
+ } T_END;
+}
+
+static int passwd_file_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+ struct passwd_file_userdb_iterate_context *ctx =
+ (struct passwd_file_userdb_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ i_stream_destroy(&ctx->input);
+ i_free(ctx->path);
+ i_free(ctx);
+ return ret;
+}
+
+static struct userdb_module *
+passwd_file_preinit(pool_t pool, const char *args)
+{
+ struct passwd_file_userdb_module *module;
+ const char *format = PASSWD_FILE_DEFAULT_USERNAME_FORMAT;
+ const char *p;
+
+ if (str_begins(args, "username_format=")) {
+ args += 16;
+ p = strchr(args, ' ');
+ if (p == NULL) {
+ format = p_strdup(pool, args);
+ args = "";
+ } else {
+ format = p_strdup_until(pool, args, p);
+ args = p + 1;
+ }
+ }
+
+ if (*args == '\0')
+ i_fatal("userdb passwd-file: Missing args");
+
+ module = p_new(pool, struct passwd_file_userdb_module, 1);
+ module->pwf = db_passwd_file_init(args, TRUE,
+ global_auth_settings->debug);
+ module->username_format = format;
+ return &module->module;
+}
+
+static void passwd_file_init(struct userdb_module *_module)
+{
+ struct passwd_file_userdb_module *module =
+ (struct passwd_file_userdb_module *)_module;
+
+ db_passwd_file_parse(module->pwf);
+}
+
+static void passwd_file_deinit(struct userdb_module *_module)
+{
+ struct passwd_file_userdb_module *module =
+ (struct passwd_file_userdb_module *)_module;
+
+ db_passwd_file_unref(&module->pwf);
+}
+
+struct userdb_module_interface userdb_passwd_file = {
+ "passwd-file",
+
+ passwd_file_preinit,
+ passwd_file_init,
+ passwd_file_deinit,
+
+ passwd_file_lookup,
+
+ passwd_file_iterate_init,
+ passwd_file_iterate_next,
+ passwd_file_iterate_deinit
+};
+#else
+struct userdb_module_interface userdb_passwd_file = {
+ .name = "passwd-file"
+};
+#endif
diff --git a/src/auth/userdb-passwd.c b/src/auth/userdb-passwd.c
new file mode 100644
index 0000000..3c99102
--- /dev/null
+++ b/src/auth/userdb-passwd.c
@@ -0,0 +1,253 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#ifdef USERDB_PASSWD
+
+#include "ioloop.h"
+#include "ipwd.h"
+#include "time-util.h"
+#include "userdb-template.h"
+
+#define USER_CACHE_KEY "%u"
+#define PASSWD_SLOW_WARN_MSECS (10*1000)
+#define PASSWD_SLOW_MASTER_WARN_MSECS 50
+#define PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL 100
+#define PASSDB_SLOW_MASTER_WARN_MIN_PERCENTAGE 5
+
+struct passwd_userdb_module {
+ struct userdb_module module;
+ struct userdb_template *tmpl;
+
+ unsigned int fast_count, slow_count;
+ bool slow_warned:1;
+};
+
+struct passwd_userdb_iterate_context {
+ struct userdb_iterate_context ctx;
+ struct passwd_userdb_iterate_context *next_waiting;
+};
+
+static struct passwd_userdb_iterate_context *cur_userdb_iter = NULL;
+static struct timeout *cur_userdb_iter_to = NULL;
+
+static void
+passwd_check_warnings(struct auth_request *auth_request,
+ struct passwd_userdb_module *module,
+ const struct timeval *start_tv)
+{
+ struct timeval end_tv;
+ unsigned int msecs, percentage;
+
+ i_gettimeofday(&end_tv);
+
+ msecs = timeval_diff_msecs(&end_tv, start_tv);
+ if (msecs >= PASSWD_SLOW_WARN_MSECS) {
+ e_warning(authdb_event(auth_request), "Lookup for %s took %u secs",
+ auth_request->fields.user, msecs/1000);
+ return;
+ }
+ if (worker || module->slow_warned)
+ return;
+
+ if (msecs < PASSWD_SLOW_MASTER_WARN_MSECS) {
+ module->fast_count++;
+ return;
+ }
+ module->slow_count++;
+ if (module->fast_count + module->slow_count <
+ PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL)
+ return;
+
+ percentage = module->slow_count * 100 /
+ (module->slow_count + module->fast_count);
+ if (percentage < PASSDB_SLOW_MASTER_WARN_MIN_PERCENTAGE) {
+ /* start from beginning */
+ module->slow_count = module->fast_count = 0;
+ } else {
+ e_warning(authdb_event(auth_request),
+ "%u%% of last %u lookups took over "
+ "%u milliseconds, "
+ "you may want to set blocking=yes for userdb",
+ percentage, PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL,
+ PASSWD_SLOW_MASTER_WARN_MSECS);
+ module->slow_warned = TRUE;
+ }
+}
+
+static void passwd_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct passwd_userdb_module *module =
+ (struct passwd_userdb_module *)_module;
+ struct passwd pw;
+ struct timeval start_tv;
+ const char *error;
+ int ret;
+
+ e_debug(authdb_event(auth_request), "lookup");
+
+ i_gettimeofday(&start_tv);
+ ret = i_getpwnam(auth_request->fields.user, &pw);
+ if (start_tv.tv_sec != 0)
+ passwd_check_warnings(auth_request, module, &start_tv);
+
+ switch (ret) {
+ case -1:
+ e_error(authdb_event(auth_request),
+ "getpwnam() failed: %m");
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ case 0:
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ callback(USERDB_RESULT_USER_UNKNOWN, auth_request);
+ return;
+ }
+
+ auth_request_set_field(auth_request, "user", pw.pw_name, NULL);
+
+ auth_request_set_userdb_field(auth_request, "system_groups_user",
+ pw.pw_name);
+ auth_request_set_userdb_field(auth_request, "uid", dec2str(pw.pw_uid));
+ auth_request_set_userdb_field(auth_request, "gid", dec2str(pw.pw_gid));
+ auth_request_set_userdb_field(auth_request, "home", pw.pw_dir);
+
+ if (userdb_template_export(module->tmpl, auth_request, &error) < 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand template: %s", error);
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ }
+
+ callback(USERDB_RESULT_OK, auth_request);
+}
+
+static struct userdb_iterate_context *
+passwd_iterate_init(struct auth_request *auth_request,
+ userdb_iter_callback_t *callback, void *context)
+{
+ struct passwd_userdb_iterate_context *ctx;
+
+ ctx = i_new(struct passwd_userdb_iterate_context, 1);
+ ctx->ctx.auth_request = auth_request;
+ ctx->ctx.callback = callback;
+ ctx->ctx.context = context;
+ setpwent();
+
+ if (cur_userdb_iter == NULL)
+ cur_userdb_iter = ctx;
+ return &ctx->ctx;
+}
+
+static bool
+passwd_iterate_want_pw(struct passwd *pw, const struct auth_settings *set)
+{
+ /* skip entries not in valid UID range.
+ they're users for daemons and such. */
+ if (pw->pw_uid < (uid_t)set->first_valid_uid)
+ return FALSE;
+ if (pw->pw_uid > (uid_t)set->last_valid_uid && set->last_valid_uid != 0)
+ return FALSE;
+ if (pw->pw_gid < (gid_t)set->first_valid_gid)
+ return FALSE;
+ if (pw->pw_gid > (gid_t)set->last_valid_gid && set->last_valid_gid != 0)
+ return FALSE;
+ return TRUE;
+}
+
+static void passwd_iterate_next(struct userdb_iterate_context *_ctx)
+{
+ struct passwd_userdb_iterate_context *ctx =
+ (struct passwd_userdb_iterate_context *)_ctx;
+ const struct auth_settings *set = _ctx->auth_request->set;
+ struct passwd *pw;
+
+ if (cur_userdb_iter != NULL && cur_userdb_iter != ctx) {
+ /* we can't support concurrent userdb iteration.
+ wait until the previous one is done */
+ ctx->next_waiting = cur_userdb_iter->next_waiting;
+ cur_userdb_iter->next_waiting = ctx;
+ return;
+ }
+
+ /* reset errno since it might have been set when we got here */
+ errno = 0;
+ while ((pw = getpwent()) != NULL) {
+ if (passwd_iterate_want_pw(pw, set)) {
+ _ctx->callback(pw->pw_name, _ctx->context);
+ return;
+ }
+ /* getpwent might set errno to something even if it
+ returns non-NULL. */
+ errno = 0;
+ }
+ if (errno != 0) {
+ e_error(authdb_event(_ctx->auth_request),
+ "getpwent() failed: %m");
+ _ctx->failed = TRUE;
+ }
+ _ctx->callback(NULL, _ctx->context);
+}
+
+static void ATTR_NULL(1)
+passwd_iterate_next_timeout(void *context ATTR_UNUSED)
+{
+ timeout_remove(&cur_userdb_iter_to);
+ passwd_iterate_next(&cur_userdb_iter->ctx);
+}
+
+static int passwd_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+ struct passwd_userdb_iterate_context *ctx =
+ (struct passwd_userdb_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ cur_userdb_iter = ctx->next_waiting;
+ i_free(ctx);
+
+ if (cur_userdb_iter != NULL) {
+ cur_userdb_iter_to = timeout_add(0, passwd_iterate_next_timeout,
+ NULL);
+ }
+ endpwent();
+ return ret;
+}
+
+static struct userdb_module *
+passwd_passwd_preinit(pool_t pool, const char *args)
+{
+ struct passwd_userdb_module *module;
+ const char *value;
+
+ module = p_new(pool, struct passwd_userdb_module, 1);
+ module->module.default_cache_key = USER_CACHE_KEY;
+ module->tmpl = userdb_template_build(pool, "passwd", args);
+ module->module.blocking = TRUE;
+
+ if (userdb_template_remove(module->tmpl, "blocking", &value))
+ module->module.blocking = strcasecmp(value, "yes") == 0;
+ /* FIXME: backwards compatibility */
+ if (!userdb_template_is_empty(module->tmpl))
+ i_warning("userdb passwd: Move templates args to override_fields setting");
+ return &module->module;
+}
+
+struct userdb_module_interface userdb_passwd = {
+ "passwd",
+
+ passwd_passwd_preinit,
+ NULL,
+ NULL,
+
+ passwd_lookup,
+
+ passwd_iterate_init,
+ passwd_iterate_next,
+ passwd_iterate_deinit
+};
+#else
+struct userdb_module_interface userdb_passwd = {
+ .name = "passwd"
+};
+#endif
diff --git a/src/auth/userdb-prefetch.c b/src/auth/userdb-prefetch.c
new file mode 100644
index 0000000..a5730b9
--- /dev/null
+++ b/src/auth/userdb-prefetch.c
@@ -0,0 +1,59 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#ifdef USERDB_PREFETCH
+
+#include "str.h"
+#include "var-expand.h"
+
+
+static void prefetch_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ /* auth_request_set_field() should have already placed the userdb_*
+ values to userdb_reply. */
+ if (!auth_request->userdb_prefetch_set) {
+ if (auth_request_get_auth(auth_request)->userdbs->next == NULL) {
+ /* no other userdbs */
+ if (auth_request->userdb_lookup) {
+ e_error(authdb_event(auth_request),
+ "userdb lookup not possible with only userdb prefetch");
+ } else {
+ e_error(authdb_event(auth_request),
+ "passdb didn't return userdb entries");
+ }
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ }
+ /* more userdbs, they may know the user */
+ e_debug(authdb_event(auth_request),
+ "passdb didn't return userdb entries, "
+ "trying the next userdb");
+ callback(USERDB_RESULT_USER_UNKNOWN, auth_request);
+ return;
+ }
+
+ e_debug(authdb_event(auth_request), "success");
+ callback(USERDB_RESULT_OK, auth_request);
+}
+
+struct userdb_module_interface userdb_prefetch = {
+ "prefetch",
+
+ NULL,
+ NULL,
+ NULL,
+
+ prefetch_lookup,
+
+ NULL,
+ NULL,
+ NULL
+};
+#else
+struct userdb_module_interface userdb_prefetch = {
+ .name = "prefetch"
+};
+#endif
diff --git a/src/auth/userdb-sql.c b/src/auth/userdb-sql.c
new file mode 100644
index 0000000..3a87640
--- /dev/null
+++ b/src/auth/userdb-sql.c
@@ -0,0 +1,319 @@
+/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#ifdef USERDB_SQL
+
+#include "auth-cache.h"
+#include "db-sql.h"
+
+#include <string.h>
+
+struct sql_userdb_module {
+ struct userdb_module module;
+
+ struct db_sql_connection *conn;
+};
+
+struct userdb_sql_request {
+ struct auth_request *auth_request;
+ userdb_callback_t *callback;
+};
+
+struct sql_userdb_iterate_context {
+ struct userdb_iterate_context ctx;
+ struct sql_result *result;
+ bool freed:1;
+ bool call_iter:1;
+};
+
+static void userdb_sql_iterate_next(struct userdb_iterate_context *_ctx);
+static int userdb_sql_iterate_deinit(struct userdb_iterate_context *_ctx);
+
+static void
+sql_query_get_result(struct sql_result *result,
+ struct auth_request *auth_request)
+{
+ const char *name, *value;
+ unsigned int i, fields_count;
+
+ fields_count = sql_result_get_fields_count(result);
+ for (i = 0; i < fields_count; i++) {
+ name = sql_result_get_field_name(result, i);
+ value = sql_result_get_field_value(result, i);
+
+ if (*name != '\0' && value != NULL) {
+ auth_request_set_userdb_field(auth_request,
+ name, value);
+ }
+ }
+}
+
+static void sql_query_callback(struct sql_result *sql_result,
+ struct userdb_sql_request *sql_request)
+{
+ struct auth_request *auth_request = sql_request->auth_request;
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct sql_userdb_module *module =
+ (struct sql_userdb_module *)_module;
+ enum userdb_result result = USERDB_RESULT_INTERNAL_FAILURE;
+ int ret;
+
+ ret = sql_result_next_row(sql_result);
+ if (ret >= 0)
+ db_sql_success(module->conn);
+ if (ret < 0) {
+ if (!module->conn->default_user_query) {
+ e_error(authdb_event(auth_request),
+ "User query failed: %s",
+ sql_result_get_error(sql_result));
+ } else {
+ e_error(authdb_event(auth_request),
+ "User query failed: %s "
+ "(using built-in default user_query: %s)",
+ sql_result_get_error(sql_result),
+ module->conn->set.user_query);
+ }
+ } else if (ret == 0) {
+ result = USERDB_RESULT_USER_UNKNOWN;
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ } else {
+ sql_query_get_result(sql_result, auth_request);
+ result = USERDB_RESULT_OK;
+ }
+
+ sql_request->callback(result, auth_request);
+ auth_request_unref(&auth_request);
+ i_free(sql_request);
+}
+
+static const char *
+userdb_sql_escape(const char *str, const struct auth_request *auth_request)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct sql_userdb_module *module =
+ (struct sql_userdb_module *)_module;
+
+ return sql_escape_string(module->conn->db, str);
+}
+
+static void userdb_sql_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct sql_userdb_module *module =
+ (struct sql_userdb_module *)_module;
+ struct userdb_sql_request *sql_request;
+ const char *query, *error;
+
+ if (t_auth_request_var_expand(module->conn->set.user_query,
+ auth_request, userdb_sql_escape,
+ &query, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand user_query=%s: %s",
+ module->conn->set.user_query, error);
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ }
+
+ auth_request_ref(auth_request);
+ sql_request = i_new(struct userdb_sql_request, 1);
+ sql_request->callback = callback;
+ sql_request->auth_request = auth_request;
+
+ e_debug(authdb_event(auth_request), "%s", query);
+
+ sql_query(module->conn->db, query,
+ sql_query_callback, sql_request);
+}
+
+static void sql_iter_query_callback(struct sql_result *sql_result,
+ struct sql_userdb_iterate_context *ctx)
+{
+ ctx->result = sql_result;
+ sql_result_ref(sql_result);
+
+ if (ctx->freed)
+ (void)userdb_sql_iterate_deinit(&ctx->ctx);
+ else if (ctx->call_iter)
+ userdb_sql_iterate_next(&ctx->ctx);
+}
+
+static struct userdb_iterate_context *
+userdb_sql_iterate_init(struct auth_request *auth_request,
+ userdb_iter_callback_t *callback, void *context)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct sql_userdb_module *module =
+ (struct sql_userdb_module *)_module;
+ struct sql_userdb_iterate_context *ctx;
+ const char *query, *error;
+
+ if (t_auth_request_var_expand(module->conn->set.iterate_query,
+ auth_request, userdb_sql_escape,
+ &query, &error) <= 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand iterate_query=%s: %s",
+ module->conn->set.iterate_query, error);
+ }
+
+ ctx = i_new(struct sql_userdb_iterate_context, 1);
+ ctx->ctx.auth_request = auth_request;
+ ctx->ctx.callback = callback;
+ ctx->ctx.context = context;
+ auth_request_ref(auth_request);
+
+ sql_query(module->conn->db, query,
+ sql_iter_query_callback, ctx);
+ e_debug(authdb_event(auth_request), "%s", query);
+ return &ctx->ctx;
+}
+
+static int userdb_sql_iterate_get_user(struct sql_userdb_iterate_context *ctx,
+ const char **user_r)
+{
+ const char *domain;
+ int idx;
+
+ /* try user first */
+ idx = sql_result_find_field(ctx->result, "user");
+ if (idx == 0) {
+ *user_r = sql_result_get_field_value(ctx->result, idx);
+ return 0;
+ }
+
+ /* username [+ domain]? */
+ idx = sql_result_find_field(ctx->result, "username");
+ if (idx < 0) {
+ /* no user or username, fail */
+ return -1;
+ }
+
+ *user_r = sql_result_get_field_value(ctx->result, idx);
+ if (*user_r == NULL)
+ return 0;
+
+ domain = sql_result_find_field_value(ctx->result, "domain");
+ if (domain != NULL)
+ *user_r = t_strconcat(*user_r, "@", domain, NULL);
+ return 0;
+}
+
+static void userdb_sql_iterate_next(struct userdb_iterate_context *_ctx)
+{
+ struct sql_userdb_iterate_context *ctx =
+ (struct sql_userdb_iterate_context *)_ctx;
+ struct userdb_module *_module = _ctx->auth_request->userdb->userdb;
+ struct sql_userdb_module *module = (struct sql_userdb_module *)_module;
+ const char *user;
+ int ret;
+
+ if (ctx->result == NULL) {
+ /* query not finished yet */
+ ctx->call_iter = TRUE;
+ return;
+ }
+
+ ret = sql_result_next_row(ctx->result);
+ if (ret >= 0)
+ db_sql_success(module->conn);
+ if (ret > 0) {
+ if (userdb_sql_iterate_get_user(ctx, &user) < 0)
+ e_error(authdb_event(_ctx->auth_request),
+ "sql: Iterate query didn't return 'user' field");
+ else if (user == NULL)
+ e_error(authdb_event(_ctx->auth_request),
+ "sql: Iterate query returned NULL user");
+ else {
+ _ctx->callback(user, _ctx->context);
+ return;
+ }
+ _ctx->failed = TRUE;
+ } else if (ret < 0) {
+ if (!module->conn->default_iterate_query) {
+ e_error(authdb_event(_ctx->auth_request),
+ "sql: Iterate query failed: %s",
+ sql_result_get_error(ctx->result));
+ } else {
+ e_error(authdb_event(_ctx->auth_request),
+ "sql: Iterate query failed: %s "
+ "(using built-in default iterate_query: %s)",
+ sql_result_get_error(ctx->result),
+ module->conn->set.iterate_query);
+ }
+ _ctx->failed = TRUE;
+ }
+ _ctx->callback(NULL, _ctx->context);
+}
+
+static int userdb_sql_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+ struct sql_userdb_iterate_context *ctx =
+ (struct sql_userdb_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ auth_request_unref(&_ctx->auth_request);
+ if (ctx->result == NULL) {
+ /* sql query hasn't finished yet */
+ ctx->freed = TRUE;
+ } else {
+ if (ctx->result != NULL)
+ sql_result_unref(ctx->result);
+ i_free(ctx);
+ }
+ return ret;
+}
+
+static struct userdb_module *
+userdb_sql_preinit(pool_t pool, const char *args)
+{
+ struct sql_userdb_module *module;
+
+ module = p_new(pool, struct sql_userdb_module, 1);
+ module->conn = db_sql_init(args, TRUE);
+
+ module->module.default_cache_key =
+ auth_cache_parse_key(pool, module->conn->set.user_query);
+ return &module->module;
+}
+
+static void userdb_sql_init(struct userdb_module *_module)
+{
+ struct sql_userdb_module *module =
+ (struct sql_userdb_module *)_module;
+ enum sql_db_flags flags;
+
+ flags = sql_get_flags(module->conn->db);
+ _module->blocking = (flags & SQL_DB_FLAG_BLOCKING) != 0;
+
+ if (!_module->blocking || worker)
+ db_sql_connect(module->conn);
+}
+
+static void userdb_sql_deinit(struct userdb_module *_module)
+{
+ struct sql_userdb_module *module =
+ (struct sql_userdb_module *)_module;
+
+ db_sql_unref(&module->conn);
+}
+
+struct userdb_module_interface userdb_sql = {
+ "sql",
+
+ userdb_sql_preinit,
+ userdb_sql_init,
+ userdb_sql_deinit,
+
+ userdb_sql_lookup,
+
+ userdb_sql_iterate_init,
+ userdb_sql_iterate_next,
+ userdb_sql_iterate_deinit
+};
+#else
+struct userdb_module_interface userdb_sql = {
+ .name = "sql"
+};
+#endif
diff --git a/src/auth/userdb-static.c b/src/auth/userdb-static.c
new file mode 100644
index 0000000..bed0f58
--- /dev/null
+++ b/src/auth/userdb-static.c
@@ -0,0 +1,144 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+
+#include "array.h"
+#include "str.h"
+#include "var-expand.h"
+#include "userdb.h"
+#include "userdb-template.h"
+
+
+struct static_context {
+ userdb_callback_t *callback, *old_callback;
+ void *old_context;
+};
+
+struct static_userdb_module {
+ struct userdb_module module;
+ struct userdb_template *tmpl;
+
+ bool allow_all_users:1;
+};
+
+static void static_lookup_real(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct static_userdb_module *module =
+ (struct static_userdb_module *)_module;
+ const char *error;
+
+ if (userdb_template_export(module->tmpl, auth_request, &error) < 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand template: %s", error);
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ }
+ callback(USERDB_RESULT_OK, auth_request);
+}
+
+static void
+static_credentials_callback(enum passdb_result result,
+ const unsigned char *credentials ATTR_UNUSED,
+ size_t size ATTR_UNUSED,
+ struct auth_request *auth_request)
+{
+ struct static_context *ctx = auth_request->context;
+
+ auth_request->userdb_lookup = TRUE;
+
+ auth_request->private_callback.userdb = ctx->old_callback;
+ auth_request->context = ctx->old_context;
+ auth_request_set_state(auth_request, AUTH_REQUEST_STATE_USERDB);
+
+ switch (result) {
+ case PASSDB_RESULT_OK:
+ static_lookup_real(auth_request, ctx->callback);
+ break;
+ case PASSDB_RESULT_USER_UNKNOWN:
+ case PASSDB_RESULT_USER_DISABLED:
+ case PASSDB_RESULT_PASS_EXPIRED:
+ ctx->callback(USERDB_RESULT_USER_UNKNOWN, auth_request);
+ break;
+ case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+ e_error(authdb_event(auth_request),
+ "passdb doesn't support lookups, "
+ "can't verify user's existence");
+ /* fall through */
+ default:
+ ctx->callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ break;
+ }
+
+ i_free(ctx);
+}
+
+static void static_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct static_userdb_module *module =
+ (struct static_userdb_module *)_module;
+ struct static_context *ctx;
+
+ if (!auth_request->fields.successful && !module->allow_all_users) {
+ /* this is a userdb-only lookup. we need to know if this
+ users exists or not. use a passdb lookup to do that.
+ if the passdb doesn't support returning credentials, this
+ will of course fail.. */
+ ctx = i_new(struct static_context, 1);
+ ctx->old_callback = auth_request->private_callback.userdb;
+ ctx->old_context = auth_request->context;
+ ctx->callback = callback;
+
+ i_assert(auth_request->state == AUTH_REQUEST_STATE_USERDB);
+ auth_request_set_state(auth_request,
+ AUTH_REQUEST_STATE_MECH_CONTINUE);
+
+ auth_request->context = ctx;
+ if (auth_request->passdb != NULL) {
+ /* kludge: temporarily work as if we weren't doing
+ a userdb lookup. this is to get auth cache to use
+ passdb caching instead of userdb caching. */
+ auth_request->userdb_lookup = FALSE;
+ auth_request_lookup_credentials(auth_request, "",
+ static_credentials_callback);
+ } else {
+ static_credentials_callback(
+ PASSDB_RESULT_SCHEME_NOT_AVAILABLE,
+ uchar_empty_ptr, 0, auth_request);
+ }
+ } else {
+ static_lookup_real(auth_request, callback);
+ }
+}
+
+static struct userdb_module *
+static_preinit(pool_t pool, const char *args)
+{
+ struct static_userdb_module *module;
+ const char *value;
+
+ module = p_new(pool, struct static_userdb_module, 1);
+ module->tmpl = userdb_template_build(pool, "static", args);
+
+ if (userdb_template_remove(module->tmpl, "allow_all_users", &value)) {
+ module->allow_all_users = value == NULL ||
+ strcasecmp(value, "yes") == 0;
+ }
+ return &module->module;
+}
+
+struct userdb_module_interface userdb_static = {
+ "static",
+
+ static_preinit,
+ NULL,
+ NULL,
+
+ static_lookup,
+
+ NULL,
+ NULL,
+ NULL
+};
diff --git a/src/auth/userdb-template.c b/src/auth/userdb-template.c
new file mode 100644
index 0000000..a1e3011
--- /dev/null
+++ b/src/auth/userdb-template.c
@@ -0,0 +1,124 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "str.h"
+#include "userdb.h"
+#include "userdb-template.h"
+
+struct userdb_template {
+ ARRAY(const char *) args;
+};
+
+struct userdb_template *
+userdb_template_build(pool_t pool, const char *userdb_name, const char *args)
+{
+ struct userdb_template *tmpl;
+ const char *const *tmp, *key, *value, *nonull_value;
+ uid_t uid;
+ gid_t gid;
+
+ tmpl = p_new(pool, struct userdb_template, 1);
+
+ tmp = t_strsplit_spaces(args, " ");
+ p_array_init(&tmpl->args, pool, str_array_length(tmp));
+
+ for (; *tmp != NULL; tmp++) {
+ value = strchr(*tmp, '=');
+ if (value == NULL)
+ key = *tmp;
+ else
+ key = t_strdup_until(*tmp, value++);
+
+
+ if (*key == '\0')
+ i_fatal("Invalid userdb template %s - key must not be empty",
+ args);
+
+ nonull_value = value == NULL ? "" : value;
+ if (strcasecmp(key, "uid") == 0) {
+ uid = userdb_parse_uid(NULL, nonull_value);
+ if (uid == (uid_t)-1) {
+ i_fatal("%s userdb: Invalid uid: %s",
+ userdb_name, nonull_value);
+ }
+ value = dec2str(uid);
+ } else if (strcasecmp(key, "gid") == 0) {
+ gid = userdb_parse_gid(NULL, nonull_value);
+ if (gid == (gid_t)-1) {
+ i_fatal("%s userdb: Invalid gid: %s",
+ userdb_name, nonull_value);
+ }
+ value = dec2str(gid);
+ } else if (*key == '\0') {
+ i_fatal("%s userdb: Empty key (=%s)",
+ userdb_name, nonull_value);
+ }
+ key = p_strdup(pool, key);
+ value = p_strdup(pool, value);
+
+ array_push_back(&tmpl->args, &key);
+ array_push_back(&tmpl->args, &value);
+ }
+ return tmpl;
+}
+
+int userdb_template_export(struct userdb_template *tmpl,
+ struct auth_request *auth_request,
+ const char **error_r)
+{
+ const struct var_expand_table *table;
+ string_t *str;
+ const char *const *args, *value;
+ unsigned int i, count;
+
+ if (userdb_template_is_empty(tmpl))
+ return 0;
+
+ str = t_str_new(256);
+ table = auth_request_get_var_expand_table(auth_request, NULL);
+
+ args = array_get(&tmpl->args, &count);
+ i_assert((count % 2) == 0);
+ for (i = 0; i < count; i += 2) {
+ if (args[i+1] == NULL)
+ value = "";
+ else {
+ str_truncate(str, 0);
+ if (auth_request_var_expand_with_table(str, args[i+1],
+ auth_request, table, NULL, error_r) <= 0)
+ return -1;
+ value = str_c(str);
+ }
+ auth_request_set_userdb_field(auth_request, args[i], value);
+ }
+ return 0;
+}
+
+bool userdb_template_remove(struct userdb_template *tmpl,
+ const char *key, const char **value_r)
+{
+ const char *const *args;
+ unsigned int i, count;
+
+ args = array_get(&tmpl->args, &count);
+ i_assert((count % 2) == 0);
+ for (i = 0; i < count; i += 2) {
+ if (strcmp(args[i], key) == 0) {
+ *value_r = args[i+1];
+ array_delete(&tmpl->args, i, 2);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+bool userdb_template_is_empty(struct userdb_template *tmpl)
+{
+ return array_count(&tmpl->args) == 0;
+}
+
+const char *const *userdb_template_get_args(struct userdb_template *tmpl, unsigned int *count_r)
+{
+ return array_get(&tmpl->args, count_r);
+}
diff --git a/src/auth/userdb-template.h b/src/auth/userdb-template.h
new file mode 100644
index 0000000..a146a87
--- /dev/null
+++ b/src/auth/userdb-template.h
@@ -0,0 +1,15 @@
+#ifndef USERDB_TEMPLATE_H
+#define USERDB_TEMPLATE_H
+
+struct userdb_template *
+userdb_template_build(pool_t pool, const char *userdb_name, const char *args);
+int userdb_template_export(struct userdb_template *tmpl,
+ struct auth_request *auth_request,
+ const char **error_r);
+bool userdb_template_remove(struct userdb_template *tmpl,
+ const char *key, const char **value_r);
+bool userdb_template_is_empty(struct userdb_template *tmpl);
+const char *const *userdb_template_get_args(struct userdb_template *tmpl,
+ unsigned int *count_r);
+
+#endif
diff --git a/src/auth/userdb.c b/src/auth/userdb.c
new file mode 100644
index 0000000..21751f9
--- /dev/null
+++ b/src/auth/userdb.c
@@ -0,0 +1,256 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "array.h"
+#include "ipwd.h"
+#include "auth-worker-server.h"
+#include "userdb.h"
+
+static ARRAY(struct userdb_module_interface *) userdb_interfaces;
+static ARRAY(struct userdb_module *) userdb_modules;
+
+static const struct userdb_module_interface userdb_iface_deinit = {
+ .name = "deinit"
+};
+
+static struct userdb_module_interface *userdb_interface_find(const char *name)
+{
+ struct userdb_module_interface *iface;
+
+ array_foreach_elem(&userdb_interfaces, iface) {
+ if (strcmp(iface->name, name) == 0)
+ return iface;
+ }
+ return NULL;
+}
+
+void userdb_register_module(struct userdb_module_interface *iface)
+{
+ struct userdb_module_interface *old_iface;
+
+ old_iface = userdb_interface_find(iface->name);
+ if (old_iface != NULL && old_iface->lookup == NULL) {
+ /* replacing a "support not compiled in" userdb */
+ userdb_unregister_module(old_iface);
+ } else if (old_iface != NULL) {
+ i_panic("userdb_register_module(%s): Already registered",
+ iface->name);
+ }
+ array_push_back(&userdb_interfaces, &iface);
+}
+
+void userdb_unregister_module(struct userdb_module_interface *iface)
+{
+ struct userdb_module_interface *const *ifaces;
+ unsigned int idx;
+
+ array_foreach(&userdb_interfaces, ifaces) {
+ if (*ifaces == iface) {
+ idx = array_foreach_idx(&userdb_interfaces, ifaces);
+ array_delete(&userdb_interfaces, idx, 1);
+ return;
+ }
+ }
+ i_panic("userdb_unregister_module(%s): Not registered", iface->name);
+}
+
+uid_t userdb_parse_uid(struct auth_request *request, const char *str)
+{
+ struct passwd pw;
+ uid_t uid;
+
+ if (str == NULL)
+ return (uid_t)-1;
+
+ if (str_to_uid(str, &uid) == 0)
+ return uid;
+
+ switch (i_getpwnam(str, &pw)) {
+ case -1:
+ e_error(request == NULL ? auth_event : authdb_event(request),
+ "getpwnam() failed: %m");
+ return (uid_t)-1;
+ case 0:
+ e_error(request == NULL ? auth_event : authdb_event(request),
+ "Invalid UID value '%s'", str);
+ return (uid_t)-1;
+ default:
+ return pw.pw_uid;
+ }
+}
+
+gid_t userdb_parse_gid(struct auth_request *request, const char *str)
+{
+ struct group gr;
+ gid_t gid;
+
+ if (str == NULL)
+ return (gid_t)-1;
+
+ if (str_to_gid(str, &gid) == 0)
+ return gid;
+
+ switch (i_getgrnam(str, &gr)) {
+ case -1:
+ e_error(request == NULL ? auth_event : authdb_event(request),
+ "getgrnam() failed: %m");
+ return (gid_t)-1;
+ case 0:
+ e_error(request == NULL ? auth_event : authdb_event(request),
+ "Invalid GID value '%s'", str);
+ return (gid_t)-1;
+ default:
+ return gr.gr_gid;
+ }
+}
+
+static struct userdb_module *
+userdb_find(const char *driver, const char *args, unsigned int *idx_r)
+{
+ struct userdb_module *const *userdbs;
+ unsigned int i, count;
+
+ userdbs = array_get(&userdb_modules, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(userdbs[i]->iface->name, driver) == 0 &&
+ strcmp(userdbs[i]->args, args) == 0) {
+ *idx_r = i;
+ return userdbs[i];
+ }
+ }
+ return NULL;
+}
+
+struct userdb_module *
+userdb_preinit(pool_t pool, const struct auth_userdb_settings *set)
+{
+ static unsigned int auth_userdb_id = 0;
+ struct userdb_module_interface *iface;
+ struct userdb_module *userdb;
+ unsigned int idx;
+
+ iface = userdb_interface_find(set->driver);
+ if (iface == NULL || iface->lookup == NULL) {
+ /* maybe it's a plugin. try to load it. */
+ auth_module_load(t_strconcat("authdb_", set->driver, NULL));
+ iface = userdb_interface_find(set->driver);
+ }
+ if (iface == NULL)
+ i_fatal("Unknown userdb driver '%s'", set->driver);
+ if (iface->lookup == NULL) {
+ i_fatal("Support not compiled in for userdb driver '%s'",
+ set->driver);
+ }
+ if (iface->preinit == NULL && iface->init == NULL &&
+ *set->args != '\0') {
+ i_fatal("userdb %s: No args are supported: %s",
+ set->driver, set->args);
+ }
+
+ userdb = userdb_find(set->driver, set->args, &idx);
+ if (userdb != NULL)
+ return userdb;
+
+ if (iface->preinit == NULL)
+ userdb = p_new(pool, struct userdb_module, 1);
+ else
+ userdb = iface->preinit(pool, set->args);
+ userdb->id = ++auth_userdb_id;
+ userdb->iface = iface;
+ userdb->args = p_strdup(pool, set->args);
+
+ array_push_back(&userdb_modules, &userdb);
+ return userdb;
+}
+
+void userdb_init(struct userdb_module *userdb)
+{
+ if (userdb->iface->init != NULL && userdb->init_refcount == 0)
+ userdb->iface->init(userdb);
+ userdb->init_refcount++;
+}
+
+void userdb_deinit(struct userdb_module *userdb)
+{
+ unsigned int idx;
+
+ i_assert(userdb->init_refcount > 0);
+
+ if (--userdb->init_refcount > 0)
+ return;
+
+ if (userdb_find(userdb->iface->name, userdb->args, &idx) == NULL)
+ i_unreached();
+ array_delete(&userdb_modules, idx, 1);
+
+ if (userdb->iface->deinit != NULL)
+ userdb->iface->deinit(userdb);
+
+ /* make sure userdb isn't accessed again */
+ userdb->iface = &userdb_iface_deinit;
+}
+
+void userdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN])
+{
+ struct md5_context ctx;
+ struct userdb_module *const *userdbs;
+ unsigned int i, count;
+
+ md5_init(&ctx);
+ userdbs = array_get(&userdb_modules, &count);
+ for (i = 0; i < count; i++) {
+ md5_update(&ctx, &userdbs[i]->id, sizeof(userdbs[i]->id));
+ md5_update(&ctx, userdbs[i]->iface->name,
+ strlen(userdbs[i]->iface->name));
+ md5_update(&ctx, userdbs[i]->args, strlen(userdbs[i]->args));
+ }
+ md5_final(&ctx, md5);
+}
+
+const char *userdb_result_to_string(enum userdb_result result)
+{
+ switch (result) {
+ case USERDB_RESULT_INTERNAL_FAILURE:
+ return "internal_failure";
+ case USERDB_RESULT_USER_UNKNOWN:
+ return "user_unknown";
+ case USERDB_RESULT_OK:
+ return "ok";
+ }
+ i_unreached();
+}
+
+extern struct userdb_module_interface userdb_prefetch;
+extern struct userdb_module_interface userdb_static;
+extern struct userdb_module_interface userdb_passwd;
+extern struct userdb_module_interface userdb_passwd_file;
+extern struct userdb_module_interface userdb_ldap;
+extern struct userdb_module_interface userdb_sql;
+extern struct userdb_module_interface userdb_checkpassword;
+extern struct userdb_module_interface userdb_dict;
+#ifdef HAVE_LUA
+extern struct userdb_module_interface userdb_lua;
+#endif
+
+void userdbs_init(void)
+{
+ i_array_init(&userdb_interfaces, 16);
+ i_array_init(&userdb_modules, 16);
+ userdb_register_module(&userdb_passwd);
+ userdb_register_module(&userdb_passwd_file);
+ userdb_register_module(&userdb_prefetch);
+ userdb_register_module(&userdb_static);
+ userdb_register_module(&userdb_ldap);
+ userdb_register_module(&userdb_sql);
+ userdb_register_module(&userdb_checkpassword);
+ userdb_register_module(&userdb_dict);
+#ifdef HAVE_LUA
+ userdb_register_module(&userdb_lua);
+#endif
+}
+
+void userdbs_deinit(void)
+{
+ array_free(&userdb_modules);
+ array_free(&userdb_interfaces);
+}
diff --git a/src/auth/userdb.h b/src/auth/userdb.h
new file mode 100644
index 0000000..be64144
--- /dev/null
+++ b/src/auth/userdb.h
@@ -0,0 +1,92 @@
+#ifndef USERDB_H
+#define USERDB_H
+
+#include "md5.h"
+#include "auth-fields.h"
+
+struct auth;
+struct auth_request;
+struct auth_userdb_settings;
+
+enum userdb_result {
+ USERDB_RESULT_INTERNAL_FAILURE = -1,
+ USERDB_RESULT_USER_UNKNOWN = -2,
+
+ USERDB_RESULT_OK = 1
+};
+
+typedef void userdb_callback_t(enum userdb_result result,
+ struct auth_request *request);
+/* user=NULL when there are no more users */
+typedef void userdb_iter_callback_t(const char *user, void *context);
+
+struct userdb_module {
+ const char *args;
+ /* The default caching key for this module, or NULL if caching isn't
+ wanted. This is updated by settings in auth_userdb. */
+ const char *default_cache_key;
+
+ /* If blocking is set to TRUE, use child processes to access
+ this userdb. */
+ bool blocking;
+ /* id is used by blocking userdb to identify the userdb */
+ unsigned int id;
+
+ /* number of time init() has been called */
+ int init_refcount;
+
+ /* WARNING: avoid adding anything here that isn't based on args.
+ if you do, you need to change userdb.c:userdb_find() also to avoid
+ accidentally merging wrong userdbs. */
+
+ const struct userdb_module_interface *iface;
+};
+
+struct userdb_iterate_context {
+ struct auth_request *auth_request;
+ userdb_iter_callback_t *callback;
+ void *context;
+ bool failed;
+};
+
+struct userdb_module_interface {
+ const char *name;
+
+ struct userdb_module *(*preinit)(pool_t pool, const char *args);
+ void (*init)(struct userdb_module *module);
+ void (*deinit)(struct userdb_module *module);
+
+ void (*lookup)(struct auth_request *auth_request,
+ userdb_callback_t *callback);
+
+ struct userdb_iterate_context *
+ (*iterate_init)(struct auth_request *auth_request,
+ userdb_iter_callback_t *callback,
+ void *context);
+ void (*iterate_next)(struct userdb_iterate_context *ctx);
+ int (*iterate_deinit)(struct userdb_iterate_context *ctx);
+};
+
+const char *userdb_result_to_string(enum userdb_result result);
+
+uid_t userdb_parse_uid(struct auth_request *request, const char *str)
+ ATTR_NULL(1);
+gid_t userdb_parse_gid(struct auth_request *request, const char *str)
+ ATTR_NULL(1);
+
+struct userdb_module *
+userdb_preinit(pool_t pool, const struct auth_userdb_settings *set);
+void userdb_init(struct userdb_module *userdb);
+void userdb_deinit(struct userdb_module *userdb);
+
+void userdb_register_module(struct userdb_module_interface *iface);
+void userdb_unregister_module(struct userdb_module_interface *iface);
+
+void userdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN]);
+
+void userdbs_init(void);
+void userdbs_deinit(void);
+
+#include "auth-request.h"
+
+#endif