From f7548d6d28c313cf80e6f3ef89aed16a19815df1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:51:24 +0200 Subject: Adding upstream version 1:2.3.19.1+dfsg1. Signed-off-by: Daniel Baumann --- src/lib-dict-backend/Makefile.am | 116 +++ src/lib-dict-backend/Makefile.in | 1016 +++++++++++++++++++ src/lib-dict-backend/dict-cdb.c | 266 +++++ src/lib-dict-backend/dict-ldap-settings.c | 313 ++++++ src/lib-dict-backend/dict-ldap-settings.h | 36 + src/lib-dict-backend/dict-ldap.c | 500 +++++++++ src/lib-dict-backend/dict-sql-private.h | 12 + src/lib-dict-backend/dict-sql-settings.c | 345 +++++++ src/lib-dict-backend/dict-sql-settings.h | 47 + src/lib-dict-backend/dict-sql.c | 1564 +++++++++++++++++++++++++++++ src/lib-dict-backend/dict-sql.h | 7 + src/lib-dict-backend/dict.conf | 49 + src/lib-dict-backend/test-dict-sql.c | 314 ++++++ 13 files changed, 4585 insertions(+) create mode 100644 src/lib-dict-backend/Makefile.am create mode 100644 src/lib-dict-backend/Makefile.in create mode 100644 src/lib-dict-backend/dict-cdb.c create mode 100644 src/lib-dict-backend/dict-ldap-settings.c create mode 100644 src/lib-dict-backend/dict-ldap-settings.h create mode 100644 src/lib-dict-backend/dict-ldap.c create mode 100644 src/lib-dict-backend/dict-sql-private.h create mode 100644 src/lib-dict-backend/dict-sql-settings.c create mode 100644 src/lib-dict-backend/dict-sql-settings.h create mode 100644 src/lib-dict-backend/dict-sql.c create mode 100644 src/lib-dict-backend/dict-sql.h create mode 100644 src/lib-dict-backend/dict.conf create mode 100644 src/lib-dict-backend/test-dict-sql.c (limited to 'src/lib-dict-backend') 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 +#include +#include +#include + +#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 + +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 + +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 +#include + +#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, ¶m->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, ¶m->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(¶ms, 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, + ¶ms, &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), ¶ms); + 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(¶ms, 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, ¶ms, 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), ¶ms); + 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(¶ms, 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, + "", ¶ms, error_r) < 0) + return -1; + } + if (build->add_username) { + struct sql_dict_param *param = array_append_space(¶ms); + 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], "", + ¶ms, 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), ¶ms); + 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, + "", ¶ms, error_r) < 0) + return -1; + } + *stmt_r = sql_dict_transaction_stmt_init(ctx, str_c(prefix), ¶ms); + 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(¶ms, 4); + if (sql_dict_where_build(set->username, map, &pattern_values, + key[0] == DICT_PATH_PRIVATE[0], + SQL_DICT_RECURSE_NONE, query, + ¶ms, &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), ¶ms); + 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(¶ms, 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(¶ms); + param->value_type = DICT_SQL_TYPE_INT; + param->value_int64 = prev_incs[i].value.diff; + } + + if (sql_dict_update_query(&build, set, &query, ¶ms, &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, ¶ms); + 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; +} -- cgit v1.2.3