summaryrefslogtreecommitdiffstats
path: root/src/lib-dict-backend
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-dict-backend/Makefile.am116
-rw-r--r--src/lib-dict-backend/Makefile.in1016
-rw-r--r--src/lib-dict-backend/dict-cdb.c266
-rw-r--r--src/lib-dict-backend/dict-ldap-settings.c313
-rw-r--r--src/lib-dict-backend/dict-ldap-settings.h36
-rw-r--r--src/lib-dict-backend/dict-ldap.c500
-rw-r--r--src/lib-dict-backend/dict-sql-private.h12
-rw-r--r--src/lib-dict-backend/dict-sql-settings.c345
-rw-r--r--src/lib-dict-backend/dict-sql-settings.h47
-rw-r--r--src/lib-dict-backend/dict-sql.c1564
-rw-r--r--src/lib-dict-backend/dict-sql.h7
-rw-r--r--src/lib-dict-backend/dict.conf49
-rw-r--r--src/lib-dict-backend/test-dict-sql.c314
13 files changed, 4585 insertions, 0 deletions
diff --git a/src/lib-dict-backend/Makefile.am b/src/lib-dict-backend/Makefile.am
new file mode 100644
index 0000000..80c5050
--- /dev/null
+++ b/src/lib-dict-backend/Makefile.am
@@ -0,0 +1,116 @@
+noinst_LTLIBRARIES = libdict_backend.la
+
+module_dictdir = $(moduledir)/dict
+dict_drivers = @dict_drivers@
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-ldap \
+ -I$(top_srcdir)/src/lib-sql \
+ -I$(top_srcdir)/src/lib-settings \
+ $(SQL_CFLAGS)
+
+NOPLUGIN_LDFLAGS =
+
+ldap_sources = \
+ dict-ldap.c \
+ dict-ldap-settings.c
+
+libdict_backend_la_SOURCES = \
+ dict-cdb.c \
+ dict-sql.c \
+ dict-sql-settings.c \
+ $(ldap_sources)
+libdict_backend_la_LIBADD =
+
+nodist_libdict_backend_la_SOURCES = \
+ dict-drivers-register.c
+
+noinst_HEADERS = \
+ dict-ldap-settings.h \
+ dict-sql.h \
+ dict-sql-private.h \
+ dict-sql-settings.h
+
+if LDAP_PLUGIN
+LIBDICT_LDAP = libdict_ldap.la
+libdict_ldap_la_DEPENDENCIES = $(LIBDOVECOT_LDAP) $(LIBDOVECOT_DEPS)
+libdict_ldap_la_LDFLAGS = -module -avoid-version
+libdict_ldap_la_LIBADD = $(LIBDOVECOT_LDAP) $(LIBDOVECOT)
+libdict_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+libdict_ldap_la_SOURCES = $(ldap_sources)
+else
+if HAVE_LDAP
+libdict_backend_la_LIBADD += $(LIBDOVECOT_LDAP)
+dict_drivers += ldap
+endif
+endif
+
+module_dict_LTLIBRARIES = \
+ $(LIBDICT_LDAP)
+
+EXTRA_DIST = dict.conf
+
+dict-drivers-register.c: Makefile $(top_builddir)/config.h
+ rm -f $@
+ echo '/* this file automatically generated by Makefile */' >$@
+ echo '#include "lib.h"' >>$@
+ echo '#include "dict.h"' >>$@
+ echo '#include "ldap-client.h"' >>$@
+ echo '#include "dict-sql.h"' >>$@
+ for i in $(dict_drivers) null; do \
+ if [ "$${i}" != "null" ]; then \
+ echo "extern struct dict dict_driver_$${i};" >>$@ ; \
+ fi; \
+ done
+ echo 'void dict_drivers_register_all(void) {' >>$@
+ echo 'dict_drivers_register_builtin();' >>$@
+ echo 'dict_sql_register();' >>$@
+ for i in $(dict_drivers) null; do \
+ if [ "$${i}" != "null" ]; then \
+ echo "dict_driver_register(&dict_driver_$${i});" >>$@ ; \
+ fi; \
+ done
+ echo '}' >>$@
+ echo 'void dict_drivers_unregister_all(void) {' >>$@
+ echo '#ifdef BUILTIN_LDAP' >>$@
+ echo 'ldap_clients_cleanup();' >>$@
+ echo '#endif' >>$@
+ echo 'dict_drivers_unregister_builtin();' >>$@
+ echo 'dict_sql_unregister();' >>$@
+ for i in $(dict_drivers) null; do \
+ if [ "$${i}" != "null" ]; then \
+ echo "dict_driver_unregister(&dict_driver_$${i});" >>$@ ; \
+ fi; \
+ done
+ echo '}' >>$@
+
+distclean-generic:
+ rm -f Makefile dict-drivers-register.c
+
+test_programs = \
+ test-dict-sql
+
+noinst_PROGRAMS = $(test_programs)
+
+test_dict_sql_CFLAGS = $(AM_CPPFLAGS) -DDICT_SRC_DIR=\"$(top_srcdir)/src/lib-dict-backend\"
+test_dict_sql_SOURCES = \
+ test-dict-sql.c
+test_dict_sql_LDADD = \
+ $(noinst_LTLIBRARIES) \
+ $(DICT_LIBS) \
+ ../lib-sql/libdriver_test.la \
+ ../lib-sql/libsql.la \
+ ../lib-dovecot/libdovecot.la
+test_dict_sql_DEPENDENCIES = \
+ $(noinst_LTLIBRARIES) \
+ ../lib-sql/libdriver_test.la \
+ ../lib-sql/libsql.la \
+ ../lib-dovecot/libdovecot.la
+
+check-local:
+ for bin in $(test_programs) $(check_PROGRAMS); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/lib-dict-backend/Makefile.in b/src/lib-dict-backend/Makefile.in
new file mode 100644
index 0000000..e5162e9
--- /dev/null
+++ b/src/lib-dict-backend/Makefile.in
@@ -0,0 +1,1016 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_LDAP_TRUE@@LDAP_PLUGIN_FALSE@am__append_1 = $(LIBDOVECOT_LDAP)
+@HAVE_LDAP_TRUE@@LDAP_PLUGIN_FALSE@am__append_2 = ldap
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/lib-dict-backend
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-dict-sql$(EXEEXT)
+PROGRAMS = $(noinst_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; }; \
+ }
+am__installdirs = "$(DESTDIR)$(module_dictdir)"
+LTLIBRARIES = $(module_dict_LTLIBRARIES) $(noinst_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@HAVE_LDAP_TRUE@@LDAP_PLUGIN_FALSE@am__DEPENDENCIES_2 = \
+@HAVE_LDAP_TRUE@@LDAP_PLUGIN_FALSE@ $(am__DEPENDENCIES_1)
+libdict_backend_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am__objects_1 = dict-ldap.lo dict-ldap-settings.lo
+am_libdict_backend_la_OBJECTS = dict-cdb.lo dict-sql.lo \
+ dict-sql-settings.lo $(am__objects_1)
+nodist_libdict_backend_la_OBJECTS = dict-drivers-register.lo
+libdict_backend_la_OBJECTS = $(am_libdict_backend_la_OBJECTS) \
+ $(nodist_libdict_backend_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__libdict_ldap_la_SOURCES_DIST = dict-ldap.c dict-ldap-settings.c
+am__objects_2 = libdict_ldap_la-dict-ldap.lo \
+ libdict_ldap_la-dict-ldap-settings.lo
+@LDAP_PLUGIN_TRUE@am_libdict_ldap_la_OBJECTS = $(am__objects_2)
+libdict_ldap_la_OBJECTS = $(am_libdict_ldap_la_OBJECTS)
+libdict_ldap_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libdict_ldap_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+@LDAP_PLUGIN_TRUE@am_libdict_ldap_la_rpath = -rpath $(module_dictdir)
+am_test_dict_sql_OBJECTS = test_dict_sql-test-dict-sql.$(OBJEXT)
+test_dict_sql_OBJECTS = $(am_test_dict_sql_OBJECTS)
+test_dict_sql_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_dict_sql_CFLAGS) \
+ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+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)/dict-cdb.Plo \
+ ./$(DEPDIR)/dict-drivers-register.Plo \
+ ./$(DEPDIR)/dict-ldap-settings.Plo ./$(DEPDIR)/dict-ldap.Plo \
+ ./$(DEPDIR)/dict-sql-settings.Plo ./$(DEPDIR)/dict-sql.Plo \
+ ./$(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo \
+ ./$(DEPDIR)/libdict_ldap_la-dict-ldap.Plo \
+ ./$(DEPDIR)/test_dict_sql-test-dict-sql.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libdict_backend_la_SOURCES) \
+ $(nodist_libdict_backend_la_SOURCES) \
+ $(libdict_ldap_la_SOURCES) $(test_dict_sql_SOURCES)
+DIST_SOURCES = $(libdict_backend_la_SOURCES) \
+ $(am__libdict_ldap_la_SOURCES_DIST) $(test_dict_sql_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS =
+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@ $(am__append_2)
+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 = libdict_backend.la
+module_dictdir = $(moduledir)/dict
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-ldap \
+ -I$(top_srcdir)/src/lib-sql \
+ -I$(top_srcdir)/src/lib-settings \
+ $(SQL_CFLAGS)
+
+ldap_sources = \
+ dict-ldap.c \
+ dict-ldap-settings.c
+
+libdict_backend_la_SOURCES = \
+ dict-cdb.c \
+ dict-sql.c \
+ dict-sql-settings.c \
+ $(ldap_sources)
+
+libdict_backend_la_LIBADD = $(am__append_1)
+nodist_libdict_backend_la_SOURCES = \
+ dict-drivers-register.c
+
+noinst_HEADERS = \
+ dict-ldap-settings.h \
+ dict-sql.h \
+ dict-sql-private.h \
+ dict-sql-settings.h
+
+@LDAP_PLUGIN_TRUE@LIBDICT_LDAP = libdict_ldap.la
+@LDAP_PLUGIN_TRUE@libdict_ldap_la_DEPENDENCIES = $(LIBDOVECOT_LDAP) $(LIBDOVECOT_DEPS)
+@LDAP_PLUGIN_TRUE@libdict_ldap_la_LDFLAGS = -module -avoid-version
+@LDAP_PLUGIN_TRUE@libdict_ldap_la_LIBADD = $(LIBDOVECOT_LDAP) $(LIBDOVECOT)
+@LDAP_PLUGIN_TRUE@libdict_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+@LDAP_PLUGIN_TRUE@libdict_ldap_la_SOURCES = $(ldap_sources)
+module_dict_LTLIBRARIES = \
+ $(LIBDICT_LDAP)
+
+EXTRA_DIST = dict.conf
+test_programs = \
+ test-dict-sql
+
+test_dict_sql_CFLAGS = $(AM_CPPFLAGS) -DDICT_SRC_DIR=\"$(top_srcdir)/src/lib-dict-backend\"
+test_dict_sql_SOURCES = \
+ test-dict-sql.c
+
+test_dict_sql_LDADD = \
+ $(noinst_LTLIBRARIES) \
+ $(DICT_LIBS) \
+ ../lib-sql/libdriver_test.la \
+ ../lib-sql/libsql.la \
+ ../lib-dovecot/libdovecot.la
+
+test_dict_sql_DEPENDENCIES = \
+ $(noinst_LTLIBRARIES) \
+ ../lib-sql/libdriver_test.la \
+ ../lib-sql/libsql.la \
+ ../lib-dovecot/libdovecot.la
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-dict-backend/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-dict-backend/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-module_dictLTLIBRARIES: $(module_dict_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_dict_LTLIBRARIES)'; test -n "$(module_dictdir)" || 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)$(module_dictdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(module_dictdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(module_dictdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(module_dictdir)"; \
+ }
+
+uninstall-module_dictLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_dict_LTLIBRARIES)'; test -n "$(module_dictdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(module_dictdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(module_dictdir)/$$f"; \
+ done
+
+clean-module_dictLTLIBRARIES:
+ -test -z "$(module_dict_LTLIBRARIES)" || rm -f $(module_dict_LTLIBRARIES)
+ @list='$(module_dict_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}; \
+ }
+
+libdict_backend.la: $(libdict_backend_la_OBJECTS) $(libdict_backend_la_DEPENDENCIES) $(EXTRA_libdict_backend_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libdict_backend_la_OBJECTS) $(libdict_backend_la_LIBADD) $(LIBS)
+
+libdict_ldap.la: $(libdict_ldap_la_OBJECTS) $(libdict_ldap_la_DEPENDENCIES) $(EXTRA_libdict_ldap_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libdict_ldap_la_LINK) $(am_libdict_ldap_la_rpath) $(libdict_ldap_la_OBJECTS) $(libdict_ldap_la_LIBADD) $(LIBS)
+
+test-dict-sql$(EXEEXT): $(test_dict_sql_OBJECTS) $(test_dict_sql_DEPENDENCIES) $(EXTRA_test_dict_sql_DEPENDENCIES)
+ @rm -f test-dict-sql$(EXEEXT)
+ $(AM_V_CCLD)$(test_dict_sql_LINK) $(test_dict_sql_OBJECTS) $(test_dict_sql_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-cdb.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-drivers-register.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-ldap-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-ldap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-sql-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-sql.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdict_ldap_la-dict-ldap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_dict_sql-test-dict-sql.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+libdict_ldap_la-dict-ldap.lo: dict-ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdict_ldap_la-dict-ldap.lo -MD -MP -MF $(DEPDIR)/libdict_ldap_la-dict-ldap.Tpo -c -o libdict_ldap_la-dict-ldap.lo `test -f 'dict-ldap.c' || echo '$(srcdir)/'`dict-ldap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdict_ldap_la-dict-ldap.Tpo $(DEPDIR)/libdict_ldap_la-dict-ldap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dict-ldap.c' object='libdict_ldap_la-dict-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) $(libdict_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdict_ldap_la-dict-ldap.lo `test -f 'dict-ldap.c' || echo '$(srcdir)/'`dict-ldap.c
+
+libdict_ldap_la-dict-ldap-settings.lo: dict-ldap-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdict_ldap_la-dict-ldap-settings.lo -MD -MP -MF $(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Tpo -c -o libdict_ldap_la-dict-ldap-settings.lo `test -f 'dict-ldap-settings.c' || echo '$(srcdir)/'`dict-ldap-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Tpo $(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dict-ldap-settings.c' object='libdict_ldap_la-dict-ldap-settings.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) $(libdict_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdict_ldap_la-dict-ldap-settings.lo `test -f 'dict-ldap-settings.c' || echo '$(srcdir)/'`dict-ldap-settings.c
+
+test_dict_sql-test-dict-sql.o: test-dict-sql.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_dict_sql_CFLAGS) $(CFLAGS) -MT test_dict_sql-test-dict-sql.o -MD -MP -MF $(DEPDIR)/test_dict_sql-test-dict-sql.Tpo -c -o test_dict_sql-test-dict-sql.o `test -f 'test-dict-sql.c' || echo '$(srcdir)/'`test-dict-sql.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_dict_sql-test-dict-sql.Tpo $(DEPDIR)/test_dict_sql-test-dict-sql.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-dict-sql.c' object='test_dict_sql-test-dict-sql.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) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_dict_sql_CFLAGS) $(CFLAGS) -c -o test_dict_sql-test-dict-sql.o `test -f 'test-dict-sql.c' || echo '$(srcdir)/'`test-dict-sql.c
+
+test_dict_sql-test-dict-sql.obj: test-dict-sql.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_dict_sql_CFLAGS) $(CFLAGS) -MT test_dict_sql-test-dict-sql.obj -MD -MP -MF $(DEPDIR)/test_dict_sql-test-dict-sql.Tpo -c -o test_dict_sql-test-dict-sql.obj `if test -f 'test-dict-sql.c'; then $(CYGPATH_W) 'test-dict-sql.c'; else $(CYGPATH_W) '$(srcdir)/test-dict-sql.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_dict_sql-test-dict-sql.Tpo $(DEPDIR)/test_dict_sql-test-dict-sql.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-dict-sql.c' object='test_dict_sql-test-dict-sql.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) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_dict_sql_CFLAGS) $(CFLAGS) -c -o test_dict_sql-test-dict-sql.obj `if test -f 'test-dict-sql.c'; then $(CYGPATH_W) 'test-dict-sql.c'; else $(CYGPATH_W) '$(srcdir)/test-dict-sql.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(module_dictdir)"; 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:
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-module_dictLTLIBRARIES \
+ clean-noinstLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/dict-cdb.Plo
+ -rm -f ./$(DEPDIR)/dict-drivers-register.Plo
+ -rm -f ./$(DEPDIR)/dict-ldap-settings.Plo
+ -rm -f ./$(DEPDIR)/dict-ldap.Plo
+ -rm -f ./$(DEPDIR)/dict-sql-settings.Plo
+ -rm -f ./$(DEPDIR)/dict-sql.Plo
+ -rm -f ./$(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo
+ -rm -f ./$(DEPDIR)/libdict_ldap_la-dict-ldap.Plo
+ -rm -f ./$(DEPDIR)/test_dict_sql-test-dict-sql.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-module_dictLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/dict-cdb.Plo
+ -rm -f ./$(DEPDIR)/dict-drivers-register.Plo
+ -rm -f ./$(DEPDIR)/dict-ldap-settings.Plo
+ -rm -f ./$(DEPDIR)/dict-ldap.Plo
+ -rm -f ./$(DEPDIR)/dict-sql-settings.Plo
+ -rm -f ./$(DEPDIR)/dict-sql.Plo
+ -rm -f ./$(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo
+ -rm -f ./$(DEPDIR)/libdict_ldap_la-dict-ldap.Plo
+ -rm -f ./$(DEPDIR)/test_dict_sql-test-dict-sql.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-module_dictLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-module_dictLTLIBRARIES clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-module_dictLTLIBRARIES install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-module_dictLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+dict-drivers-register.c: Makefile $(top_builddir)/config.h
+ rm -f $@
+ echo '/* this file automatically generated by Makefile */' >$@
+ echo '#include "lib.h"' >>$@
+ echo '#include "dict.h"' >>$@
+ echo '#include "ldap-client.h"' >>$@
+ echo '#include "dict-sql.h"' >>$@
+ for i in $(dict_drivers) null; do \
+ if [ "$${i}" != "null" ]; then \
+ echo "extern struct dict dict_driver_$${i};" >>$@ ; \
+ fi; \
+ done
+ echo 'void dict_drivers_register_all(void) {' >>$@
+ echo 'dict_drivers_register_builtin();' >>$@
+ echo 'dict_sql_register();' >>$@
+ for i in $(dict_drivers) null; do \
+ if [ "$${i}" != "null" ]; then \
+ echo "dict_driver_register(&dict_driver_$${i});" >>$@ ; \
+ fi; \
+ done
+ echo '}' >>$@
+ echo 'void dict_drivers_unregister_all(void) {' >>$@
+ echo '#ifdef BUILTIN_LDAP' >>$@
+ echo 'ldap_clients_cleanup();' >>$@
+ echo '#endif' >>$@
+ echo 'dict_drivers_unregister_builtin();' >>$@
+ echo 'dict_sql_unregister();' >>$@
+ for i in $(dict_drivers) null; do \
+ if [ "$${i}" != "null" ]; then \
+ echo "dict_driver_unregister(&dict_driver_$${i});" >>$@ ; \
+ fi; \
+ done
+ echo '}' >>$@
+
+distclean-generic:
+ rm -f Makefile dict-drivers-register.c
+
+check-local:
+ for bin in $(test_programs) $(check_PROGRAMS); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-dict-backend/dict-cdb.c b/src/lib-dict-backend/dict-cdb.c
new file mode 100644
index 0000000..c6fad30
--- /dev/null
+++ b/src/lib-dict-backend/dict-cdb.c
@@ -0,0 +1,266 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+
+#ifdef BUILD_CDB
+#include "dict-private.h"
+
+#include <string.h>
+#include <cdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define CDB_WITH_NULL 1
+#define CDB_WITHOUT_NULL 2
+
+struct cdb_dict {
+ struct dict dict;
+ struct cdb cdb;
+ char *path;
+ int fd, flag;
+};
+
+struct cdb_dict_iterate_context {
+ struct dict_iterate_context ctx;
+
+ enum dict_iterate_flags flags;
+ buffer_t *buffer;
+ const char *values[2];
+ char *path;
+ unsigned cptr;
+ char *error;
+};
+
+static void cdb_dict_deinit(struct dict *_dict);
+
+static int
+cdb_dict_init(struct dict *driver, const char *uri,
+ const struct dict_settings *set ATTR_UNUSED,
+ struct dict **dict_r, const char **error_r)
+{
+ struct cdb_dict *dict;
+
+ dict = i_new(struct cdb_dict, 1);
+ dict->dict = *driver;
+ dict->path = i_strdup(uri);
+ dict->flag = CDB_WITH_NULL | CDB_WITHOUT_NULL;
+
+ /* initialize cdb to 0 (unallocated) */
+ i_zero(&dict->cdb);
+
+ dict->fd = open(dict->path, O_RDONLY);
+ if (dict->fd == -1) {
+ *error_r = t_strdup_printf("open(%s) failed: %m", dict->path);
+ cdb_dict_deinit(&dict->dict);
+ return -1;
+ }
+
+#ifdef TINYCDB_VERSION
+ if (cdb_init(&dict->cdb, dict->fd) < 0) {
+ *error_r = t_strdup_printf("cdb_init(%s) failed: %m", dict->path);
+ cdb_dict_deinit(&dict->dict);
+ return -1;
+ }
+#else
+ cdb_init(&dict->cdb, dict->fd);
+#endif
+
+ *dict_r = &dict->dict;
+ return 0;
+}
+
+static void cdb_dict_deinit(struct dict *_dict)
+{
+ struct cdb_dict *dict = (struct cdb_dict *)_dict;
+
+ /* we can safely deinit unallocated cdb */
+ cdb_free(&dict->cdb);
+
+ i_close_fd_path(&dict->fd, dict->path);
+
+ i_free(dict->path);
+ i_free(dict);
+}
+
+static int
+cdb_dict_lookup(struct dict *_dict,
+ const struct dict_op_settings *set ATTR_UNUSED,
+ pool_t pool,
+ const char *key, const char **value_r,
+ const char **error_r)
+{
+ struct cdb_dict *dict = (struct cdb_dict *)_dict;
+ unsigned datalen;
+ int ret = 0;
+ char *data;
+
+ /* keys and values may be null terminated... */
+ if ((dict->flag & CDB_WITH_NULL) != 0) {
+ ret = cdb_find(&dict->cdb, key, (unsigned)strlen(key)+1);
+ if (ret > 0)
+ dict->flag &= ENUM_NEGATE(CDB_WITHOUT_NULL);
+ }
+
+ /* ...or not */
+ if (ret == 0 && (dict->flag & CDB_WITHOUT_NULL) != 0) {
+ ret = cdb_find(&dict->cdb, key, (unsigned)strlen(key));
+ if (ret > 0)
+ dict->flag &= ENUM_NEGATE(CDB_WITH_NULL);
+ }
+
+ if (ret <= 0) {
+ *value_r = NULL;
+ /* something bad with db */
+ if (ret < 0) {
+ *error_r = t_strdup_printf("cdb_find(%s) failed: %m", dict->path);
+ return -1;
+ }
+ /* found nothing */
+ return 0;
+ }
+
+ datalen = cdb_datalen(&dict->cdb);
+ data = p_malloc(pool, datalen + 1);
+ if (cdb_read(&dict->cdb, data, datalen, cdb_datapos(&dict->cdb)) < 0) {
+ *error_r = t_strdup_printf("cdb_read(%s) failed: %m", dict->path);
+ return -1;
+ }
+ *value_r = data;
+ return 1;
+}
+
+static struct dict_iterate_context *
+cdb_dict_iterate_init(struct dict *_dict,
+ const struct dict_op_settings *set ATTR_UNUSED,
+ const char *path, enum dict_iterate_flags flags)
+{
+ struct cdb_dict_iterate_context *ctx =
+ i_new(struct cdb_dict_iterate_context, 1);
+ struct cdb_dict *dict = (struct cdb_dict *)_dict;
+
+ ctx->ctx.dict = &dict->dict;
+ ctx->path = i_strdup(path);
+ ctx->flags = flags;
+ ctx->buffer = buffer_create_dynamic(default_pool, 256);
+
+ cdb_seqinit(&ctx->cptr, &dict->cdb);
+
+ return &ctx->ctx;
+}
+
+static bool
+cdb_dict_next(struct cdb_dict_iterate_context *ctx, const char **key_r)
+{
+ struct cdb_dict *dict = (struct cdb_dict *)ctx->ctx.dict;
+ char *data;
+ unsigned datalen;
+ int ret;
+
+ if ((ret = cdb_seqnext(&ctx->cptr, &dict->cdb)) < 1) {
+ if (ret < 0)
+ ctx->error = i_strdup_printf("cdb_seqnext(%s) failed: %m",
+ dict->path);
+ return FALSE;
+ }
+
+ buffer_set_used_size(ctx->buffer, 0);
+
+ datalen = cdb_keylen(&dict->cdb);
+ data = buffer_append_space_unsafe(ctx->buffer, datalen + 1);
+
+ if (cdb_read(&dict->cdb, data, datalen, cdb_keypos(&dict->cdb)) < 0) {
+ ctx->error = i_strdup_printf("cdb_read(%s) failed: %m",
+ dict->path);
+ return FALSE;
+ }
+
+ data[datalen] = '\0';
+ *key_r = data;
+
+ return TRUE;
+}
+
+static bool cdb_dict_iterate(struct dict_iterate_context *_ctx,
+ const char **key_r, const char *const **values_r)
+{
+ struct cdb_dict_iterate_context *ctx =
+ (struct cdb_dict_iterate_context *)_ctx;
+ struct cdb_dict *dict = (struct cdb_dict *)_ctx->dict;
+ const char *key;
+ bool match = FALSE;
+ char *data;
+ unsigned datalen;
+
+ if (ctx->error != NULL)
+ return FALSE;
+
+ while(!match && cdb_dict_next(ctx, &key)) {
+ if (((ctx->flags & DICT_ITERATE_FLAG_EXACT_KEY) != 0 &&
+ strcmp(key, ctx->path) == 0) ||
+ ((ctx->flags & DICT_ITERATE_FLAG_RECURSE) != 0 &&
+ str_begins(key, ctx->path)) ||
+ ((ctx->flags & DICT_ITERATE_FLAG_RECURSE) == 0 &&
+ str_begins(key, ctx->path) &&
+ strchr(key + strlen(ctx->path), '/') == NULL)) {
+ match = TRUE;
+ break;
+ }
+ }
+
+ if (!match)
+ return FALSE;
+
+ *key_r = key;
+
+ if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) != 0)
+ return TRUE;
+
+ datalen = cdb_datalen(&dict->cdb);
+ data = buffer_append_space_unsafe(ctx->buffer, datalen + 1);
+
+ if (cdb_read(&dict->cdb, data, datalen, cdb_datapos(&dict->cdb)) < 0) {
+ ctx->error = i_strdup_printf("cdb_read(%s) failed: %m",
+ dict->path);
+ return FALSE;
+ }
+
+ data[datalen] = '\0';
+ ctx->values[0] = data;
+ *values_r = ctx->values;
+
+ return TRUE;
+}
+
+static int cdb_dict_iterate_deinit(struct dict_iterate_context *_ctx,
+ const char **error_r)
+{
+ int ret = 0;
+ struct cdb_dict_iterate_context *ctx =
+ (struct cdb_dict_iterate_context *)_ctx;
+ if (ctx->error != NULL) {
+ *error_r = t_strdup(ctx->error);
+ ret = -1;
+ }
+
+ buffer_free(&ctx->buffer);
+ i_free(ctx->error);
+ i_free(ctx->path);
+ i_free(ctx);
+
+ return ret;
+}
+
+
+struct dict dict_driver_cdb = {
+ .name = "cdb",
+ {
+ .init = cdb_dict_init,
+ .deinit = cdb_dict_deinit,
+ .lookup = cdb_dict_lookup,
+ .iterate_init = cdb_dict_iterate_init,
+ .iterate = cdb_dict_iterate,
+ .iterate_deinit = cdb_dict_iterate_deinit,
+ }
+};
+#endif
diff --git a/src/lib-dict-backend/dict-ldap-settings.c b/src/lib-dict-backend/dict-ldap-settings.c
new file mode 100644
index 0000000..01205eb
--- /dev/null
+++ b/src/lib-dict-backend/dict-ldap-settings.c
@@ -0,0 +1,313 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "array.h"
+#include "str.h"
+#include "settings.h"
+#include "dict-ldap-settings.h"
+
+#include <ctype.h>
+
+static const char *dict_ldap_commonName = "cn";
+static const char *dict_ldap_empty_filter = "";
+
+enum section_type {
+ SECTION_ROOT = 0,
+ SECTION_MAP,
+ SECTION_FIELDS
+};
+
+struct dict_ldap_map_attribute {
+ const char *name;
+ const char *variable;
+};
+
+struct setting_parser_ctx {
+ pool_t pool;
+ struct dict_ldap_settings *set;
+ enum section_type type;
+
+ struct dict_ldap_map cur_map;
+ ARRAY(struct dict_ldap_map_attribute) cur_attributes;
+};
+
+#undef DEF_STR
+#undef DEF_BOOL
+#undef DEF_UINT
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, dict_ldap_map)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_ldap_map)
+#define DEF_UINT(name) DEF_STRUCT_UINT(name ,dict_ldap_map)
+
+static const struct setting_def dict_ldap_map_setting_defs[] = {
+ DEF_STR(pattern),
+ DEF_STR(filter),
+ DEF_STR(filter_iter),
+ DEF_STR(username_attribute),
+ DEF_STR(value_attribute),
+ DEF_STR(base_dn),
+ DEF_STR(scope),
+ { 0, NULL, 0 }
+};
+
+static const char *pattern_read_name(const char **pattern)
+{
+ const char *p = *pattern, *name;
+
+ if (*p == '{') {
+ /* ${name} */
+ name = ++p;
+ p = strchr(p, '}');
+ if (p == NULL) {
+ /* error, but allow anyway */
+ *pattern += strlen(*pattern);
+ return "";
+ }
+ *pattern = p + 1;
+ } else {
+ /* $name - ends at the first non-alnum_ character */
+ name = p;
+ for (; *p != '\0'; p++) {
+ if (!i_isalnum(*p) && *p != '_')
+ break;
+ }
+ *pattern = p;
+ }
+ name = t_strdup_until(name, p);
+ return name;
+}
+
+static const char *dict_ldap_attributes_map(struct setting_parser_ctx *ctx)
+{
+ struct dict_ldap_map_attribute *attributes;
+ string_t *pattern;
+ const char *p, *name;
+ unsigned int i, count;
+
+ /* go through the variables in the pattern, replace them with plain
+ '$' character and add its ldap attribute */
+ pattern = t_str_new(strlen(ctx->cur_map.pattern) + 1);
+ attributes = array_get_modifiable(&ctx->cur_attributes, &count);
+
+ p_array_init(&ctx->cur_map.ldap_attributes, ctx->pool, count);
+ for (p = ctx->cur_map.pattern; *p != '\0';) {
+ if (*p != '$') {
+ str_append_c(pattern, *p);
+ p++;
+ continue;
+ }
+ p++;
+ str_append_c(pattern, '$');
+
+ name = pattern_read_name(&p);
+ for (i = 0; i < count; i++) {
+ if (attributes[i].variable != NULL &&
+ strcmp(attributes[i].variable, name) == 0)
+ break;
+ }
+ if (i == count) {
+ return t_strconcat("Missing LDAP attribute for variable: ",
+ name, NULL);
+ }
+
+ /* mark this attribute as used */
+ attributes[i].variable = NULL;
+ array_push_back(&ctx->cur_map.ldap_attributes,
+ &attributes[i].name);
+ }
+
+ /* make sure there aren't any unused attributes */
+ for (i = 0; i < count; i++) {
+ if (attributes[i].variable != NULL) {
+ return t_strconcat("Unused variable: ",
+ attributes[i].variable, NULL);
+ }
+ }
+
+ if (ctx->set->max_attribute_count < count)
+ ctx->set->max_attribute_count = count;
+ ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern));
+ return NULL;
+}
+
+static const char *dict_ldap_map_finish(struct setting_parser_ctx *ctx)
+{
+ if (ctx->cur_map.pattern == NULL)
+ return "Missing setting: pattern";
+ if (ctx->cur_map.filter == NULL)
+ ctx->cur_map.filter = dict_ldap_empty_filter;
+ if (*ctx->cur_map.filter != '\0') {
+ const char *ptr = ctx->cur_map.filter;
+ if (*ptr != '(')
+ return "Filter must start with (";
+ while(*ptr != '\0') ptr++;
+ ptr--;
+ if (*ptr != ')')
+ return "Filter must end with )";
+ }
+ if (ctx->cur_map.value_attribute == NULL)
+ return "Missing setting: value_attribute";
+
+ if (ctx->cur_map.username_attribute == NULL) {
+ /* default to commonName */
+ ctx->cur_map.username_attribute = dict_ldap_commonName;
+ }
+ if (ctx->cur_map.scope == NULL) {
+ ctx->cur_map.scope_val = 2; /* subtree */
+ } else {
+ if (strcasecmp(ctx->cur_map.scope, "one") == 0) ctx->cur_map.scope_val = 1;
+ else if (strcasecmp(ctx->cur_map.scope, "base") == 0) ctx->cur_map.scope_val = 0;
+ else if (strcasecmp(ctx->cur_map.scope, "subtree") == 0) ctx->cur_map.scope_val = 2;
+ else return "Scope must be one, base or subtree";
+ }
+ if (!array_is_created(&ctx->cur_map.ldap_attributes)) {
+ /* no attributes besides value. allocate the array anyway. */
+ p_array_init(&ctx->cur_map.ldap_attributes, ctx->pool, 1);
+ if (strchr(ctx->cur_map.pattern, '$') != NULL)
+ return "Missing attributes for pattern variables";
+ }
+ array_push_back(&ctx->set->maps, &ctx->cur_map);
+ i_zero(&ctx->cur_map);
+ return NULL;
+}
+
+static const char *
+parse_setting(const char *key, const char *value,
+ struct setting_parser_ctx *ctx)
+{
+ struct dict_ldap_map_attribute *attribute;
+
+ switch (ctx->type) {
+ case SECTION_ROOT:
+ if (strcmp(key, "uri") == 0) {
+ ctx->set->uri = p_strdup(ctx->pool, value);
+ return NULL;
+ }
+ if (strcmp(key, "bind_dn") == 0) {
+ ctx->set->bind_dn = p_strdup(ctx->pool, value);
+ return NULL;
+ }
+ if (strcmp(key, "password") == 0) {
+ ctx->set->password = p_strdup(ctx->pool, value);
+ return NULL;
+ }
+ if (strcmp(key, "timeout") == 0) {
+ if (str_to_uint(value, &ctx->set->timeout) != 0) {
+ return "Invalid timeout value";
+ }
+ return NULL;
+ }
+ if (strcmp(key, "max_idle_time") == 0) {
+ if (str_to_uint(value, &ctx->set->max_idle_time) != 0) {
+ return "Invalid max_idle_time value";
+ }
+ return NULL;
+ }
+ if (strcmp(key, "debug") == 0) {
+ if (str_to_uint(value, &ctx->set->debug) != 0) {
+ return "invalid debug value";
+ }
+ return NULL;
+ }
+ if (strcmp(key, "tls") == 0) {
+ if (strcasecmp(value, "yes") == 0) {
+ ctx->set->require_ssl = TRUE;
+ ctx->set->start_tls = TRUE;
+ } else if (strcasecmp(value, "no") == 0) {
+ ctx->set->require_ssl = FALSE;
+ ctx->set->start_tls = FALSE;
+ } else if (strcasecmp(value, "try") == 0) {
+ ctx->set->require_ssl = FALSE;
+ ctx->set->start_tls = TRUE;
+ } else {
+ return "tls must be yes, try or no";
+ }
+ return NULL;
+ }
+ break;
+ case SECTION_MAP:
+ return parse_setting_from_defs(ctx->pool,
+ dict_ldap_map_setting_defs,
+ &ctx->cur_map, key, value);
+ case SECTION_FIELDS:
+ if (*value != '$') {
+ return t_strconcat("Value is missing '$' for attribute: ",
+ key, NULL);
+ }
+ attribute = array_append_space(&ctx->cur_attributes);
+ attribute->name = p_strdup(ctx->pool, key);
+ attribute->variable = p_strdup(ctx->pool, value + 1);
+ return NULL;
+ }
+ return t_strconcat("Unknown setting: ", key, NULL);
+}
+
+static bool
+parse_section(const char *type, const char *name ATTR_UNUSED,
+ struct setting_parser_ctx *ctx, const char **error_r)
+{
+ switch (ctx->type) {
+ case SECTION_ROOT:
+ if (type == NULL)
+ return FALSE;
+ if (strcmp(type, "map") == 0) {
+ array_clear(&ctx->cur_attributes);
+ ctx->type = SECTION_MAP;
+ return TRUE;
+ }
+ break;
+ case SECTION_MAP:
+ if (type == NULL) {
+ ctx->type = SECTION_ROOT;
+ *error_r = dict_ldap_map_finish(ctx);
+ return FALSE;
+ }
+ if (strcmp(type, "fields") == 0) {
+ ctx->type = SECTION_FIELDS;
+ return TRUE;
+ }
+ break;
+ case SECTION_FIELDS:
+ if (type == NULL) {
+ ctx->type = SECTION_MAP;
+ *error_r = dict_ldap_attributes_map(ctx);
+ return FALSE;
+ }
+ break;
+ }
+ *error_r = t_strconcat("Unknown section: ", type, NULL);
+ return FALSE;
+}
+
+struct dict_ldap_settings *
+dict_ldap_settings_read(pool_t pool, const char *path, const char **error_r)
+{
+ struct setting_parser_ctx ctx;
+
+ i_zero(&ctx);
+ ctx.pool = pool;
+ ctx.set = p_new(pool, struct dict_ldap_settings, 1);
+ t_array_init(&ctx.cur_attributes, 16);
+ p_array_init(&ctx.set->maps, pool, 8);
+
+ ctx.set->timeout = 30; /* default timeout */
+ ctx.set->require_ssl = FALSE; /* try to start SSL */
+ ctx.set->start_tls = TRUE;
+
+ if (!settings_read(path, NULL, parse_setting, parse_section,
+ &ctx, error_r))
+ return NULL;
+
+ if (ctx.set->uri == NULL) {
+ *error_r = t_strdup_printf("Error in configuration file %s: "
+ "Missing ldap uri", path);
+ return NULL;
+ }
+
+ return ctx.set;
+}
+
+#endif
diff --git a/src/lib-dict-backend/dict-ldap-settings.h b/src/lib-dict-backend/dict-ldap-settings.h
new file mode 100644
index 0000000..0919ca9
--- /dev/null
+++ b/src/lib-dict-backend/dict-ldap-settings.h
@@ -0,0 +1,36 @@
+#ifndef DICT_LDAP_SETTINGS_H
+#define DICT_LDAP_SETTINGS_H
+
+struct dict_ldap_map {
+ /* pattern is in simplified form: all variables are stored as simple
+ '$' character. fields array is sorted by the variable index. */
+ const char *pattern;
+ const char *filter;
+ const char *filter_iter;
+ const char *username_attribute;
+ const char *value_attribute;
+ const char *base_dn;
+ const char *scope;
+ int scope_val;
+ unsigned int timeout;
+
+ ARRAY_TYPE(const_string) ldap_attributes;
+};
+
+struct dict_ldap_settings {
+ const char *uri;
+ const char *bind_dn;
+ const char *password;
+ unsigned int timeout;
+ unsigned int max_idle_time;
+ unsigned int debug;
+ unsigned int max_attribute_count;
+ bool require_ssl;
+ bool start_tls;
+ ARRAY(struct dict_ldap_map) maps;
+};
+
+struct dict_ldap_settings *
+dict_ldap_settings_read(pool_t pool, const char *path, const char **error_r);
+
+#endif
diff --git a/src/lib-dict-backend/dict-ldap.c b/src/lib-dict-backend/dict-ldap.c
new file mode 100644
index 0000000..433871b
--- /dev/null
+++ b/src/lib-dict-backend/dict-ldap.c
@@ -0,0 +1,500 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING memcached */
+
+#include "lib.h"
+
+#if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "array.h"
+#include "module-dir.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "var-expand.h"
+#include "connection.h"
+#include "llist.h"
+#include "ldap-client.h"
+#include "dict.h"
+#include "dict-private.h"
+#include "dict-ldap-settings.h"
+
+static const char *LDAP_ESCAPE_CHARS = "*,\\#+<>;\"()= ";
+
+struct ldap_dict;
+
+struct dict_ldap_op {
+ struct ldap_dict *dict;
+ const struct dict_ldap_map *map;
+ pool_t pool;
+ unsigned long txid;
+ struct dict_lookup_result res;
+ dict_lookup_callback_t *callback;
+ void *callback_ctx;
+};
+
+struct ldap_dict {
+ struct dict dict;
+ struct dict_ldap_settings *set;
+
+ const char *uri;
+ const char *base_dn;
+ enum ldap_scope scope;
+
+ pool_t pool;
+
+ struct ldap_client *client;
+
+ unsigned long last_txid;
+ unsigned int pending;
+
+ struct ldap_dict *prev,*next;
+};
+
+static
+void ldap_dict_lookup_async(struct dict *dict,
+ const struct dict_op_settings *set, const char *key,
+ dict_lookup_callback_t *callback, void *context);
+
+
+static bool
+dict_ldap_map_match(const struct dict_ldap_map *map, const char *path,
+ ARRAY_TYPE(const_string) *values, size_t *pat_len_r,
+ size_t *path_len_r, bool partial_ok, bool recurse)
+{
+ const char *path_start = path;
+ const char *pat, *attribute, *p;
+ size_t len;
+
+ array_clear(values);
+ pat = map->pattern;
+ while (*pat != '\0' && *path != '\0') {
+ if (*pat == '$') {
+ /* variable */
+ pat++;
+ if (*pat == '\0') {
+ /* pattern ended with this variable,
+ it'll match the rest of the path */
+ len = strlen(path);
+ if (partial_ok) {
+ /* iterating - the last field never
+ matches fully. if there's a trailing
+ '/', drop it. */
+ pat--;
+ if (path[len-1] == '/') {
+ attribute = t_strndup(path, len-1);
+ array_push_back(values,
+ &attribute);
+ } else {
+ array_push_back(values, &path);
+ }
+ } else {
+ array_push_back(values, &path);
+ path += len;
+ }
+ *path_len_r = path - path_start;
+ *pat_len_r = pat - map->pattern;
+ return TRUE;
+ }
+ /* pattern matches until the next '/' in path */
+ p = strchr(path, '/');
+ if (p != NULL) {
+ attribute = t_strdup_until(path, p);
+ array_push_back(values, &attribute);
+ path = p;
+ } else {
+ /* no '/' anymore, but it'll still match a
+ partial */
+ array_push_back(values, &path);
+ path += strlen(path);
+ pat++;
+ }
+ } else if (*pat == *path) {
+ pat++;
+ path++;
+ } else {
+ return FALSE;
+ }
+ }
+
+ *path_len_r = path - path_start;
+ *pat_len_r = pat - map->pattern;
+
+ if (*pat == '\0')
+ return *path == '\0';
+ else if (!partial_ok)
+ return FALSE;
+ else {
+ /* partial matches must end with '/'. */
+ if (pat != map->pattern && pat[-1] != '/')
+ return FALSE;
+ /* if we're not recursing, there should be only one $variable
+ left. */
+ if (recurse)
+ return TRUE;
+ return pat[0] == '$' && strchr(pat, '/') == NULL;
+ }
+}
+
+static const struct dict_ldap_map *
+ldap_dict_find_map(struct ldap_dict *dict, const char *path,
+ ARRAY_TYPE(const_string) *values)
+{
+ const struct dict_ldap_map *maps;
+ unsigned int i, count;
+ size_t len;
+
+ t_array_init(values, dict->set->max_attribute_count);
+ maps = array_get(&dict->set->maps, &count);
+ for (i = 0; i < count; i++) {
+ if (dict_ldap_map_match(&maps[i], path, values,
+ &len, &len, FALSE, FALSE))
+ return &maps[i];
+ }
+ return NULL;
+}
+
+static
+int dict_ldap_connect(struct ldap_dict *dict, const char **error_r)
+{
+ struct ldap_client_settings set;
+ i_zero(&set);
+ set.uri = dict->set->uri;
+ set.bind_dn = dict->set->bind_dn;
+ set.password = dict->set->password;
+ set.timeout_secs = dict->set->timeout;
+ set.max_idle_time_secs = dict->set->max_idle_time;
+ set.debug = dict->set->debug;
+ set.require_ssl = dict->set->require_ssl;
+ set.start_tls = dict->set->start_tls;
+ return ldap_client_init(&set, &dict->client, error_r);
+}
+
+#define IS_LDAP_ESCAPED_CHAR(c) \
+ ((((unsigned char)(c)) & 0x80) != 0 || strchr(LDAP_ESCAPE_CHARS, (c)) != NULL)
+
+static const char *ldap_escape(const char *str)
+{
+ 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_dict_build_query(const struct dict_op_settings *set,
+ const struct dict_ldap_map *map,
+ ARRAY_TYPE(const_string) *values, bool priv,
+ string_t *query_r, const char **error_r)
+{
+ const char *template, *error;
+ ARRAY(struct var_expand_table) exp;
+ struct var_expand_table entry;
+
+ t_array_init(&exp, 8);
+ entry.key = '\0';
+ entry.value = ldap_escape(set->username);
+ entry.long_key = "username";
+ array_push_back(&exp, &entry);
+
+ if (priv) {
+ template = t_strdup_printf("(&(%s=%s)%s)", map->username_attribute, "%{username}", map->filter);
+ } else {
+ template = map->filter;
+ }
+
+ for(size_t i = 0; i < array_count(values) && i < array_count(&map->ldap_attributes); i++) {
+ struct var_expand_table entry;
+ const char *value = array_idx_elem(values, i);
+ const char *long_key = array_idx_elem(&map->ldap_attributes, i);
+
+ entry.value = ldap_escape(value);
+ entry.long_key = long_key;
+ array_push_back(&exp, &entry);
+ }
+
+ array_append_zero(&exp);
+
+ if (var_expand(query_r, template, array_front(&exp), &error) <= 0) {
+ *error_r = t_strdup_printf("Failed to expand %s: %s", template, error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static
+int ldap_dict_init(struct dict *dict_driver, const char *uri,
+ const struct dict_settings *set ATTR_UNUSED,
+ struct dict **dict_r, const char **error_r)
+{
+ pool_t pool = pool_alloconly_create("ldap dict", 2048);
+ struct ldap_dict *dict = p_new(pool, struct ldap_dict, 1);
+ dict->pool = pool;
+ dict->dict = *dict_driver;
+ dict->uri = p_strdup(pool, uri);
+ dict->set = dict_ldap_settings_read(pool, uri, error_r);
+
+ if (dict->set == NULL) {
+ pool_unref(&pool);
+ return -1;
+ }
+
+ if (dict_ldap_connect(dict, error_r) < 0) {
+ pool_unref(&pool);
+ return -1;
+ }
+
+ *dict_r = (struct dict*)dict;
+ *error_r = NULL;
+ return 0;
+}
+
+static
+void ldap_dict_deinit(struct dict *dict)
+{
+ struct ldap_dict *ctx = (struct ldap_dict *)dict;
+
+ ldap_client_deinit(&ctx->client);
+ pool_unref(&ctx->pool);
+}
+
+static void ldap_dict_wait(struct dict *dict)
+{
+ struct ldap_dict *ctx = (struct ldap_dict *)dict;
+
+ i_assert(ctx->dict.ioloop == NULL);
+
+ ctx->dict.prev_ioloop = current_ioloop;
+ ctx->dict.ioloop = io_loop_create();
+ dict_switch_ioloop(dict);
+
+ do {
+ io_loop_run(current_ioloop);
+ } while (ctx->pending > 0);
+
+ io_loop_set_current(ctx->dict.prev_ioloop);
+ dict_switch_ioloop(dict);
+ io_loop_set_current(ctx->dict.ioloop);
+ io_loop_destroy(&ctx->dict.ioloop);
+ ctx->dict.prev_ioloop = NULL;
+}
+
+static bool ldap_dict_switch_ioloop(struct dict *dict)
+{
+ struct ldap_dict *ctx = (struct ldap_dict *)dict;
+
+ ldap_client_switch_ioloop(ctx->client);
+ return ctx->pending > 0;
+}
+
+static
+void ldap_dict_lookup_done(const struct dict_lookup_result *result, void *ctx)
+{
+ struct dict_lookup_result *res = ctx;
+ res->ret = result->ret;
+ res->value = t_strdup(result->value);
+ res->error = t_strdup(result->error);
+}
+
+static void
+ldap_dict_lookup_callback(struct ldap_result *result, struct dict_ldap_op *op)
+{
+ pool_t pool = op->pool;
+ struct ldap_search_iterator *iter;
+ const struct ldap_entry *entry;
+
+ op->dict->pending--;
+
+ if (ldap_result_has_failed(result)) {
+ op->res.ret = -1;
+ op->res.error = ldap_result_get_error(result);
+ } else {
+ iter = ldap_search_iterator_init(result);
+ entry = ldap_search_iterator_next(iter);
+ if (entry != NULL) {
+ if (op->dict->set->debug > 0)
+ i_debug("ldap_dict_lookup_callback got dn %s", ldap_entry_dn(entry));
+ /* try extract value */
+ const char *const *values = ldap_entry_get_attribute(entry, op->map->value_attribute);
+ if (values != NULL) {
+ const char **new_values;
+
+ if (op->dict->set->debug > 0)
+ i_debug("ldap_dict_lookup_callback got attribute %s", op->map->value_attribute);
+ op->res.ret = 1;
+ new_values = p_new(op->pool, const char *, 2);
+ new_values[0] = p_strdup(op->pool, values[0]);
+ op->res.values = new_values;
+ op->res.value = op->res.values[0];
+ } else {
+ if (op->dict->set->debug > 0)
+ i_debug("ldap_dict_lookup_callback dit not get attribute %s", op->map->value_attribute);
+ op->res.value = NULL;
+ }
+ }
+ ldap_search_iterator_deinit(&iter);
+ }
+ if (op->dict->dict.prev_ioloop != NULL)
+ io_loop_set_current(op->dict->dict.prev_ioloop);
+ op->callback(&op->res, op->callback_ctx);
+ if (op->dict->dict.prev_ioloop != NULL) {
+ io_loop_set_current(op->dict->dict.ioloop);
+ io_loop_stop(op->dict->dict.ioloop);
+ }
+ pool_unref(&pool);
+}
+
+static int
+ldap_dict_lookup(struct dict *dict, const struct dict_op_settings *set,
+ pool_t pool, const char *key,
+ const char **value_r, const char **error_r)
+{
+ struct dict_lookup_result res;
+
+ ldap_dict_lookup_async(dict, set, key, ldap_dict_lookup_done, &res);
+
+ ldap_dict_wait(dict);
+ if (res.ret < 0) {
+ *error_r = res.error;
+ return -1;
+ }
+ if (res.ret > 0)
+ *value_r = p_strdup(pool, res.value);
+ return res.ret;
+}
+
+/*
+static
+struct dict_iterate_context *ldap_dict_iterate_init(struct dict *dict,
+ const char *const *paths,
+ enum dict_iterate_flags flags)
+{
+ return NULL;
+}
+
+static
+bool ldap_dict_iterate(struct dict_iterate_context *ctx,
+ const char **key_r, const char **value_r)
+{
+ return FALSE;
+}
+
+static
+int ldap_dict_iterate_deinit(struct dict_iterate_context *ctx)
+{
+ return -1;
+}
+
+static
+struct dict_transaction_context ldap_dict_transaction_init(struct dict *dict);
+
+static
+int ldap_dict_transaction_commit(struct dict_transaction_context *ctx,
+ bool async,
+ dict_transaction_commit_callback_t *callback,
+ void *context);
+static
+void ldap_dict_transaction_rollback(struct dict_transaction_context *ctx);
+
+static
+void ldap_dict_set(struct dict_transaction_context *ctx,
+ const char *key, const char *value);
+static
+void ldap_dict_unset(struct dict_transaction_context *ctx,
+ const char *key);
+static
+void ldap_dict_atomic_inc(struct dict_transaction_context *ctx,
+ const char *key, long long diff);
+*/
+
+static
+void ldap_dict_lookup_async(struct dict *dict,
+ const struct dict_op_settings *set,
+ const char *key,
+ dict_lookup_callback_t *callback, void *context)
+{
+ struct ldap_search_input input;
+ struct ldap_dict *ctx = (struct ldap_dict*)dict;
+ struct dict_ldap_op *op;
+ const char *error;
+
+ pool_t oppool = pool_alloconly_create("ldap dict lookup", 64);
+ string_t *query = str_new(oppool, 64);
+ op = p_new(oppool, struct dict_ldap_op, 1);
+ op->pool = oppool;
+ op->dict = ctx;
+ op->callback = callback;
+ op->callback_ctx = context;
+ op->txid = ctx->last_txid++;
+
+ /* key needs to be transformed into something else */
+ ARRAY_TYPE(const_string) values;
+ const char *attributes[2] = {0, 0};
+ t_array_init(&values, 8);
+ const struct dict_ldap_map *map = ldap_dict_find_map(ctx, key, &values);
+
+ if (map != NULL) {
+ op->map = map;
+ attributes[0] = map->value_attribute;
+ /* build lookup */
+ i_zero(&input);
+ input.base_dn = map->base_dn;
+ input.scope = map->scope_val;
+ if (!ldap_dict_build_query(set, map, &values, strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE))==0, query, &error)) {
+ op->res.error = error;
+ callback(&op->res, context);
+ pool_unref(&oppool);
+ return;
+ }
+ input.filter = str_c(query);
+ input.attributes = attributes;
+ input.timeout_secs = ctx->set->timeout;
+ ctx->pending++;
+ ldap_search_start(ctx->client, &input, ldap_dict_lookup_callback, op);
+ } else {
+ op->res.error = "no such key";
+ callback(&op->res, context);
+ pool_unref(&oppool);
+ }
+}
+
+struct dict dict_driver_ldap = {
+ .name = "ldap",
+ {
+ .init = ldap_dict_init,
+ .deinit = ldap_dict_deinit,
+ .wait = ldap_dict_wait,
+ .lookup = ldap_dict_lookup,
+ .lookup_async = ldap_dict_lookup_async,
+ .switch_ioloop = ldap_dict_switch_ioloop,
+ }
+};
+
+#ifndef BUILTIN_LDAP
+/* Building a plugin */
+void dict_ldap_init(struct module *module ATTR_UNUSED);
+void dict_ldap_deinit(void);
+
+void dict_ldap_init(struct module *module ATTR_UNUSED)
+{
+ dict_driver_register(&dict_driver_ldap);
+}
+
+void dict_ldap_deinit(void)
+{
+ ldap_clients_cleanup();
+ dict_driver_unregister(&dict_driver_ldap);
+}
+
+const char *dict_ldap_plugin_dependencies[] = { NULL };
+#endif
+
+#endif
diff --git a/src/lib-dict-backend/dict-sql-private.h b/src/lib-dict-backend/dict-sql-private.h
new file mode 100644
index 0000000..11e9dfc
--- /dev/null
+++ b/src/lib-dict-backend/dict-sql-private.h
@@ -0,0 +1,12 @@
+#ifndef DICT_SQL_PRIVATE_H
+#define DICT_SQL_PRIVATE_H 1
+
+struct sql_dict {
+ struct dict dict;
+
+ pool_t pool;
+ struct sql_db *db;
+ const struct dict_sql_settings *set;
+};
+
+#endif
diff --git a/src/lib-dict-backend/dict-sql-settings.c b/src/lib-dict-backend/dict-sql-settings.c
new file mode 100644
index 0000000..0d44923
--- /dev/null
+++ b/src/lib-dict-backend/dict-sql-settings.c
@@ -0,0 +1,345 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "hash.h"
+#include "settings.h"
+#include "dict-sql-settings.h"
+
+#include <ctype.h>
+
+enum section_type {
+ SECTION_ROOT = 0,
+ SECTION_MAP,
+ SECTION_FIELDS
+};
+
+struct dict_sql_map_field {
+ struct dict_sql_field sql_field;
+ const char *variable;
+};
+
+struct setting_parser_ctx {
+ pool_t pool;
+ struct dict_sql_settings *set;
+ enum section_type type;
+
+ struct dict_sql_map cur_map;
+ ARRAY(struct dict_sql_map_field) cur_fields;
+};
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, dict_sql_map)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_sql_map)
+
+static const struct setting_def dict_sql_map_setting_defs[] = {
+ DEF_STR(pattern),
+ DEF_STR(table),
+ DEF_STR(username_field),
+ DEF_STR(value_field),
+ DEF_STR(value_type),
+ DEF_BOOL(value_hexblob),
+
+ { 0, NULL, 0 }
+};
+
+struct dict_sql_settings_cache {
+ pool_t pool;
+ const char *path;
+ struct dict_sql_settings *set;
+};
+
+static HASH_TABLE(const char *, struct dict_sql_settings_cache *) dict_sql_settings_cache;
+
+static const char *pattern_read_name(const char **pattern)
+{
+ const char *p = *pattern, *name;
+
+ if (*p == '{') {
+ /* ${name} */
+ name = ++p;
+ p = strchr(p, '}');
+ if (p == NULL) {
+ /* error, but allow anyway */
+ *pattern += strlen(*pattern);
+ return "";
+ }
+ *pattern = p + 1;
+ } else {
+ /* $name - ends at the first non-alnum_ character */
+ name = p;
+ for (; *p != '\0'; p++) {
+ if (!i_isalnum(*p) && *p != '_')
+ break;
+ }
+ *pattern = p;
+ }
+ name = t_strdup_until(name, p);
+ return name;
+}
+
+static const char *dict_sql_fields_map(struct setting_parser_ctx *ctx)
+{
+ struct dict_sql_map_field *fields;
+ string_t *pattern;
+ const char *p, *name;
+ unsigned int i, count;
+
+ /* go through the variables in the pattern, replace them with plain
+ '$' character and add its sql field */
+ pattern = t_str_new(strlen(ctx->cur_map.pattern) + 1);
+ fields = array_get_modifiable(&ctx->cur_fields, &count);
+
+ p_array_init(&ctx->cur_map.pattern_fields, ctx->pool, count);
+ for (p = ctx->cur_map.pattern; *p != '\0';) {
+ if (*p != '$') {
+ str_append_c(pattern, *p);
+ p++;
+ continue;
+ }
+ p++;
+ str_append_c(pattern, '$');
+
+ name = pattern_read_name(&p);
+ for (i = 0; i < count; i++) {
+ if (fields[i].variable != NULL &&
+ strcmp(fields[i].variable, name) == 0)
+ break;
+ }
+ if (i == count) {
+ return t_strconcat("Missing SQL field for variable: ",
+ name, NULL);
+ }
+
+ /* mark this field as used */
+ fields[i].variable = NULL;
+ array_push_back(&ctx->cur_map.pattern_fields,
+ &fields[i].sql_field);
+ }
+
+ /* make sure there aren't any unused fields */
+ for (i = 0; i < count; i++) {
+ if (fields[i].variable != NULL) {
+ return t_strconcat("Unused variable: ",
+ fields[i].variable, NULL);
+ }
+ }
+
+ if (ctx->set->max_pattern_fields_count < count)
+ ctx->set->max_pattern_fields_count = count;
+ ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern));
+ return NULL;
+}
+
+static bool
+dict_sql_value_type_parse(const char *value_type, enum dict_sql_type *type_r)
+{
+ if (strcmp(value_type, "string") == 0)
+ *type_r = DICT_SQL_TYPE_STRING;
+ else if (strcmp(value_type, "hexblob") == 0)
+ *type_r = DICT_SQL_TYPE_HEXBLOB;
+ else if (strcmp(value_type, "int") == 0)
+ *type_r = DICT_SQL_TYPE_INT;
+ else if (strcmp(value_type, "uint") == 0)
+ *type_r = DICT_SQL_TYPE_UINT;
+ else
+ return FALSE;
+ return TRUE;
+}
+
+static const char *dict_sql_map_finish(struct setting_parser_ctx *ctx)
+{
+ unsigned int i;
+
+ if (ctx->cur_map.pattern == NULL)
+ return "Missing setting: pattern";
+ if (ctx->cur_map.table == NULL)
+ return "Missing setting: table";
+ if (ctx->cur_map.value_field == NULL)
+ return "Missing setting: value_field";
+
+ ctx->cur_map.value_fields = (const char *const *)
+ p_strsplit_spaces(ctx->pool, ctx->cur_map.value_field, ",");
+ ctx->cur_map.values_count = str_array_length(ctx->cur_map.value_fields);
+
+ enum dict_sql_type *value_types =
+ p_new(ctx->pool, enum dict_sql_type, ctx->cur_map.values_count);
+ if (ctx->cur_map.value_type != NULL) {
+ const char *const *types =
+ t_strsplit_spaces(ctx->cur_map.value_type, ",");
+ if (str_array_length(types) != ctx->cur_map.values_count)
+ return "Number of fields in value_fields doesn't match value_type";
+ for (i = 0; i < ctx->cur_map.values_count; i++) {
+ if (!dict_sql_value_type_parse(types[i], &value_types[i]))
+ return "Invalid value in value_type";
+ }
+ } else {
+ for (i = 0; i < ctx->cur_map.values_count; i++) {
+ value_types[i] = ctx->cur_map.value_hexblob ?
+ DICT_SQL_TYPE_HEXBLOB : DICT_SQL_TYPE_STRING;
+ }
+ }
+ ctx->cur_map.value_types = value_types;
+
+ if (ctx->cur_map.username_field == NULL) {
+ /* not all queries require this */
+ ctx->cur_map.username_field = "'username_field not set'";
+ }
+
+ if (!array_is_created(&ctx->cur_map.pattern_fields)) {
+ /* no fields besides value. allocate the array anyway. */
+ p_array_init(&ctx->cur_map.pattern_fields, ctx->pool, 1);
+ if (strchr(ctx->cur_map.pattern, '$') != NULL)
+ return "Missing fields for pattern variables";
+ }
+ array_push_back(&ctx->set->maps, &ctx->cur_map);
+ i_zero(&ctx->cur_map);
+ return NULL;
+}
+
+static const char *
+parse_setting(const char *key, const char *value,
+ struct setting_parser_ctx *ctx)
+{
+ struct dict_sql_map_field *field;
+ size_t value_len;
+
+ switch (ctx->type) {
+ case SECTION_ROOT:
+ if (strcmp(key, "connect") == 0) {
+ ctx->set->connect = p_strdup(ctx->pool, value);
+ return NULL;
+ }
+ break;
+ case SECTION_MAP:
+ return parse_setting_from_defs(ctx->pool,
+ dict_sql_map_setting_defs,
+ &ctx->cur_map, key, value);
+ case SECTION_FIELDS:
+ if (*value != '$') {
+ return t_strconcat("Value is missing '$' for field: ",
+ key, NULL);
+ }
+ field = array_append_space(&ctx->cur_fields);
+ field->sql_field.name = p_strdup(ctx->pool, key);
+ value_len = strlen(value);
+ if (str_begins(value, "${hexblob:") &&
+ value[value_len-1] == '}') {
+ field->variable = p_strndup(ctx->pool, value + 10,
+ value_len-10-1);
+ field->sql_field.value_type = DICT_SQL_TYPE_HEXBLOB;
+ } else if (str_begins(value, "${int:") &&
+ value[value_len-1] == '}') {
+ field->variable = p_strndup(ctx->pool, value + 6,
+ value_len-6-1);
+ field->sql_field.value_type = DICT_SQL_TYPE_INT;
+ } else if (str_begins(value, "${uint:") &&
+ value[value_len-1] == '}') {
+ field->variable = p_strndup(ctx->pool, value + 7,
+ value_len-7-1);
+ field->sql_field.value_type = DICT_SQL_TYPE_UINT;
+ } else {
+ field->variable = p_strdup(ctx->pool, value + 1);
+ }
+ return NULL;
+ }
+ return t_strconcat("Unknown setting: ", key, NULL);
+}
+
+static bool
+parse_section(const char *type, const char *name ATTR_UNUSED,
+ struct setting_parser_ctx *ctx, const char **error_r)
+{
+ switch (ctx->type) {
+ case SECTION_ROOT:
+ if (type == NULL)
+ return FALSE;
+ if (strcmp(type, "map") == 0) {
+ array_clear(&ctx->cur_fields);
+ ctx->type = SECTION_MAP;
+ return TRUE;
+ }
+ break;
+ case SECTION_MAP:
+ if (type == NULL) {
+ ctx->type = SECTION_ROOT;
+ *error_r = dict_sql_map_finish(ctx);
+ return FALSE;
+ }
+ if (strcmp(type, "fields") == 0) {
+ ctx->type = SECTION_FIELDS;
+ return TRUE;
+ }
+ break;
+ case SECTION_FIELDS:
+ if (type == NULL) {
+ ctx->type = SECTION_MAP;
+ *error_r = dict_sql_fields_map(ctx);
+ return FALSE;
+ }
+ break;
+ }
+ *error_r = t_strconcat("Unknown section: ", type, NULL);
+ return FALSE;
+}
+
+struct dict_sql_settings *
+dict_sql_settings_read(const char *path, const char **error_r)
+{
+ struct setting_parser_ctx ctx;
+ struct dict_sql_settings_cache *cache;
+ pool_t pool;
+
+ if (!hash_table_is_created(dict_sql_settings_cache)) {
+ hash_table_create(&dict_sql_settings_cache, default_pool, 0,
+ str_hash, strcmp);
+ }
+
+ cache = hash_table_lookup(dict_sql_settings_cache, path);
+ if (cache != NULL)
+ return cache->set;
+
+ i_zero(&ctx);
+ pool = pool_alloconly_create("dict sql settings", 1024);
+ ctx.pool = pool;
+ ctx.set = p_new(pool, struct dict_sql_settings, 1);
+ t_array_init(&ctx.cur_fields, 16);
+ p_array_init(&ctx.set->maps, pool, 8);
+
+ if (!settings_read(path, NULL, parse_setting, parse_section,
+ &ctx, error_r)) {
+ pool_unref(&pool);
+ return NULL;
+ }
+
+ if (ctx.set->connect == NULL) {
+ *error_r = t_strdup_printf("Error in configuration file %s: "
+ "Missing connect setting", path);
+ pool_unref(&pool);
+ return NULL;
+ }
+
+ cache = p_new(pool, struct dict_sql_settings_cache, 1);
+ cache->pool = pool;
+ cache->path = p_strdup(pool, path);
+ cache->set = ctx.set;
+
+ hash_table_insert(dict_sql_settings_cache, cache->path, cache);
+ return ctx.set;
+}
+
+void dict_sql_settings_deinit(void)
+{
+ struct hash_iterate_context *iter;
+ struct dict_sql_settings_cache *cache;
+ const char *key;
+
+ if (!hash_table_is_created(dict_sql_settings_cache))
+ return;
+
+ iter = hash_table_iterate_init(dict_sql_settings_cache);
+ while (hash_table_iterate(iter, dict_sql_settings_cache, &key, &cache))
+ pool_unref(&cache->pool);
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&dict_sql_settings_cache);
+}
diff --git a/src/lib-dict-backend/dict-sql-settings.h b/src/lib-dict-backend/dict-sql-settings.h
new file mode 100644
index 0000000..c9ee037
--- /dev/null
+++ b/src/lib-dict-backend/dict-sql-settings.h
@@ -0,0 +1,47 @@
+#ifndef DICT_SQL_SETTINGS_H
+#define DICT_SQL_SETTINGS_H
+
+enum dict_sql_type {
+ DICT_SQL_TYPE_STRING = 0,
+ DICT_SQL_TYPE_INT,
+ DICT_SQL_TYPE_UINT,
+ DICT_SQL_TYPE_HEXBLOB
+};
+
+struct dict_sql_field {
+ const char *name;
+ enum dict_sql_type value_type;
+};
+
+struct dict_sql_map {
+ /* pattern is in simplified form: all variables are stored as simple
+ '$' character. fields array is sorted by the variable index. */
+ const char *pattern;
+ const char *table;
+ const char *username_field;
+ const char *value_field;
+ const char *value_type;
+ bool value_hexblob;
+
+ /* SQL field names, one for each $ variable in the pattern */
+ ARRAY(struct dict_sql_field) pattern_fields;
+
+ /* generated: */
+ unsigned int values_count;
+ const char *const *value_fields;
+ const enum dict_sql_type *value_types;
+};
+
+struct dict_sql_settings {
+ const char *connect;
+
+ unsigned int max_pattern_fields_count;
+ ARRAY(struct dict_sql_map) maps;
+};
+
+struct dict_sql_settings *
+dict_sql_settings_read(const char *path, const char **error_r);
+
+void dict_sql_settings_deinit(void);
+
+#endif
diff --git a/src/lib-dict-backend/dict-sql.c b/src/lib-dict-backend/dict-sql.c
new file mode 100644
index 0000000..c44330b
--- /dev/null
+++ b/src/lib-dict-backend/dict-sql.c
@@ -0,0 +1,1564 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "hex-binary.h"
+#include "hash.h"
+#include "str.h"
+#include "sql-api-private.h"
+#include "sql-db-cache.h"
+#include "dict-private.h"
+#include "dict-sql-settings.h"
+#include "dict-sql.h"
+#include "dict-sql-private.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#define DICT_SQL_MAX_UNUSED_CONNECTIONS 10
+
+enum sql_recurse_type {
+ SQL_DICT_RECURSE_NONE,
+ SQL_DICT_RECURSE_ONE,
+ SQL_DICT_RECURSE_FULL
+};
+
+struct sql_dict_param {
+ enum dict_sql_type value_type;
+
+ const char *value_str;
+ int64_t value_int64;
+ const void *value_binary;
+ size_t value_binary_size;
+};
+ARRAY_DEFINE_TYPE(sql_dict_param, struct sql_dict_param);
+
+struct sql_dict_iterate_context {
+ struct dict_iterate_context ctx;
+ pool_t pool;
+
+ enum dict_iterate_flags flags;
+ const char *path;
+
+ struct sql_result *result;
+ string_t *key;
+ const struct dict_sql_map *map;
+ size_t key_prefix_len, pattern_prefix_len;
+ unsigned int sql_fields_start_idx, next_map_idx;
+ bool destroyed;
+ bool synchronous_result;
+ bool iter_query_sent;
+ bool allow_null_map; /* allow next map to be NULL */
+ const char *error;
+};
+
+struct sql_dict_inc_row {
+ struct sql_dict_inc_row *prev;
+ unsigned int rows;
+};
+
+struct sql_dict_prev {
+ const struct dict_sql_map *map;
+ char *key;
+ union {
+ char *str;
+ long long diff;
+ } value;
+};
+
+struct sql_dict_transaction_context {
+ struct dict_transaction_context ctx;
+
+ struct sql_transaction_context *sql_ctx;
+
+ pool_t inc_row_pool;
+ struct sql_dict_inc_row *inc_row;
+
+ ARRAY(struct sql_dict_prev) prev_inc;
+ ARRAY(struct sql_dict_prev) prev_set;
+
+ dict_transaction_commit_callback_t *async_callback;
+ void *async_context;
+
+ char *error;
+};
+
+static struct sql_db_cache *dict_sql_db_cache;
+
+static void sql_dict_prev_inc_flush(struct sql_dict_transaction_context *ctx);
+static void sql_dict_prev_set_flush(struct sql_dict_transaction_context *ctx);
+static void sql_dict_prev_inc_free(struct sql_dict_transaction_context *ctx);
+static void sql_dict_prev_set_free(struct sql_dict_transaction_context *ctx);
+
+static int
+sql_dict_init(struct dict *driver, const char *uri,
+ const struct dict_settings *set,
+ struct dict **dict_r, const char **error_r)
+{
+ struct sql_settings sql_set;
+ struct sql_dict *dict;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sql dict", 2048);
+ dict = p_new(pool, struct sql_dict, 1);
+ dict->pool = pool;
+ dict->dict = *driver;
+ dict->set = dict_sql_settings_read(uri, error_r);
+ if (dict->set == NULL) {
+ pool_unref(&pool);
+ return -1;
+ }
+ i_zero(&sql_set);
+ sql_set.driver = driver->name;
+ sql_set.connect_string = dict->set->connect;
+ sql_set.event_parent = set->event_parent;
+
+ if (sql_db_cache_new(dict_sql_db_cache, &sql_set, &dict->db, error_r) < 0) {
+ pool_unref(&pool);
+ return -1;
+ }
+
+ *dict_r = &dict->dict;
+ return 0;
+}
+
+static void sql_dict_deinit(struct dict *_dict)
+{
+ struct sql_dict *dict = (struct sql_dict *)_dict;
+
+ sql_unref(&dict->db);
+ pool_unref(&dict->pool);
+}
+
+static void sql_dict_wait(struct dict *_dict)
+{
+ struct sql_dict *dict = (struct sql_dict *)_dict;
+ sql_wait(dict->db);
+}
+
+/* Try to match path to map->pattern. For example pattern="shared/x/$/$/y"
+ and path="shared/x/1/2/y", this is match and pattern_values=[1, 2]. */
+static bool
+dict_sql_map_match(const struct dict_sql_map *map, const char *path,
+ ARRAY_TYPE(const_string) *pattern_values, size_t *pat_len_r,
+ size_t *path_len_r, bool partial_ok, bool recurse)
+{
+ const char *path_start = path;
+ const char *pat, *field, *p;
+ size_t len;
+
+ array_clear(pattern_values);
+ pat = map->pattern;
+ while (*pat != '\0' && *path != '\0') {
+ if (*pat == '$') {
+ /* variable */
+ pat++;
+ if (*pat == '\0') {
+ /* pattern ended with this variable,
+ it'll match the rest of the path */
+ len = strlen(path);
+ if (partial_ok) {
+ /* iterating - the last field never
+ matches fully. if there's a trailing
+ '/', drop it. */
+ pat--;
+ if (path[len-1] == '/') {
+ field = t_strndup(path, len-1);
+ array_push_back(pattern_values,
+ &field);
+ } else {
+ array_push_back(pattern_values,
+ &path);
+ }
+ } else {
+ array_push_back(pattern_values, &path);
+ path += len;
+ }
+ *path_len_r = path - path_start;
+ *pat_len_r = pat - map->pattern;
+ return TRUE;
+ }
+ /* pattern matches until the next '/' in path */
+ p = strchr(path, '/');
+ if (p != NULL) {
+ field = t_strdup_until(path, p);
+ array_push_back(pattern_values, &field);
+ path = p;
+ } else {
+ /* no '/' anymore, but it'll still match a
+ partial */
+ array_push_back(pattern_values, &path);
+ path += strlen(path);
+ pat++;
+ }
+ } else if (*pat == *path) {
+ pat++;
+ path++;
+ } else {
+ return FALSE;
+ }
+ }
+
+ *path_len_r = path - path_start;
+ *pat_len_r = pat - map->pattern;
+
+ if (*pat == '\0')
+ return *path == '\0';
+ else if (!partial_ok)
+ return FALSE;
+ else {
+ /* partial matches must end with '/'. */
+ if (pat != map->pattern && pat[-1] != '/')
+ return FALSE;
+ /* if we're not recursing, there should be only one $variable
+ left. */
+ if (recurse)
+ return TRUE;
+ return pat[0] == '$' && strchr(pat, '/') == NULL;
+ }
+}
+
+static const struct dict_sql_map *
+sql_dict_find_map(struct sql_dict *dict, const char *path,
+ ARRAY_TYPE(const_string) *pattern_values)
+{
+ const struct dict_sql_map *maps;
+ unsigned int i, count;
+ size_t len;
+
+ t_array_init(pattern_values, dict->set->max_pattern_fields_count);
+ maps = array_get(&dict->set->maps, &count);
+ for (i = 0; i < count; i++) {
+ if (dict_sql_map_match(&maps[i], path, pattern_values,
+ &len, &len, FALSE, FALSE))
+ return &maps[i];
+ }
+ return NULL;
+}
+
+static void
+sql_dict_statement_bind(struct sql_statement *stmt, unsigned int column_idx,
+ const struct sql_dict_param *param)
+{
+ switch (param->value_type) {
+ case DICT_SQL_TYPE_STRING:
+ sql_statement_bind_str(stmt, column_idx, param->value_str);
+ break;
+ case DICT_SQL_TYPE_INT:
+ case DICT_SQL_TYPE_UINT:
+ sql_statement_bind_int64(stmt, column_idx, param->value_int64);
+ break;
+ case DICT_SQL_TYPE_HEXBLOB:
+ sql_statement_bind_binary(stmt, column_idx, param->value_binary,
+ param->value_binary_size);
+ break;
+ }
+}
+
+static struct sql_statement *
+sql_dict_statement_init(struct sql_dict *dict, const char *query,
+ const ARRAY_TYPE(sql_dict_param) *params)
+{
+ struct sql_statement *stmt;
+ struct sql_prepared_statement *prep_stmt;
+ const struct sql_dict_param *param;
+
+ if ((sql_get_flags(dict->db) & SQL_DB_FLAG_PREP_STATEMENTS) != 0) {
+ prep_stmt = sql_prepared_statement_init(dict->db, query);
+ stmt = sql_statement_init_prepared(prep_stmt);
+ sql_prepared_statement_unref(&prep_stmt);
+ } else {
+ /* Prepared statements not supported by the backend.
+ Just use regular statements to avoid wasting memory. */
+ stmt = sql_statement_init(dict->db, query);
+ }
+
+ array_foreach(params, param) {
+ sql_dict_statement_bind(stmt, array_foreach_idx(params, param),
+ param);
+ }
+ return stmt;
+}
+
+static int
+sql_dict_value_get(const struct dict_sql_map *map,
+ enum dict_sql_type value_type, const char *field_name,
+ const char *value, const char *value_suffix,
+ ARRAY_TYPE(sql_dict_param) *params, const char **error_r)
+{
+ struct sql_dict_param *param;
+ buffer_t *buf;
+
+ param = array_append_space(params);
+ param->value_type = value_type;
+
+ switch (value_type) {
+ case DICT_SQL_TYPE_STRING:
+ if (value_suffix[0] != '\0')
+ value = t_strconcat(value, value_suffix, NULL);
+ param->value_str = value;
+ return 0;
+ case DICT_SQL_TYPE_INT:
+ if (value_suffix[0] != '\0' ||
+ str_to_int64(value, &param->value_int64) < 0) {
+ *error_r = t_strdup_printf(
+ "%s field's value isn't 64bit signed integer: %s%s (in pattern: %s)",
+ field_name, value, value_suffix, map->pattern);
+ return -1;
+ }
+ return 0;
+ case DICT_SQL_TYPE_UINT:
+ if (value_suffix[0] != '\0' || value[0] == '-' ||
+ str_to_int64(value, &param->value_int64) < 0) {
+ *error_r = t_strdup_printf(
+ "%s field's value isn't 64bit unsigned integer: %s%s (in pattern: %s)",
+ field_name, value, value_suffix, map->pattern);
+ return -1;
+ }
+ return 0;
+ case DICT_SQL_TYPE_HEXBLOB:
+ break;
+ }
+
+ buf = t_buffer_create(strlen(value)/2);
+ if (hex_to_binary(value, buf) < 0) {
+ /* we shouldn't get untrusted input here. it's also a bit
+ annoying to handle this error. */
+ *error_r = t_strdup_printf("%s field's value isn't hexblob: %s (in pattern: %s)",
+ field_name, value, map->pattern);
+ return -1;
+ }
+ str_append(buf, value_suffix);
+ param->value_binary = buf->data;
+ param->value_binary_size = buf->used;
+ return 0;
+}
+
+static int
+sql_dict_field_get_value(const struct dict_sql_map *map,
+ const struct dict_sql_field *field,
+ const char *value, const char *value_suffix,
+ ARRAY_TYPE(sql_dict_param) *params,
+ const char **error_r)
+{
+ return sql_dict_value_get(map, field->value_type, field->name,
+ value, value_suffix, params, error_r);
+}
+
+static int
+sql_dict_where_build(const char *username, const struct dict_sql_map *map,
+ const ARRAY_TYPE(const_string) *values_arr,
+ bool add_username, enum sql_recurse_type recurse_type,
+ string_t *query, ARRAY_TYPE(sql_dict_param) *params,
+ const char **error_r)
+{
+ const struct dict_sql_field *pattern_fields;
+ const char *const *pattern_values;
+ unsigned int i, count, count2, exact_count;
+
+ pattern_fields = array_get(&map->pattern_fields, &count);
+ pattern_values = array_get(values_arr, &count2);
+ /* if we came here from iteration code there may be fewer
+ pattern_values */
+ i_assert(count2 <= count);
+
+ if (count2 == 0 && !add_username) {
+ /* we want everything */
+ return 0;
+ }
+
+ str_append(query, " WHERE");
+ exact_count = count == count2 && recurse_type != SQL_DICT_RECURSE_NONE ?
+ count2-1 : count2;
+ if (exact_count != array_count(values_arr)) {
+ *error_r = t_strdup_printf("Key continues past the matched pattern %s", map->pattern);
+ return -1;
+ }
+
+ for (i = 0; i < exact_count; i++) {
+ if (i > 0)
+ str_append(query, " AND");
+ str_printfa(query, " %s = ?", pattern_fields[i].name);
+ if (sql_dict_field_get_value(map, &pattern_fields[i],
+ pattern_values[i], "",
+ params, error_r) < 0)
+ return -1;
+ }
+ switch (recurse_type) {
+ case SQL_DICT_RECURSE_NONE:
+ break;
+ case SQL_DICT_RECURSE_ONE:
+ if (i > 0)
+ str_append(query, " AND");
+ if (i < count2) {
+ str_printfa(query, " %s LIKE ?", pattern_fields[i].name);
+ if (sql_dict_field_get_value(map, &pattern_fields[i],
+ pattern_values[i], "/%",
+ params, error_r) < 0)
+ return -1;
+ str_printfa(query, " AND %s NOT LIKE ?", pattern_fields[i].name);
+ if (sql_dict_field_get_value(map, &pattern_fields[i],
+ pattern_values[i], "/%/%",
+ params, error_r) < 0)
+ return -1;
+ } else {
+ str_printfa(query, " %s LIKE '%%' AND "
+ "%s NOT LIKE '%%/%%'",
+ pattern_fields[i].name,
+ pattern_fields[i].name);
+ }
+ break;
+ case SQL_DICT_RECURSE_FULL:
+ if (i < count2) {
+ if (i > 0)
+ str_append(query, " AND");
+ str_printfa(query, " %s LIKE ",
+ pattern_fields[i].name);
+ if (sql_dict_field_get_value(map, &pattern_fields[i],
+ pattern_values[i], "/%",
+ params, error_r) < 0)
+ return -1;
+ }
+ break;
+ }
+ if (add_username) {
+ struct sql_dict_param *param = array_append_space(params);
+ if (count2 > 0)
+ str_append(query, " AND");
+ str_printfa(query, " %s = ?", map->username_field);
+ param->value_type = DICT_SQL_TYPE_STRING;
+ param->value_str = t_strdup(username);
+ }
+ return 0;
+}
+
+static int
+sql_lookup_get_query(struct sql_dict *dict,
+ const struct dict_op_settings *set,
+ const char *key,
+ const struct dict_sql_map **map_r,
+ struct sql_statement **stmt_r,
+ const char **error_r)
+{
+ const struct dict_sql_map *map;
+ ARRAY_TYPE(const_string) pattern_values;
+ const char *error;
+
+ map = *map_r = sql_dict_find_map(dict, key, &pattern_values);
+ if (map == NULL) {
+ *error_r = t_strdup_printf(
+ "sql dict lookup: Invalid/unmapped key: %s", key);
+ return -1;
+ }
+
+ string_t *query = t_str_new(256);
+ ARRAY_TYPE(sql_dict_param) params;
+ t_array_init(&params, 4);
+ str_printfa(query, "SELECT %s FROM %s",
+ map->value_field, map->table);
+ if (sql_dict_where_build(set->username, map, &pattern_values,
+ key[0] == DICT_PATH_PRIVATE[0],
+ SQL_DICT_RECURSE_NONE, query,
+ &params, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "sql dict lookup: Failed to lookup key %s: %s", key, error);
+ return -1;
+ }
+ *stmt_r = sql_dict_statement_init(dict, str_c(query), &params);
+ return 0;
+}
+
+static const char *
+sql_dict_result_unescape(enum dict_sql_type type, pool_t pool,
+ struct sql_result *result, unsigned int result_idx)
+{
+ const unsigned char *data;
+ size_t size;
+ const char *value;
+ string_t *str;
+
+ switch (type) {
+ case DICT_SQL_TYPE_STRING:
+ case DICT_SQL_TYPE_INT:
+ case DICT_SQL_TYPE_UINT:
+ value = sql_result_get_field_value(result, result_idx);
+ return value == NULL ? "" : p_strdup(pool, value);
+ case DICT_SQL_TYPE_HEXBLOB:
+ break;
+ }
+
+ data = sql_result_get_field_value_binary(result, result_idx, &size);
+ str = str_new(pool, size*2 + 1);
+ binary_to_hex_append(str, data, size);
+ return str_c(str);
+}
+
+static const char *
+sql_dict_result_unescape_value(const struct dict_sql_map *map, pool_t pool,
+ struct sql_result *result)
+{
+ return sql_dict_result_unescape(map->value_types[0], pool, result, 0);
+}
+
+static const char *const *
+sql_dict_result_unescape_values(const struct dict_sql_map *map, pool_t pool,
+ struct sql_result *result)
+{
+ const char **values;
+ unsigned int i;
+
+ values = p_new(pool, const char *, map->values_count + 1);
+ for (i = 0; i < map->values_count; i++) {
+ values[i] = sql_dict_result_unescape(map->value_types[i],
+ pool, result, i);
+ }
+ return values;
+}
+
+static const char *
+sql_dict_result_unescape_field(const struct dict_sql_map *map, pool_t pool,
+ struct sql_result *result, unsigned int result_idx,
+ unsigned int sql_field_idx)
+{
+ const struct dict_sql_field *sql_field;
+
+ sql_field = array_idx(&map->pattern_fields, sql_field_idx);
+ return sql_dict_result_unescape(sql_field->value_type, pool,
+ result, result_idx);
+}
+
+static int sql_dict_lookup(struct dict *_dict, const struct dict_op_settings *set,
+ pool_t pool, const char *key,
+ const char **value_r, const char **error_r)
+{
+ struct sql_dict *dict = (struct sql_dict *)_dict;
+ const struct dict_sql_map *map;
+ struct sql_statement *stmt;
+ struct sql_result *result = NULL;
+ int ret;
+
+ *value_r = NULL;
+
+ if (sql_lookup_get_query(dict, set, key, &map, &stmt, error_r) < 0)
+ return -1;
+
+ result = sql_statement_query_s(&stmt);
+ ret = sql_result_next_row(result);
+ if (ret < 0) {
+ *error_r = t_strdup_printf("dict sql lookup failed: %s",
+ sql_result_get_error(result));
+ } else if (ret > 0) {
+ *value_r = sql_dict_result_unescape_value(map, pool, result);
+ }
+
+ sql_result_unref(result);
+ return ret;
+}
+
+struct sql_dict_lookup_context {
+ const struct dict_sql_map *map;
+ dict_lookup_callback_t *callback;
+ void *context;
+};
+
+static void
+sql_dict_lookup_async_callback(struct sql_result *sql_result,
+ struct sql_dict_lookup_context *ctx)
+{
+ struct dict_lookup_result result;
+
+ i_zero(&result);
+ result.ret = sql_result_next_row(sql_result);
+ if (result.ret < 0)
+ result.error = sql_result_get_error(sql_result);
+ else if (result.ret > 0) {
+ result.values = sql_dict_result_unescape_values(ctx->map,
+ pool_datastack_create(), sql_result);
+ result.value = result.values[0];
+ if (result.value == NULL) {
+ /* NULL value returned. we'll treat this as
+ "not found", which is probably what is usually
+ wanted. */
+ result.ret = 0;
+ }
+ }
+ ctx->callback(&result, ctx->context);
+
+ i_free(ctx);
+}
+
+static void
+sql_dict_lookup_async(struct dict *_dict,
+ const struct dict_op_settings *set,
+ const char *key,
+ dict_lookup_callback_t *callback, void *context)
+{
+ struct sql_dict *dict = (struct sql_dict *)_dict;
+ const struct dict_sql_map *map;
+ struct sql_dict_lookup_context *ctx;
+ struct sql_statement *stmt;
+ const char *error;
+
+ if (sql_lookup_get_query(dict, set, key, &map, &stmt, &error) < 0) {
+ struct dict_lookup_result result;
+
+ i_zero(&result);
+ result.ret = -1;
+ result.error = error;
+ callback(&result, context);
+ } else {
+ ctx = i_new(struct sql_dict_lookup_context, 1);
+ ctx->callback = callback;
+ ctx->context = context;
+ ctx->map = map;
+ sql_statement_query(&stmt, sql_dict_lookup_async_callback, ctx);
+ }
+}
+
+static const struct dict_sql_map *
+sql_dict_iterate_find_next_map(struct sql_dict_iterate_context *ctx,
+ ARRAY_TYPE(const_string) *pattern_values)
+{
+ struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict;
+ const struct dict_sql_map *maps;
+ unsigned int i, count;
+ size_t pat_len, path_len;
+ bool recurse = (ctx->flags & DICT_ITERATE_FLAG_RECURSE) != 0;
+
+ t_array_init(pattern_values, dict->set->max_pattern_fields_count);
+ maps = array_get(&dict->set->maps, &count);
+ for (i = ctx->next_map_idx; i < count; i++) {
+ if (dict_sql_map_match(&maps[i], ctx->path,
+ pattern_values, &pat_len, &path_len,
+ TRUE, recurse) &&
+ (recurse ||
+ array_count(pattern_values)+1 >= array_count(&maps[i].pattern_fields))) {
+ ctx->key_prefix_len = path_len;
+ ctx->pattern_prefix_len = pat_len;
+ ctx->next_map_idx = i + 1;
+
+ str_truncate(ctx->key, 0);
+ str_append(ctx->key, ctx->path);
+ return &maps[i];
+ }
+ }
+ return NULL;
+}
+
+static int
+sql_dict_iterate_build_next_query(struct sql_dict_iterate_context *ctx,
+ struct sql_statement **stmt_r,
+ const char **error_r)
+{
+ struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict;
+ const struct dict_op_settings_private *set = &ctx->ctx.set;
+ const struct dict_sql_map *map;
+ ARRAY_TYPE(const_string) pattern_values;
+ const struct dict_sql_field *pattern_fields;
+ enum sql_recurse_type recurse_type;
+ unsigned int i, count;
+
+ map = sql_dict_iterate_find_next_map(ctx, &pattern_values);
+ /* NULL map is allowed if we have already done some lookups */
+ if (map == NULL) {
+ if (!ctx->allow_null_map) {
+ *error_r = "Invalid/unmapped path";
+ return -1;
+ }
+ return 0;
+ }
+
+ if (ctx->result != NULL) {
+ sql_result_unref(ctx->result);
+ ctx->result = NULL;
+ }
+
+ string_t *query = t_str_new(256);
+ str_append(query, "SELECT ");
+ if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) == 0)
+ str_printfa(query, "%s,", map->value_field);
+
+ /* get all missing fields */
+ pattern_fields = array_get(&map->pattern_fields, &count);
+ i = array_count(&pattern_values);
+ if (i == count) {
+ /* we always want to know the last field since we're
+ iterating its children */
+ i_assert(i > 0);
+ i--;
+ }
+ ctx->sql_fields_start_idx = i;
+ for (; i < count; i++)
+ str_printfa(query, "%s,", pattern_fields[i].name);
+ str_truncate(query, str_len(query)-1);
+
+ str_printfa(query, " FROM %s", map->table);
+
+ if ((ctx->flags & DICT_ITERATE_FLAG_RECURSE) != 0)
+ recurse_type = SQL_DICT_RECURSE_FULL;
+ else if ((ctx->flags & DICT_ITERATE_FLAG_EXACT_KEY) != 0)
+ recurse_type = SQL_DICT_RECURSE_NONE;
+ else
+ recurse_type = SQL_DICT_RECURSE_ONE;
+
+ ARRAY_TYPE(sql_dict_param) params;
+ t_array_init(&params, 4);
+ bool add_username = (ctx->path[0] == DICT_PATH_PRIVATE[0]);
+ if (sql_dict_where_build(set->username, map, &pattern_values, add_username,
+ recurse_type, query, &params, error_r) < 0)
+ return -1;
+
+ if ((ctx->flags & DICT_ITERATE_FLAG_SORT_BY_KEY) != 0) {
+ str_append(query, " ORDER BY ");
+ for (i = 0; i < count; i++) {
+ str_printfa(query, "%s", pattern_fields[i].name);
+ if (i < count-1)
+ str_append_c(query, ',');
+ }
+ } else if ((ctx->flags & DICT_ITERATE_FLAG_SORT_BY_VALUE) != 0)
+ str_printfa(query, " ORDER BY %s", map->value_field);
+
+ if (ctx->ctx.max_rows > 0) {
+ i_assert(ctx->ctx.row_count < ctx->ctx.max_rows);
+ str_printfa(query, " LIMIT %"PRIu64,
+ ctx->ctx.max_rows - ctx->ctx.row_count);
+ }
+
+ *stmt_r = sql_dict_statement_init(dict, str_c(query), &params);
+ ctx->map = map;
+ return 1;
+}
+
+static void sql_dict_iterate_callback(struct sql_result *result,
+ struct sql_dict_iterate_context *ctx)
+{
+ if (!ctx->destroyed) {
+ sql_result_ref(result);
+ ctx->result = result;
+ if (ctx->ctx.async_callback != NULL && !ctx->synchronous_result)
+ ctx->ctx.async_callback(ctx->ctx.async_context);
+ }
+
+ pool_t pool_copy = ctx->pool;
+ pool_unref(&pool_copy);
+}
+
+static int sql_dict_iterate_next_query(struct sql_dict_iterate_context *ctx)
+{
+ struct sql_statement *stmt;
+ const char *error;
+ int ret;
+
+ ret = sql_dict_iterate_build_next_query(ctx, &stmt, &error);
+ if (ret <= 0) {
+ /* this is expected error */
+ if (ret == 0)
+ return ret;
+ /* failed */
+ ctx->error = p_strdup_printf(ctx->pool,
+ "sql dict iterate failed for %s: %s",
+ ctx->path, error);
+ return -1;
+ }
+
+ if ((ctx->flags & DICT_ITERATE_FLAG_ASYNC) == 0) {
+ ctx->result = sql_statement_query_s(&stmt);
+ } else {
+ i_assert(ctx->result == NULL);
+ ctx->synchronous_result = TRUE;
+ pool_ref(ctx->pool);
+ sql_statement_query(&stmt, sql_dict_iterate_callback, ctx);
+ ctx->synchronous_result = FALSE;
+ }
+ return ret;
+}
+
+static struct dict_iterate_context *
+sql_dict_iterate_init(struct dict *_dict,
+ const struct dict_op_settings *set ATTR_UNUSED,
+ const char *path, enum dict_iterate_flags flags)
+{
+ struct sql_dict_iterate_context *ctx;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sql dict iterate", 512);
+ ctx = p_new(pool, struct sql_dict_iterate_context, 1);
+ ctx->ctx.dict = _dict;
+ ctx->pool = pool;
+ ctx->flags = flags;
+
+ ctx->path = p_strdup(pool, path);
+
+ ctx->key = str_new(pool, 256);
+ return &ctx->ctx;
+}
+
+static bool sql_dict_iterate(struct dict_iterate_context *_ctx,
+ const char **key_r, const char *const **values_r)
+{
+ struct sql_dict_iterate_context *ctx =
+ (struct sql_dict_iterate_context *)_ctx;
+ const char *p, *value;
+ unsigned int i, sql_field_i, count;
+ int ret;
+
+ _ctx->has_more = FALSE;
+ if (ctx->error != NULL)
+ return FALSE;
+ if (!ctx->iter_query_sent) {
+ ctx->iter_query_sent = TRUE;
+ if (sql_dict_iterate_next_query(ctx) <= 0)
+ return FALSE;
+ }
+
+ if (ctx->result == NULL) {
+ /* wait for async lookup to finish */
+ i_assert((ctx->flags & DICT_ITERATE_FLAG_ASYNC) != 0);
+ _ctx->has_more = TRUE;
+ return FALSE;
+ }
+
+ ret = sql_result_next_row(ctx->result);
+ while (ret == SQL_RESULT_NEXT_MORE) {
+ if ((ctx->flags & DICT_ITERATE_FLAG_ASYNC) == 0)
+ sql_result_more_s(&ctx->result);
+ else {
+ /* get more results asynchronously */
+ ctx->synchronous_result = TRUE;
+ pool_ref(ctx->pool);
+ sql_result_more(&ctx->result, sql_dict_iterate_callback, ctx);
+ ctx->synchronous_result = FALSE;
+ if (ctx->result == NULL) {
+ _ctx->has_more = TRUE;
+ return FALSE;
+ }
+ }
+ ret = sql_result_next_row(ctx->result);
+ }
+ if (ret == 0) {
+ /* see if there are more results in the next map.
+ don't do it if we're looking for an exact match, since we
+ already should have handled it. */
+ if ((ctx->flags & DICT_ITERATE_FLAG_EXACT_KEY) != 0)
+ return FALSE;
+ ctx->iter_query_sent = FALSE;
+ /* we have gotten *SOME* results, so can allow
+ unmapped next key now. */
+ ctx->allow_null_map = TRUE;
+ return sql_dict_iterate(_ctx, key_r, values_r);
+ }
+ if (ret < 0) {
+ ctx->error = p_strdup_printf(ctx->pool,
+ "dict sql iterate failed: %s",
+ sql_result_get_error(ctx->result));
+ return FALSE;
+ }
+
+ /* convert fetched row to dict key */
+ str_truncate(ctx->key, ctx->key_prefix_len);
+ if (ctx->key_prefix_len > 0 &&
+ str_c(ctx->key)[ctx->key_prefix_len-1] != '/')
+ str_append_c(ctx->key, '/');
+
+ count = sql_result_get_fields_count(ctx->result);
+ i = (ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) != 0 ? 0 :
+ ctx->map->values_count;
+ sql_field_i = ctx->sql_fields_start_idx;
+ for (p = ctx->map->pattern + ctx->pattern_prefix_len; *p != '\0'; p++) {
+ if (*p != '$')
+ str_append_c(ctx->key, *p);
+ else {
+ i_assert(i < count);
+ value = sql_dict_result_unescape_field(ctx->map,
+ pool_datastack_create(), ctx->result, i, sql_field_i);
+ if (value != NULL)
+ str_append(ctx->key, value);
+ i++; sql_field_i++;
+ }
+ }
+
+ *key_r = str_c(ctx->key);
+ if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) == 0) {
+ *values_r = sql_dict_result_unescape_values(ctx->map,
+ pool_datastack_create(), ctx->result);
+ }
+ return TRUE;
+}
+
+static int sql_dict_iterate_deinit(struct dict_iterate_context *_ctx,
+ const char **error_r)
+{
+ struct sql_dict_iterate_context *ctx =
+ (struct sql_dict_iterate_context *)_ctx;
+ int ret = ctx->error != NULL ? -1 : 0;
+
+ *error_r = t_strdup(ctx->error);
+ if (ctx->result != NULL)
+ sql_result_unref(ctx->result);
+ ctx->destroyed = TRUE;
+
+ pool_t pool_copy = ctx->pool;
+ pool_unref(&pool_copy);
+ return ret;
+}
+
+static struct dict_transaction_context *
+sql_dict_transaction_init(struct dict *_dict)
+{
+ struct sql_dict *dict = (struct sql_dict *)_dict;
+ struct sql_dict_transaction_context *ctx;
+
+ ctx = i_new(struct sql_dict_transaction_context, 1);
+ ctx->ctx.dict = _dict;
+ ctx->sql_ctx = sql_transaction_begin(dict->db);
+
+ return &ctx->ctx;
+}
+
+static void sql_dict_transaction_free(struct sql_dict_transaction_context *ctx)
+{
+ if (array_is_created(&ctx->prev_inc))
+ sql_dict_prev_inc_free(ctx);
+ if (array_is_created(&ctx->prev_set))
+ sql_dict_prev_set_free(ctx);
+
+ pool_unref(&ctx->inc_row_pool);
+ i_free(ctx->error);
+ i_free(ctx);
+}
+
+static bool
+sql_dict_transaction_has_nonexistent(struct sql_dict_transaction_context *ctx)
+{
+ struct sql_dict_inc_row *inc_row;
+
+ for (inc_row = ctx->inc_row; inc_row != NULL; inc_row = inc_row->prev) {
+ i_assert(inc_row->rows != UINT_MAX);
+ if (inc_row->rows == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+sql_dict_transaction_commit_callback(const struct sql_commit_result *sql_result,
+ struct sql_dict_transaction_context *ctx)
+{
+ struct dict_commit_result result;
+
+ i_zero(&result);
+ if (sql_result->error == NULL)
+ result.ret = sql_dict_transaction_has_nonexistent(ctx) ?
+ DICT_COMMIT_RET_NOTFOUND : DICT_COMMIT_RET_OK;
+ else {
+ result.error = t_strdup_printf("sql dict: commit failed: %s",
+ sql_result->error);
+ switch (sql_result->error_type) {
+ case SQL_RESULT_ERROR_TYPE_UNKNOWN:
+ default:
+ result.ret = DICT_COMMIT_RET_FAILED;
+ break;
+ case SQL_RESULT_ERROR_TYPE_WRITE_UNCERTAIN:
+ result.ret = DICT_COMMIT_RET_WRITE_UNCERTAIN;
+ break;
+ }
+ }
+
+ if (ctx->async_callback != NULL)
+ ctx->async_callback(&result, ctx->async_context);
+ else if (result.ret < 0)
+ i_error("%s", result.error);
+ sql_dict_transaction_free(ctx);
+}
+
+static void
+sql_dict_transaction_commit(struct dict_transaction_context *_ctx, bool async,
+ dict_transaction_commit_callback_t *callback,
+ void *context)
+{
+ struct sql_dict_transaction_context *ctx =
+ (struct sql_dict_transaction_context *)_ctx;
+ const char *error;
+ struct dict_commit_result result;
+
+ /* flush any pending set/inc */
+ if (array_is_created(&ctx->prev_inc))
+ sql_dict_prev_inc_flush(ctx);
+ if (array_is_created(&ctx->prev_set))
+ sql_dict_prev_set_flush(ctx);
+
+ /* note that the above calls might still set ctx->error */
+ i_zero(&result);
+ result.ret = DICT_COMMIT_RET_FAILED;
+ result.error = t_strdup(ctx->error);
+
+ if (ctx->error != NULL) {
+ sql_transaction_rollback(&ctx->sql_ctx);
+ } else if (!_ctx->changed) {
+ /* nothing changed, no need to commit */
+ sql_transaction_rollback(&ctx->sql_ctx);
+ result.ret = DICT_COMMIT_RET_OK;
+ } else if (async) {
+ ctx->async_callback = callback;
+ ctx->async_context = context;
+ sql_transaction_commit(&ctx->sql_ctx,
+ sql_dict_transaction_commit_callback, ctx);
+ return;
+ } else if (sql_transaction_commit_s(&ctx->sql_ctx, &error) < 0) {
+ result.error = t_strdup_printf(
+ "sql dict: commit failed: %s", error);
+ } else {
+ if (sql_dict_transaction_has_nonexistent(ctx))
+ result.ret = DICT_COMMIT_RET_NOTFOUND;
+ else
+ result.ret = DICT_COMMIT_RET_OK;
+ }
+ sql_dict_transaction_free(ctx);
+
+ callback(&result, context);
+}
+
+static void sql_dict_transaction_rollback(struct dict_transaction_context *_ctx)
+{
+ struct sql_dict_transaction_context *ctx =
+ (struct sql_dict_transaction_context *)_ctx;
+
+ sql_transaction_rollback(&ctx->sql_ctx);
+ sql_dict_transaction_free(ctx);
+}
+
+static struct sql_statement *
+sql_dict_transaction_stmt_init(struct sql_dict_transaction_context *ctx,
+ const char *query,
+ const ARRAY_TYPE(sql_dict_param) *params)
+{
+ struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict;
+ struct sql_statement *stmt =
+ sql_dict_statement_init(dict, query, params);
+
+ if (ctx->ctx.timestamp.tv_sec != 0)
+ sql_statement_set_timestamp(stmt, &ctx->ctx.timestamp);
+ return stmt;
+}
+
+struct dict_sql_build_query_field {
+ const struct dict_sql_map *map;
+ const char *value;
+};
+
+struct dict_sql_build_query {
+ struct sql_dict *dict;
+
+ ARRAY(struct dict_sql_build_query_field) fields;
+ const ARRAY_TYPE(const_string) *pattern_values;
+ bool add_username;
+};
+
+static int sql_dict_set_query(struct sql_dict_transaction_context *ctx,
+ const struct dict_sql_build_query *build,
+ struct sql_statement **stmt_r,
+ const char **error_r)
+{
+ struct sql_dict *dict = build->dict;
+ const struct dict_sql_build_query_field *fields;
+ const struct dict_sql_field *pattern_fields;
+ ARRAY_TYPE(sql_dict_param) params;
+ const char *const *pattern_values;
+ unsigned int i, field_count, count, count2;
+ string_t *prefix, *suffix;
+
+ fields = array_get(&build->fields, &field_count);
+ i_assert(field_count > 0);
+
+ t_array_init(&params, 4);
+ prefix = t_str_new(64);
+ suffix = t_str_new(256);
+ /* SQL table is guaranteed to be the same for all fields.
+ Build all the SQL field names into prefix and '?' placeholders for
+ each value into the suffix. The actual field values will be added
+ into params[]. */
+ str_printfa(prefix, "INSERT INTO %s", fields[0].map->table);
+ str_append(prefix, " (");
+ str_append(suffix, ") VALUES (");
+ for (i = 0; i < field_count; i++) {
+ if (i > 0) {
+ str_append_c(prefix, ',');
+ str_append_c(suffix, ',');
+ }
+ str_append(prefix, t_strcut(fields[i].map->value_field, ','));
+
+ enum dict_sql_type value_type =
+ fields[i].map->value_types[0];
+ str_append_c(suffix, '?');
+ if (sql_dict_value_get(fields[i].map,
+ value_type, "value", fields[i].value,
+ "", &params, error_r) < 0)
+ return -1;
+ }
+ if (build->add_username) {
+ struct sql_dict_param *param = array_append_space(&params);
+ str_printfa(prefix, ",%s", fields[0].map->username_field);
+ str_append(suffix, ",?");
+ param->value_type = DICT_SQL_TYPE_STRING;
+ param->value_str = ctx->ctx.set.username;
+ }
+
+ /* add the variable fields that were parsed from the path */
+ pattern_fields = array_get(&fields[0].map->pattern_fields, &count);
+ pattern_values = array_get(build->pattern_values, &count2);
+ i_assert(count == count2);
+ for (i = 0; i < count; i++) {
+ str_printfa(prefix, ",%s", pattern_fields[i].name);
+ str_append(suffix, ",?");
+ if (sql_dict_field_get_value(fields[0].map, &pattern_fields[i],
+ pattern_values[i], "",
+ &params, error_r) < 0)
+ return -1;
+ }
+
+ str_append_str(prefix, suffix);
+ str_append_c(prefix, ')');
+
+ enum sql_db_flags flags = sql_get_flags(dict->db);
+ if ((flags & SQL_DB_FLAG_ON_DUPLICATE_KEY) != 0)
+ str_append(prefix, " ON DUPLICATE KEY UPDATE ");
+ else if ((flags & SQL_DB_FLAG_ON_CONFLICT_DO) != 0) {
+ str_append(prefix, " ON CONFLICT (");
+ for (i = 0; i < count; i++) {
+ if (i > 0)
+ str_append_c(prefix, ',');
+ str_append(prefix, pattern_fields[i].name);
+ }
+ if (build->add_username) {
+ if (count > 0)
+ str_append_c(prefix, ',');
+ str_append(prefix, fields[0].map->username_field);
+ }
+ str_append(prefix, ") DO UPDATE SET ");
+ } else {
+ *stmt_r = sql_dict_transaction_stmt_init(ctx, str_c(prefix), &params);
+ return 0;
+ }
+
+ /* If the row already exists, UPDATE it instead. The pattern_values
+ don't need to be updated here, because they are expected to be part
+ of the row's primary key. */
+ for (i = 0; i < field_count; i++) {
+ const char *first_value_field =
+ t_strcut(fields[i].map->value_field, ',');
+ if (i > 0)
+ str_append_c(prefix, ',');
+ str_append(prefix, first_value_field);
+ str_append_c(prefix, '=');
+
+ enum dict_sql_type value_type =
+ fields[i].map->value_types[0];
+ str_append_c(prefix, '?');
+ if (sql_dict_value_get(fields[i].map,
+ value_type, "value", fields[i].value,
+ "", &params, error_r) < 0)
+ return -1;
+ }
+ *stmt_r = sql_dict_transaction_stmt_init(ctx, str_c(prefix), &params);
+ return 0;
+}
+
+static int
+sql_dict_update_query(const struct dict_sql_build_query *build,
+ const struct dict_op_settings_private *set,
+ const char **query_r, ARRAY_TYPE(sql_dict_param) *params,
+ const char **error_r)
+{
+ const struct dict_sql_build_query_field *fields;
+ unsigned int i, field_count;
+ string_t *query;
+
+ fields = array_get(&build->fields, &field_count);
+ i_assert(field_count > 0);
+
+ query = t_str_new(64);
+ str_printfa(query, "UPDATE %s SET ", fields[0].map->table);
+ for (i = 0; i < field_count; i++) {
+ const char *first_value_field =
+ t_strcut(fields[i].map->value_field, ',');
+ if (i > 0)
+ str_append_c(query, ',');
+ str_printfa(query, "%s=%s+?", first_value_field,
+ first_value_field);
+ }
+
+ if (sql_dict_where_build(set->username, fields[0].map, build->pattern_values,
+ build->add_username, SQL_DICT_RECURSE_NONE,
+ query, params, error_r) < 0)
+ return -1;
+ *query_r = str_c(query);
+ return 0;
+}
+
+static void sql_dict_prev_set_free(struct sql_dict_transaction_context *ctx)
+{
+ struct sql_dict_prev *prev_set;
+
+ array_foreach_modifiable(&ctx->prev_set, prev_set) {
+ i_free(prev_set->value.str);
+ i_free(prev_set->key);
+ }
+ array_free(&ctx->prev_set);
+}
+
+static void sql_dict_prev_set_flush(struct sql_dict_transaction_context *ctx)
+{
+ struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict;
+ const struct sql_dict_prev *prev_sets;
+ unsigned int count;
+ struct sql_statement *stmt;
+ ARRAY_TYPE(const_string) pattern_values;
+ struct dict_sql_build_query build;
+ struct dict_sql_build_query_field *field;
+ const char *error;
+
+ i_assert(array_is_created(&ctx->prev_set));
+
+ if (ctx->error != NULL) {
+ sql_dict_prev_set_free(ctx);
+ return;
+ }
+
+ prev_sets = array_get(&ctx->prev_set, &count);
+ i_assert(count > 0);
+
+ /* Get the variable values from the dict path. We already verified that
+ these are all exactly the same for everything in prev_sets. */
+ if (sql_dict_find_map(dict, prev_sets[0].key, &pattern_values) == NULL)
+ i_unreached(); /* this was already checked */
+
+ i_zero(&build);
+ build.dict = dict;
+ build.pattern_values = &pattern_values;
+ build.add_username = (prev_sets[0].key[0] == DICT_PATH_PRIVATE[0]);
+
+ /* build.fields[] is used to get the map { value_field } for the
+ SQL field names, as well as the values for them.
+
+ Example: INSERT INTO ... (build.fields[0].map->value_field,
+ ...[1], ...) VALUES (build.fields[0].value, ...[1], ...) */
+ t_array_init(&build.fields, count);
+ for (unsigned int i = 0; i < count; i++) {
+ i_assert(build.add_username ==
+ (prev_sets[i].key[0] == DICT_PATH_PRIVATE[0]));
+ field = array_append_space(&build.fields);
+ field->map = prev_sets[i].map;
+ field->value = prev_sets[i].value.str;
+ }
+
+ if (sql_dict_set_query(ctx, &build, &stmt, &error) < 0) {
+ ctx->error = i_strdup_printf(
+ "dict-sql: Failed to set %u fields (first %s): %s",
+ count, prev_sets[0].key, error);
+ } else {
+ sql_update_stmt(ctx->sql_ctx, &stmt);
+ }
+ sql_dict_prev_set_free(ctx);
+}
+
+static void sql_dict_unset(struct dict_transaction_context *_ctx,
+ const char *key)
+{
+ struct sql_dict_transaction_context *ctx =
+ (struct sql_dict_transaction_context *)_ctx;
+ struct sql_dict *dict = (struct sql_dict *)_ctx->dict;
+ const struct dict_op_settings_private *set = &_ctx->set;
+ const struct dict_sql_map *map;
+ ARRAY_TYPE(const_string) pattern_values;
+ string_t *query = t_str_new(256);
+ ARRAY_TYPE(sql_dict_param) params;
+ const char *error;
+
+ if (ctx->error != NULL)
+ return;
+
+ /* In theory we could unset one of the previous set/incs in this
+ same transaction, so flush them first. */
+ if (array_is_created(&ctx->prev_inc))
+ sql_dict_prev_inc_flush(ctx);
+ if (array_is_created(&ctx->prev_set))
+ sql_dict_prev_set_flush(ctx);
+
+ map = sql_dict_find_map(dict, key, &pattern_values);
+ if (map == NULL) {
+ ctx->error = i_strdup_printf("dict-sql: Invalid/unmapped key: %s", key);
+ return;
+ }
+
+ str_printfa(query, "DELETE FROM %s", map->table);
+ t_array_init(&params, 4);
+ if (sql_dict_where_build(set->username, map, &pattern_values,
+ key[0] == DICT_PATH_PRIVATE[0],
+ SQL_DICT_RECURSE_NONE, query,
+ &params, &error) < 0) {
+ ctx->error = i_strdup_printf(
+ "dict-sql: Failed to delete %s: %s", key, error);
+ } else {
+ struct sql_statement *stmt =
+ sql_dict_transaction_stmt_init(ctx, str_c(query), &params);
+ sql_update_stmt(ctx->sql_ctx, &stmt);
+ }
+}
+
+static unsigned int *
+sql_dict_next_inc_row(struct sql_dict_transaction_context *ctx)
+{
+ struct sql_dict_inc_row *row;
+
+ if (ctx->inc_row_pool == NULL) {
+ ctx->inc_row_pool =
+ pool_alloconly_create("sql dict inc rows", 128);
+ }
+ row = p_new(ctx->inc_row_pool, struct sql_dict_inc_row, 1);
+ row->prev = ctx->inc_row;
+ row->rows = UINT_MAX;
+ ctx->inc_row = row;
+ return &row->rows;
+}
+
+static void sql_dict_prev_inc_free(struct sql_dict_transaction_context *ctx)
+{
+ struct sql_dict_prev *prev_inc;
+
+ array_foreach_modifiable(&ctx->prev_inc, prev_inc)
+ i_free(prev_inc->key);
+ array_free(&ctx->prev_inc);
+}
+
+static void sql_dict_prev_inc_flush(struct sql_dict_transaction_context *ctx)
+{
+ struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict;
+ const struct dict_op_settings_private *set = &ctx->ctx.set;
+ const struct sql_dict_prev *prev_incs;
+ unsigned int count;
+ ARRAY_TYPE(const_string) pattern_values;
+ struct dict_sql_build_query build;
+ struct dict_sql_build_query_field *field;
+ ARRAY_TYPE(sql_dict_param) params;
+ struct sql_dict_param *param;
+ const char *query, *error;
+
+ i_assert(array_is_created(&ctx->prev_inc));
+
+ if (ctx->error != NULL) {
+ sql_dict_prev_inc_free(ctx);
+ return;
+ }
+
+ prev_incs = array_get(&ctx->prev_inc, &count);
+ i_assert(count > 0);
+
+ /* Get the variable values from the dict path. We already verified that
+ these are all exactly the same for everything in prev_incs. */
+ if (sql_dict_find_map(dict, prev_incs[0].key, &pattern_values) == NULL)
+ i_unreached(); /* this was already checked */
+
+ i_zero(&build);
+ build.dict = dict;
+ build.pattern_values = &pattern_values;
+ build.add_username = (prev_incs[0].key[0] == DICT_PATH_PRIVATE[0]);
+
+ /* build.fields[] is an array of maps, which are used to get the
+ map { value_field } for the SQL field names.
+
+ params[] specifies the list of values to use for each field.
+
+ Example: UPDATE .. SET build.fields[0].map->value_field =
+ ...->value_field + params[0]->value_int64, ...[1]... */
+ t_array_init(&build.fields, count);
+ t_array_init(&params, count);
+ for (unsigned int i = 0; i < count; i++) {
+ i_assert(build.add_username ==
+ (prev_incs[i].key[0] == DICT_PATH_PRIVATE[0]));
+ field = array_append_space(&build.fields);
+ field->map = prev_incs[i].map;
+ field->value = NULL; /* unused */
+
+ param = array_append_space(&params);
+ param->value_type = DICT_SQL_TYPE_INT;
+ param->value_int64 = prev_incs[i].value.diff;
+ }
+
+ if (sql_dict_update_query(&build, set, &query, &params, &error) < 0) {
+ ctx->error = i_strdup_printf(
+ "dict-sql: Failed to increase %u fields (first %s): %s",
+ count, prev_incs[0].key, error);
+ } else {
+ struct sql_statement *stmt =
+ sql_dict_transaction_stmt_init(ctx, query, &params);
+ sql_update_stmt_get_rows(ctx->sql_ctx, &stmt,
+ sql_dict_next_inc_row(ctx));
+ }
+ sql_dict_prev_inc_free(ctx);
+}
+
+static bool
+sql_dict_maps_are_mergeable(struct sql_dict *dict,
+ const struct sql_dict_prev *prev1,
+ const struct dict_sql_map *map2,
+ const char *map2_key,
+ const ARRAY_TYPE(const_string) *map2_pattern_values)
+{
+ const struct dict_sql_map *map3;
+ ARRAY_TYPE(const_string) map1_pattern_values;
+
+ /* sql table names must equal */
+ if (strcmp(prev1->map->table, map2->table) != 0)
+ return FALSE;
+ /* private vs shared prefix must equal */
+ if (prev1->key[0] != map2_key[0])
+ return FALSE;
+ if (prev1->key[0] == DICT_PATH_PRIVATE[0]) {
+ /* for private keys, username must equal */
+ if (strcmp(prev1->map->username_field, map2->username_field) != 0)
+ return FALSE;
+ }
+
+ /* variable values in the paths must equal exactly */
+ map3 = sql_dict_find_map(dict, prev1->key, &map1_pattern_values);
+ i_assert(map3 == prev1->map);
+
+ return array_equal_fn(&map1_pattern_values, map2_pattern_values,
+ i_strcmp_p);
+}
+
+static void sql_dict_set(struct dict_transaction_context *_ctx,
+ const char *key, const char *value)
+{
+ struct sql_dict_transaction_context *ctx =
+ (struct sql_dict_transaction_context *)_ctx;
+ struct sql_dict *dict = (struct sql_dict *)_ctx->dict;
+ const struct dict_sql_map *map;
+ ARRAY_TYPE(const_string) pattern_values;
+
+ if (ctx->error != NULL)
+ return;
+
+ /* In theory we could set the previous inc in this same transaction,
+ so flush it first. */
+ if (array_is_created(&ctx->prev_inc))
+ sql_dict_prev_inc_flush(ctx);
+
+ map = sql_dict_find_map(dict, key, &pattern_values);
+ if (map == NULL) {
+ ctx->error = i_strdup_printf(
+ "sql dict set: Invalid/unmapped key: %s", key);
+ return;
+ }
+
+ if (array_is_created(&ctx->prev_set) &&
+ !sql_dict_maps_are_mergeable(dict, array_front(&ctx->prev_set),
+ map, key, &pattern_values)) {
+ /* couldn't merge to the previous set - flush it */
+ sql_dict_prev_set_flush(ctx);
+ }
+
+ if (!array_is_created(&ctx->prev_set))
+ i_array_init(&ctx->prev_set, 4);
+ /* Either this is the first set, or this can be merged with the
+ previous set. */
+ struct sql_dict_prev *prev_set = array_append_space(&ctx->prev_set);
+ prev_set->map = map;
+ prev_set->key = i_strdup(key);
+ prev_set->value.str = i_strdup(value);
+}
+
+static void sql_dict_atomic_inc(struct dict_transaction_context *_ctx,
+ const char *key, long long diff)
+{
+ struct sql_dict_transaction_context *ctx =
+ (struct sql_dict_transaction_context *)_ctx;
+ struct sql_dict *dict = (struct sql_dict *)_ctx->dict;
+ const struct dict_sql_map *map;
+ ARRAY_TYPE(const_string) pattern_values;
+
+ if (ctx->error != NULL)
+ return;
+
+ /* In theory we could inc the previous set in this same transaction,
+ so flush it first. */
+ if (array_is_created(&ctx->prev_set))
+ sql_dict_prev_set_flush(ctx);
+
+ map = sql_dict_find_map(dict, key, &pattern_values);
+ if (map == NULL) {
+ ctx->error = i_strdup_printf(
+ "sql dict atomic inc: Invalid/unmapped key: %s", key);
+ return;
+ }
+
+ if (array_is_created(&ctx->prev_inc) &&
+ !sql_dict_maps_are_mergeable(dict, array_front(&ctx->prev_inc),
+ map, key, &pattern_values)) {
+ /* couldn't merge to the previous inc - flush it */
+ sql_dict_prev_inc_flush(ctx);
+ }
+
+ if (!array_is_created(&ctx->prev_inc))
+ i_array_init(&ctx->prev_inc, 4);
+ /* Either this is the first inc, or this can be merged with the
+ previous inc. */
+ struct sql_dict_prev *prev_inc = array_append_space(&ctx->prev_inc);
+ prev_inc->map = map;
+ prev_inc->key = i_strdup(key);
+ prev_inc->value.diff = diff;
+}
+
+static struct dict sql_dict = {
+ .name = "sql",
+
+ {
+ .init = sql_dict_init,
+ .deinit = sql_dict_deinit,
+ .wait = sql_dict_wait,
+ .lookup = sql_dict_lookup,
+ .iterate_init = sql_dict_iterate_init,
+ .iterate = sql_dict_iterate,
+ .iterate_deinit = sql_dict_iterate_deinit,
+ .transaction_init = sql_dict_transaction_init,
+ .transaction_commit = sql_dict_transaction_commit,
+ .transaction_rollback = sql_dict_transaction_rollback,
+ .set = sql_dict_set,
+ .unset = sql_dict_unset,
+ .atomic_inc = sql_dict_atomic_inc,
+ .lookup_async = sql_dict_lookup_async,
+ }
+};
+
+static struct dict *dict_sql_drivers;
+
+void dict_sql_register(void)
+{
+ const struct sql_db *const *drivers;
+ unsigned int i, count;
+
+ dict_sql_db_cache = sql_db_cache_init(DICT_SQL_MAX_UNUSED_CONNECTIONS);
+
+ /* @UNSAFE */
+ drivers = array_get(&sql_drivers, &count);
+ dict_sql_drivers = i_new(struct dict, count + 1);
+
+ for (i = 0; i < count; i++) {
+ dict_sql_drivers[i] = sql_dict;
+ dict_sql_drivers[i].name = drivers[i]->name;
+
+ dict_driver_register(&dict_sql_drivers[i]);
+ }
+}
+
+void dict_sql_unregister(void)
+{
+ int i;
+
+ for (i = 0; dict_sql_drivers[i].name != NULL; i++)
+ dict_driver_unregister(&dict_sql_drivers[i]);
+ i_free(dict_sql_drivers);
+ sql_db_cache_deinit(&dict_sql_db_cache);
+ dict_sql_settings_deinit();
+}
diff --git a/src/lib-dict-backend/dict-sql.h b/src/lib-dict-backend/dict-sql.h
new file mode 100644
index 0000000..418a650
--- /dev/null
+++ b/src/lib-dict-backend/dict-sql.h
@@ -0,0 +1,7 @@
+#ifndef DICT_SQL_H
+#define DICT_SQL_H
+
+void dict_sql_register(void);
+void dict_sql_unregister(void);
+
+#endif
diff --git a/src/lib-dict-backend/dict.conf b/src/lib-dict-backend/dict.conf
new file mode 100644
index 0000000..04dd63a
--- /dev/null
+++ b/src/lib-dict-backend/dict.conf
@@ -0,0 +1,49 @@
+connect = host=localhost
+
+map {
+ pattern = shared/dictmap/$key1/$key2
+ table = table
+ value_field = value
+ value_type = string
+
+ fields {
+ a = $key1
+ b = $key2
+ }
+}
+
+map {
+ pattern = shared/counters/$class/$name
+ table = counters
+ value_field = value
+ value_type = uint
+
+ fields {
+ class = $class
+ name = $name
+ }
+}
+
+map {
+ pattern = priv/quota/bytes
+ table = quota
+ username_field = username
+ value_field = bytes
+ value_type = uint
+}
+
+map {
+ pattern = priv/quota/count
+ table = quota
+ username_field = username
+ value_field = count
+ value_type = uint
+}
+
+map {
+ pattern = priv/quota/folders
+ table = quota
+ username_field = username
+ value_field = folders
+ value_type = uint
+}
diff --git a/src/lib-dict-backend/test-dict-sql.c b/src/lib-dict-backend/test-dict-sql.c
new file mode 100644
index 0000000..4de45eb
--- /dev/null
+++ b/src/lib-dict-backend/test-dict-sql.c
@@ -0,0 +1,314 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-lib.h"
+#include "sql-api.h"
+#include "dict.h"
+#include "dict-private.h"
+#include "dict-sql.h"
+#include "dict-sql-private.h"
+#include "driver-test.h"
+
+struct dict_op_settings dict_op_settings = {
+ .username = "testuser",
+};
+
+static void test_setup(struct dict **dict_r)
+{
+ const char *error = NULL;
+ struct dict_settings set = {
+ .base_dir = "."
+ };
+ struct dict *dict = NULL;
+
+ if (dict_init("mysql:" DICT_SRC_DIR "/dict.conf", &set, &dict, &error) < 0)
+ i_fatal("cannot initialize dict: %s", error);
+
+ *dict_r = dict;
+}
+
+static void test_teardown(struct dict **_dict)
+{
+ struct dict *dict = *_dict;
+ *_dict = NULL;
+ if (dict != NULL) {
+ dict_deinit(&dict);
+ }
+}
+
+static void test_set_expected(struct dict *_dict,
+ const struct test_driver_result *result)
+{
+ struct sql_dict *dict =
+ (struct sql_dict *)_dict;
+
+ sql_driver_test_add_expected_result(dict->db, result);
+}
+
+static void test_lookup_one(void)
+{
+ const char *value = NULL, *error = NULL;
+ struct test_driver_result_set rset = {
+ .rows = 1,
+ .cols = 1,
+ .col_names = (const char *[]){"value", NULL},
+ .row_data = (const char **[]){(const char*[]){"one", NULL}},
+ };
+ struct test_driver_result res = {
+ .nqueries = 1,
+ .queries = (const char *[]){"SELECT value FROM table WHERE a = 'hello' AND b = 'world'", NULL},
+ .result = &rset,
+ };
+ const struct dict_op_settings set = {
+ .username = "testuser",
+ };
+ struct dict *dict;
+ pool_t pool = pool_datastack_create();
+
+ test_begin("dict lookup one");
+ test_setup(&dict);
+
+ test_set_expected(dict, &res);
+
+ test_assert(dict_lookup(dict, &set, pool, "shared/dictmap/hello/world", &value, &error) == 1);
+ test_assert_strcmp(value, "one");
+ if (error != NULL)
+ i_error("dict_lookup failed: %s", error);
+ test_teardown(&dict);
+ test_end();
+}
+
+static void test_atomic_inc(void)
+{
+ const char *error;
+ struct test_driver_result res = {
+ .nqueries = 3,
+ .queries = (const char *[]){
+ "UPDATE counters SET value=value+128 WHERE class = 'global' AND name = 'counter'",
+ "UPDATE quota SET bytes=bytes+128,count=count+1 WHERE username = 'testuser'",
+ "UPDATE quota SET bytes=bytes+128,count=count+1,folders=folders+123 WHERE username = 'testuser'",
+ NULL},
+ .result = NULL,
+ };
+ struct dict_op_settings set = {
+ .username = "testuser",
+ };
+ struct dict *dict;
+
+ test_begin("dict atomic inc");
+ test_setup(&dict);
+
+ test_set_expected(dict, &res);
+
+ /* 1 field */
+ struct dict_transaction_context *ctx = dict_transaction_begin(dict, &set);
+ dict_atomic_inc(ctx, "shared/counters/global/counter", 128);
+ test_assert(dict_transaction_commit(&ctx, &error) == 0);
+ if (error != NULL)
+ i_error("dict_transaction_commit failed: %s", error);
+ error = NULL;
+
+ /* 2 fields */
+ ctx = dict_transaction_begin(dict, &set);
+ dict_atomic_inc(ctx, "priv/quota/bytes", 128);
+ dict_atomic_inc(ctx, "priv/quota/count", 1);
+ test_assert(dict_transaction_commit(&ctx, &error) == 0);
+ if (error != NULL)
+ i_error("dict_transaction_commit failed: %s", error);
+ error = NULL;
+
+ /* 3 fields */
+ ctx = dict_transaction_begin(dict, &set);
+ dict_atomic_inc(ctx, "priv/quota/bytes", 128);
+ dict_atomic_inc(ctx, "priv/quota/count", 1);
+ dict_atomic_inc(ctx, "priv/quota/folders", 123);
+ test_assert(dict_transaction_commit(&ctx, &error) == 0);
+ if (error != NULL)
+ i_error("dict_transaction_commit failed: %s", error);
+ test_teardown(&dict);
+ test_end();
+}
+
+static void test_set(void)
+{
+ const char *error;
+ struct test_driver_result res = {
+ .affected_rows = 1,
+ .nqueries = 3,
+ .queries = (const char *[]){
+ "INSERT INTO counters (value,class,name) VALUES (128,'global','counter') ON DUPLICATE KEY UPDATE value=128",
+ "INSERT INTO quota (bytes,count,username) VALUES (128,1,'testuser') ON DUPLICATE KEY UPDATE bytes=128,count=1",
+ "INSERT INTO quota (bytes,count,folders,username) VALUES (128,1,123,'testuser') ON DUPLICATE KEY UPDATE bytes=128,count=1,folders=123",
+ NULL},
+ .result = NULL,
+ };
+ struct dict *dict;
+
+ test_begin("dict set");
+ test_setup(&dict);
+
+ test_set_expected(dict, &res);
+
+ /* 1 field */
+ struct dict_transaction_context *ctx = dict_transaction_begin(dict, &dict_op_settings);
+ dict_set(ctx, "shared/counters/global/counter", "128");
+ test_assert(dict_transaction_commit(&ctx, &error) == 1);
+ if (error != NULL)
+ i_error("dict_transaction_commit failed: %s", error);
+ error = NULL;
+
+ /* 2 fields */
+ ctx = dict_transaction_begin(dict, &dict_op_settings);
+ dict_set(ctx, "priv/quota/bytes", "128");
+ dict_set(ctx, "priv/quota/count", "1");
+ test_assert(dict_transaction_commit(&ctx, &error) == 1);
+ if (error != NULL)
+ i_error("dict_transaction_commit failed: %s", error);
+ error = NULL;
+
+ /* 3 fields */
+ ctx = dict_transaction_begin(dict, &dict_op_settings);
+ dict_set(ctx, "priv/quota/bytes", "128");
+ dict_set(ctx, "priv/quota/count", "1");
+ dict_set(ctx, "priv/quota/folders", "123");
+ test_assert(dict_transaction_commit(&ctx, &error) == 1);
+ if (error != NULL)
+ i_error("dict_transaction_commit failed: %s", error);
+ test_teardown(&dict);
+ test_end();
+}
+
+static void test_unset(void)
+{
+ const char *error;
+ struct test_driver_result res = {
+ .affected_rows = 1,
+ .nqueries = 3,
+ .queries = (const char *[]){
+ "DELETE FROM counters WHERE class = 'global' AND name = 'counter'",
+ "DELETE FROM quota WHERE username = 'testuser'",
+ "DELETE FROM quota WHERE username = 'testuser'",
+ NULL},
+ .result = NULL,
+ };
+ struct dict *dict;
+
+ test_begin("dict unset");
+ test_setup(&dict);
+
+ test_set_expected(dict, &res);
+
+ struct dict_transaction_context *ctx = dict_transaction_begin(dict, &dict_op_settings);
+ dict_unset(ctx, "shared/counters/global/counter");
+ test_assert(dict_transaction_commit(&ctx, &error) == 1);
+ if (error != NULL)
+ i_error("dict_transaction_commit failed: %s", error);
+ error = NULL;
+ ctx = dict_transaction_begin(dict, &dict_op_settings);
+ dict_unset(ctx, "priv/quota/bytes");
+ dict_unset(ctx, "priv/quota/count");
+ test_assert(dict_transaction_commit(&ctx, &error) == 1);
+ if (error != NULL)
+ i_error("dict_transaction_commit failed: %s", error);
+ test_teardown(&dict);
+ test_end();
+}
+
+static void test_iterate(void)
+{
+ const char *key = NULL, *value = NULL, *error;
+ struct test_driver_result_set rset = {
+ .rows = 5,
+ .cols = 2,
+ .col_names = (const char *[]){"value", "name", NULL},
+ .row_data = (const char **[]){
+ (const char*[]){"one", "counter", NULL},
+ (const char*[]){"two", "counter", NULL},
+ (const char*[]){"three", "counter", NULL},
+ (const char*[]){"four", "counter", NULL},
+ (const char*[]){"five", "counter", NULL},
+ },
+ };
+ struct test_driver_result res = {
+ .nqueries = 1,
+ .queries = (const char *[]){
+ "SELECT value,name FROM counters WHERE class = 'global' AND name = 'counter'",
+ NULL},
+ .result = &rset,
+ };
+ struct dict *dict;
+
+ test_begin("dict iterate");
+ test_setup(&dict);
+
+ test_set_expected(dict, &res);
+
+ struct dict_iterate_context *iter =
+ dict_iterate_init(dict, &dict_op_settings, "shared/counters/global/counter",
+ DICT_ITERATE_FLAG_EXACT_KEY);
+
+ size_t idx = 0;
+ while(dict_iterate(iter, &key, &value)) {
+ i_assert(idx < rset.rows);
+ test_assert_strcmp_idx(key, "shared/counters/global/counter", idx);
+ test_assert_strcmp_idx(value, rset.row_data[idx][0], idx);
+ idx++;
+ }
+
+ test_assert(idx == rset.rows);
+ test_assert(dict_iterate_deinit(&iter, &error) == 0);
+ if (error != NULL)
+ i_error("dict_iterate_deinit failed: %s", error);
+ error = NULL;
+
+ res.queries = (const char*[]){
+ "SELECT value,name FROM counters WHERE class = 'global' AND name LIKE '%' AND name NOT LIKE '%/%'",
+ NULL
+ };
+
+ res.cur = 0;
+ res.result->cur = 0;
+
+ test_set_expected(dict, &res);
+
+ iter = dict_iterate_init(dict, &dict_op_settings, "shared/counters/global/", 0);
+
+ idx = 0;
+
+ while(dict_iterate(iter, &key, &value)) {
+ i_assert(idx < rset.rows);
+ test_assert_strcmp_idx(key, "shared/counters/global/counter", idx);
+ test_assert_strcmp_idx(value, rset.row_data[idx][0], idx);
+ idx++;
+ }
+
+ test_assert(idx == rset.rows);
+ test_assert(dict_iterate_deinit(&iter, &error) == 0);
+ if (error != NULL)
+ i_error("dict_iterate_deinit failed: %s", error);
+ test_teardown(&dict);
+ test_end();
+}
+
+int main(void) {
+ sql_drivers_init();
+ sql_driver_test_register();
+ dict_sql_register();
+
+ static void (*const test_functions[])(void) = {
+ test_lookup_one,
+ test_atomic_inc,
+ test_set,
+ test_unset,
+ test_iterate,
+ NULL
+ };
+
+ int ret = test_run(test_functions);
+
+ dict_sql_unregister();
+ sql_driver_test_unregister();
+ sql_drivers_deinit();
+
+ return ret;
+}