diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/config | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/config/Makefile.am | 98 | ||||
-rw-r--r-- | src/config/Makefile.in | 1079 | ||||
-rw-r--r-- | src/config/all-settings.c | 6110 | ||||
-rw-r--r-- | src/config/all-settings.h | 8 | ||||
-rw-r--r-- | src/config/config-connection.c | 267 | ||||
-rw-r--r-- | src/config/config-connection.h | 9 | ||||
-rw-r--r-- | src/config/config-filter.c | 401 | ||||
-rw-r--r-- | src/config/config-filter.h | 58 | ||||
-rw-r--r-- | src/config/config-parser-private.h | 75 | ||||
-rw-r--r-- | src/config/config-parser.c | 1212 | ||||
-rw-r--r-- | src/config/config-parser.h | 33 | ||||
-rw-r--r-- | src/config/config-request.c | 524 | ||||
-rw-r--r-- | src/config/config-request.h | 61 | ||||
-rw-r--r-- | src/config/config-settings.c | 46 | ||||
-rw-r--r-- | src/config/doveconf.c | 1072 | ||||
-rw-r--r-- | src/config/main.c | 53 | ||||
-rw-r--r-- | src/config/old-set-parser.c | 824 | ||||
-rw-r--r-- | src/config/old-set-parser.h | 16 | ||||
-rwxr-xr-x | src/config/settings-get.pl | 162 | ||||
-rw-r--r-- | src/config/sysinfo-get.c | 127 | ||||
-rw-r--r-- | src/config/sysinfo-get.h | 6 | ||||
-rw-r--r-- | src/config/test-config-parser.c | 170 |
22 files changed, 12411 insertions, 0 deletions
diff --git a/src/config/Makefile.am b/src/config/Makefile.am new file mode 100644 index 0000000..084ec0e --- /dev/null +++ b/src/config/Makefile.am @@ -0,0 +1,98 @@ +pkgsysconfdir = $(sysconfdir)/dovecot +pkglibexecdir = $(libexecdir)/dovecot +exampledir = $(docdir)/example-config + +bin_PROGRAMS = doveconf +pkglibexec_PROGRAMS = config + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master \ + -DPKG_RUNDIR=\""$(rundir)"\" \ + -DPKG_STATEDIR=\""$(statedir)"\" \ + -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ + -DEXAMPLE_CONFIG_DIR=\""$(exampledir)"\" \ + -DMODULEDIR=\""$(moduledir)"\" \ + -DSSLDIR=\""$(ssldir)\"" \ + -DSYSCONFDIR=\""$(pkgsysconfdir)"\" \ + $(BINARY_CFLAGS) + +noinst_LTLIBRARIES = libconfig.la + +config_LDADD = \ + $(noinst_LTLIBRARIES) \ + $(LIBDOVECOT) \ + $(RAND_LIBS) \ + $(BINARY_LDFLAGS) \ + -lm + +config_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(noinst_LTLIBRARIES) + +doveconf_LDADD = \ + $(noinst_LTLIBRARIES) \ + $(LIBDOVECOT) \ + $(RAND_LIBS) \ + $(BINARY_LDFLAGS) \ + -lm + +doveconf_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(noinst_LTLIBRARIES) + +common = \ + config-connection.c \ + config-filter.c \ + config-parser.c \ + config-request.c \ + old-set-parser.c \ + sysinfo-get.c + +libconfig_la_SOURCES = $(common) + +config_SOURCES = \ + all-settings.c \ + main.c + +doveconf_SOURCES = \ + all-settings.c \ + doveconf.c + +noinst_HEADERS = \ + all-settings.h \ + config-connection.h \ + old-set-parser.h \ + sysinfo-get.h + +pkginclude_HEADERS = \ + config-filter.h \ + config-parser.h \ + config-parser-private.h \ + config-request.h + +all-settings.c: $(SETTING_FILES) $(top_srcdir)/src/config/settings-get.pl + $(top_srcdir)/src/config/settings-get.pl $(SETTING_FILES) > all-settings.c || rm -f all-settings.c + +EXTRA_DIST = \ + config-settings.c \ + settings-get.pl + +test_programs = \ + test-config-parser + +noinst_PROGRAMS = $(test_programs) + +test_libs = \ + $(noinst_LTLIBRARIES) \ + $(LIBDOVECOT) + +test_config_parser_CFLAGS = $(AM_CPPFLAGS) +test_config_parser_SOURCES = test-config-parser.c +test_config_parser_LDADD = $(test_libs) +test_config_parser_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(noinst_LTLIBRARIES) + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/config/Makefile.in b/src/config/Makefile.in new file mode 100644 index 0000000..d26133c --- /dev/null +++ b/src/config/Makefile.in @@ -0,0 +1,1079 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = doveconf$(EXEEXT) +pkglibexec_PROGRAMS = config$(EXEEXT) +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/config +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) \ + $(pkginclude_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(pkglibexecdir)" \ + "$(DESTDIR)$(pkgincludedir)" +am__EXEEXT_1 = test-config-parser$(EXEEXT) +PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libconfig_la_LIBADD = +am__objects_1 = config-connection.lo config-filter.lo config-parser.lo \ + config-request.lo old-set-parser.lo sysinfo-get.lo +am_libconfig_la_OBJECTS = $(am__objects_1) +libconfig_la_OBJECTS = $(am_libconfig_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_config_OBJECTS = all-settings.$(OBJEXT) main.$(OBJEXT) +config_OBJECTS = $(am_config_OBJECTS) +am__DEPENDENCIES_1 = +am_doveconf_OBJECTS = all-settings.$(OBJEXT) doveconf.$(OBJEXT) +doveconf_OBJECTS = $(am_doveconf_OBJECTS) +am_test_config_parser_OBJECTS = \ + test_config_parser-test-config-parser.$(OBJEXT) +test_config_parser_OBJECTS = $(am_test_config_parser_OBJECTS) +am__DEPENDENCIES_2 = $(noinst_LTLIBRARIES) $(am__DEPENDENCIES_1) +test_config_parser_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(test_config_parser_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)/all-settings.Po \ + ./$(DEPDIR)/config-connection.Plo \ + ./$(DEPDIR)/config-filter.Plo ./$(DEPDIR)/config-parser.Plo \ + ./$(DEPDIR)/config-request.Plo ./$(DEPDIR)/doveconf.Po \ + ./$(DEPDIR)/main.Po ./$(DEPDIR)/old-set-parser.Plo \ + ./$(DEPDIR)/sysinfo-get.Plo \ + ./$(DEPDIR)/test_config_parser-test-config-parser.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 = $(libconfig_la_SOURCES) $(config_SOURCES) \ + $(doveconf_SOURCES) $(test_config_parser_SOURCES) +DIST_SOURCES = $(libconfig_la_SOURCES) $(config_SOURCES) \ + $(doveconf_SOURCES) $(test_config_parser_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +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; }; \ + } +HEADERS = $(noinst_HEADERS) $(pkginclude_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +pkglibexecdir = $(libexecdir)/dovecot +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +pkgsysconfdir = $(sysconfdir)/dovecot +exampledir = $(docdir)/example-config +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master \ + -DPKG_RUNDIR=\""$(rundir)"\" \ + -DPKG_STATEDIR=\""$(statedir)"\" \ + -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ + -DEXAMPLE_CONFIG_DIR=\""$(exampledir)"\" \ + -DMODULEDIR=\""$(moduledir)"\" \ + -DSSLDIR=\""$(ssldir)\"" \ + -DSYSCONFDIR=\""$(pkgsysconfdir)"\" \ + $(BINARY_CFLAGS) + +noinst_LTLIBRARIES = libconfig.la +config_LDADD = \ + $(noinst_LTLIBRARIES) \ + $(LIBDOVECOT) \ + $(RAND_LIBS) \ + $(BINARY_LDFLAGS) \ + -lm + +config_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(noinst_LTLIBRARIES) +doveconf_LDADD = \ + $(noinst_LTLIBRARIES) \ + $(LIBDOVECOT) \ + $(RAND_LIBS) \ + $(BINARY_LDFLAGS) \ + -lm + +doveconf_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(noinst_LTLIBRARIES) +common = \ + config-connection.c \ + config-filter.c \ + config-parser.c \ + config-request.c \ + old-set-parser.c \ + sysinfo-get.c + +libconfig_la_SOURCES = $(common) +config_SOURCES = \ + all-settings.c \ + main.c + +doveconf_SOURCES = \ + all-settings.c \ + doveconf.c + +noinst_HEADERS = \ + all-settings.h \ + config-connection.h \ + old-set-parser.h \ + sysinfo-get.h + +pkginclude_HEADERS = \ + config-filter.h \ + config-parser.h \ + config-parser-private.h \ + config-request.h + +EXTRA_DIST = \ + config-settings.c \ + settings-get.pl + +test_programs = \ + test-config-parser + +test_libs = \ + $(noinst_LTLIBRARIES) \ + $(LIBDOVECOT) + +test_config_parser_CFLAGS = $(AM_CPPFLAGS) +test_config_parser_SOURCES = test-config-parser.c +test_config_parser_LDADD = $(test_libs) +test_config_parser_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(noinst_LTLIBRARIES) +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/config/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/config/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_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 + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-pkglibexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files + +clean-pkglibexecPROGRAMS: + @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +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}; \ + } + +libconfig.la: $(libconfig_la_OBJECTS) $(libconfig_la_DEPENDENCIES) $(EXTRA_libconfig_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libconfig_la_OBJECTS) $(libconfig_la_LIBADD) $(LIBS) + +config$(EXEEXT): $(config_OBJECTS) $(config_DEPENDENCIES) $(EXTRA_config_DEPENDENCIES) + @rm -f config$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(config_OBJECTS) $(config_LDADD) $(LIBS) + +doveconf$(EXEEXT): $(doveconf_OBJECTS) $(doveconf_DEPENDENCIES) $(EXTRA_doveconf_DEPENDENCIES) + @rm -f doveconf$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(doveconf_OBJECTS) $(doveconf_LDADD) $(LIBS) + +test-config-parser$(EXEEXT): $(test_config_parser_OBJECTS) $(test_config_parser_DEPENDENCIES) $(EXTRA_test_config_parser_DEPENDENCIES) + @rm -f test-config-parser$(EXEEXT) + $(AM_V_CCLD)$(test_config_parser_LINK) $(test_config_parser_OBJECTS) $(test_config_parser_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/all-settings.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-filter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-request.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveconf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/old-set-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sysinfo-get.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_config_parser-test-config-parser.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 $@ $< + +test_config_parser-test-config-parser.o: test-config-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_config_parser_CFLAGS) $(CFLAGS) -MT test_config_parser-test-config-parser.o -MD -MP -MF $(DEPDIR)/test_config_parser-test-config-parser.Tpo -c -o test_config_parser-test-config-parser.o `test -f 'test-config-parser.c' || echo '$(srcdir)/'`test-config-parser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_config_parser-test-config-parser.Tpo $(DEPDIR)/test_config_parser-test-config-parser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-config-parser.c' object='test_config_parser-test-config-parser.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_config_parser_CFLAGS) $(CFLAGS) -c -o test_config_parser-test-config-parser.o `test -f 'test-config-parser.c' || echo '$(srcdir)/'`test-config-parser.c + +test_config_parser-test-config-parser.obj: test-config-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_config_parser_CFLAGS) $(CFLAGS) -MT test_config_parser-test-config-parser.obj -MD -MP -MF $(DEPDIR)/test_config_parser-test-config-parser.Tpo -c -o test_config_parser-test-config-parser.obj `if test -f 'test-config-parser.c'; then $(CYGPATH_W) 'test-config-parser.c'; else $(CYGPATH_W) '$(srcdir)/test-config-parser.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_config_parser-test-config-parser.Tpo $(DEPDIR)/test_config_parser-test-config-parser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-config-parser.c' object='test_config_parser-test-config-parser.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_config_parser_CFLAGS) $(CFLAGS) -c -o test_config_parser-test-config-parser.obj `if test -f 'test-config-parser.c'; then $(CYGPATH_W) 'test-config-parser.c'; else $(CYGPATH_W) '$(srcdir)/test-config-parser.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkgincludeHEADERS: $(pkginclude_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgincludedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgincludedir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkgincludedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkgincludedir)" || exit $$?; \ + done + +uninstall-pkgincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkgincludedir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkgincludedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool \ + clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ + clean-pkglibexecPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/all-settings.Po + -rm -f ./$(DEPDIR)/config-connection.Plo + -rm -f ./$(DEPDIR)/config-filter.Plo + -rm -f ./$(DEPDIR)/config-parser.Plo + -rm -f ./$(DEPDIR)/config-request.Plo + -rm -f ./$(DEPDIR)/doveconf.Po + -rm -f ./$(DEPDIR)/main.Po + -rm -f ./$(DEPDIR)/old-set-parser.Plo + -rm -f ./$(DEPDIR)/sysinfo-get.Plo + -rm -f ./$(DEPDIR)/test_config_parser-test-config-parser.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-pkgincludeHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-pkglibexecPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/all-settings.Po + -rm -f ./$(DEPDIR)/config-connection.Plo + -rm -f ./$(DEPDIR)/config-filter.Plo + -rm -f ./$(DEPDIR)/config-parser.Plo + -rm -f ./$(DEPDIR)/config-request.Plo + -rm -f ./$(DEPDIR)/doveconf.Po + -rm -f ./$(DEPDIR)/main.Po + -rm -f ./$(DEPDIR)/old-set-parser.Plo + -rm -f ./$(DEPDIR)/sysinfo-get.Plo + -rm -f ./$(DEPDIR)/test_config_parser-test-config-parser.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-binPROGRAMS uninstall-pkgincludeHEADERS \ + uninstall-pkglibexecPROGRAMS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-binPROGRAMS clean-generic \ + clean-libtool clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ + clean-pkglibexecPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkgincludeHEADERS \ + install-pkglibexecPROGRAMS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-binPROGRAMS uninstall-pkgincludeHEADERS \ + uninstall-pkglibexecPROGRAMS + +.PRECIOUS: Makefile + + +all-settings.c: $(SETTING_FILES) $(top_srcdir)/src/config/settings-get.pl + $(top_srcdir)/src/config/settings-get.pl $(SETTING_FILES) > all-settings.c || rm -f all-settings.c + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/config/all-settings.c b/src/config/all-settings.c new file mode 100644 index 0000000..268a321 --- /dev/null +++ b/src/config/all-settings.c @@ -0,0 +1,6110 @@ +/* WARNING: THIS FILE IS GENERATED - DO NOT PATCH! + It's not enough alone in any case, because the defaults may be + coming from the individual *-settings.c in some situations. If you + wish to modify defaults, change the other *-settings.c files and + just delete this file. This file will be automatically regenerated + by make. (This file is distributed in the tarball only because some + systems might not have Perl installed.) */ +#include "lib.h" +#include "array.h" +#include "str.h" +#include "ipwd.h" +#include "var-expand.h" +#include "file-lock.h" +#include "fsync-mode.h" +#include "hash-format.h" +#include "net.h" +#include "unichar.h" +#include "hash-method.h" +#include "settings-parser.h" +#include "message-header-parser.h" +#include "all-settings.h" +#include <stddef.h> +#include <unistd.h> +#define CONFIG_BINARY +extern buffer_t config_all_services_buf;/* ../../src/lib-storage/mail-storage-settings.h */ +extern const struct setting_parser_info mail_user_setting_parser_info; +extern const struct setting_parser_info mail_namespace_setting_parser_info; +extern const struct setting_parser_info mail_storage_setting_parser_info; +/* <settings checks> */ +#define MAILBOX_SET_AUTO_NO "no" +#define MAILBOX_SET_AUTO_CREATE "create" +#define MAILBOX_SET_AUTO_SUBSCRIBE "subscribe" +/* </settings checks> */ +struct mail_storage_settings { + const char *mail_location; + const char *mail_attachment_fs; + const char *mail_attachment_dir; + const char *mail_attachment_hash; + uoff_t mail_attachment_min_size; + const char *mail_attribute_dict; + unsigned int mail_prefetch_count; + const char *mail_cache_fields; + const char *mail_always_cache_fields; + const char *mail_never_cache_fields; + const char *mail_server_comment; + const char *mail_server_admin; + unsigned int mail_cache_min_mail_count; + unsigned int mail_cache_unaccessed_field_drop; + uoff_t mail_cache_record_max_size; + uoff_t mail_cache_max_size; + uoff_t mail_cache_purge_min_size; + unsigned int mail_cache_purge_delete_percentage; + unsigned int mail_cache_purge_continued_percentage; + unsigned int mail_cache_purge_header_continue_count; + uoff_t mail_index_rewrite_min_log_bytes; + uoff_t mail_index_rewrite_max_log_bytes; + uoff_t mail_index_log_rotate_min_size; + uoff_t mail_index_log_rotate_max_size; + unsigned int mail_index_log_rotate_min_age; + unsigned int mail_index_log2_max_age; + unsigned int mailbox_idle_check_interval; + unsigned int mail_max_keyword_length; + unsigned int mail_max_lock_timeout; + unsigned int mail_temp_scan_interval; + unsigned int mail_vsize_bg_after_count; + unsigned int mail_sort_max_read_count; + bool mail_save_crlf; + const char *mail_fsync; + bool mmap_disable; + bool dotlock_use_excl; + bool mail_nfs_storage; + bool mail_nfs_index; + bool mailbox_list_index; + bool mailbox_list_index_very_dirty_syncs; + bool mailbox_list_index_include_inbox; + bool mail_debug; + bool mail_full_filesystem_access; + bool maildir_stat_dirs; + bool mail_shared_explicit_inbox; + const char *lock_method; + const char *pop3_uidl_format; + + const char *hostname; + const char *recipient_delimiter; + + const char *mail_attachment_detection_options; + + enum file_lock_method parsed_lock_method; + enum fsync_mode parsed_fsync_mode; + + const char *const *parsed_mail_attachment_content_type_filter; + bool parsed_mail_attachment_exclude_inlined; + bool parsed_mail_attachment_detection_add_flags; + bool parsed_mail_attachment_detection_no_flags_on_fetch; +}; +struct mail_namespace_settings { + const char *name; + const char *type; + const char *separator; + const char *prefix; + const char *location; + const char *alias_for; + + bool inbox; + bool hidden; + const char *list; + bool subscriptions; + bool ignore_on_failure; + bool disabled; + unsigned int order; + + ARRAY(struct mailbox_settings *) mailboxes; + struct mail_user_settings *user_set; +}; +struct mailbox_settings { + const char *name; + const char *autocreate; + const char *special_use; + const char *driver; + const char *comment; + unsigned int autoexpunge; + unsigned int autoexpunge_max_mails; +}; +struct mail_user_settings { + const char *base_dir; + const char *auth_socket_path; + const char *mail_temp_dir; + + const char *mail_uid; + const char *mail_gid; + const char *mail_home; + const char *mail_chroot; + const char *mail_access_groups; + const char *mail_privileged_group; + const char *valid_chroot_dirs; + + unsigned int first_valid_uid, last_valid_uid; + unsigned int first_valid_gid, last_valid_gid; + + const char *mail_plugins; + const char *mail_plugin_dir; + + const char *mail_log_prefix; + + const char *hostname; + const char *postmaster_address; + + ARRAY(struct mail_namespace_settings *) namespaces; + ARRAY(const char *) plugin_envs; + + /* May be NULL - use mail_storage_get_postmaster_address() instead of + directly accessing this. */ + const struct message_address *_parsed_postmaster_address; + const struct smtp_address *_parsed_postmaster_address_smtp; +}; +/* ../../src/lib-storage/index/pop3c/pop3c-settings.h */ +/* <settings checks> */ +enum pop3c_features { + POP3C_FEATURE_NO_PIPELINING = 0x1, +}; +/* </settings checks> */ +struct pop3c_settings { + const char *pop3c_host; + in_port_t pop3c_port; + + const char *pop3c_user; + const char *pop3c_master_user; + const char *pop3c_password; + + const char *pop3c_ssl; + bool pop3c_ssl_verify; + + const char *pop3c_rawlog_dir; + bool pop3c_quick_received_date; + + const char *pop3c_features; + enum pop3c_features parsed_features; +}; +/* ../../src/lib-storage/index/mbox/mbox-settings.h */ +struct mbox_settings { + const char *mbox_read_locks; + const char *mbox_write_locks; + unsigned int mbox_lock_timeout; + unsigned int mbox_dotlock_change_timeout; + uoff_t mbox_min_index_size; + bool mbox_dirty_syncs; + bool mbox_very_dirty_syncs; + bool mbox_lazy_writes; + const char *mbox_md5; +}; +/* ../../src/lib-storage/index/maildir/maildir-settings.h */ +struct maildir_settings { + bool maildir_copy_with_hardlinks; + bool maildir_very_dirty_syncs; + bool maildir_broken_filename_sizes; + bool maildir_empty_new; +}; +/* ../../src/lib-storage/index/imapc/imapc-settings.h */ +/* <settings checks> */ +enum imapc_features { + IMAPC_FEATURE_RFC822_SIZE = 0x01, + IMAPC_FEATURE_GUID_FORCED = 0x02, + IMAPC_FEATURE_FETCH_HEADERS = 0x04, + IMAPC_FEATURE_GMAIL_MIGRATION = 0x08, + IMAPC_FEATURE_SEARCH = 0x10, + IMAPC_FEATURE_ZIMBRA_WORKAROUNDS = 0x20, + IMAPC_FEATURE_NO_EXAMINE = 0x40, + IMAPC_FEATURE_PROXYAUTH = 0x80, + IMAPC_FEATURE_FETCH_MSN_WORKAROUNDS = 0x100, + IMAPC_FEATURE_FETCH_FIX_BROKEN_MAILS = 0x200, + IMAPC_FEATURE_MODSEQ = 0x400, + IMAPC_FEATURE_DELAY_LOGIN = 0x800, + IMAPC_FEATURE_FETCH_BODYSTRUCTURE = 0x1000, + IMAPC_FEATURE_SEND_ID = 0x2000, + IMAPC_FEATURE_FETCH_EMPTY_IS_EXPUNGED = 0x4000, + IMAPC_FEATURE_NO_MSN_UPDATES = 0x8000, + IMAPC_FEATURE_ACL = 0x10000, +}; +/* </settings checks> */ +struct imapc_settings { + const char *imapc_host; + in_port_t imapc_port; + + const char *imapc_user; + const char *imapc_master_user; + const char *imapc_password; + const char *imapc_sasl_mechanisms; + + const char *imapc_ssl; + bool imapc_ssl_verify; + + const char *imapc_features; + const char *imapc_rawlog_dir; + const char *imapc_list_prefix; + unsigned int imapc_cmd_timeout; + unsigned int imapc_max_idle_time; + unsigned int imapc_connection_retry_count; + unsigned int imapc_connection_retry_interval; + uoff_t imapc_max_line_length; + + const char *pop3_deleted_flag; + + enum imapc_features parsed_features; + unsigned int throttle_init_msecs; + unsigned int throttle_max_msecs; + unsigned int throttle_shrink_min_msecs; +}; +/* ../../src/lib-storage/index/dbox-multi/mdbox-settings.h */ +struct mdbox_settings { + bool mdbox_preallocate_space; + uoff_t mdbox_rotate_size; + unsigned int mdbox_rotate_interval; +}; +/* ../../src/lib-smtp/smtp-submit-settings.h */ +extern const struct setting_parser_info smtp_submit_setting_parser_info; +struct smtp_submit_settings { + const char *hostname; + bool mail_debug; + + const char *submission_host; + const char *sendmail_path; + unsigned int submission_timeout; + + const char *submission_ssl; +}; +/* ../../src/lib-settings/settings.h */ +#define DEF_STRUCT_STR(name, struct_name) \ + { SET_STR + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \ + ((struct struct_name *)0)->name, const char *), \ + #name, offsetof(struct struct_name, name) } +#define DEF_STRUCT_INT(name, struct_name) \ + { SET_INT + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \ + ((struct struct_name *)0)->name, unsigned int), \ + #name, offsetof(struct struct_name, name) } +#define DEF_STRUCT_BOOL(name, struct_name) \ + { SET_BOOL + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \ + ((struct struct_name *)0)->name, bool), \ + #name, offsetof(struct struct_name, name) } +/* ../../src/lib-master/service-settings.h */ +/* <settings checks> */ +enum service_user_default { + SERVICE_USER_DEFAULT_NONE = 0, + SERVICE_USER_DEFAULT_INTERNAL, + SERVICE_USER_DEFAULT_LOGIN +}; + +enum service_type { + SERVICE_TYPE_UNKNOWN, + SERVICE_TYPE_LOG, + SERVICE_TYPE_ANVIL, + SERVICE_TYPE_CONFIG, + SERVICE_TYPE_LOGIN, + SERVICE_TYPE_STARTUP, + /* Worker processes are intentionally limited to their process_limit, + and they can regularly reach it. There shouldn't be unnecessary + warnings about temporarily reaching the limit. */ + SERVICE_TYPE_WORKER, +}; +/* </settings checks> */ +struct file_listener_settings { + const char *path; + unsigned int mode; + const char *user; + const char *group; +}; +ARRAY_DEFINE_TYPE(file_listener_settings, struct file_listener_settings *); +struct inet_listener_settings { + const char *name; + const char *address; + in_port_t port; + bool ssl; + bool reuse_port; + bool haproxy; +}; +ARRAY_DEFINE_TYPE(inet_listener_settings, struct inet_listener_settings *); +struct service_settings { + const char *name; + const char *protocol; + const char *type; + const char *executable; + const char *user; + const char *group; + const char *privileged_group; + const char *extra_groups; + const char *chroot; + + bool drop_priv_before_exec; + + unsigned int process_min_avail; + unsigned int process_limit; + unsigned int client_limit; + unsigned int service_count; + unsigned int idle_kill; + uoff_t vsz_limit; + + ARRAY_TYPE(file_listener_settings) unix_listeners; + ARRAY_TYPE(file_listener_settings) fifo_listeners; + ARRAY_TYPE(inet_listener_settings) inet_listeners; + + /* internal to master: */ + struct master_settings *master_set; + enum service_type parsed_type; + enum service_user_default user_default; + bool login_dump_core:1; + + /* -- flags that can be set internally -- */ + + /* process_limit must not be higher than 1 */ + bool process_limit_1:1; +}; +ARRAY_DEFINE_TYPE(service_settings, struct service_settings *); +/* ../../src/lib-master/master-service-ssl-settings.h */ +extern const struct setting_parser_info master_service_ssl_setting_parser_info; +extern const struct setting_parser_info master_service_ssl_server_setting_parser_info; +struct master_service_ssl_settings { + const char *ssl; + const char *ssl_ca; + const char *ssl_client_ca_file; + const char *ssl_client_ca_dir; + const char *ssl_client_cert; + const char *ssl_client_key; + const char *ssl_cipher_list; + const char *ssl_cipher_suites; + const char *ssl_curve_list; + const char *ssl_min_protocol; + const char *ssl_cert_username_field; + const char *ssl_crypto_device; + const char *ssl_options; + + bool ssl_verify_client_cert; + bool ssl_client_require_valid_cert; + bool ssl_require_crl; + bool verbose_ssl; + bool ssl_prefer_server_ciphers; + + /* These are derived from ssl_options, not set directly */ + struct { + bool compression; + bool tickets; + } parsed_opts; +}; +struct master_service_ssl_server_settings { + const char *ssl_cert; + const char *ssl_alt_cert; + const char *ssl_key; + const char *ssl_alt_key; + const char *ssl_key_password; + const char *ssl_dh; +}; +/* ../../src/lib-master/master-service-settings.h */ +extern const struct setting_parser_info master_service_setting_parser_info; +struct master_service_settings { + /* NOTE: log process won't see any new settings unless they're + explicitly sent via environment variables by master process. */ + const char *base_dir; + const char *state_dir; + const char *instance_name; + const char *log_path; + const char *info_log_path; + const char *debug_log_path; + const char *log_timestamp; + const char *log_debug; + const char *log_core_filter; + const char *process_shutdown_filter; + const char *syslog_facility; + const char *import_environment; + const char *stats_writer_socket_path; + uoff_t config_cache_size; + bool version_ignore; + bool shutdown_clients; + bool verbose_proctitle; + + const char *haproxy_trusted_networks; + unsigned int haproxy_timeout; +}; +/* ../../src/lib-lda/lda-settings.h */ +extern const struct setting_parser_info lda_setting_parser_info; +struct lda_settings { + const char *hostname; + const char *rejection_subject; + const char *rejection_reason; + const char *deliver_log_format; + const char *recipient_delimiter; + const char *lda_original_recipient_header; + + bool quota_full_tempfail; + bool lda_mailbox_autocreate; + bool lda_mailbox_autosubscribe; +}; +/* ../../src/lib-dict-backend/dict-sql-settings.h */ +struct dict_sql_settings { + const char *connect; + + unsigned int max_pattern_fields_count; + ARRAY(struct dict_sql_map) maps; +}; +/* ../../src/lib-dict-backend/dict-ldap-settings.h */ +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; +}; +/* ../../src/lib-storage/mail-storage-settings.c */ +extern const struct setting_parser_info mailbox_setting_parser_info; +extern const struct setting_parser_info mail_namespace_setting_parser_info; +/* <settings checks> */ +static bool mail_cache_fields_parse(const char *key, const char *value, + const char **error_r) +{ + const char *const *arr; + + for (arr = t_strsplit_spaces(value, " ,"); *arr != NULL; arr++) { + const char *name = *arr; + + if (strncasecmp(name, "hdr.", 4) == 0 && + !message_header_name_is_valid(name+4)) { + *error_r = t_strdup_printf( + "Invalid %s: %s is not a valid header name", + key, name); + return FALSE; + } + } + return TRUE; +} + +static bool mail_storage_settings_check(void *_set, pool_t pool, + const char **error_r) +{ + struct mail_storage_settings *set = _set; + struct hash_format *format; + const char *p, *error; + bool uidl_format_ok; + char c; + + if (set->mailbox_idle_check_interval == 0) { + *error_r = "mailbox_idle_check_interval must not be 0"; + return FALSE; + } + + if (strcmp(set->mail_fsync, "optimized") == 0) + set->parsed_fsync_mode = FSYNC_MODE_OPTIMIZED; + else if (strcmp(set->mail_fsync, "never") == 0) + set->parsed_fsync_mode = FSYNC_MODE_NEVER; + else if (strcmp(set->mail_fsync, "always") == 0) + set->parsed_fsync_mode = FSYNC_MODE_ALWAYS; + else { + *error_r = t_strdup_printf("Unknown mail_fsync: %s", + set->mail_fsync); + return FALSE; + } + + if (set->mail_nfs_index && !set->mmap_disable) { + *error_r = "mail_nfs_index=yes requires mmap_disable=yes"; + return FALSE; + } + if (set->mail_nfs_index && + set->parsed_fsync_mode != FSYNC_MODE_ALWAYS) { + *error_r = "mail_nfs_index=yes requires mail_fsync=always"; + return FALSE; + } + + if (!file_lock_method_parse(set->lock_method, + &set->parsed_lock_method)) { + *error_r = t_strdup_printf("Unknown lock_method: %s", + set->lock_method); + return FALSE; + } + + if (set->mail_cache_max_size > 1024 * 1024 * 1024) { + *error_r = "mail_cache_max_size can't be over 1 GB"; + return FALSE; + } + if (set->mail_cache_purge_delete_percentage > 100) { + *error_r = "mail_cache_purge_delete_percentage can't be over 100"; + return FALSE; + } + + uidl_format_ok = FALSE; + for (p = set->pop3_uidl_format; *p != '\0'; p++) { + if (p[0] != '%' || p[1] == '\0') + continue; + + c = var_get_key(++p); + switch (c) { + case 'v': + case 'u': + case 'm': + case 'f': + case 'g': + uidl_format_ok = TRUE; + break; + case '%': + break; + default: + *error_r = t_strdup_printf( + "Unknown pop3_uidl_format variable: %%%c", c); + return FALSE; + } + } + if (!uidl_format_ok) { + *error_r = "pop3_uidl_format setting doesn't contain any " + "%% variables."; + return FALSE; + } + + if (strchr(set->mail_attachment_hash, '/') != NULL) { + *error_r = "mail_attachment_hash setting " + "must not contain '/' characters"; + return FALSE; + } + if (hash_format_init(set->mail_attachment_hash, &format, &error) < 0) { + *error_r = t_strconcat("Invalid mail_attachment_hash setting: ", + error, NULL); + return FALSE; + } + if (strchr(set->mail_attachment_hash, '-') != NULL) { + *error_r = "mail_attachment_hash setting " + "must not contain '-' characters"; + return FALSE; + } + hash_format_deinit_free(&format); + + // FIXME: check set->mail_server_admin syntax (RFC 5464, Section 6.2.2) + +#ifndef CONFIG_BINARY + if (*set->hostname == '\0') + set->hostname = p_strdup(pool, my_hostdomain()); +#endif + + /* parse mail_attachment_indicator_options */ + if (*set->mail_attachment_detection_options != '\0') { + ARRAY_TYPE(const_string) content_types; + p_array_init(&content_types, pool, 2); + + const char *const *options = + t_strsplit_spaces(set->mail_attachment_detection_options, " "); + + while(*options != NULL) { + const char *opt = *options; + + if (strcmp(opt, "add-flags") == 0 || + strcmp(opt, "add-flags-on-save") == 0) { + set->parsed_mail_attachment_detection_add_flags = TRUE; + } else if (strcmp(opt, "no-flags-on-fetch") == 0) { + set->parsed_mail_attachment_detection_no_flags_on_fetch = TRUE; + } else if (strcmp(opt, "exclude-inlined") == 0) { + set->parsed_mail_attachment_exclude_inlined = TRUE; + } else if (str_begins(opt, "content-type=")) { + const char *value = p_strdup(pool, opt+13); + array_push_back(&content_types, &value); + } else { + *error_r = t_strdup_printf("mail_attachment_detection_options: " + "Unknown option: %s", opt); + return FALSE; + } + options++; + } + + array_append_zero(&content_types); + set->parsed_mail_attachment_content_type_filter = array_front(&content_types); + } + + if (!mail_cache_fields_parse("mail_cache_fields", + set->mail_cache_fields, error_r)) + return FALSE; + if (!mail_cache_fields_parse("mail_always_cache_fields", + set->mail_always_cache_fields, error_r)) + return FALSE; + if (!mail_cache_fields_parse("mail_never_cache_fields", + set->mail_never_cache_fields, error_r)) + return FALSE; + return TRUE; +} + +static bool namespace_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct mail_namespace_settings *ns = _set; + struct mail_namespace_settings *const *namespaces; + const char *name; + unsigned int i, count; + + name = ns->prefix != NULL ? ns->prefix : ""; + + if (ns->separator[0] != '\0' && ns->separator[1] != '\0') { + *error_r = t_strdup_printf("Namespace '%s': " + "Hierarchy separator must be only one character long", + name); + return FALSE; + } + if (!uni_utf8_str_is_valid(name)) { + *error_r = t_strdup_printf("Namespace prefix not valid UTF8: %s", + name); + return FALSE; + } + + if (ns->alias_for != NULL && !ns->disabled) { + if (array_is_created(&ns->user_set->namespaces)) { + namespaces = array_get(&ns->user_set->namespaces, + &count); + } else { + namespaces = NULL; + count = 0; + } + for (i = 0; i < count; i++) { + if (strcmp(namespaces[i]->prefix, ns->alias_for) == 0) + break; + } + if (i == count) { + *error_r = t_strdup_printf( + "Namespace '%s': alias_for points to " + "unknown namespace: %s", name, ns->alias_for); + return FALSE; + } + if (namespaces[i]->alias_for != NULL) { + *error_r = t_strdup_printf( + "Namespace '%s': alias_for chaining isn't " + "allowed: %s -> %s", name, ns->alias_for, + namespaces[i]->alias_for); + return FALSE; + } + } + return TRUE; +} + +static bool mailbox_special_use_exists(const char *name) +{ + if (name[0] != '\\') + return FALSE; + name++; + + if (strcasecmp(name, "All") == 0) + return TRUE; + if (strcasecmp(name, "Archive") == 0) + return TRUE; + if (strcasecmp(name, "Drafts") == 0) + return TRUE; + if (strcasecmp(name, "Flagged") == 0) + return TRUE; + if (strcasecmp(name, "Important") == 0) + return TRUE; + if (strcasecmp(name, "Junk") == 0) + return TRUE; + if (strcasecmp(name, "Sent") == 0) + return TRUE; + if (strcasecmp(name, "Trash") == 0) + return TRUE; + return FALSE; +} + +static bool +mailbox_special_use_check(struct mailbox_settings *set, pool_t pool, + const char **error_r) +{ + const char *const *uses, *str; + unsigned int i; + + uses = t_strsplit_spaces(set->special_use, " "); + for (i = 0; uses[i] != NULL; i++) { + if (!mailbox_special_use_exists(uses[i])) { + *error_r = t_strdup_printf( + "mailbox %s: unknown special_use: %s", + set->name, uses[i]); + return FALSE; + } + } + /* make sure there are no extra spaces */ + str = t_strarray_join(uses, " "); + if (strcmp(str, set->special_use) != 0) + set->special_use = p_strdup(pool, str); + return TRUE; +} + +static bool mailbox_settings_check(void *_set, pool_t pool, + const char **error_r) +{ + struct mailbox_settings *set = _set; + + if (!uni_utf8_str_is_valid(set->name)) { + *error_r = t_strdup_printf("mailbox %s: name isn't valid UTF-8", + set->name); + return FALSE; + } + if (*set->special_use != '\0') { + if (!mailbox_special_use_check(set, pool, error_r)) + return FALSE; + } + return TRUE; +} + +static bool mail_user_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ + struct mail_user_settings *set = _set; + +#ifndef CONFIG_BINARY + fix_base_path(set, pool, &set->auth_socket_path); + + if (*set->hostname == '\0') + set->hostname = p_strdup(pool, my_hostdomain()); + if (set->postmaster_address[0] == SETTING_STRVAR_UNEXPANDED[0] && + set->postmaster_address[1] == '\0') { + /* check for valid looking fqdn in hostname */ + if (strchr(set->hostname, '.') == NULL) { + *error_r = "postmaster_address setting not given"; + return FALSE; + } + set->postmaster_address = + p_strconcat(pool, SETTING_STRVAR_UNEXPANDED, + "postmaster@", set->hostname, NULL); + } +#else + if (*set->mail_plugins != '\0' && + access(set->mail_plugin_dir, R_OK | X_OK) < 0) { + *error_r = t_strdup_printf( + "mail_plugin_dir: access(%s) failed: %m", + set->mail_plugin_dir); + return FALSE; + } +#endif + return TRUE; +} + +#ifndef CONFIG_BINARY +static bool parse_postmaster_address(const char *address, pool_t pool, + struct mail_user_settings *set, + const char **error_r) ATTR_NULL(3) +{ + struct message_address *addr; + struct smtp_address *smtp_addr; + + addr = message_address_parse(pool, + (const unsigned char *)address, + strlen(address), 2, 0); + if (addr == NULL || addr->domain == NULL || addr->invalid_syntax || + smtp_address_create_from_msg(pool, addr, &smtp_addr) < 0) { + *error_r = t_strdup_printf( + "invalid address `%s' specified for the " + "postmaster_address setting", address); + return FALSE; + } + if (addr->next != NULL) { + *error_r = "more than one address specified for the " + "postmaster_address setting"; + return FALSE; + } + if (addr->name == NULL || *addr->name == '\0') + addr->name = "Postmaster"; + if (set != NULL) { + set->_parsed_postmaster_address = addr; + set->_parsed_postmaster_address_smtp = smtp_addr; + } + return TRUE; +} + +static bool +mail_user_settings_expand_check(void *_set, pool_t pool, + const char **error_r ATTR_UNUSED) +{ + struct mail_user_settings *set = _set; + const char *error; + + /* Parse if possible. Perform error handling later. */ + (void)parse_postmaster_address(set->postmaster_address, pool, + set, &error); + return TRUE; +} +#endif + +/* </settings checks> */ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct mail_storage_settings) +static const struct setting_define mail_storage_setting_defines[] = { + DEF(STR_VARS, mail_location), + { .type = SET_ALIAS, .key = "mail" }, + DEF(STR_VARS, mail_attachment_fs), + DEF(STR_VARS, mail_attachment_dir), + DEF(STR, mail_attachment_hash), + DEF(SIZE, mail_attachment_min_size), + DEF(STR, mail_attachment_detection_options), + DEF(STR_VARS, mail_attribute_dict), + DEF(UINT, mail_prefetch_count), + DEF(STR, mail_cache_fields), + DEF(STR, mail_always_cache_fields), + DEF(STR, mail_never_cache_fields), + DEF(STR, mail_server_comment), + DEF(STR, mail_server_admin), + DEF(TIME_HIDDEN, mail_cache_unaccessed_field_drop), + DEF(SIZE_HIDDEN, mail_cache_record_max_size), + DEF(SIZE_HIDDEN, mail_cache_max_size), + DEF(UINT_HIDDEN, mail_cache_min_mail_count), + DEF(SIZE_HIDDEN, mail_cache_purge_min_size), + DEF(UINT_HIDDEN, mail_cache_purge_delete_percentage), + DEF(UINT_HIDDEN, mail_cache_purge_continued_percentage), + DEF(UINT_HIDDEN, mail_cache_purge_header_continue_count), + DEF(SIZE_HIDDEN, mail_index_rewrite_min_log_bytes), + DEF(SIZE_HIDDEN, mail_index_rewrite_max_log_bytes), + DEF(SIZE_HIDDEN, mail_index_log_rotate_min_size), + DEF(SIZE_HIDDEN, mail_index_log_rotate_max_size), + DEF(TIME_HIDDEN, mail_index_log_rotate_min_age), + DEF(TIME_HIDDEN, mail_index_log2_max_age), + DEF(TIME, mailbox_idle_check_interval), + DEF(UINT, mail_max_keyword_length), + DEF(TIME, mail_max_lock_timeout), + DEF(TIME, mail_temp_scan_interval), + DEF(UINT, mail_vsize_bg_after_count), + DEF(UINT, mail_sort_max_read_count), + DEF(BOOL, mail_save_crlf), + DEF(ENUM, mail_fsync), + DEF(BOOL, mmap_disable), + DEF(BOOL, dotlock_use_excl), + DEF(BOOL, mail_nfs_storage), + DEF(BOOL, mail_nfs_index), + DEF(BOOL, mailbox_list_index), + DEF(BOOL, mailbox_list_index_very_dirty_syncs), + DEF(BOOL, mailbox_list_index_include_inbox), + DEF(BOOL, mail_debug), + DEF(BOOL, mail_full_filesystem_access), + DEF(BOOL, maildir_stat_dirs), + DEF(BOOL, mail_shared_explicit_inbox), + DEF(ENUM, lock_method), + DEF(STR, pop3_uidl_format), + + DEF(STR, hostname), + DEF(STR, recipient_delimiter), + + SETTING_DEFINE_LIST_END +}; +const struct mail_storage_settings mail_storage_default_settings = { + .mail_location = "", + .mail_attachment_fs = "sis posix", + .mail_attachment_dir = "", + .mail_attachment_hash = "%{sha1}", + .mail_attachment_min_size = 1024*128, + .mail_attachment_detection_options = "", + .mail_attribute_dict = "", + .mail_prefetch_count = 0, + .mail_cache_fields = "flags", + .mail_always_cache_fields = "", + .mail_never_cache_fields = "imap.envelope", + .mail_server_comment = "", + .mail_server_admin = "", + .mail_cache_min_mail_count = 0, + .mail_cache_unaccessed_field_drop = 60*60*24*30, + .mail_cache_record_max_size = 64 * 1024, + .mail_cache_max_size = 1024 * 1024 * 1024, + .mail_cache_purge_min_size = 32 * 1024, + .mail_cache_purge_delete_percentage = 20, + .mail_cache_purge_continued_percentage = 200, + .mail_cache_purge_header_continue_count = 4, + .mail_index_rewrite_min_log_bytes = 8 * 1024, + .mail_index_rewrite_max_log_bytes = 128 * 1024, + .mail_index_log_rotate_min_size = 32 * 1024, + .mail_index_log_rotate_max_size = 1024 * 1024, + .mail_index_log_rotate_min_age = 5 * 60, + .mail_index_log2_max_age = 3600 * 24 * 2, + .mailbox_idle_check_interval = 30, + .mail_max_keyword_length = 50, + .mail_max_lock_timeout = 0, + .mail_temp_scan_interval = 7*24*60*60, + .mail_vsize_bg_after_count = 0, + .mail_sort_max_read_count = 0, + .mail_save_crlf = FALSE, + .mail_fsync = "optimized:never:always", + .mmap_disable = FALSE, + .dotlock_use_excl = TRUE, + .mail_nfs_storage = FALSE, + .mail_nfs_index = FALSE, + .mailbox_list_index = TRUE, + .mailbox_list_index_very_dirty_syncs = FALSE, + .mailbox_list_index_include_inbox = FALSE, + .mail_debug = FALSE, + .mail_full_filesystem_access = FALSE, + .maildir_stat_dirs = FALSE, + .mail_shared_explicit_inbox = FALSE, + .lock_method = "fcntl:flock:dotlock", + .pop3_uidl_format = "%08Xu%08Xv", + + .hostname = "", + .recipient_delimiter = "+", +}; +const struct setting_parser_info mail_storage_setting_parser_info = { + .module_name = "mail", + .defines = mail_storage_setting_defines, + .defaults = &mail_storage_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct mail_storage_settings), + + .parent_offset = SIZE_MAX, + .parent = &mail_user_setting_parser_info, + + .check_func = mail_storage_settings_check, +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct mailbox_settings) +static const struct setting_define mailbox_setting_defines[] = { + DEF(STR, name), + { .type = SET_ENUM, .key = "auto", + .offset = offsetof(struct mailbox_settings, autocreate) } , + DEF(STR, special_use), + DEF(STR, driver), + DEF(STR, comment), + DEF(TIME, autoexpunge), + DEF(UINT, autoexpunge_max_mails), + + SETTING_DEFINE_LIST_END +}; +const struct mailbox_settings mailbox_default_settings = { + .name = "", + .autocreate = MAILBOX_SET_AUTO_NO":" + MAILBOX_SET_AUTO_CREATE":" + MAILBOX_SET_AUTO_SUBSCRIBE, + .special_use = "", + .driver = "", + .comment = "", + .autoexpunge = 0, + .autoexpunge_max_mails = 0 +}; +const struct setting_parser_info mailbox_setting_parser_info = { + .defines = mailbox_setting_defines, + .defaults = &mailbox_default_settings, + + .type_offset = offsetof(struct mailbox_settings, name), + .struct_size = sizeof(struct mailbox_settings), + + .parent_offset = SIZE_MAX, + .parent = &mail_user_setting_parser_info, + + .check_func = mailbox_settings_check +}; +#undef DEF +#undef DEFLIST_UNIQUE +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct mail_namespace_settings) +#define DEFLIST_UNIQUE(field, name, defines) \ + { .type = SET_DEFLIST_UNIQUE, .key = name, \ + .offset = offsetof(struct mail_namespace_settings, field), \ + .list_info = defines } +static const struct setting_define mail_namespace_setting_defines[] = { + DEF(STR, name), + DEF(ENUM, type), + DEF(STR, separator), + DEF(STR_VARS, prefix), + DEF(STR_VARS, location), + { .type = SET_ALIAS, .key = "mail" }, + { .type = SET_ALIAS, .key = "mail_location" }, + DEF(STR_VARS, alias_for), + + DEF(BOOL, inbox), + DEF(BOOL, hidden), + DEF(ENUM, list), + DEF(BOOL, subscriptions), + DEF(BOOL, ignore_on_failure), + DEF(BOOL, disabled), + DEF(UINT, order), + + DEFLIST_UNIQUE(mailboxes, "mailbox", &mailbox_setting_parser_info), + + SETTING_DEFINE_LIST_END +}; +const struct mail_namespace_settings mail_namespace_default_settings = { + .name = "", + .type = "private:shared:public", + .separator = "", + .prefix = "", + .location = "", + .alias_for = NULL, + + .inbox = FALSE, + .hidden = FALSE, + .list = "yes:no:children", + .subscriptions = TRUE, + .ignore_on_failure = FALSE, + .disabled = FALSE, + .order = 0, + + .mailboxes = ARRAY_INIT +}; +const struct setting_parser_info mail_namespace_setting_parser_info = { + .defines = mail_namespace_setting_defines, + .defaults = &mail_namespace_default_settings, + + .type_offset = offsetof(struct mail_namespace_settings, name), + .struct_size = sizeof(struct mail_namespace_settings), + + .parent_offset = offsetof(struct mail_namespace_settings, user_set), + .parent = &mail_user_setting_parser_info, + + .check_func = namespace_settings_check +}; +#undef DEF +#undef DEFLIST_UNIQUE +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct mail_user_settings) +#define DEFLIST_UNIQUE(field, name, defines) \ + { .type = SET_DEFLIST_UNIQUE, .key = name, \ + .offset = offsetof(struct mail_user_settings, field), \ + .list_info = defines } +static const struct setting_define mail_user_setting_defines[] = { + DEF(STR, base_dir), + DEF(STR, auth_socket_path), + DEF(STR_VARS, mail_temp_dir), + + DEF(STR, mail_uid), + DEF(STR, mail_gid), + DEF(STR_VARS, mail_home), + DEF(STR_VARS, mail_chroot), + DEF(STR, mail_access_groups), + DEF(STR, mail_privileged_group), + DEF(STR, valid_chroot_dirs), + + DEF(UINT, first_valid_uid), + DEF(UINT, last_valid_uid), + DEF(UINT, first_valid_gid), + DEF(UINT, last_valid_gid), + + DEF(STR, mail_plugins), + DEF(STR, mail_plugin_dir), + + DEF(STR, mail_log_prefix), + + DEF(STR, hostname), + DEF(STR_VARS, postmaster_address), + + DEFLIST_UNIQUE(namespaces, "namespace", &mail_namespace_setting_parser_info), + { .type = SET_STRLIST, .key = "plugin", + .offset = offsetof(struct mail_user_settings, plugin_envs) }, + + SETTING_DEFINE_LIST_END +}; +static const struct mail_user_settings mail_user_default_settings = { + .base_dir = PKG_RUNDIR, + .auth_socket_path = "auth-userdb", + .mail_temp_dir = "/tmp", + + .mail_uid = "", + .mail_gid = "", + .mail_home = "", + .mail_chroot = "", + .mail_access_groups = "", + .mail_privileged_group = "", + .valid_chroot_dirs = "", + + .first_valid_uid = 500, + .last_valid_uid = 0, + .first_valid_gid = 1, + .last_valid_gid = 0, + + .mail_plugins = "", + .mail_plugin_dir = MODULEDIR, + + .mail_log_prefix = "%s(%u)<%{pid}><%{session}>: ", + + .hostname = "", + .postmaster_address = "postmaster@%{if;%d;ne;;%d;%{hostname}}", + + .namespaces = ARRAY_INIT, + .plugin_envs = ARRAY_INIT +}; +const struct setting_parser_info mail_user_setting_parser_info = { + .module_name = "mail", + .defines = mail_user_setting_defines, + .defaults = &mail_user_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct mail_user_settings), + + .parent_offset = SIZE_MAX, + + .check_func = mail_user_settings_check, +#ifndef CONFIG_BINARY + .expand_check_func = mail_user_settings_expand_check, +#endif +}; +/* ../../src/lib-storage/index/pop3c/pop3c-settings.c */ +/* <settings checks> */ +struct pop3c_feature_list { + const char *name; + enum pop3c_features num; +}; + +static const struct pop3c_feature_list pop3c_feature_list[] = { + { "no-pipelining", POP3C_FEATURE_NO_PIPELINING }, + { NULL, 0 } +}; + +static int +pop3c_settings_parse_features(struct pop3c_settings *set, + const char **error_r) +{ + enum pop3c_features features = 0; + const struct pop3c_feature_list *list; + const char *const *str; + + str = t_strsplit_spaces(set->pop3c_features, " ,"); + for (; *str != NULL; str++) { + list = pop3c_feature_list; + for (; list->name != NULL; list++) { + if (strcasecmp(*str, list->name) == 0) { + features |= list->num; + break; + } + } + if (list->name == NULL) { + *error_r = t_strdup_printf("pop3c_features: " + "Unknown feature: %s", *str); + return -1; + } + } + set->parsed_features = features; + return 0; +} + +static bool pop3c_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct pop3c_settings *set = _set; + + if (pop3c_settings_parse_features(set, error_r) < 0) + return FALSE; + return TRUE; +} +/* </settings checks> */ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct pop3c_settings) +static const struct setting_define pop3c_setting_defines[] = { + DEF(STR, pop3c_host), + DEF(IN_PORT, pop3c_port), + + DEF(STR_VARS, pop3c_user), + DEF(STR_VARS, pop3c_master_user), + DEF(STR, pop3c_password), + + DEF(ENUM, pop3c_ssl), + DEF(BOOL, pop3c_ssl_verify), + + DEF(STR, pop3c_rawlog_dir), + DEF(BOOL, pop3c_quick_received_date), + + DEF(STR, pop3c_features), + + SETTING_DEFINE_LIST_END +}; +static const struct pop3c_settings pop3c_default_settings = { + .pop3c_host = "", + .pop3c_port = 110, + + .pop3c_user = "%u", + .pop3c_master_user = "", + .pop3c_password = "", + + .pop3c_ssl = "no:pop3s:starttls", + .pop3c_ssl_verify = TRUE, + + .pop3c_rawlog_dir = "", + .pop3c_quick_received_date = FALSE, + + .pop3c_features = "" +}; +static const struct setting_parser_info pop3c_setting_parser_info = { + .module_name = "pop3c", + .defines = pop3c_setting_defines, + .defaults = &pop3c_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct pop3c_settings), + + .parent_offset = SIZE_MAX, + .parent = &mail_user_setting_parser_info, + + .check_func = pop3c_settings_check +}; +/* ../../src/lib-storage/index/mbox/mbox-settings.c */ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct mbox_settings) +static const struct setting_define mbox_setting_defines[] = { + DEF(STR, mbox_read_locks), + DEF(STR, mbox_write_locks), + DEF(TIME, mbox_lock_timeout), + DEF(TIME, mbox_dotlock_change_timeout), + DEF(SIZE, mbox_min_index_size), + DEF(BOOL, mbox_dirty_syncs), + DEF(BOOL, mbox_very_dirty_syncs), + DEF(BOOL, mbox_lazy_writes), + DEF(ENUM, mbox_md5), + + SETTING_DEFINE_LIST_END +}; +static const struct mbox_settings mbox_default_settings = { + .mbox_read_locks = "fcntl", + .mbox_write_locks = "dotlock fcntl", + .mbox_lock_timeout = 5*60, + .mbox_dotlock_change_timeout = 2*60, + .mbox_min_index_size = 0, + .mbox_dirty_syncs = TRUE, + .mbox_very_dirty_syncs = FALSE, + .mbox_lazy_writes = TRUE, + .mbox_md5 = "apop3d:all" +}; +static const struct setting_parser_info mbox_setting_parser_info = { + .module_name = "mbox", + .defines = mbox_setting_defines, + .defaults = &mbox_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct mbox_settings), + + .parent_offset = SIZE_MAX, + .parent = &mail_user_setting_parser_info +}; +/* ../../src/lib-storage/index/maildir/maildir-settings.c */ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct maildir_settings) +static const struct setting_define maildir_setting_defines[] = { + DEF(BOOL, maildir_copy_with_hardlinks), + DEF(BOOL, maildir_very_dirty_syncs), + DEF(BOOL, maildir_broken_filename_sizes), + DEF(BOOL, maildir_empty_new), + + SETTING_DEFINE_LIST_END +}; +static const struct maildir_settings maildir_default_settings = { + .maildir_copy_with_hardlinks = TRUE, + .maildir_very_dirty_syncs = FALSE, + .maildir_broken_filename_sizes = FALSE, + .maildir_empty_new = FALSE +}; +static const struct setting_parser_info maildir_setting_parser_info = { + .module_name = "maildir", + .defines = maildir_setting_defines, + .defaults = &maildir_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct maildir_settings), + + .parent_offset = SIZE_MAX, + .parent = &mail_user_setting_parser_info +}; +/* ../../src/lib-storage/index/imapc/imapc-settings.c */ +/* <settings checks> */ +struct imapc_feature_list { + const char *name; + enum imapc_features num; +}; + +static const struct imapc_feature_list imapc_feature_list[] = { + { "rfc822.size", IMAPC_FEATURE_RFC822_SIZE }, + { "guid-forced", IMAPC_FEATURE_GUID_FORCED }, + { "fetch-headers", IMAPC_FEATURE_FETCH_HEADERS }, + { "gmail-migration", IMAPC_FEATURE_GMAIL_MIGRATION }, + { "search", IMAPC_FEATURE_SEARCH }, + { "zimbra-workarounds", IMAPC_FEATURE_ZIMBRA_WORKAROUNDS }, + { "no-examine", IMAPC_FEATURE_NO_EXAMINE }, + { "proxyauth", IMAPC_FEATURE_PROXYAUTH }, + { "fetch-msn-workarounds", IMAPC_FEATURE_FETCH_MSN_WORKAROUNDS }, + { "fetch-fix-broken-mails", IMAPC_FEATURE_FETCH_FIX_BROKEN_MAILS }, + { "modseq", IMAPC_FEATURE_MODSEQ }, + { "delay-login", IMAPC_FEATURE_DELAY_LOGIN }, + { "fetch-bodystructure", IMAPC_FEATURE_FETCH_BODYSTRUCTURE }, + { "send-id", IMAPC_FEATURE_SEND_ID }, + { "fetch-empty-is-expunged", IMAPC_FEATURE_FETCH_EMPTY_IS_EXPUNGED }, + { "no-msn-updates", IMAPC_FEATURE_NO_MSN_UPDATES }, + { "acl", IMAPC_FEATURE_ACL }, + { NULL, 0 } +}; + +static int +imapc_settings_parse_throttle(struct imapc_settings *set, + const char *throttle_str, const char **error_r) +{ + const char *const *tmp; + + tmp = t_strsplit(throttle_str, ":"); + if (str_array_length(tmp) != 3 || + str_to_uint(tmp[0], &set->throttle_init_msecs) < 0 || + str_to_uint(tmp[1], &set->throttle_max_msecs) < 0 || + str_to_uint(tmp[2], &set->throttle_shrink_min_msecs) < 0) { + *error_r = "imapc_features: Invalid throttle settings"; + return -1; + } + return 0; +} + +static int +imapc_settings_parse_features(struct imapc_settings *set, + const char **error_r) +{ + enum imapc_features features = 0; + const struct imapc_feature_list *list; + const char *const *str; + + str = t_strsplit_spaces(set->imapc_features, " ,"); + for (; *str != NULL; str++) { + list = imapc_feature_list; + for (; list->name != NULL; list++) { + if (strcasecmp(*str, list->name) == 0) { + features |= list->num; + break; + } + } + if (strncasecmp(*str, "throttle:", 9) == 0) { + if (imapc_settings_parse_throttle(set, *str + 9, error_r) < 0) + return -1; + continue; + } + if (list->name == NULL) { + *error_r = t_strdup_printf("imapc_features: " + "Unknown feature: %s", *str); + return -1; + } + } + set->parsed_features = features; + return 0; +} + +static bool imapc_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct imapc_settings *set = _set; + + if (set->imapc_max_idle_time == 0) { + *error_r = "imapc_max_idle_time must not be 0"; + return FALSE; + } + if (imapc_settings_parse_features(set, error_r) < 0) + return FALSE; + return TRUE; +} +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct imapc_settings) +static const struct setting_define imapc_setting_defines[] = { + DEF(STR, imapc_host), + DEF(IN_PORT, imapc_port), + + DEF(STR_VARS, imapc_user), + DEF(STR_VARS, imapc_master_user), + DEF(STR, imapc_password), + DEF(STR, imapc_sasl_mechanisms), + + DEF(ENUM, imapc_ssl), + DEF(BOOL, imapc_ssl_verify), + + DEF(STR, imapc_features), + DEF(STR, imapc_rawlog_dir), + DEF(STR, imapc_list_prefix), + DEF(TIME, imapc_cmd_timeout), + DEF(TIME, imapc_max_idle_time), + DEF(UINT, imapc_connection_retry_count), + DEF(TIME_MSECS, imapc_connection_retry_interval), + DEF(SIZE, imapc_max_line_length), + + DEF(STR, pop3_deleted_flag), + + SETTING_DEFINE_LIST_END +}; +static const struct imapc_settings imapc_default_settings = { + .imapc_host = "", + .imapc_port = 143, + + .imapc_user = "", + .imapc_master_user = "", + .imapc_password = "", + .imapc_sasl_mechanisms = "", + + .imapc_ssl = "no:imaps:starttls", + .imapc_ssl_verify = TRUE, + + .imapc_features = "", + .imapc_rawlog_dir = "", + .imapc_list_prefix = "", + .imapc_cmd_timeout = 5*60, + .imapc_max_idle_time = 60*29, + .imapc_connection_retry_count = 1, + .imapc_connection_retry_interval = 1000, + .imapc_max_line_length = 0, + + .pop3_deleted_flag = "" +}; +static const struct setting_parser_info imapc_setting_parser_info = { + .module_name = "imapc", + .defines = imapc_setting_defines, + .defaults = &imapc_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct imapc_settings), + + .parent_offset = SIZE_MAX, + .parent = &mail_user_setting_parser_info, + + .check_func = imapc_settings_check +}; +/* ../../src/lib-storage/index/dbox-multi/mdbox-settings.c */ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct mdbox_settings) +static const struct setting_define mdbox_setting_defines[] = { + DEF(BOOL, mdbox_preallocate_space), + DEF(SIZE, mdbox_rotate_size), + DEF(TIME, mdbox_rotate_interval), + + SETTING_DEFINE_LIST_END +}; +static const struct mdbox_settings mdbox_default_settings = { + .mdbox_preallocate_space = FALSE, + .mdbox_rotate_size = 10*1024*1024, + .mdbox_rotate_interval = 0 +}; +static const struct setting_parser_info mdbox_setting_parser_info = { + .module_name = "mdbox", + .defines = mdbox_setting_defines, + .defaults = &mdbox_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct mdbox_settings), + + .parent_offset = SIZE_MAX, + .parent = &mail_user_setting_parser_info +}; +/* ../../src/lib-lda/lda-settings.c */ +#undef DEF +#undef DEFLIST +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct lda_settings) +#define DEFLIST(field, name, defines) \ + { .type = SET_DEFLIST, .key = name, \ + .offset = offsetof(struct lda_settings, field), \ + .list_info = defines } +static const struct setting_define lda_setting_defines[] = { + DEF(STR, hostname), + DEF(STR, rejection_subject), + DEF(STR, rejection_reason), + DEF(STR, deliver_log_format), + DEF(STR, recipient_delimiter), + DEF(STR, lda_original_recipient_header), + DEF(BOOL, quota_full_tempfail), + DEF(BOOL, lda_mailbox_autocreate), + DEF(BOOL, lda_mailbox_autosubscribe), + + SETTING_DEFINE_LIST_END +}; +static const struct lda_settings lda_default_settings = { + .hostname = "", + .rejection_subject = "Rejected: %s", + .rejection_reason = + "Your message to <%t> was automatically rejected:%n%r", + .deliver_log_format = "msgid=%m: %$", + .recipient_delimiter = "+", + .lda_original_recipient_header = "", + .quota_full_tempfail = FALSE, + .lda_mailbox_autocreate = FALSE, + .lda_mailbox_autosubscribe = FALSE +}; +static const struct setting_parser_info *lda_setting_dependencies[] = { + &mail_user_setting_parser_info, + &smtp_submit_setting_parser_info, + NULL +}; +const struct setting_parser_info lda_setting_parser_info = { + .module_name = "lda", + .defines = lda_setting_defines, + .defaults = &lda_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct lda_settings), + + .parent_offset = SIZE_MAX, + +#ifndef CONFIG_BINARY + .check_func = lda_settings_check, +#endif + .dependencies = lda_setting_dependencies +}; +/* ../../src/lib-dict-backend/dict-sql-settings.c */ +#define DEF_STR(name) DEF_STRUCT_STR(name, dict_sql_map) +#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_sql_map) +/* ../../src/lib-dict-backend/dict-ldap-settings.c */ +#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) +/* ../../src/submission/submission-settings.h */ +extern const struct setting_parser_info submission_setting_parser_info; +/* <settings checks> */ +enum submission_client_workarounds { + SUBMISSION_WORKAROUND_WHITESPACE_BEFORE_PATH = BIT(0), + SUBMISSION_WORKAROUND_MAILBOX_FOR_PATH = BIT(1), +}; +/* </settings checks> */ +struct submission_settings { + bool verbose_proctitle; + const char *rawlog_dir; + + const char *hostname; + + const char *login_greeting; + const char *login_trusted_networks; + + const char *recipient_delimiter; + + /* submission: */ + uoff_t submission_max_mail_size; + unsigned int submission_max_recipients; + const char *submission_client_workarounds; + const char *submission_logout_format; + + /* submission backend: */ + const char *submission_backend_capabilities; + + /* submission relay: */ + const char *submission_relay_host; + in_port_t submission_relay_port; + bool submission_relay_trusted; + + const char *submission_relay_user; + const char *submission_relay_master_user; + const char *submission_relay_password; + + const char *submission_relay_ssl; + bool submission_relay_ssl_verify; + + const char *submission_relay_rawlog_dir; + unsigned int submission_relay_max_idle_time; + + unsigned int submission_relay_connect_timeout; + unsigned int submission_relay_command_timeout; + + /* imap urlauth: */ + const char *imap_urlauth_host; + in_port_t imap_urlauth_port; + + enum submission_client_workarounds parsed_workarounds; +}; +/* ../../src/submission-login/submission-login-settings.h */ +extern const struct setting_parser_info *submission_login_setting_roots[]; +/* <settings checks> */ +enum submission_login_client_workarounds { + SUBMISSION_LOGIN_WORKAROUND_IMPLICIT_AUTH_EXTERNAL = BIT(0), + SUBMISSION_LOGIN_WORKAROUND_EXOTIC_BACKEND = BIT(1), +}; +/* </settings checks> */ +struct submission_login_settings { + const char *hostname; + + /* submission: */ + uoff_t submission_max_mail_size; + const char *submission_client_workarounds; + const char *submission_backend_capabilities; + + enum submission_login_client_workarounds parsed_workarounds; +}; +/* ../../src/stats/stats-settings.h */ +extern const struct setting_parser_info stats_setting_parser_info; +extern const struct setting_parser_info stats_metric_setting_parser_info; +/* <settings checks> */ +/* + * We allow a selection of a timestamp format. + * + * The 'time-unix' format generates a number with the number of seconds + * since 1970-01-01 00:00 UTC. + * + * The 'time-rfc3339' format uses the YYYY-MM-DDTHH:MM:SS.uuuuuuZ format as + * defined by RFC 3339. + * + * The special native format (not explicitly selectable in the config, but + * default if no time-* token is used) uses the format's native timestamp + * format. Note that not all formats have a timestamp data format. + * + * The native format and the rules below try to address the question: can a + * parser that doesn't have any knowledge of fields' values' types losslessly + * reconstruct the fields? + * + * For example, JSON only has strings and numbers, so it cannot represent a + * timestamp in a "context-free lossless" way. Therefore, when making a + * JSON blob, we need to decide which way to serialize timestamps. No + * matter how we do it, we incur some loss. If a decoder sees 1557232304 in + * a field, it cannot be certain if the field is an integer that just + * happens to be a reasonable timestamp, or if it actually is a timestamp. + * Same goes with RFC3339 - it could just be that the user supplied a string + * that looks like a timestamp, and that string made it into an event field. + * + * Other common serialization formats, such as CBOR, have a lossless way of + * encoding timestamps. + * + * Note that there are two concepts at play: native and default. + * + * The rules for how the format's timestamp formats are used: + * + * 1. The default time format is the native format. + * 2. The native time format may or may not exist for a given format (e.g., + * in JSON) + * 3. If the native format doesn't exist and no time format was specified in + * the config, it is a config error. + * + * We went with these rules because: + * + * 1. It prevents type information loss by default. + * 2. It completely isolates the policy from the algorithm. + * 3. It defers the decision whether each format without a native timestamp + * type should have a default acting as native until after we've had some + * operational experience. + * 4. A future decision to add a default (via 3. point) will be 100% compatible. + */ +enum event_exporter_time_fmt { + EVENT_EXPORTER_TIME_FMT_NATIVE = 0, + EVENT_EXPORTER_TIME_FMT_UNIX, + EVENT_EXPORTER_TIME_FMT_RFC3339, +}; +/* </settings checks> */ +/* <settings checks> */ +enum stats_metric_group_by_func { + STATS_METRIC_GROUPBY_DISCRETE = 0, + STATS_METRIC_GROUPBY_QUANTIZED, +}; + +/* + * A range covering a stats bucket. The the interval is half closed - the + * minimum is excluded and the maximum is included. In other words: (min, max]. + * Because we don't have a +Inf and -Inf, we use INTMAX_MIN and INTMAX_MAX + * respectively. + */ +struct stats_metric_settings_bucket_range { + intmax_t min; + intmax_t max; +}; + +struct stats_metric_settings_group_by { + const char *field; + enum stats_metric_group_by_func func; + unsigned int num_ranges; + struct stats_metric_settings_bucket_range *ranges; +}; +/* </settings checks> */ +#define STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE \ + "name hostname timestamps categories fields" +struct stats_exporter_settings { + const char *name; + const char *transport; + const char *transport_args; + unsigned int transport_timeout; + const char *format; + const char *format_args; + + /* parsed values */ + enum event_exporter_time_fmt parsed_time_format; +}; +struct stats_metric_settings { + const char *metric_name; + const char *description; + const char *fields; + const char *group_by; + const char *filter; + + ARRAY(struct stats_metric_settings_group_by) parsed_group_by; + struct event_filter *parsed_filter; + + /* exporter related fields */ + const char *exporter; + const char *exporter_include; +}; +struct stats_settings { + const char *stats_http_rawlog_dir; + + ARRAY(struct stats_exporter_settings *) exporters; + ARRAY(struct stats_metric_settings *) metrics; +}; +/* ../../src/replication/replicator/replicator-settings.h */ +extern const struct setting_parser_info replicator_setting_parser_info; +struct replicator_settings { + const char *auth_socket_path; + const char *doveadm_socket_path; + const char *replication_dsync_parameters; + + unsigned int replication_full_sync_interval; + unsigned int replication_max_conns; +}; +/* ../../src/replication/aggregator/aggregator-settings.h */ +extern const struct setting_parser_info aggregator_setting_parser_info; +struct aggregator_settings { + const char *replicator_host; + in_port_t replicator_port; +}; +/* ../../src/pop3/pop3-settings.h */ +extern const struct setting_parser_info pop3_setting_parser_info; +/* <settings checks> */ +enum pop3_client_workarounds { + WORKAROUND_OUTLOOK_NO_NULS = 0x01, + WORKAROUND_OE_NS_EOH = 0x02 +}; +enum pop3_delete_type { + POP3_DELETE_TYPE_EXPUNGE = 0, + POP3_DELETE_TYPE_FLAG, +}; +/* </settings checks> */ +struct pop3_settings { + bool verbose_proctitle; + const char *rawlog_dir; + + /* pop3: */ + bool pop3_no_flag_updates; + bool pop3_enable_last; + bool pop3_reuse_xuidl; + bool pop3_save_uidl; + bool pop3_lock_session; + bool pop3_fast_size_lookups; + const char *pop3_client_workarounds; + const char *pop3_logout_format; + const char *pop3_uidl_duplicates; + const char *pop3_deleted_flag; + const char *pop3_delete_type; + + enum pop3_client_workarounds parsed_workarounds; + enum pop3_delete_type parsed_delete_type; +}; +/* ../../src/pop3-login/pop3-login-settings.h */ +extern const struct setting_parser_info *pop3_login_setting_roots[]; +/* ../../src/plugins/quota/quota-status-settings.h */ +extern const struct setting_parser_info quota_status_setting_parser_info; +struct quota_status_settings { + const char *recipient_delimiter; +}; +/* ../../src/plugins/mail-crypt/fs-crypt-settings.h */ +extern const struct setting_parser_info fs_crypt_setting_parser_info; +struct fs_crypt_settings { + ARRAY(const char *) plugin_envs; +}; +/* ../../src/old-stats/stats-settings.h */ +extern const struct setting_parser_info old_stats_setting_parser_info; +struct old_stats_settings { + uoff_t memory_limit; + + unsigned int command_min_time; + unsigned int session_min_time; + unsigned int user_min_time; + unsigned int domain_min_time; + unsigned int ip_min_time; + + unsigned int carbon_interval; + const char *carbon_server; + const char *carbon_name; +}; +/* ../../src/master/master-settings.h */ +extern const struct setting_parser_info master_setting_parser_info; +struct master_settings { + const char *base_dir; + const char *state_dir; + const char *libexec_dir; + const char *instance_name; + const char *protocols; + const char *listen; + const char *ssl; + const char *default_internal_user; + const char *default_internal_group; + const char *default_login_user; + unsigned int default_process_limit; + unsigned int default_client_limit; + unsigned int default_idle_kill; + uoff_t default_vsz_limit; + + bool version_ignore; + + unsigned int first_valid_uid, last_valid_uid; + unsigned int first_valid_gid, last_valid_gid; + + ARRAY_TYPE(service_settings) services; + char **protocols_split; +}; +/* ../../src/login-common/login-settings.h */ +extern const struct setting_parser_info **login_set_roots; +extern const struct setting_parser_info login_setting_parser_info; +struct login_settings { + const char *login_trusted_networks; + const char *login_source_ips; + const char *login_greeting; + const char *login_log_format_elements, *login_log_format; + const char *login_access_sockets; + const char *login_proxy_notify_path; + const char *login_plugin_dir; + const char *login_plugins; + unsigned int login_proxy_timeout; + unsigned int login_proxy_max_reconnects; + unsigned int login_proxy_max_disconnect_delay; + const char *login_proxy_rawlog_dir; + const char *director_username_hash; + + bool auth_ssl_require_client_cert; + bool auth_ssl_username_from_cert; + + bool disable_plaintext_auth; + bool auth_verbose; + bool auth_debug; + bool auth_debug_passwords; + bool verbose_proctitle; + + unsigned int mail_max_userip_connections; + + /* generated: */ + char *const *log_format_elements_split; +}; +/* ../../src/lmtp/lmtp-settings.h */ +extern const struct setting_parser_info lmtp_setting_parser_info; +/* <settings checks> */ +enum lmtp_hdr_delivery_address { + LMTP_HDR_DELIVERY_ADDRESS_NONE, + LMTP_HDR_DELIVERY_ADDRESS_FINAL, + LMTP_HDR_DELIVERY_ADDRESS_ORIGINAL +}; + +enum lmtp_client_workarounds { + LMTP_WORKAROUND_WHITESPACE_BEFORE_PATH = BIT(0), + LMTP_WORKAROUND_MAILBOX_FOR_PATH = BIT(1), +}; +/* </settings checks> */ +struct lmtp_settings { + bool lmtp_proxy; + bool lmtp_save_to_detail_mailbox; + bool lmtp_rcpt_check_quota; + bool lmtp_add_received_header; + bool lmtp_verbose_replies; + unsigned int lmtp_user_concurrency_limit; + const char *lmtp_hdr_delivery_address; + const char *lmtp_rawlog_dir; + const char *lmtp_proxy_rawlog_dir; + + const char *lmtp_client_workarounds; + + const char *login_greeting; + const char *login_trusted_networks; + + const char *mail_plugins; + const char *mail_plugin_dir; + + enum lmtp_hdr_delivery_address parsed_lmtp_hdr_delivery_address; + + enum lmtp_client_workarounds parsed_workarounds; +}; +/* ../../src/imap/imap-settings.h */ +extern const struct setting_parser_info imap_setting_parser_info; +/* <settings checks> */ +enum imap_client_workarounds { + WORKAROUND_DELAY_NEWMAIL = 0x01, + WORKAROUND_TB_EXTRA_MAILBOX_SEP = 0x08, + WORKAROUND_TB_LSUB_FLAGS = 0x10 +}; + +enum imap_client_fetch_failure { + IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY, + IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_AFTER, + IMAP_CLIENT_FETCH_FAILURE_NO_AFTER, +}; +/* </settings checks> */ +struct imap_settings { + bool verbose_proctitle; + const char *rawlog_dir; + + /* imap: */ + uoff_t imap_max_line_length; + unsigned int imap_idle_notify_interval; + const char *imap_capability; + const char *imap_client_workarounds; + const char *imap_logout_format; + const char *imap_id_send; + const char *imap_id_log; + const char *imap_fetch_failure; + bool imap_metadata; + bool imap_literal_minus; + unsigned int imap_hibernate_timeout; + + /* imap urlauth: */ + const char *imap_urlauth_host; + in_port_t imap_urlauth_port; + + enum imap_client_workarounds parsed_workarounds; + enum imap_client_fetch_failure parsed_fetch_failure; +}; +/* ../../src/imap-urlauth/imap-urlauth-worker-settings.h */ +extern const struct setting_parser_info imap_urlauth_worker_setting_parser_info; +struct imap_urlauth_worker_settings { + bool verbose_proctitle; + + /* imap_urlauth: */ + const char *imap_urlauth_host; + in_port_t imap_urlauth_port; +}; +/* ../../src/imap-urlauth/imap-urlauth-settings.h */ +extern const struct setting_parser_info imap_urlauth_setting_parser_info; +struct imap_urlauth_settings { + const char *base_dir; + + bool mail_debug; + + bool verbose_proctitle; + + /* imap_urlauth: */ + const char *imap_urlauth_logout_format; + + const char *imap_urlauth_submit_user; + const char *imap_urlauth_stream_user; +}; +/* ../../src/imap-urlauth/imap-urlauth-login-settings.h */ +extern const struct setting_parser_info *imap_urlauth_login_setting_roots[]; +/* ../../src/imap-login/imap-login-settings.h */ +extern const struct setting_parser_info *imap_login_setting_roots[]; +struct imap_login_settings { + const char *imap_capability; + const char *imap_id_send; + const char *imap_id_log; + bool imap_literal_minus; + bool imap_id_retain; +}; +/* ../../src/doveadm/doveadm-settings.h */ +extern const struct setting_parser_info doveadm_setting_parser_info; +/* <settings checks> */ +enum dsync_features { + DSYNC_FEATURE_EMPTY_HDR_WORKAROUND = 0x1, +}; +/* </settings checks> */ +struct doveadm_settings { + const char *base_dir; + const char *libexec_dir; + const char *mail_plugins; + const char *mail_plugin_dir; + const char *mail_temp_dir; + bool auth_debug; + const char *auth_socket_path; + const char *doveadm_socket_path; + unsigned int doveadm_worker_count; + in_port_t doveadm_port; + const char *doveadm_ssl; + const char *doveadm_username; + const char *doveadm_password; + const char *doveadm_allowed_commands; + const char *dsync_alt_char; + const char *dsync_remote_cmd; + const char *director_username_hash; + const char *doveadm_api_key; + const char *dsync_features; + const char *dsync_hashed_headers; + unsigned int dsync_commit_msgs_interval; + const char *doveadm_http_rawlog_dir; + enum dsync_features parsed_features; + ARRAY(const char *) plugin_envs; +}; +/* ../../src/director/director-settings.h */ +extern const struct setting_parser_info director_setting_parser_info; +struct director_settings { + const char *master_user_separator; + + const char *director_servers; + const char *director_mail_servers; + const char *director_username_hash; + const char *director_flush_socket; + + unsigned int director_ping_idle_timeout; + unsigned int director_ping_max_timeout; + unsigned int director_user_expire; + unsigned int director_user_kick_delay; + unsigned int director_max_parallel_moves; + unsigned int director_max_parallel_kicks; + uoff_t director_output_buffer_size; +}; +/* ../../src/dict/dict-settings.h */ +extern const struct setting_parser_info dict_setting_parser_info; +struct dict_server_settings { + const char *base_dir; + bool verbose_proctitle; + + const char *dict_db_config; + ARRAY(const char *) dicts; +}; +/* ../../src/auth/auth-settings.h */ +extern const struct setting_parser_info auth_setting_parser_info; +struct auth_passdb_settings { + const char *name; + const char *driver; + const char *args; + const char *default_fields; + const char *override_fields; + const char *mechanisms; + const char *username_filter; + + const char *skip; + const char *result_success; + const char *result_failure; + const char *result_internalfail; + bool deny; + bool pass; /* deprecated, use result_success=continue instead */ + bool master; + const char *auth_verbose; +}; +struct auth_userdb_settings { + const char *name; + const char *driver; + const char *args; + const char *default_fields; + const char *override_fields; + + const char *skip; + const char *result_success; + const char *result_failure; + const char *result_internalfail; + const char *auth_verbose; +}; +struct auth_settings { + const char *mechanisms; + const char *realms; + const char *default_realm; + uoff_t cache_size; + unsigned int cache_ttl; + unsigned int cache_negative_ttl; + bool cache_verify_password_with_worker; + const char *username_chars; + const char *username_translation; + const char *username_format; + const char *master_user_separator; + const char *anonymous_username; + const char *krb5_keytab; + const char *gssapi_hostname; + const char *winbind_helper_path; + const char *proxy_self; + unsigned int failure_delay; + + const char *policy_server_url; + const char *policy_server_api_header; + unsigned int policy_server_timeout_msecs; + const char *policy_hash_mech; + const char *policy_hash_nonce; + const char *policy_request_attributes; + bool policy_reject_on_fail; + bool policy_check_before_auth; + bool policy_check_after_auth; + bool policy_report_after_auth; + bool policy_log_only; + unsigned int policy_hash_truncate; + + bool stats; + bool verbose, debug, debug_passwords; + const char *verbose_passwords; + bool ssl_require_client_cert; + bool ssl_username_from_cert; + bool use_winbind; + + unsigned int worker_max_count; + + /* settings that don't have auth_ prefix: */ + ARRAY(struct auth_passdb_settings *) passdbs; + ARRAY(struct auth_userdb_settings *) userdbs; + + const char *base_dir; + const char *ssl_client_ca_dir; + const char *ssl_client_ca_file; + + bool verbose_proctitle; + unsigned int first_valid_uid; + unsigned int last_valid_uid; + unsigned int first_valid_gid; + unsigned int last_valid_gid; + + /* generated: */ + char username_chars_map[256]; + char username_translation_map[256]; + const char *const *realms_arr; + const struct ip_addr *proxy_self_ips; +}; +/* ../../src/util/tcpwrap-settings.c */ +#ifdef HAVE_LIBWRAP +struct service_settings tcpwrap_service_settings = { + .name = "tcpwrap", + .protocol = "", + .type = "", + .executable = "tcpwrap", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 1, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = ARRAY_INIT, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#endif +/* ../../src/util/health-check-settings.c */ +struct service_settings health_check_service_settings = { + .name = "health-check", + .protocol = "", + .type = "", + .executable = "script -p health-check.sh", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = TRUE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 1, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = ARRAY_INIT, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +/* ../../src/submission/submission-settings.c */ +/* <settings checks> */ +static struct file_listener_settings submission_unix_listeners_array[] = { + { "login/submission", 0666, "", "" } +}; +static struct file_listener_settings *submission_unix_listeners[] = { + &submission_unix_listeners_array[0] +}; +static buffer_t submission_unix_listeners_buf = { + { { submission_unix_listeners, sizeof(submission_unix_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +struct submission_client_workaround_list { + const char *name; + enum submission_client_workarounds num; +}; + +/* These definitions need to be kept in sync with equivalent definitions present + in src/submission-login/submission-login-settings.c. Workarounds that are not + relevant to the submission service are defined as 0 here to prevent "Unknown + workaround" errors below. */ +static const struct submission_client_workaround_list +submission_client_workaround_list[] = { + { "whitespace-before-path", + SUBMISSION_WORKAROUND_WHITESPACE_BEFORE_PATH }, + { "mailbox-for-path", + SUBMISSION_WORKAROUND_MAILBOX_FOR_PATH }, + { "implicit-auth-external", 0 }, + { "exotic-backend", 0 }, + { NULL, 0 } +}; + +static int +submission_settings_parse_workarounds(struct submission_settings *set, + const char **error_r) +{ + enum submission_client_workarounds client_workarounds = 0; + const struct submission_client_workaround_list *list; + const char *const *str; + + str = t_strsplit_spaces(set->submission_client_workarounds, " ,"); + for (; *str != NULL; str++) { + list = submission_client_workaround_list; + for (; list->name != NULL; list++) { + if (strcasecmp(*str, list->name) == 0) { + client_workarounds |= list->num; + break; + } + } + if (list->name == NULL) { + *error_r = t_strdup_printf( + "submission_client_workarounds: " + "Unknown workaround: %s", *str); + return -1; + } + } + set->parsed_workarounds = client_workarounds; + return 0; +} + +static bool +submission_settings_verify(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) +{ + struct submission_settings *set = _set; + + if (submission_settings_parse_workarounds(set, error_r) < 0) + return FALSE; + +#ifndef CONFIG_BINARY + if (set->submission_relay_max_idle_time == 0) { + *error_r = "submission_relay_max_idle_time must not be 0"; + return FALSE; + } + if (*set->hostname == '\0') + set->hostname = p_strdup(pool, my_hostdomain()); +#endif + return TRUE; +} +/* </settings checks> */ +struct service_settings submission_service_settings = { + .name = "submission", + .protocol = "submission", + .type = "", + .executable = "submission", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "$default_internal_group", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1024, + .client_limit = 1, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &submission_unix_listeners_buf, + sizeof(submission_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct submission_settings) +static const struct setting_define submission_setting_defines[] = { + DEF(BOOL, verbose_proctitle), + DEF(STR_VARS, rawlog_dir), + + DEF(STR, hostname), + + DEF(STR, login_greeting), + DEF(STR, login_trusted_networks), + + DEF(STR, recipient_delimiter), + + DEF(SIZE, submission_max_mail_size), + DEF(UINT, submission_max_recipients), + DEF(STR, submission_client_workarounds), + DEF(STR, submission_logout_format), + + DEF(STR, submission_backend_capabilities), + + DEF(STR, submission_relay_host), + DEF(IN_PORT, submission_relay_port), + DEF(BOOL, submission_relay_trusted), + + DEF(STR, submission_relay_user), + DEF(STR, submission_relay_master_user), + DEF(STR, submission_relay_password), + + DEF(ENUM, submission_relay_ssl), + DEF(BOOL, submission_relay_ssl_verify), + + DEF(STR_VARS, submission_relay_rawlog_dir), + DEF(TIME, submission_relay_max_idle_time), + + DEF(TIME_MSECS, submission_relay_connect_timeout), + DEF(TIME_MSECS, submission_relay_command_timeout), + + DEF(STR, imap_urlauth_host), + DEF(IN_PORT, imap_urlauth_port), + + SETTING_DEFINE_LIST_END +}; +static const struct submission_settings submission_default_settings = { + .verbose_proctitle = FALSE, + .rawlog_dir = "", + + .hostname = "", + + .login_greeting = PACKAGE_NAME" ready.", + .login_trusted_networks = "", + + .recipient_delimiter = "+", + + .submission_max_mail_size = 40*1024*1024, + .submission_max_recipients = 0, + .submission_client_workarounds = "", + .submission_logout_format = "in=%i out=%o", + + .submission_backend_capabilities = NULL, + + .submission_relay_host = "", + .submission_relay_port = 25, + .submission_relay_trusted = FALSE, + + .submission_relay_user = "", + .submission_relay_master_user = "", + .submission_relay_password = "", + + .submission_relay_ssl = "no:smtps:starttls", + .submission_relay_ssl_verify = TRUE, + + .submission_relay_rawlog_dir = "", + .submission_relay_max_idle_time = 60*29, + + .submission_relay_connect_timeout = 30*1000, + .submission_relay_command_timeout = 60*5*1000, + + .imap_urlauth_host = "", + .imap_urlauth_port = 143, +}; +static const struct setting_parser_info *submission_setting_dependencies[] = { + &mail_user_setting_parser_info, + NULL +}; +const struct setting_parser_info submission_setting_parser_info = { + .module_name = "submission", + .defines = submission_setting_defines, + .defaults = &submission_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct submission_settings), + + .parent_offset = SIZE_MAX, + + .check_func = submission_settings_verify, + .dependencies = submission_setting_dependencies +}; +/* ../../src/submission-login/submission-login-settings.c */ +/* <settings checks> */ +static struct inet_listener_settings submission_login_inet_listeners_array[] = { + { .name = "submission", .address = "", .port = 587 }, + { .name = "submissions", .address = "", .port = 465, .ssl = TRUE } +}; +static struct inet_listener_settings *submission_login_inet_listeners[] = { + &submission_login_inet_listeners_array[0] +}; +static buffer_t submission_login_inet_listeners_buf = { + { { submission_login_inet_listeners, + sizeof(submission_login_inet_listeners) } } +}; + +/* </settings checks> */ +/* <settings checks> */ +struct submission_login_client_workaround_list { + const char *name; + enum submission_login_client_workarounds num; +}; + +/* These definitions need to be kept in sync with equivalent definitions present + in src/submission/submission-settings.c. Workarounds that are not relevant + to the submission-login service are defined as 0 here to prevent "Unknown + workaround" errors below. */ +static const struct submission_login_client_workaround_list +submission_login_client_workaround_list[] = { + { "whitespace-before-path", 0}, + { "mailbox-for-path", 0 }, + { "implicit-auth-external", + SUBMISSION_LOGIN_WORKAROUND_IMPLICIT_AUTH_EXTERNAL }, + { "exotic-backend", + SUBMISSION_LOGIN_WORKAROUND_EXOTIC_BACKEND }, + { NULL, 0 } +}; + +static int +submission_login_settings_parse_workarounds( + struct submission_login_settings *set, const char **error_r) +{ + enum submission_login_client_workarounds client_workarounds = 0; + const struct submission_login_client_workaround_list *list; + const char *const *str; + + str = t_strsplit_spaces(set->submission_client_workarounds, " ,"); + for (; *str != NULL; str++) { + list = submission_login_client_workaround_list; + for (; list->name != NULL; list++) { + if (strcasecmp(*str, list->name) == 0) { + client_workarounds |= list->num; + break; + } + } + if (list->name == NULL) { + *error_r = t_strdup_printf( + "submission_client_workarounds: " + "Unknown workaround: %s", *str); + return -1; + } + } + set->parsed_workarounds = client_workarounds; + return 0; +} + +static bool +submission_login_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct submission_login_settings *set = _set; + + if (submission_login_settings_parse_workarounds(set, error_r) < 0) + return FALSE; + +#ifndef CONFIG_BINARY + if (*set->hostname == '\0') + set->hostname = p_strdup(pool, my_hostdomain()); +#endif + return TRUE; +} +/* </settings checks> */ +struct service_settings submission_login_service_settings = { + .name = "submission-login", + .protocol = "submission", + .type = "login", + .executable = "submission-login", + .user = "$default_login_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "login", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = ARRAY_INIT, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = { { &submission_login_inet_listeners_buf, + sizeof(submission_login_inet_listeners[0]) } } +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct submission_login_settings) +static const struct setting_define submission_login_setting_defines[] = { + DEF(STR, hostname), + + DEF(SIZE, submission_max_mail_size), + DEF(STR, submission_client_workarounds), + DEF(STR, submission_backend_capabilities), + + SETTING_DEFINE_LIST_END +}; +static const struct submission_login_settings submission_login_default_settings = { + .hostname = "", + + .submission_max_mail_size = 0, + .submission_client_workarounds = "", + .submission_backend_capabilities = NULL +}; +static const struct setting_parser_info *submission_login_setting_dependencies[] = { + &login_setting_parser_info, + NULL +}; +const struct setting_parser_info submission_login_setting_parser_info = { + .module_name = "submission-login", + .defines = submission_login_setting_defines, + .defaults = &submission_login_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct submission_login_settings), + .parent_offset = SIZE_MAX, + + .check_func = submission_login_settings_check, + .dependencies = submission_login_setting_dependencies +}; +const struct setting_parser_info *submission_login_setting_roots[] = { + &login_setting_parser_info, + &submission_login_setting_parser_info, + NULL +}; +/* ../../src/stats/stats-settings.c */ +extern const struct setting_parser_info stats_metric_setting_parser_info; +extern const struct setting_parser_info stats_exporter_setting_parser_info; +/* <settings checks> */ +#include "event-filter.h" +#include <math.h> +/* </settings checks> */ +/* <settings checks> */ +static struct file_listener_settings stats_unix_listeners_array[] = { + { "stats-reader", 0600, "", "" }, + { "stats-writer", 0660, "", "$default_internal_group" }, + { "login/stats-writer", 0600, "$default_login_user", "" }, +}; +static struct file_listener_settings *stats_unix_listeners[] = { + &stats_unix_listeners_array[0], + &stats_unix_listeners_array[1], + &stats_unix_listeners_array[2], +}; +static buffer_t stats_unix_listeners_buf = { + { { stats_unix_listeners, sizeof(stats_unix_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +static bool parse_format_args_set_time(struct stats_exporter_settings *set, + enum event_exporter_time_fmt fmt, + const char **error_r) +{ + if ((set->parsed_time_format != EVENT_EXPORTER_TIME_FMT_NATIVE) && + (set->parsed_time_format != fmt)) { + *error_r = t_strdup_printf("Exporter '%s' specifies multiple " + "time format args", set->name); + return FALSE; + } + + set->parsed_time_format = fmt; + + return TRUE; +} + +static bool parse_format_args(struct stats_exporter_settings *set, + const char **error_r) +{ + const char *const *tmp; + + /* Defaults */ + set->parsed_time_format = EVENT_EXPORTER_TIME_FMT_NATIVE; + + tmp = t_strsplit_spaces(set->format_args, " "); + + /* + * If the config contains multiple types of the same type (e.g., + * both time-rfc3339 and time-unix) we fail the config check. + * + * Note: At the moment, we have only time-* tokens. In the future + * when we have other tokens, they should be parsed here. + */ + for (; *tmp != NULL; tmp++) { + enum event_exporter_time_fmt fmt; + + if (strcmp(*tmp, "time-rfc3339") == 0) { + fmt = EVENT_EXPORTER_TIME_FMT_RFC3339; + } else if (strcmp(*tmp, "time-unix") == 0) { + fmt = EVENT_EXPORTER_TIME_FMT_UNIX; + } else { + *error_r = t_strdup_printf("Unknown exporter format " + "arg: %s", *tmp); + return FALSE; + } + + if (!parse_format_args_set_time(set, fmt, error_r)) + return FALSE; + } + + return TRUE; +} + +static bool stats_exporter_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct stats_exporter_settings *set = _set; + bool time_fmt_required; + + if (set->name[0] == '\0') { + *error_r = "Exporter name can't be empty"; + return FALSE; + } + + /* TODO: The following should be plugable. + * + * Note: Make sure to mirror any changes to the below code in + * stats_exporters_add_set(). + */ + if (set->format[0] == '\0') { + *error_r = "Exporter format name can't be empty"; + return FALSE; + } else if (strcmp(set->format, "none") == 0) { + time_fmt_required = FALSE; + } else if (strcmp(set->format, "json") == 0) { + time_fmt_required = TRUE; + } else if (strcmp(set->format, "tab-text") == 0) { + time_fmt_required = TRUE; + } else { + *error_r = t_strdup_printf("Unknown exporter format '%s'", + set->format); + return FALSE; + } + + /* TODO: The following should be plugable. + * + * Note: Make sure to mirror any changes to the below code in + * stats_exporters_add_set(). + */ + if (set->transport[0] == '\0') { + *error_r = "Exporter transport name can't be empty"; + return FALSE; + } else if (strcmp(set->transport, "drop") == 0 || + strcmp(set->transport, "http-post") == 0 || + strcmp(set->transport, "log") == 0) { + /* no-op */ + } else { + *error_r = t_strdup_printf("Unknown transport type '%s'", + set->transport); + return FALSE; + } + + if (!parse_format_args(set, error_r)) + return FALSE; + + /* Some formats don't have a native way of serializing time stamps */ + if (time_fmt_required && + set->parsed_time_format == EVENT_EXPORTER_TIME_FMT_NATIVE) { + *error_r = t_strdup_printf("%s exporter format requires a " + "time-* argument", set->format); + return FALSE; + } + + return TRUE; +} + +static bool parse_metric_group_by_common(const char *func, + const char *const *params, + intmax_t *min_r, + intmax_t *max_r, + intmax_t *other_r, + const char **error_r) +{ + intmax_t min, max, other; + + if ((str_array_length(params) != 3) || + (str_to_intmax(params[0], &min) < 0) || + (str_to_intmax(params[1], &max) < 0) || + (str_to_intmax(params[2], &other) < 0)) { + *error_r = t_strdup_printf("group_by '%s' aggregate function takes " + "3 int args", func); + return FALSE; + } + + if ((min < 0) || (max < 0) || (other < 0)) { + *error_r = t_strdup_printf("group_by '%s' aggregate function " + "arguments must be >= 0", func); + return FALSE; + } + + if (min >= max) { + *error_r = t_strdup_printf("group_by '%s' aggregate function " + "min must be < max (%ju must be < %ju)", + func, min, max); + return FALSE; + } + + *min_r = min; + *max_r = max; + *other_r = other; + + return TRUE; +} + +static bool parse_metric_group_by_exp(pool_t pool, struct stats_metric_settings_group_by *group_by, + const char *const *params, const char **error_r) +{ + intmax_t min, max, base; + + if (!parse_metric_group_by_common("exponential", params, &min, &max, &base, error_r)) + return FALSE; + + if ((base != 2) && (base != 10)) { + *error_r = t_strdup_printf("group_by 'exponential' aggregate function " + "base must be one of: 2, 10 (base=%ju)", + base); + return FALSE; + } + + group_by->func = STATS_METRIC_GROUPBY_QUANTIZED; + + /* + * Allocate the bucket range array and fill it in + * + * The first bucket is special - it contains everything less than or + * equal to 'base^min'. The last bucket is also special - it + * contains everything greater than 'base^max'. + * + * The second bucket begins at 'base^min + 1', the third bucket + * begins at 'base^(min + 1) + 1', and so on. + */ + group_by->num_ranges = max - min + 2; + group_by->ranges = p_new(pool, struct stats_metric_settings_bucket_range, + group_by->num_ranges); + + /* set up min & max buckets */ + group_by->ranges[0].min = INTMAX_MIN; + group_by->ranges[0].max = pow(base, min); + group_by->ranges[group_by->num_ranges - 1].min = pow(base, max); + group_by->ranges[group_by->num_ranges - 1].max = INTMAX_MAX; + + /* remaining buckets */ + for (unsigned int i = 1; i < group_by->num_ranges - 1; i++) { + group_by->ranges[i].min = pow(base, min + (i - 1)); + group_by->ranges[i].max = pow(base, min + i); + } + + return TRUE; +} + +static bool parse_metric_group_by_lin(pool_t pool, struct stats_metric_settings_group_by *group_by, + const char *const *params, const char **error_r) +{ + intmax_t min, max, step; + + if (!parse_metric_group_by_common("linear", params, &min, &max, &step, error_r)) + return FALSE; + + if ((min + step) > max) { + *error_r = t_strdup_printf("group_by 'linear' aggregate function " + "min+step must be <= max (%ju must be <= %ju)", + min + step, max); + return FALSE; + } + + group_by->func = STATS_METRIC_GROUPBY_QUANTIZED; + + /* + * Allocate the bucket range array and fill it in + * + * The first bucket is special - it contains everything less than or + * equal to 'min'. The last bucket is also special - it contains + * everything greater than 'max'. + * + * The second bucket begins at 'min + 1', the third bucket begins at + * 'min + 1 * step + 1', the fourth at 'min + 2 * step + 1', and so on. + */ + group_by->num_ranges = (max - min) / step + 2; + group_by->ranges = p_new(pool, struct stats_metric_settings_bucket_range, + group_by->num_ranges); + + /* set up min & max buckets */ + group_by->ranges[0].min = INTMAX_MIN; + group_by->ranges[0].max = min; + group_by->ranges[group_by->num_ranges - 1].min = max; + group_by->ranges[group_by->num_ranges - 1].max = INTMAX_MAX; + + /* remaining buckets */ + for (unsigned int i = 1; i < group_by->num_ranges - 1; i++) { + group_by->ranges[i].min = min + (i - 1) * step; + group_by->ranges[i].max = min + i * step; + } + + return TRUE; +} + +static bool parse_metric_group_by(struct stats_metric_settings *set, + pool_t pool, const char **error_r) +{ + const char *const *tmp = t_strsplit_spaces(set->group_by, " "); + + if (tmp[0] == NULL) + return TRUE; + + p_array_init(&set->parsed_group_by, pool, str_array_length(tmp)); + + /* For each group_by field */ + for (; *tmp != NULL; tmp++) { + struct stats_metric_settings_group_by group_by; + const char *const *params; + + i_zero(&group_by); + + /* <field name>:<aggregation func>... */ + params = t_strsplit(*tmp, ":"); + + if (params[1] == NULL) { + /* <field name> - alias for <field>:discrete */ + group_by.func = STATS_METRIC_GROUPBY_DISCRETE; + } else if (strcmp(params[1], "discrete") == 0) { + /* <field>:discrete */ + group_by.func = STATS_METRIC_GROUPBY_DISCRETE; + if (params[2] != NULL) { + *error_r = "group_by 'discrete' aggregate function " + "does not take any args"; + return FALSE; + } + } else if (strcmp(params[1], "exponential") == 0) { + /* <field>:exponential:<min mag>:<max mag>:<base> */ + if (!parse_metric_group_by_exp(pool, &group_by, ¶ms[2], error_r)) + return FALSE; + } else if (strcmp(params[1], "linear") == 0) { + /* <field>:linear:<min val>:<max val>:<step> */ + if (!parse_metric_group_by_lin(pool, &group_by, ¶ms[2], error_r)) + return FALSE; + } else { + *error_r = t_strdup_printf("unknown aggregation function " + "'%s' on field '%s'", params[1], params[0]); + return FALSE; + } + + group_by.field = p_strdup(pool, params[0]); + + array_push_back(&set->parsed_group_by, &group_by); + } + + return TRUE; +} + +static bool stats_metric_settings_check(void *_set, pool_t pool, const char **error_r) +{ + struct stats_metric_settings *set = _set; + + if (set->metric_name[0] == '\0') { + *error_r = "Metric name can't be empty"; + return FALSE; + } + + if (set->filter[0] == '\0') { + *error_r = t_strdup_printf("metric %s { filter } is empty - " + "will not match anything", set->metric_name); + return FALSE; + } + + set->parsed_filter = event_filter_create_fragment(pool); + if (event_filter_parse(set->filter, set->parsed_filter, error_r) < 0) + return FALSE; + + if (!parse_metric_group_by(set, pool, error_r)) + return FALSE; + + return TRUE; +} + +static bool stats_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct stats_settings *set = _set; + struct stats_exporter_settings *exporter; + struct stats_metric_settings *metric; + + if (!array_is_created(&set->metrics) || !array_is_created(&set->exporters)) + return TRUE; + + /* check that all metrics refer to exporters that exist */ + array_foreach_elem(&set->metrics, metric) { + bool found = FALSE; + + if (metric->exporter[0] == '\0') + continue; /* metric not exported */ + + array_foreach_elem(&set->exporters, exporter) { + if (strcmp(metric->exporter, exporter->name) == 0) { + found = TRUE; + break; + } + } + + if (!found) { + *error_r = t_strdup_printf("metric %s refers to " + "non-existent exporter '%s'", + metric->metric_name, + metric->exporter); + return FALSE; + } + } + + return TRUE; +} +/* </settings checks> */ +struct service_settings stats_service_settings = { + .name = "stats", + .protocol = "", + .type = "", + .executable = "stats", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = UINT_MAX, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &stats_unix_listeners_buf, + sizeof(stats_unix_listeners[0]) } }, + .inet_listeners = ARRAY_INIT, +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct stats_exporter_settings) +static const struct setting_define stats_exporter_setting_defines[] = { + DEF(STR, name), + DEF(STR, transport), + DEF(STR, transport_args), + DEF(TIME_MSECS, transport_timeout), + DEF(STR, format), + DEF(STR, format_args), + SETTING_DEFINE_LIST_END +}; +static const struct stats_exporter_settings stats_exporter_default_settings = { + .name = "", + .transport = "", + .transport_args = "", + .transport_timeout = 250, /* ms */ + .format = "", + .format_args = "", +}; +const struct setting_parser_info stats_exporter_setting_parser_info = { + .defines = stats_exporter_setting_defines, + .defaults = &stats_exporter_default_settings, + + .type_offset = offsetof(struct stats_exporter_settings, name), + .struct_size = sizeof(struct stats_exporter_settings), + + .parent_offset = SIZE_MAX, + .check_func = stats_exporter_settings_check, +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct stats_metric_settings) +static const struct setting_define stats_metric_setting_defines[] = { + DEF(STR, metric_name), + DEF(STR, fields), + DEF(STR, group_by), + DEF(STR, filter), + DEF(STR, exporter), + DEF(STR, exporter_include), + DEF(STR, description), + SETTING_DEFINE_LIST_END +}; +static const struct stats_metric_settings stats_metric_default_settings = { + .metric_name = "", + .fields = "", + .filter = "", + .exporter = "", + .group_by = "", + .exporter_include = STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE, + .description = "", +}; +const struct setting_parser_info stats_metric_setting_parser_info = { + .defines = stats_metric_setting_defines, + .defaults = &stats_metric_default_settings, + + .type_offset = offsetof(struct stats_metric_settings, metric_name), + .struct_size = sizeof(struct stats_metric_settings), + + .parent_offset = SIZE_MAX, + .check_func = stats_metric_settings_check, +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct stats_settings) +#undef DEFLIST_UNIQUE +#define DEFLIST_UNIQUE(field, name, defines) \ + { .type = SET_DEFLIST_UNIQUE, .key = name, \ + .offset = offsetof(struct stats_settings, field), \ + .list_info = defines } +static const struct setting_define stats_setting_defines[] = { + DEF(STR, stats_http_rawlog_dir), + + DEFLIST_UNIQUE(metrics, "metric", &stats_metric_setting_parser_info), + DEFLIST_UNIQUE(exporters, "event_exporter", &stats_exporter_setting_parser_info), + SETTING_DEFINE_LIST_END +}; +const struct stats_settings stats_default_settings = { + .stats_http_rawlog_dir = "", + + .metrics = ARRAY_INIT, + .exporters = ARRAY_INIT, +}; +const struct setting_parser_info stats_setting_parser_info = { + .module_name = "stats", + .defines = stats_setting_defines, + .defaults = &stats_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct stats_settings), + + .parent_offset = SIZE_MAX, + .check_func = stats_settings_check, +}; +/* ../../src/replication/replicator/replicator-settings.c */ +/* <settings checks> */ +static struct file_listener_settings replicator_unix_listeners_array[] = { + { "replicator", 0600, "$default_internal_user", "" }, + { "replicator-doveadm", 0, "$default_internal_user", "" } +}; +static struct file_listener_settings *replicator_unix_listeners[] = { + &replicator_unix_listeners_array[0], + &replicator_unix_listeners_array[1] +}; +static buffer_t replicator_unix_listeners_buf = { + { { replicator_unix_listeners, sizeof(replicator_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings replicator_service_settings = { + .name = "replicator", + .protocol = "", + .type = "", + .executable = "replicator", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = UINT_MAX, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &replicator_unix_listeners_buf, + sizeof(replicator_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct replicator_settings) +static const struct setting_define replicator_setting_defines[] = { + DEF(STR, auth_socket_path), + DEF(STR, doveadm_socket_path), + DEF(STR, replication_dsync_parameters), + + DEF(TIME, replication_full_sync_interval), + DEF(UINT, replication_max_conns), + + SETTING_DEFINE_LIST_END +}; +const struct replicator_settings replicator_default_settings = { + .auth_socket_path = "auth-userdb", + .doveadm_socket_path = "doveadm-server", + .replication_dsync_parameters = "-d -N -l 30 -U", + + .replication_full_sync_interval = 60*60*24, + .replication_max_conns = 10 +}; +const struct setting_parser_info replicator_setting_parser_info = { + .module_name = "replicator", + .defines = replicator_setting_defines, + .defaults = &replicator_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct replicator_settings), + + .parent_offset = SIZE_MAX +}; +/* ../../src/replication/aggregator/aggregator-settings.c */ +/* <settings checks> */ +static struct file_listener_settings aggregator_unix_listeners_array[] = { + { "replication-notify", 0600, "", "" } +}; +static struct file_listener_settings *aggregator_unix_listeners[] = { + &aggregator_unix_listeners_array[0] +}; +static buffer_t aggregator_unix_listeners_buf = { + { { aggregator_unix_listeners, sizeof(aggregator_unix_listeners) } } +}; + +static struct file_listener_settings aggregator_fifo_listeners_array[] = { + { "replication-notify-fifo", 0600, "", "" } +}; +static struct file_listener_settings *aggregator_fifo_listeners[] = { + &aggregator_fifo_listeners_array[0] +}; +static buffer_t aggregator_fifo_listeners_buf = { + { { aggregator_fifo_listeners, sizeof(aggregator_fifo_listeners) } } +}; +/* </settings checks> */ +struct service_settings aggregator_service_settings = { + .name = "aggregator", + .protocol = "", + .type = "", + .executable = "aggregator", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = ".", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &aggregator_unix_listeners_buf, + sizeof(aggregator_unix_listeners[0]) } }, + .fifo_listeners = { { &aggregator_fifo_listeners_buf, + sizeof(aggregator_fifo_listeners[0]) } }, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct aggregator_settings) +static const struct setting_define aggregator_setting_defines[] = { + DEF(STR, replicator_host), + DEF(IN_PORT, replicator_port), + + SETTING_DEFINE_LIST_END +}; +const struct aggregator_settings aggregator_default_settings = { + .replicator_host = "replicator", + .replicator_port = 0 +}; +const struct setting_parser_info aggregator_setting_parser_info = { + .module_name = "aggregator", + .defines = aggregator_setting_defines, + .defaults = &aggregator_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct aggregator_settings), + + .parent_offset = SIZE_MAX +}; +/* ../../src/pop3/pop3-settings.c */ +/* <settings checks> */ +static struct file_listener_settings pop3_unix_listeners_array[] = { + { "login/pop3", 0666, "", "" } +}; +static struct file_listener_settings *pop3_unix_listeners[] = { + &pop3_unix_listeners_array[0] +}; +static buffer_t pop3_unix_listeners_buf = { + { { pop3_unix_listeners, sizeof(pop3_unix_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +struct pop3_client_workaround_list { + const char *name; + enum pop3_client_workarounds num; +}; + +static const struct pop3_client_workaround_list pop3_client_workaround_list[] = { + { "outlook-no-nuls", WORKAROUND_OUTLOOK_NO_NULS }, + { "oe-ns-eoh", WORKAROUND_OE_NS_EOH }, + { NULL, 0 } +}; + +static int +pop3_settings_parse_workarounds(struct pop3_settings *set, + const char **error_r) +{ + enum pop3_client_workarounds client_workarounds = 0; + const struct pop3_client_workaround_list *list; + const char *const *str; + + str = t_strsplit_spaces(set->pop3_client_workarounds, " ,"); + for (; *str != NULL; str++) { + list = pop3_client_workaround_list; + for (; list->name != NULL; list++) { + if (strcasecmp(*str, list->name) == 0) { + client_workarounds |= list->num; + break; + } + } + if (list->name == NULL) { + *error_r = t_strdup_printf("pop3_client_workarounds: " + "Unknown workaround: %s", *str); + return -1; + } + } + set->parsed_workarounds = client_workarounds; + return 0; +} + +static bool +pop3_settings_verify(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) +{ + struct pop3_settings *set = _set; + + if (pop3_settings_parse_workarounds(set, error_r) < 0) + return FALSE; + if (strcmp(set->pop3_delete_type, "default") == 0) { + if (set->pop3_deleted_flag[0] == '\0') + set->parsed_delete_type = POP3_DELETE_TYPE_EXPUNGE; + else + set->parsed_delete_type = POP3_DELETE_TYPE_FLAG; + } else if (strcmp(set->pop3_delete_type, "expunge") == 0) { + set->parsed_delete_type = POP3_DELETE_TYPE_EXPUNGE; + } else if (strcmp(set->pop3_delete_type, "flag") == 0) { + if (set->pop3_deleted_flag[0] == '\0') { + *error_r = "pop3_delete_type=flag, but pop3_deleted_flag not set"; + return FALSE; + } + set->parsed_delete_type = POP3_DELETE_TYPE_FLAG; + } else { + *error_r = t_strdup_printf("pop3_delete_type: Unknown value '%s'", + set->pop3_delete_type); + return FALSE; + } + return TRUE; +} +/* </settings checks> */ +struct service_settings pop3_service_settings = { + .name = "pop3", + .protocol = "pop3", + .type = "", + .executable = "pop3", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "$default_internal_group", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1024, + .client_limit = 1, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &pop3_unix_listeners_buf, + sizeof(pop3_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#undef DEFLIST +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct pop3_settings) +#define DEFLIST(field, name, defines) \ + { .type = SET_DEFLIST, .key = name, \ + .offset = offsetof(struct pop3_settings, field), \ + .list_info = defines } +static const struct setting_define pop3_setting_defines[] = { + DEF(BOOL, verbose_proctitle), + DEF(STR_VARS, rawlog_dir), + + DEF(BOOL, pop3_no_flag_updates), + DEF(BOOL, pop3_enable_last), + DEF(BOOL, pop3_reuse_xuidl), + DEF(BOOL, pop3_save_uidl), + DEF(BOOL, pop3_lock_session), + DEF(BOOL, pop3_fast_size_lookups), + DEF(STR, pop3_client_workarounds), + DEF(STR, pop3_logout_format), + DEF(ENUM, pop3_uidl_duplicates), + DEF(STR, pop3_deleted_flag), + DEF(ENUM, pop3_delete_type), + + SETTING_DEFINE_LIST_END +}; +static const struct pop3_settings pop3_default_settings = { + .verbose_proctitle = FALSE, + .rawlog_dir = "", + + .pop3_no_flag_updates = FALSE, + .pop3_enable_last = FALSE, + .pop3_reuse_xuidl = FALSE, + .pop3_save_uidl = FALSE, + .pop3_lock_session = FALSE, + .pop3_fast_size_lookups = FALSE, + .pop3_client_workarounds = "", + .pop3_logout_format = "top=%t/%p, retr=%r/%b, del=%d/%m, size=%s", + .pop3_uidl_duplicates = "allow:rename", + .pop3_deleted_flag = "", + .pop3_delete_type = "default:expunge:flag" +}; +static const struct setting_parser_info *pop3_setting_dependencies[] = { + &mail_user_setting_parser_info, + NULL +}; +const struct setting_parser_info pop3_setting_parser_info = { + .module_name = "pop3", + .defines = pop3_setting_defines, + .defaults = &pop3_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct pop3_settings), + + .parent_offset = SIZE_MAX, + + .check_func = pop3_settings_verify, + .dependencies = pop3_setting_dependencies +}; +/* ../../src/pop3-login/pop3-login-settings.c */ +/* <settings checks> */ +static struct inet_listener_settings pop3_login_inet_listeners_array[] = { + { .name = "pop3", .address = "", .port = 110 }, + { .name = "pop3s", .address = "", .port = 995, .ssl = TRUE } +}; +static struct inet_listener_settings *pop3_login_inet_listeners[] = { + &pop3_login_inet_listeners_array[0], + &pop3_login_inet_listeners_array[1] +}; +static buffer_t pop3_login_inet_listeners_buf = { + { { pop3_login_inet_listeners, sizeof(pop3_login_inet_listeners) } } +}; + +/* </settings checks> */ +struct service_settings pop3_login_service_settings = { + .name = "pop3-login", + .protocol = "pop3", + .type = "login", + .executable = "pop3-login", + .user = "$default_login_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "login", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = ARRAY_INIT, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = { { &pop3_login_inet_listeners_buf, + sizeof(pop3_login_inet_listeners[0]) } } +}; +static const struct setting_define pop3_login_setting_defines[] = { + SETTING_DEFINE_LIST_END +}; +static const struct setting_parser_info *pop3_login_setting_dependencies[] = { + &login_setting_parser_info, + NULL +}; +const struct setting_parser_info pop3_login_setting_parser_info = { + .module_name = "pop3-login", + .defines = pop3_login_setting_defines, + + .type_offset = SIZE_MAX, + .parent_offset = SIZE_MAX, + + .dependencies = pop3_login_setting_dependencies +}; +const struct setting_parser_info *pop3_login_setting_roots[] = { + &login_setting_parser_info, + &pop3_login_setting_parser_info, + NULL +}; +/* ../../src/plugins/quota/quota-status-settings.c */ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct quota_status_settings) +static const struct setting_define quota_status_setting_defines[] = { + DEF(STR, recipient_delimiter), + + SETTING_DEFINE_LIST_END +}; +static const struct quota_status_settings quota_status_default_settings = { + .recipient_delimiter = "+", +}; +static const struct setting_parser_info *quota_status_setting_dependencies[] = { + NULL +}; +const struct setting_parser_info quota_status_setting_parser_info = { + .module_name = "mail", + .defines = quota_status_setting_defines, + .defaults = "a_status_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct quota_status_settings), + + .parent_offset = SIZE_MAX, + .dependencies = quota_status_setting_dependencies +}; +/* ../../src/plugins/mail-crypt/fs-crypt-settings.c */ +static const struct setting_define fs_crypt_setting_defines[] = { + { .type = SET_STRLIST, .key = "plugin", + .offset = offsetof(struct fs_crypt_settings, plugin_envs) }, + + SETTING_DEFINE_LIST_END +}; +const struct fs_crypt_settings fs_crypt_default_settings = { + .plugin_envs = ARRAY_INIT +}; +static const struct setting_parser_info *fs_crypt_setting_dependencies[] = { + NULL +}; +const struct setting_parser_info fs_crypt_setting_parser_info = { + .module_name = "fs-crypt", + .defines = fs_crypt_setting_defines, + .defaults = &fs_crypt_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct fs_crypt_settings), + + .parent_offset = SIZE_MAX, + .dependencies = fs_crypt_setting_dependencies +}; +/* ../../src/old-stats/stats-settings.c */ +/* <settings checks> */ +static struct file_listener_settings old_stats_unix_listeners_array[] = { + { "old-stats", 0600, "", "" } +}; +static struct file_listener_settings *old_stats_unix_listeners[] = { + &old_stats_unix_listeners_array[0] +}; +static buffer_t old_stats_unix_listeners_buf = { + { { old_stats_unix_listeners, sizeof(old_stats_unix_listeners) } } +}; +static struct file_listener_settings old_stats_fifo_listeners_array[] = { + { "old-stats-mail", 0600, "", "" }, + { "old-stats-user", 0600, "", "" } +}; +static struct file_listener_settings *old_stats_fifo_listeners[] = { + &old_stats_fifo_listeners_array[0], + &old_stats_fifo_listeners_array[1] +}; +static buffer_t old_stats_fifo_listeners_buf = { + { { old_stats_fifo_listeners, sizeof(old_stats_fifo_listeners) } } +}; +/* </settings checks> */ +struct service_settings old_stats_service_settings = { + .name = "old-stats", + .protocol = "", + .type = "", + .executable = "old-stats", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "empty", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = UINT_MAX, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &old_stats_unix_listeners_buf, + sizeof(old_stats_unix_listeners[0]) } }, + .fifo_listeners = { { &old_stats_fifo_listeners_buf, + sizeof(old_stats_fifo_listeners[0]) } }, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type("old_stats_"#name, name, struct old_stats_settings) +static const struct setting_define old_stats_setting_defines[] = { + DEF(SIZE, memory_limit), + DEF(TIME, command_min_time), + DEF(TIME, session_min_time), + DEF(TIME, user_min_time), + DEF(TIME, domain_min_time), + DEF(TIME, ip_min_time), + DEF(STR, carbon_server), + DEF(TIME, carbon_interval), + DEF(STR, carbon_name), + SETTING_DEFINE_LIST_END +}; +const struct old_stats_settings old_stats_default_settings = { + .memory_limit = 1024*1024*16, + + .command_min_time = 60, + .session_min_time = 60*15, + .user_min_time = 60*60, + .domain_min_time = 60*60*12, + .ip_min_time = 60*60*12, + + .carbon_interval = 30, + .carbon_server = "", + .carbon_name = "" +}; +const struct setting_parser_info old_stats_setting_parser_info = { + .module_name = "stats", + .defines = old_stats_setting_defines, + .defaults = &old_stats_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct old_stats_settings), + + .parent_offset = SIZE_MAX +}; +/* ../../src/master/master-settings.c */ +extern const struct setting_parser_info service_setting_parser_info; +extern const struct setting_parser_info service_setting_parser_info; +/* <settings checks> */ +static void +expand_user(const char **user, enum service_user_default *default_r, + const struct master_settings *set) +{ + /* $variable expansion is typically done by doveconf, but these + variables can come from built-in settings, so we need to expand + them here */ + if (strcmp(*user, "$default_internal_user") == 0) { + *user = set->default_internal_user; + *default_r = SERVICE_USER_DEFAULT_INTERNAL; + } else if (strcmp(*user, "$default_login_user") == 0) { + *user = set->default_login_user; + *default_r = SERVICE_USER_DEFAULT_LOGIN; + } else { + *default_r = SERVICE_USER_DEFAULT_NONE; + } +} + +static void +expand_group(const char **group, const struct master_settings *set) +{ + /* $variable expansion is typically done by doveconf, but these + variables can come from built-in settings, so we need to expand + them here */ + if (strcmp(*group, "$default_internal_group") == 0) + *group = set->default_internal_group; +} + +static bool +fix_file_listener_paths(ARRAY_TYPE(file_listener_settings) *l, + pool_t pool, const struct master_settings *master_set, + ARRAY_TYPE(const_string) *all_listeners, + const char **error_r) +{ + struct file_listener_settings *set; + size_t base_dir_len = strlen(master_set->base_dir); + enum service_user_default user_default; + + if (!array_is_created(l)) + return TRUE; + + array_foreach_elem(l, set) { + if (set->path[0] == '\0') { + *error_r = "path must not be empty"; + return FALSE; + } + + expand_user(&set->user, &user_default, master_set); + expand_group(&set->group, master_set); + if (*set->path != '/') { + set->path = p_strconcat(pool, master_set->base_dir, "/", + set->path, NULL); + } else if (strncmp(set->path, master_set->base_dir, + base_dir_len) == 0 && + set->path[base_dir_len] == '/') { + i_warning("You should remove base_dir prefix from " + "unix_listener: %s", set->path); + } + if (set->mode != 0) + array_push_back(all_listeners, &set->path); + } + return TRUE; +} + +static void add_inet_listeners(ARRAY_TYPE(inet_listener_settings) *l, + ARRAY_TYPE(const_string) *all_listeners) +{ + struct inet_listener_settings *set; + const char *str; + + if (!array_is_created(l)) + return; + + array_foreach_elem(l, set) { + if (set->port != 0) { + str = t_strdup_printf("%u:%s", set->port, set->address); + array_push_back(all_listeners, &str); + } + } +} + +static bool master_settings_parse_type(struct service_settings *set, + const char **error_r) +{ + if (*set->type == '\0') + set->parsed_type = SERVICE_TYPE_UNKNOWN; + else if (strcmp(set->type, "log") == 0) + set->parsed_type = SERVICE_TYPE_LOG; + else if (strcmp(set->type, "config") == 0) + set->parsed_type = SERVICE_TYPE_CONFIG; + else if (strcmp(set->type, "anvil") == 0) + set->parsed_type = SERVICE_TYPE_ANVIL; + else if (strcmp(set->type, "login") == 0) + set->parsed_type = SERVICE_TYPE_LOGIN; + else if (strcmp(set->type, "startup") == 0) + set->parsed_type = SERVICE_TYPE_STARTUP; + else if (strcmp(set->type, "worker") == 0) + set->parsed_type = SERVICE_TYPE_WORKER; + else { + *error_r = t_strconcat("Unknown service type: ", + set->type, NULL); + return FALSE; + } + return TRUE; +} + +static void service_set_login_dump_core(struct service_settings *set) +{ + const char *p; + + if (set->parsed_type != SERVICE_TYPE_LOGIN) + return; + + p = strstr(set->executable, " -D"); + if (p != NULL && (p[3] == '\0' || p[3] == ' ')) + set->login_dump_core = TRUE; +} + +static bool +services_have_protocol(struct master_settings *set, const char *name) +{ + struct service_settings *service; + + array_foreach_elem(&set->services, service) { + if (strcmp(service->protocol, name) == 0) + return TRUE; + } + return FALSE; +} + +#ifdef CONFIG_BINARY +static const struct service_settings * +master_default_settings_get_service(const char *name) +{ + extern struct master_settings master_default_settings; + struct service_settings *set; + + array_foreach_elem(&master_default_settings.services, set) { + if (strcmp(set->name, name) == 0) + return set; + } + return NULL; +} +#endif + +static unsigned int +service_get_client_limit(struct master_settings *set, const char *name) +{ + struct service_settings *service; + + array_foreach_elem(&set->services, service) { + if (strcmp(service->name, name) == 0) { + if (service->client_limit != 0) + return service->client_limit; + else + return set->default_client_limit; + } + } + return set->default_client_limit; +} + +static bool +master_settings_verify(void *_set, pool_t pool, const char **error_r) +{ + static bool warned_auth = FALSE, warned_anvil = FALSE; + struct master_settings *set = _set; + struct service_settings *const *services; + const char *const *strings; + ARRAY_TYPE(const_string) all_listeners; + struct passwd pw; + unsigned int i, j, count, client_limit, process_limit; + unsigned int max_auth_client_processes, max_anvil_client_processes; + string_t *max_auth_client_processes_reason = t_str_new(64); + string_t *max_anvil_client_processes_reason = t_str_new(64); + size_t len; +#ifdef CONFIG_BINARY + const struct service_settings *default_service; +#else + rlim_t fd_limit; + const char *max_client_limit_source = "default_client_limit"; + unsigned int max_client_limit = set->default_client_limit; +#endif + + if (*set->listen == '\0') { + *error_r = "listen can't be set empty"; + return FALSE; + } + + len = strlen(set->base_dir); + if (len > 0 && set->base_dir[len-1] == '/') { + /* drop trailing '/' */ + set->base_dir = p_strndup(pool, set->base_dir, len - 1); + } + + if (set->last_valid_uid != 0 && + set->first_valid_uid > set->last_valid_uid) { + *error_r = "first_valid_uid can't be larger than last_valid_uid"; + return FALSE; + } + if (set->last_valid_gid != 0 && + set->first_valid_gid > set->last_valid_gid) { + *error_r = "first_valid_gid can't be larger than last_valid_gid"; + return FALSE; + } + + if (i_getpwnam(set->default_login_user, &pw) == 0) { + *error_r = t_strdup_printf("default_login_user doesn't exist: %s", + set->default_login_user); + return FALSE; + } + if (i_getpwnam(set->default_internal_user, &pw) == 0) { + *error_r = t_strdup_printf("default_internal_user doesn't exist: %s", + set->default_internal_user); + return FALSE; + } + + /* check that we have at least one service. the actual service + structure validity is checked later while creating them. */ + if (!array_is_created(&set->services) || + array_count(&set->services) == 0) { + *error_r = "No services defined"; + return FALSE; + } + services = array_get(&set->services, &count); + for (i = 0; i < count; i++) { + struct service_settings *service = services[i]; + + if (*service->name == '\0') { + *error_r = t_strdup_printf( + "Service #%d is missing name", i); + return FALSE; + } + if (!master_settings_parse_type(service, error_r)) + return FALSE; + for (j = 0; j < i; j++) { + if (strcmp(service->name, services[j]->name) == 0) { + *error_r = t_strdup_printf( + "Duplicate service name: %s", + service->name); + return FALSE; + } + } + expand_user(&service->user, &service->user_default, set); + expand_group(&service->extra_groups, set); + service_set_login_dump_core(service); + } + set->protocols_split = p_strsplit_spaces(pool, set->protocols, " "); + if (set->protocols_split[0] != NULL && + strcmp(set->protocols_split[0], "none") == 0 && + set->protocols_split[1] == NULL) + set->protocols_split[0] = NULL; + + for (i = 0; set->protocols_split[i] != NULL; i++) { + if (!services_have_protocol(set, set->protocols_split[i])) { + *error_r = t_strdup_printf("protocols: " + "Unknown protocol: %s", + set->protocols_split[i]); + return FALSE; + } + } + t_array_init(&all_listeners, 64); + max_auth_client_processes = 0; + max_anvil_client_processes = 2; /* blocking, nonblocking pipes */ + for (i = 0; i < count; i++) { + struct service_settings *service = services[i]; + + if (*service->protocol != '\0' && + !str_array_find((const char **)set->protocols_split, + service->protocol)) { + /* protocol not enabled, ignore its settings */ + continue; + } + + if (*service->executable != '/' && + *service->executable != '\0') { + service->executable = + p_strconcat(pool, set->libexec_dir, "/", + service->executable, NULL); + } + if (*service->chroot != '/' && *service->chroot != '\0') { + service->chroot = + p_strconcat(pool, set->base_dir, "/", + service->chroot, NULL); + } + if (service->drop_priv_before_exec && + *service->chroot != '\0') { + *error_r = t_strdup_printf("service(%s): " + "drop_priv_before_exec=yes can't be " + "used with chroot", service->name); + return FALSE; + } + process_limit = service->process_limit; + if (process_limit == 0) + process_limit = set->default_process_limit; + if (service->process_min_avail > process_limit) { + *error_r = t_strdup_printf("service(%s): " + "process_min_avail is higher than process_limit", + service->name); + return FALSE; + } + if (service->vsz_limit < 1024*1024 && service->vsz_limit != 0) { + *error_r = t_strdup_printf("service(%s): " + "vsz_limit is too low", service->name); + return FALSE; + } + +#ifdef CONFIG_BINARY + default_service = + master_default_settings_get_service(service->name); + if (default_service != NULL && + default_service->process_limit_1 && process_limit > 1) { + *error_r = t_strdup_printf("service(%s): " + "process_limit must be 1", service->name); + return FALSE; + } +#else + if (max_client_limit < service->client_limit) { + max_client_limit = service->client_limit; + max_client_limit_source = t_strdup_printf( + "service %s { client_limit }", service->name); + } +#endif + + if (*service->protocol != '\0') { + /* each imap/pop3/lmtp process can use up a connection, + although if service_count=1 it's only temporary. + imap-hibernate doesn't do any auth lookups. */ + if ((service->service_count != 1 || + strcmp(service->type, "login") == 0) && + strcmp(service->name, "imap-hibernate") != 0) { + str_printfa(max_auth_client_processes_reason, + " + service %s { process_limit=%u }", + service->name, process_limit); + max_auth_client_processes += process_limit; + } + } + if (strcmp(service->type, "login") == 0 || + strcmp(service->name, "auth") == 0) { + max_anvil_client_processes += process_limit; + str_printfa(max_anvil_client_processes_reason, + " + service %s { process_limit=%u }", + service->name, process_limit); + } + + if (!fix_file_listener_paths(&service->unix_listeners, pool, + set, &all_listeners, error_r)) { + *error_r = t_strdup_printf("service(%s): unix_listener: %s", + service->name, *error_r); + return FALSE; + } + if (!fix_file_listener_paths(&service->fifo_listeners, pool, + set, &all_listeners, error_r)) { + *error_r = t_strdup_printf("service(%s): fifo_listener: %s", + service->name, *error_r); + return FALSE; + } + add_inet_listeners(&service->inet_listeners, &all_listeners); + } + + client_limit = service_get_client_limit(set, "auth"); + if (client_limit < max_auth_client_processes && !warned_auth) { + warned_auth = TRUE; + str_delete(max_auth_client_processes_reason, 0, 3); + i_warning("service auth { client_limit=%u } is lower than " + "required under max. load (%u). " + "Counted for protocol services with service_count != 1: %s", + client_limit, max_auth_client_processes, + str_c(max_auth_client_processes_reason)); + } + + client_limit = service_get_client_limit(set, "anvil"); + if (client_limit < max_anvil_client_processes && !warned_anvil) { + warned_anvil = TRUE; + str_delete(max_anvil_client_processes_reason, 0, 3); + i_warning("service anvil { client_limit=%u } is lower than " + "required under max. load (%u). Counted with: %s", + client_limit, max_anvil_client_processes, + str_c(max_anvil_client_processes_reason)); + } +#ifndef CONFIG_BINARY + if (restrict_get_fd_limit(&fd_limit) == 0 && + fd_limit < (rlim_t)max_client_limit) { + i_warning("fd limit (ulimit -n) is lower than required " + "under max. load (%u < %u), because of %s", + (unsigned int)fd_limit, max_client_limit, + max_client_limit_source); + } +#endif + + /* check for duplicate listeners */ + array_sort(&all_listeners, i_strcmp_p); + strings = array_get(&all_listeners, &count); + for (i = 1; i < count; i++) { + if (strcmp(strings[i-1], strings[i]) == 0) { + *error_r = t_strdup_printf("duplicate listener: %s", + strings[i]); + return FALSE; + } + } + return TRUE; +} +/* </settings checks> */ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct file_listener_settings) +static const struct setting_define file_listener_setting_defines[] = { + DEF(STR, path), + DEF(UINT_OCT, mode), + DEF(STR, user), + DEF(STR, group), + + SETTING_DEFINE_LIST_END +}; +static const struct file_listener_settings file_listener_default_settings = { + .path = "", + .mode = 0600, + .user = "", + .group = "", +}; +static const struct setting_parser_info file_listener_setting_parser_info = { + .defines = file_listener_setting_defines, + .defaults = &file_listener_default_settings, + + .type_offset = offsetof(struct file_listener_settings, path), + .struct_size = sizeof(struct file_listener_settings), + + .parent_offset = SIZE_MAX, + .parent = &service_setting_parser_info +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct inet_listener_settings) +static const struct setting_define inet_listener_setting_defines[] = { + DEF(STR, name), + DEF(STR, address), + DEF(IN_PORT, port), + DEF(BOOL, ssl), + DEF(BOOL, reuse_port), + DEF(BOOL, haproxy), + + SETTING_DEFINE_LIST_END +}; +static const struct inet_listener_settings inet_listener_default_settings = { + .name = "", + .address = "", + .port = 0, + .ssl = FALSE, + .reuse_port = FALSE, + .haproxy = FALSE +}; +static const struct setting_parser_info inet_listener_setting_parser_info = { + .defines = inet_listener_setting_defines, + .defaults = &inet_listener_default_settings, + + .type_offset = offsetof(struct inet_listener_settings, name), + .struct_size = sizeof(struct inet_listener_settings), + + .parent_offset = SIZE_MAX, + .parent = &service_setting_parser_info +}; +#undef DEF +#undef DEFLIST_UNIQUE +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct service_settings) +#define DEFLIST_UNIQUE(field, name, defines) \ + { .type = SET_DEFLIST_UNIQUE, .key = name, \ + .offset = offsetof(struct service_settings, field), \ + .list_info = defines } +static const struct setting_define service_setting_defines[] = { + DEF(STR, name), + DEF(STR, protocol), + DEF(STR, type), + DEF(STR, executable), + DEF(STR, user), + DEF(STR, group), + DEF(STR, privileged_group), + DEF(STR, extra_groups), + DEF(STR, chroot), + + DEF(BOOL, drop_priv_before_exec), + + DEF(UINT, process_min_avail), + DEF(UINT, process_limit), + DEF(UINT, client_limit), + DEF(UINT, service_count), + DEF(TIME, idle_kill), + DEF(SIZE, vsz_limit), + + DEFLIST_UNIQUE(unix_listeners, "unix_listener", + &file_listener_setting_parser_info), + DEFLIST_UNIQUE(fifo_listeners, "fifo_listener", + &file_listener_setting_parser_info), + DEFLIST_UNIQUE(inet_listeners, "inet_listener", + &inet_listener_setting_parser_info), + + SETTING_DEFINE_LIST_END +}; +static const struct service_settings service_default_settings = { + .name = "", + .protocol = "", + .type = "", + .executable = "", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = ARRAY_INIT, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +const struct setting_parser_info service_setting_parser_info = { + .defines = service_setting_defines, + .defaults = &service_default_settings, + + .type_offset = offsetof(struct service_settings, name), + .struct_size = sizeof(struct service_settings), + + .parent_offset = offsetof(struct service_settings, master_set), + .parent = &master_setting_parser_info +}; +#undef DEF +#undef DEFLIST_UNIQUE +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct master_settings) +#define DEFLIST_UNIQUE(field, name, defines) \ + { .type = SET_DEFLIST_UNIQUE, .key = name, \ + .offset = offsetof(struct master_settings, field), \ + .list_info = defines } +static const struct setting_define master_setting_defines[] = { + DEF(STR, base_dir), + DEF(STR, state_dir), + DEF(STR, libexec_dir), + DEF(STR, instance_name), + DEF(STR, protocols), + DEF(STR, listen), + DEF(ENUM, ssl), + DEF(STR, default_internal_user), + DEF(STR, default_internal_group), + DEF(STR, default_login_user), + DEF(UINT, default_process_limit), + DEF(UINT, default_client_limit), + DEF(TIME, default_idle_kill), + DEF(SIZE, default_vsz_limit), + + DEF(BOOL, version_ignore), + + DEF(UINT, first_valid_uid), + DEF(UINT, last_valid_uid), + DEF(UINT, first_valid_gid), + DEF(UINT, last_valid_gid), + + DEFLIST_UNIQUE(services, "service", &service_setting_parser_info), + + SETTING_DEFINE_LIST_END +}; +struct master_settings master_default_settings = { + .base_dir = PKG_RUNDIR, + .state_dir = PKG_STATEDIR, + .libexec_dir = PKG_LIBEXECDIR, + .instance_name = PACKAGE, + .protocols = "imap pop3 lmtp", + .listen = "*, ::", + .ssl = "yes:no:required", + .default_internal_user = "dovecot", + .default_internal_group = "dovecot", + .default_login_user = "dovenull", + .default_process_limit = 100, + .default_client_limit = 1000, + .default_idle_kill = 60, + .default_vsz_limit = 256*1024*1024, + + .version_ignore = FALSE, + + .first_valid_uid = 500, + .last_valid_uid = 0, + .first_valid_gid = 1, + .last_valid_gid = 0, + +#ifndef CONFIG_BINARY + .services = ARRAY_INIT +#else + .services = { { &config_all_services_buf, + sizeof(struct service_settings *) } }, +#endif +}; +const struct setting_parser_info master_setting_parser_info = { + .module_name = "master", + .defines = master_setting_defines, + .defaults = &master_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct master_settings), + + .parent_offset = SIZE_MAX, + + .check_func = master_settings_verify +}; +/* ../../src/login-common/login-settings.c */ +/* <settings checks> */ +static bool login_settings_check(void *_set, pool_t pool, + const char **error_r ATTR_UNUSED) +{ + struct login_settings *set = _set; + + set->log_format_elements_split = + p_strsplit(pool, set->login_log_format_elements, " "); + + if (set->auth_debug_passwords) + set->auth_debug = TRUE; + if (set->auth_debug) + set->auth_verbose = TRUE; + return TRUE; +} +/* </settings checks> */ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct login_settings) +static const struct setting_define login_setting_defines[] = { + DEF(STR, login_trusted_networks), + DEF(STR, login_source_ips), + DEF(STR_VARS, login_greeting), + DEF(STR, login_log_format_elements), + DEF(STR, login_log_format), + DEF(STR, login_access_sockets), + DEF(STR_VARS, login_proxy_notify_path), + DEF(STR, login_plugin_dir), + DEF(STR, login_plugins), + DEF(TIME_MSECS, login_proxy_timeout), + DEF(UINT, login_proxy_max_reconnects), + DEF(TIME, login_proxy_max_disconnect_delay), + DEF(STR, login_proxy_rawlog_dir), + DEF(STR, director_username_hash), + + DEF(BOOL, auth_ssl_require_client_cert), + DEF(BOOL, auth_ssl_username_from_cert), + + DEF(BOOL, disable_plaintext_auth), + DEF(BOOL, auth_verbose), + DEF(BOOL, auth_debug), + DEF(BOOL, verbose_proctitle), + + DEF(UINT, mail_max_userip_connections), + + SETTING_DEFINE_LIST_END +}; +static const struct login_settings login_default_settings = { + .login_trusted_networks = "", + .login_source_ips = "", + .login_greeting = PACKAGE_NAME" ready.", + .login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c session=<%{session}>", + .login_log_format = "%$: %s", + .login_access_sockets = "", + .login_proxy_notify_path = "proxy-notify", + .login_plugin_dir = MODULEDIR"/login", + .login_plugins = "", + .login_proxy_timeout = 30*1000, + .login_proxy_max_reconnects = 3, + .login_proxy_max_disconnect_delay = 0, + .login_proxy_rawlog_dir = "", + .director_username_hash = "%Lu", + + .auth_ssl_require_client_cert = FALSE, + .auth_ssl_username_from_cert = FALSE, + + .disable_plaintext_auth = TRUE, + .auth_verbose = FALSE, + .auth_debug = FALSE, + .verbose_proctitle = FALSE, + + .mail_max_userip_connections = 10 +}; +const struct setting_parser_info login_setting_parser_info = { + .module_name = "login", + .defines = login_setting_defines, + .defaults = &login_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct login_settings), + + .parent_offset = SIZE_MAX, + + .check_func = login_settings_check +}; +/* ../../src/log/log-settings.c */ +/* <settings checks> */ +static struct file_listener_settings log_unix_listeners_array[] = { + { "log-errors", 0600, "", "" } +}; +static struct file_listener_settings *log_unix_listeners[] = { + &log_unix_listeners_array[0] +}; +static buffer_t log_unix_listeners_buf = { + { { log_unix_listeners, sizeof(log_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings log_service_settings = { + .name = "log", + .protocol = "", + .type = "log", + .executable = "log", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = UINT_MAX, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &log_unix_listeners_buf, + sizeof(log_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; +/* ../../src/lmtp/lmtp-settings.c */ +/* <settings checks> */ +static struct file_listener_settings lmtp_unix_listeners_array[] = { + { "lmtp", 0666, "", "" } +}; +static struct file_listener_settings *lmtp_unix_listeners[] = { + &lmtp_unix_listeners_array[0] +}; +static buffer_t lmtp_unix_listeners_buf = { + { { lmtp_unix_listeners, sizeof(lmtp_unix_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +struct lmtp_client_workaround_list { + const char *name; + enum lmtp_client_workarounds num; +}; + +static const struct lmtp_client_workaround_list +lmtp_client_workaround_list[] = { + { "whitespace-before-path", LMTP_WORKAROUND_WHITESPACE_BEFORE_PATH }, + { "mailbox-for-path", LMTP_WORKAROUND_MAILBOX_FOR_PATH }, + { NULL, 0 } +}; + +static int +lmtp_settings_parse_workarounds(struct lmtp_settings *set, + const char **error_r) +{ + enum lmtp_client_workarounds client_workarounds = 0; + const struct lmtp_client_workaround_list *list; + const char *const *str; + + str = t_strsplit_spaces(set->lmtp_client_workarounds, " ,"); + for (; *str != NULL; str++) { + list = lmtp_client_workaround_list; + for (; list->name != NULL; list++) { + if (strcasecmp(*str, list->name) == 0) { + client_workarounds |= list->num; + break; + } + } + if (list->name == NULL) { + *error_r = t_strdup_printf( + "lmtp_client_workarounds: " + "Unknown workaround: %s", *str); + return -1; + } + } + set->parsed_workarounds = client_workarounds; + return 0; +} + +static bool lmtp_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct lmtp_settings *set = _set; + + if (lmtp_settings_parse_workarounds(set, error_r) < 0) + return FALSE; + + if (strcmp(set->lmtp_hdr_delivery_address, "none") == 0) { + set->parsed_lmtp_hdr_delivery_address = + LMTP_HDR_DELIVERY_ADDRESS_NONE; + } else if (strcmp(set->lmtp_hdr_delivery_address, "final") == 0) { + set->parsed_lmtp_hdr_delivery_address = + LMTP_HDR_DELIVERY_ADDRESS_FINAL; + } else if (strcmp(set->lmtp_hdr_delivery_address, "original") == 0) { + set->parsed_lmtp_hdr_delivery_address = + LMTP_HDR_DELIVERY_ADDRESS_ORIGINAL; + } else { + *error_r = t_strdup_printf("Unknown lmtp_hdr_delivery_address: %s", + set->lmtp_hdr_delivery_address); + return FALSE; + } + return TRUE; +} +/* </settings checks> */ +struct service_settings lmtp_service_settings = { + .name = "lmtp", + .protocol = "lmtp", + .type = "", + .executable = "lmtp", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "$default_internal_group", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 1, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &lmtp_unix_listeners_buf, + sizeof(lmtp_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct lmtp_settings) +static const struct setting_define lmtp_setting_defines[] = { + DEF(BOOL, lmtp_proxy), + DEF(BOOL, lmtp_save_to_detail_mailbox), + DEF(BOOL, lmtp_rcpt_check_quota), + DEF(BOOL, lmtp_add_received_header), + DEF(BOOL, lmtp_verbose_replies), + DEF(UINT, lmtp_user_concurrency_limit), + DEF(ENUM, lmtp_hdr_delivery_address), + DEF(STR_VARS, lmtp_rawlog_dir), + DEF(STR_VARS, lmtp_proxy_rawlog_dir), + + DEF(STR, lmtp_client_workarounds), + + DEF(STR_VARS, login_greeting), + DEF(STR, login_trusted_networks), + + DEF(STR, mail_plugins), + DEF(STR, mail_plugin_dir), + + SETTING_DEFINE_LIST_END +}; +static const struct lmtp_settings lmtp_default_settings = { + .lmtp_proxy = FALSE, + .lmtp_save_to_detail_mailbox = FALSE, + .lmtp_rcpt_check_quota = FALSE, + .lmtp_add_received_header = TRUE, + .lmtp_verbose_replies = FALSE, + .lmtp_user_concurrency_limit = 0, + .lmtp_hdr_delivery_address = "final:none:original", + .lmtp_rawlog_dir = "", + .lmtp_proxy_rawlog_dir = "", + + .lmtp_client_workarounds = "", + + .login_greeting = PACKAGE_NAME" ready.", + .login_trusted_networks = "", + + .mail_plugins = "", + .mail_plugin_dir = MODULEDIR, +}; +static const struct setting_parser_info *lmtp_setting_dependencies[] = { + &lda_setting_parser_info, + NULL +}; +const struct setting_parser_info lmtp_setting_parser_info = { + .module_name = "lmtp", + .defines = lmtp_setting_defines, + .defaults = &lmtp_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct lmtp_settings), + + .parent_offset = SIZE_MAX, + + .check_func = lmtp_settings_check, + .dependencies = lmtp_setting_dependencies +}; +/* ../../src/ipc/ipc-settings.c */ +/* <settings checks> */ +static struct file_listener_settings ipc_unix_listeners_array[] = { + { "ipc", 0600, "$default_internal_user", "" }, + { "login/ipc-proxy", 0600, "$default_login_user", "" } +}; +static struct file_listener_settings *ipc_unix_listeners[] = { + &ipc_unix_listeners_array[0], + &ipc_unix_listeners_array[1] +}; +static buffer_t ipc_unix_listeners_buf = { + { { ipc_unix_listeners, sizeof(ipc_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings ipc_service_settings = { + .name = "ipc", + .protocol = "", + .type = "", + .executable = "ipc", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "empty", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &ipc_unix_listeners_buf, + sizeof(ipc_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; +/* ../../src/indexer/indexer-worker-settings.c */ +/* <settings checks> */ +static struct file_listener_settings indexer_worker_unix_listeners_array[] = { + { "indexer-worker", 0600, "$default_internal_user", "" } +}; +static struct file_listener_settings *indexer_worker_unix_listeners[] = { + &indexer_worker_unix_listeners_array[0] +}; +static buffer_t indexer_worker_unix_listeners_buf = { + { { indexer_worker_unix_listeners, sizeof(indexer_worker_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings indexer_worker_service_settings = { + .name = "indexer-worker", + .protocol = "", + .type = "worker", + .executable = "indexer-worker", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "$default_internal_group", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 10, + .client_limit = 1, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &indexer_worker_unix_listeners_buf, + sizeof(indexer_worker_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +/* ../../src/indexer/indexer-settings.c */ +extern const struct setting_parser_info service_setting_parser_info; +/* <settings checks> */ +static struct file_listener_settings indexer_unix_listeners_array[] = { + { "indexer", 0666, "", "" } +}; +static struct file_listener_settings *indexer_unix_listeners[] = { + &indexer_unix_listeners_array[0] +}; +static buffer_t indexer_unix_listeners_buf = { + { { indexer_unix_listeners, sizeof(indexer_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings indexer_service_settings = { + .name = "indexer", + .protocol = "", + .type = "", + .executable = "indexer", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &indexer_unix_listeners_buf, + sizeof(indexer_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; +/* ../../src/imap/imap-settings.c */ +/* <settings checks> */ +static struct file_listener_settings imap_unix_listeners_array[] = { + { "login/imap", 0666, "", "" }, + { "imap-master", 0600, "", "" } +}; +static struct file_listener_settings *imap_unix_listeners[] = { + &imap_unix_listeners_array[0], + &imap_unix_listeners_array[1] +}; +static buffer_t imap_unix_listeners_buf = { + { { imap_unix_listeners, sizeof(imap_unix_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +struct imap_client_workaround_list { + const char *name; + enum imap_client_workarounds num; +}; + +static const struct imap_client_workaround_list imap_client_workaround_list[] = { + { "delay-newmail", WORKAROUND_DELAY_NEWMAIL }, + { "tb-extra-mailbox-sep", WORKAROUND_TB_EXTRA_MAILBOX_SEP }, + { "tb-lsub-flags", WORKAROUND_TB_LSUB_FLAGS }, + { NULL, 0 } +}; + +static int +imap_settings_parse_workarounds(struct imap_settings *set, + const char **error_r) +{ + enum imap_client_workarounds client_workarounds = 0; + const struct imap_client_workaround_list *list; + const char *const *str; + + str = t_strsplit_spaces(set->imap_client_workarounds, " ,"); + for (; *str != NULL; str++) { + list = imap_client_workaround_list; + for (; list->name != NULL; list++) { + if (strcasecmp(*str, list->name) == 0) { + client_workarounds |= list->num; + break; + } + } + if (list->name == NULL) { + *error_r = t_strdup_printf("imap_client_workarounds: " + "Unknown workaround: %s", *str); + return -1; + } + } + set->parsed_workarounds = client_workarounds; + return 0; +} + + +static bool +imap_settings_verify(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) +{ + struct imap_settings *set = _set; + + if (imap_settings_parse_workarounds(set, error_r) < 0) + return FALSE; + + if (strcmp(set->imap_fetch_failure, "disconnect-immediately") == 0) + set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY; + else if (strcmp(set->imap_fetch_failure, "disconnect-after") == 0) + set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_AFTER; + else if (strcmp(set->imap_fetch_failure, "no-after") == 0) + set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_NO_AFTER; + else { + *error_r = t_strdup_printf("Unknown imap_fetch_failure: %s", + set->imap_fetch_failure); + return FALSE; + } + return TRUE; +} +/* </settings checks> */ +struct service_settings imap_service_settings = { + .name = "imap", + .protocol = "imap", + .type = "", + .executable = "imap", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "$default_internal_group", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1024, + .client_limit = 1, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &imap_unix_listeners_buf, + sizeof(imap_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#undef DEFLIST +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct imap_settings) +#define DEFLIST(field, name, defines) \ + { .type = SET_DEFLIST, .key = name, \ + .offset = offsetof(struct imap_settings, field), \ + .list_info = defines } +static const struct setting_define imap_setting_defines[] = { + DEF(BOOL, verbose_proctitle), + DEF(STR_VARS, rawlog_dir), + + DEF(SIZE, imap_max_line_length), + DEF(TIME, imap_idle_notify_interval), + DEF(STR, imap_capability), + DEF(STR, imap_client_workarounds), + DEF(STR, imap_logout_format), + DEF(STR, imap_id_send), + DEF(STR, imap_id_log), + DEF(ENUM, imap_fetch_failure), + DEF(BOOL, imap_metadata), + DEF(BOOL, imap_literal_minus), + DEF(TIME, imap_hibernate_timeout), + + DEF(STR, imap_urlauth_host), + DEF(IN_PORT, imap_urlauth_port), + + SETTING_DEFINE_LIST_END +}; +static const struct imap_settings imap_default_settings = { + .verbose_proctitle = FALSE, + .rawlog_dir = "", + + /* RFC-2683 recommends at least 8000 bytes. Some clients however don't + break large message sets to multiple commands, so we're pretty + liberal by default. */ + .imap_max_line_length = 64*1024, + .imap_idle_notify_interval = 2*60, + .imap_capability = "", + .imap_client_workarounds = "", + .imap_logout_format = "in=%i out=%o deleted=%{deleted} " + "expunged=%{expunged} trashed=%{trashed} " + "hdr_count=%{fetch_hdr_count} hdr_bytes=%{fetch_hdr_bytes} " + "body_count=%{fetch_body_count} body_bytes=%{fetch_body_bytes}", + .imap_id_send = "name *", + .imap_id_log = "", + .imap_fetch_failure = "disconnect-immediately:disconnect-after:no-after", + .imap_metadata = FALSE, + .imap_literal_minus = FALSE, + .imap_hibernate_timeout = 0, + + .imap_urlauth_host = "", + .imap_urlauth_port = 143 +}; +static const struct setting_parser_info *imap_setting_dependencies[] = { + &mail_user_setting_parser_info, + &smtp_submit_setting_parser_info, + NULL +}; +const struct setting_parser_info imap_setting_parser_info = { + .module_name = "imap", + .defines = imap_setting_defines, + .defaults = &imap_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct imap_settings), + + .parent_offset = SIZE_MAX, + + .check_func = imap_settings_verify, + .dependencies = imap_setting_dependencies +}; +/* ../../src/imap-urlauth/imap-urlauth-worker-settings.c */ +/* <settings checks> */ +static struct file_listener_settings imap_urlauth_worker_unix_listeners_array[] = { + { "imap-urlauth-worker", 0600, "$default_internal_user", "" } +}; +static struct file_listener_settings *imap_urlauth_worker_unix_listeners[] = { + &imap_urlauth_worker_unix_listeners_array[0] +}; +static buffer_t imap_urlauth_worker_unix_listeners_buf = { + { { imap_urlauth_worker_unix_listeners, + sizeof(imap_urlauth_worker_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings imap_urlauth_worker_service_settings = { + .name = "imap-urlauth-worker", + .protocol = "imap", + .type = "", + .executable = "imap-urlauth-worker", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "$default_internal_group", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1024, + .client_limit = 1, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &imap_urlauth_worker_unix_listeners_buf, + sizeof(imap_urlauth_worker_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct imap_urlauth_worker_settings) +static const struct setting_define imap_urlauth_worker_setting_defines[] = { + DEF(BOOL, verbose_proctitle), + + DEF(STR, imap_urlauth_host), + DEF(IN_PORT, imap_urlauth_port), + + SETTING_DEFINE_LIST_END +}; +const struct imap_urlauth_worker_settings imap_urlauth_worker_default_settings = { + .verbose_proctitle = FALSE, + + .imap_urlauth_host = "", + .imap_urlauth_port = 143 +}; +static const struct setting_parser_info *imap_urlauth_worker_setting_dependencies[] = { + &mail_user_setting_parser_info, + NULL +}; +const struct setting_parser_info imap_urlauth_worker_setting_parser_info = { + .module_name = "imap-urlauth-worker", + .defines = imap_urlauth_worker_setting_defines, + .defaults = &imap_urlauth_worker_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct imap_urlauth_worker_settings), + + .parent_offset = SIZE_MAX, + + .dependencies = imap_urlauth_worker_setting_dependencies +}; +/* ../../src/imap-urlauth/imap-urlauth-settings.c */ +/* <settings checks> */ +static struct file_listener_settings imap_urlauth_unix_listeners_array[] = { + { "token-login/imap-urlauth", 0666, "", "" } +}; +static struct file_listener_settings *imap_urlauth_unix_listeners[] = { + &imap_urlauth_unix_listeners_array[0] +}; +static buffer_t imap_urlauth_unix_listeners_buf = { + { { imap_urlauth_unix_listeners, sizeof(imap_urlauth_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings imap_urlauth_service_settings = { + .name = "imap-urlauth", + .protocol = "imap", + .type = "", + .executable = "imap-urlauth", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1024, + .client_limit = 1, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &imap_urlauth_unix_listeners_buf, + sizeof(imap_urlauth_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct imap_urlauth_settings) +static const struct setting_define imap_urlauth_setting_defines[] = { + DEF(STR, base_dir), + + DEF(BOOL, mail_debug), + + DEF(BOOL, verbose_proctitle), + + DEF(STR, imap_urlauth_logout_format), + DEF(STR, imap_urlauth_submit_user), + DEF(STR, imap_urlauth_stream_user), + + SETTING_DEFINE_LIST_END +}; +const struct imap_urlauth_settings imap_urlauth_default_settings = { + .base_dir = PKG_RUNDIR, + .mail_debug = FALSE, + + .verbose_proctitle = FALSE, + + .imap_urlauth_logout_format = "in=%i out=%o", + .imap_urlauth_submit_user = NULL, + .imap_urlauth_stream_user = NULL +}; +static const struct setting_parser_info *imap_urlauth_setting_dependencies[] = { + NULL +}; +const struct setting_parser_info imap_urlauth_setting_parser_info = { + .module_name = "imap-urlauth", + .defines = imap_urlauth_setting_defines, + .defaults = &imap_urlauth_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct imap_urlauth_settings), + + .parent_offset = SIZE_MAX, + + .dependencies = imap_urlauth_setting_dependencies +}; +/* ../../src/imap-urlauth/imap-urlauth-login-settings.c */ +/* <settings checks> */ +static struct file_listener_settings +imap_urlauth_login_unix_listeners_array[] = { + { "imap-urlauth", 0666, "", "" } +}; +static struct file_listener_settings *imap_urlauth_login_unix_listeners[] = { + &imap_urlauth_login_unix_listeners_array[0] +}; +static buffer_t imap_urlauth_login_unix_listeners_buf = { + { { imap_urlauth_login_unix_listeners, + sizeof(imap_urlauth_login_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings imap_urlauth_login_service_settings = { + .name = "imap-urlauth-login", + .protocol = "imap", + .type = "login", + .executable = "imap-urlauth-login", + .user = "$default_login_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "token-login", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &imap_urlauth_login_unix_listeners_buf, + sizeof(imap_urlauth_login_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +static const struct setting_define imap_urlauth_login_setting_defines[] = { + SETTING_DEFINE_LIST_END +}; +static const struct setting_parser_info *imap_urlauth_login_setting_dependencies[] = { + &login_setting_parser_info, + NULL +}; +const struct setting_parser_info imap_urlauth_login_setting_parser_info = { + .module_name = "imap-urlauth-login", + .defines = imap_urlauth_login_setting_defines, + + .type_offset = SIZE_MAX, + .parent_offset = SIZE_MAX, + + .dependencies = imap_urlauth_login_setting_dependencies +}; +const struct setting_parser_info *imap_urlauth_login_setting_roots[] = { + &login_setting_parser_info, + &imap_urlauth_login_setting_parser_info, + NULL +}; +/* ../../src/imap-login/imap-login-settings.c */ +/* <settings checks> */ +static struct inet_listener_settings imap_login_inet_listeners_array[] = { + { .name = "imap", .address = "", .port = 143 }, + { .name = "imaps", .address = "", .port = 993, .ssl = TRUE } +}; +static struct inet_listener_settings *imap_login_inet_listeners[] = { + &imap_login_inet_listeners_array[0], + &imap_login_inet_listeners_array[1] +}; +static buffer_t imap_login_inet_listeners_buf = { + { { imap_login_inet_listeners, sizeof(imap_login_inet_listeners) } } +}; +/* </settings checks> */ +struct service_settings imap_login_service_settings = { + .name = "imap-login", + .protocol = "imap", + .type = "login", + .executable = "imap-login", + .user = "$default_login_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "login", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = ARRAY_INIT, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = { { &imap_login_inet_listeners_buf, + sizeof(imap_login_inet_listeners[0]) } } +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct imap_login_settings) +static const struct setting_define imap_login_setting_defines[] = { + DEF(STR, imap_capability), + DEF(STR, imap_id_send), + DEF(STR, imap_id_log), + DEF(BOOL, imap_literal_minus), + DEF(BOOL, imap_id_retain), + + SETTING_DEFINE_LIST_END +}; +static const struct imap_login_settings imap_login_default_settings = { + .imap_capability = "", + .imap_id_send = "name *", + .imap_id_log = "", + .imap_literal_minus = FALSE, + .imap_id_retain = FALSE, +}; +static const struct setting_parser_info *imap_login_setting_dependencies[] = { + &login_setting_parser_info, + NULL +}; +static const struct setting_parser_info imap_login_setting_parser_info = { + .module_name = "imap-login", + .defines = imap_login_setting_defines, + .defaults = &imap_login_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct imap_login_settings), + + .parent_offset = SIZE_MAX, + .dependencies = imap_login_setting_dependencies +}; +const struct setting_parser_info *imap_login_setting_roots[] = { + &login_setting_parser_info, + &imap_login_setting_parser_info, + NULL +}; +/* ../../src/imap-hibernate/imap-hibernate-settings.c */ +/* <settings checks> */ +static struct file_listener_settings imap_hibernate_unix_listeners_array[] = { + { "imap-hibernate", 0660, "", "$default_internal_group" } +}; +static struct file_listener_settings *imap_hibernate_unix_listeners[] = { + &imap_hibernate_unix_listeners_array[0] +}; +static buffer_t imap_hibernate_unix_listeners_buf = { + { { imap_hibernate_unix_listeners, sizeof(imap_hibernate_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings imap_hibernate_service_settings = { + .name = "imap-hibernate", + .protocol = "imap", + .type = "", + .executable = "imap-hibernate", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &imap_hibernate_unix_listeners_buf, + sizeof(imap_hibernate_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +/* ../../src/doveadm/doveadm-settings.c */ +/* <settings checks> */ +static struct file_listener_settings doveadm_unix_listeners_array[] = { + { "doveadm-server", 0600, "", "" } +}; +static struct file_listener_settings *doveadm_unix_listeners[] = { + &doveadm_unix_listeners_array[0] +}; +static buffer_t doveadm_unix_listeners_buf = { + { { doveadm_unix_listeners, sizeof(doveadm_unix_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +struct dsync_feature_list { + const char *name; + enum dsync_features num; +}; + +static const struct dsync_feature_list dsync_feature_list[] = { + { "empty-header-workaround", DSYNC_FEATURE_EMPTY_HDR_WORKAROUND }, + { NULL, 0 } +}; + +static int +dsync_settings_parse_features(struct doveadm_settings *set, + const char **error_r) +{ + enum dsync_features features = 0; + const struct dsync_feature_list *list; + const char *const *str; + + str = t_strsplit_spaces(set->dsync_features, " ,"); + for (; *str != NULL; str++) { + list = dsync_feature_list; + for (; list->name != NULL; list++) { + if (strcasecmp(*str, list->name) == 0) { + features |= list->num; + break; + } + } + if (list->name == NULL) { + *error_r = t_strdup_printf("dsync_features: " + "Unknown feature: %s", *str); + return -1; + } + } + set->parsed_features = features; + return 0; +} + +static bool doveadm_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct doveadm_settings *set = _set; + +#ifndef CONFIG_BINARY + fix_base_path(set, pool, &set->auth_socket_path); + fix_base_path(set, pool, &set->doveadm_socket_path); +#endif + if (*set->dsync_hashed_headers == '\0') { + *error_r = "dsync_hashed_headers must not be empty"; + return FALSE; + } + if (*set->dsync_alt_char == '\0') { + *error_r = "dsync_alt_char must not be empty"; + return FALSE; + } + if (dsync_settings_parse_features(set, error_r) != 0) + return FALSE; + return TRUE; +} +/* </settings checks> */ +struct service_settings doveadm_service_settings = { + .name = "doveadm", + .protocol = "", + .type = "", + .executable = "doveadm-server", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "$default_internal_group", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 1, + .service_count = 1, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &doveadm_unix_listeners_buf, + sizeof(doveadm_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct doveadm_settings) +static const struct setting_define doveadm_setting_defines[] = { + DEF(STR, base_dir), + DEF(STR, libexec_dir), + DEF(STR, mail_plugins), + DEF(STR, mail_plugin_dir), + DEF(STR_VARS, mail_temp_dir), + DEF(BOOL, auth_debug), + DEF(STR, auth_socket_path), + DEF(STR, doveadm_socket_path), + DEF(UINT, doveadm_worker_count), + DEF(IN_PORT, doveadm_port), + { .type = SET_ALIAS, .key = "doveadm_proxy_port" }, + DEF(ENUM, doveadm_ssl), + DEF(STR, doveadm_username), + DEF(STR, doveadm_password), + DEF(STR, doveadm_allowed_commands), + DEF(STR, dsync_alt_char), + DEF(STR, dsync_remote_cmd), + DEF(STR, director_username_hash), + DEF(STR, doveadm_api_key), + DEF(STR, dsync_features), + DEF(UINT, dsync_commit_msgs_interval), + DEF(STR, doveadm_http_rawlog_dir), + DEF(STR, dsync_hashed_headers), + + { .type = SET_STRLIST, .key = "plugin", + .offset = offsetof(struct doveadm_settings, plugin_envs) }, + + SETTING_DEFINE_LIST_END +}; +const struct doveadm_settings doveadm_default_settings = { + .base_dir = PKG_RUNDIR, + .libexec_dir = PKG_LIBEXECDIR, + .mail_plugins = "", + .mail_plugin_dir = MODULEDIR, + .mail_temp_dir = "/tmp", + .auth_debug = FALSE, + .auth_socket_path = "auth-userdb", + .doveadm_socket_path = "doveadm-server", + .doveadm_worker_count = 0, + .doveadm_port = 0, + .doveadm_ssl = "no:ssl:starttls", + .doveadm_username = "doveadm", + .doveadm_password = "", + .doveadm_allowed_commands = "", + .dsync_alt_char = "_", + .dsync_remote_cmd = "ssh -l%{login} %{host} doveadm dsync-server -u%u -U", + .dsync_features = "", + .dsync_hashed_headers = "Date Message-ID", + .dsync_commit_msgs_interval = 100, + .director_username_hash = "%Lu", + .doveadm_api_key = "", + .doveadm_http_rawlog_dir = "", + + .plugin_envs = ARRAY_INIT +}; +static const struct setting_parser_info *doveadm_setting_dependencies[] = { + &mail_user_setting_parser_info, + NULL +}; +const struct setting_parser_info doveadm_setting_parser_info = { + .module_name = "doveadm", + .defines = doveadm_setting_defines, + .defaults = &doveadm_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct doveadm_settings), + + .parent_offset = SIZE_MAX, + .check_func = doveadm_settings_check, + .dependencies = doveadm_setting_dependencies +}; +/* ../../src/dns/dns-client-settings.c */ +/* <settings checks> */ +static struct file_listener_settings dns_client_unix_listeners_array[] = { + { "dns-client", 0666, "", "" }, + { "login/dns-client", 0666, "", "" }, +}; +static struct file_listener_settings *dns_client_unix_listeners[] = { + &dns_client_unix_listeners_array[0], + &dns_client_unix_listeners_array[1], +}; +static buffer_t dns_client_unix_listeners_buf = { + { { dns_client_unix_listeners, sizeof(dns_client_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings dns_client_service_settings = { + .name = "dns-client", + .protocol = "", + .type = "", + .executable = "dns-client", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 1, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &dns_client_unix_listeners_buf, + sizeof(dns_client_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +/* ../../src/director/director-settings.c */ +/* <settings checks> */ +static bool director_settings_verify(void *_set, pool_t pool, const char **error_r); + +static struct file_listener_settings director_unix_listeners_array[] = { + { "login/director", 0, "", "" }, + { "director-admin", 0600, "", "" } +}; +static struct file_listener_settings *director_unix_listeners[] = { + &director_unix_listeners_array[0], + &director_unix_listeners_array[1] +}; +static buffer_t director_unix_listeners_buf = { + { { director_unix_listeners, sizeof(director_unix_listeners) } } +}; +static struct file_listener_settings director_fifo_listeners_array[] = { + { "login/proxy-notify", 0, "", "" } +}; +static struct file_listener_settings *director_fifo_listeners[] = { + &director_fifo_listeners_array[0] +}; +static buffer_t director_fifo_listeners_buf = { + { { director_fifo_listeners, sizeof(director_fifo_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +static bool +director_settings_verify(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) +{ + struct director_settings *set = _set; + + if (set->director_user_expire < 10) { + *error_r = "director_user_expire is too low"; + return FALSE; + } + return TRUE; +} +/* </settings checks> */ +struct service_settings director_service_settings = { + .name = "director", + .protocol = "", + .type = "", + .executable = "director", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = ".", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = UINT_MAX, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &director_unix_listeners_buf, + sizeof(director_unix_listeners[0]) } }, + .fifo_listeners = { { &director_fifo_listeners_buf, + sizeof(director_fifo_listeners[0]) } }, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct director_settings) +static const struct setting_define director_setting_defines[] = { + DEF(STR, master_user_separator), + + DEF(STR, director_servers), + DEF(STR, director_mail_servers), + DEF(STR, director_username_hash), + DEF(STR, director_flush_socket), + DEF(TIME, director_ping_idle_timeout), + DEF(TIME, director_ping_max_timeout), + DEF(TIME, director_user_expire), + DEF(TIME, director_user_kick_delay), + DEF(UINT, director_max_parallel_moves), + DEF(UINT, director_max_parallel_kicks), + DEF(SIZE, director_output_buffer_size), + + SETTING_DEFINE_LIST_END +}; +const struct director_settings director_default_settings = { + .master_user_separator = "", + + .director_servers = "", + .director_mail_servers = "", + .director_username_hash = "%Lu", + .director_flush_socket = "", + .director_ping_idle_timeout = 30, + .director_ping_max_timeout = 60, + .director_user_expire = 60*15, + .director_user_kick_delay = 2, + .director_max_parallel_moves = 100, + .director_max_parallel_kicks = 100, + .director_output_buffer_size = 10 * 1024 * 1024, +}; +const struct setting_parser_info director_setting_parser_info = { + .module_name = "director", + .defines = director_setting_defines, + .defaults = &director_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct director_settings), + + .parent_offset = SIZE_MAX, + + .check_func = director_settings_verify +}; +/* ../../src/dict/dict-settings.c */ +/* <settings checks> */ +static struct file_listener_settings dict_unix_listeners_array[] = { + { "dict", 0660, "", "$default_internal_group" } +}; +static struct file_listener_settings *dict_unix_listeners[] = { + &dict_unix_listeners_array[0] +}; +static buffer_t dict_unix_listeners_buf = { + { { dict_unix_listeners, sizeof(dict_unix_listeners) } } +}; + +static struct file_listener_settings dict_async_unix_listeners_array[] = { + { "dict-async", 0660, "", "$default_internal_group" } +}; +static struct file_listener_settings *dict_async_unix_listeners[] = { + &dict_async_unix_listeners_array[0] +}; +static buffer_t dict_async_unix_listeners_buf = { + { { dict_async_unix_listeners, sizeof(dict_async_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings dict_service_settings = { + .name = "dict", + .protocol = "", + .type = "", + .executable = "dict", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 1, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &dict_unix_listeners_buf, + sizeof(dict_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +struct service_settings dict_async_service_settings = { + .name = "dict-async", + .protocol = "", + .type = "", + .executable = "dict", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &dict_async_unix_listeners_buf, + sizeof(dict_async_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct dict_server_settings) +static const struct setting_define dict_setting_defines[] = { + DEF(STR, base_dir), + DEF(BOOL, verbose_proctitle), + + DEF(STR, dict_db_config), + { .type = SET_STRLIST, .key = "dict", + .offset = offsetof(struct dict_server_settings, dicts) }, + + SETTING_DEFINE_LIST_END +}; +const struct dict_server_settings dict_default_settings = { + .base_dir = PKG_RUNDIR, + .verbose_proctitle = FALSE, + + .dict_db_config = "", + .dicts = ARRAY_INIT +}; +const struct setting_parser_info dict_setting_parser_info = { + .module_name = "dict", + .defines = dict_setting_defines, + .defaults = &dict_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct dict_server_settings), + + .parent_offset = SIZE_MAX +}; +/* ../../src/config/config-settings.c */ +/* <settings checks> */ +static struct file_listener_settings config_unix_listeners_array[] = { + { "config", 0600, "", "" } +}; +static struct file_listener_settings *config_unix_listeners[] = { + &config_unix_listeners_array[0] +}; +static buffer_t config_unix_listeners_buf = { + { { config_unix_listeners, sizeof(config_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings config_service_settings = { + .name = "config", + .protocol = "", + .type = "config", + .executable = "config", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 0, + .idle_kill = UINT_MAX, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &config_unix_listeners_buf, + sizeof(config_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +/* ../../src/auth/auth-settings.c */ +extern const struct setting_parser_info auth_passdb_setting_parser_info; +extern const struct setting_parser_info auth_userdb_setting_parser_info; +/* <settings checks> */ +static struct file_listener_settings auth_unix_listeners_array[] = { + { "login/login", 0666, "", "" }, + { "token-login/tokenlogin", 0666, "", "" }, + { "auth-login", 0600, "$default_internal_user", "" }, + { "auth-client", 0600, "$default_internal_user", "" }, + { "auth-userdb", 0666, "$default_internal_user", "" }, + { "auth-master", 0600, "", "" } +}; +static struct file_listener_settings *auth_unix_listeners[] = { + &auth_unix_listeners_array[0], + &auth_unix_listeners_array[1], + &auth_unix_listeners_array[2], + &auth_unix_listeners_array[3], + &auth_unix_listeners_array[4], + &auth_unix_listeners_array[5] +}; +static buffer_t auth_unix_listeners_buf = { + { { auth_unix_listeners, sizeof(auth_unix_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +static struct file_listener_settings auth_worker_unix_listeners_array[] = { + { "auth-worker", 0600, "$default_internal_user", "" } +}; +static struct file_listener_settings *auth_worker_unix_listeners[] = { + &auth_worker_unix_listeners_array[0] +}; +static buffer_t auth_worker_unix_listeners_buf = { + { { auth_worker_unix_listeners, sizeof(auth_worker_unix_listeners) } } +}; +/* </settings checks> */ +/* <settings checks> */ +static bool +auth_settings_set_self_ips(struct auth_settings *set, pool_t pool, + const char **error_r) +{ + const char *const *tmp; + ARRAY(struct ip_addr) ips_array; + struct ip_addr *ips; + unsigned int ips_count; + int ret; + + if (*set->proxy_self == '\0') { + set->proxy_self_ips = p_new(pool, struct ip_addr, 1); + return TRUE; + } + + p_array_init(&ips_array, pool, 4); + tmp = t_strsplit_spaces(set->proxy_self, " "); + for (; *tmp != NULL; tmp++) { + ret = net_gethostbyname(*tmp, &ips, &ips_count); + if (ret != 0) { + *error_r = t_strdup_printf("auth_proxy_self_ips: " + "gethostbyname(%s) failed: %s", + *tmp, net_gethosterror(ret)); + } + array_append(&ips_array, ips, ips_count); + } + array_append_zero(&ips_array); + set->proxy_self_ips = array_front(&ips_array); + return TRUE; +} + +static bool +auth_verify_verbose_password(struct auth_settings *set, + const char **error_r) +{ + const char *p, *value = set->verbose_passwords; + unsigned int num; + + p = strchr(value, ':'); + if (p != NULL) { + if (str_to_uint(p+1, &num) < 0 || num == 0) { + *error_r = t_strdup_printf("auth_verbose_passwords: " + "Invalid truncation number: '%s'", p+1); + return FALSE; + } + value = t_strdup_until(value, p); + } + if (strcmp(value, "no") == 0) + return TRUE; + else if (strcmp(value, "plain") == 0) + return TRUE; + else if (strcmp(value, "sha1") == 0) + return TRUE; + else if (strcmp(value, "yes") == 0) { + /* just use it as alias for "plain" */ + set->verbose_passwords = "plain"; + return TRUE; + } else { + *error_r = "auth_verbose_passwords: Invalid value"; + return FALSE; + } +} + +static bool auth_settings_check(void *_set, pool_t pool, + const char **error_r) +{ + struct auth_settings *set = _set; + const char *p; + + if (set->debug_passwords) + set->debug = TRUE; + if (set->debug) + set->verbose = TRUE; + + if (set->worker_max_count == 0) { + *error_r = "auth_worker_max_count must be above zero"; + return FALSE; + } + + if (set->cache_size > 0 && set->cache_size < 1024) { + /* probably a configuration error. + older versions used megabyte numbers */ + *error_r = t_strdup_printf("auth_cache_size value is too small " + "(%"PRIuUOFF_T" bytes)", + set->cache_size); + return FALSE; + } + + if (!auth_verify_verbose_password(set, error_r)) + return FALSE; + + if (*set->username_chars == '\0') { + /* all chars are allowed */ + memset(set->username_chars_map, 1, + sizeof(set->username_chars_map)); + } else { + for (p = set->username_chars; *p != '\0'; p++) + set->username_chars_map[(int)(uint8_t)*p] = 1; + } + + if (*set->username_translation != '\0') { + p = set->username_translation; + for (; *p != '\0' && p[1] != '\0'; p += 2) + set->username_translation_map[(int)(uint8_t)*p] = p[1]; + } + set->realms_arr = + (const char *const *)p_strsplit_spaces(pool, set->realms, " "); + + if (*set->policy_server_url != '\0') { + if (*set->policy_hash_nonce == '\0') { + + *error_r = "auth_policy_hash_nonce must be set when policy server is used"; + return FALSE; + } + const struct hash_method *digest = hash_method_lookup(set->policy_hash_mech); + if (digest == NULL) { + *error_r = "invalid auth_policy_hash_mech given"; + return FALSE; + } + if (set->policy_hash_truncate > 0 && set->policy_hash_truncate >= digest->digest_size*8) { + *error_r = t_strdup_printf("policy_hash_truncate is not smaller than digest size (%u >= %u)", + set->policy_hash_truncate, + digest->digest_size*8); + return FALSE; + } + } + + if (!auth_settings_set_self_ips(set, pool, error_r)) + return FALSE; + return TRUE; +} + +static bool +auth_passdb_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct auth_passdb_settings *set = _set; + + if (set->driver == NULL || *set->driver == '\0') { + *error_r = "passdb is missing driver"; + return FALSE; + } + if (set->pass && strcmp(set->result_success, "return-ok") != 0) { + *error_r = "Obsolete pass=yes setting mixed with non-default result_success"; + return FALSE; + } + return TRUE; +} + +static bool +auth_userdb_settings_check(void *_set, pool_t pool ATTR_UNUSED, + const char **error_r) +{ + struct auth_userdb_settings *set = _set; + + if (set->driver == NULL || *set->driver == '\0') { + *error_r = "userdb is missing driver"; + return FALSE; + } + return TRUE; +} +/* </settings checks> */ +struct service_settings auth_service_settings = { + .name = "auth", + .protocol = "", + .type = "", + .executable = "auth", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &auth_unix_listeners_buf, + sizeof(auth_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; +struct service_settings auth_worker_service_settings = { + .name = "auth-worker", + .protocol = "", + .type = "worker", + .executable = "auth -w", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 1, + .service_count = 0, + .idle_kill = 0, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &auth_worker_unix_listeners_buf, + sizeof(auth_worker_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_passdb_settings) +static const struct setting_define auth_passdb_setting_defines[] = { + DEF(STR, name), + DEF(STR, driver), + DEF(STR, args), + DEF(STR, default_fields), + DEF(STR, override_fields), + DEF(STR, mechanisms), + DEF(STR, username_filter), + + DEF(ENUM, skip), + DEF(ENUM, result_success), + DEF(ENUM, result_failure), + DEF(ENUM, result_internalfail), + + DEF(BOOL, deny), + DEF(BOOL, pass), + DEF(BOOL, master), + DEF(ENUM, auth_verbose), + + SETTING_DEFINE_LIST_END +}; +static const struct auth_passdb_settings auth_passdb_default_settings = { + .name = "", + .driver = "", + .args = "", + .default_fields = "", + .override_fields = "", + .mechanisms = "", + .username_filter = "", + + .skip = "never:authenticated:unauthenticated", + .result_success = "return-ok:return:return-fail:continue:continue-ok:continue-fail", + .result_failure = "continue:return:return-ok:return-fail:continue-ok:continue-fail", + .result_internalfail = "continue:return:return-ok:return-fail:continue-ok:continue-fail", + + .deny = FALSE, + .pass = FALSE, + .master = FALSE, + .auth_verbose = "default:yes:no" +}; +const struct setting_parser_info auth_passdb_setting_parser_info = { + .defines = auth_passdb_setting_defines, + .defaults = &auth_passdb_default_settings, + + .type_offset = offsetof(struct auth_passdb_settings, name), + .struct_size = sizeof(struct auth_passdb_settings), + + .parent_offset = SIZE_MAX, + .parent = &auth_setting_parser_info, + + .check_func = auth_passdb_settings_check +}; +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_userdb_settings) +static const struct setting_define auth_userdb_setting_defines[] = { + DEF(STR, name), + DEF(STR, driver), + DEF(STR, args), + DEF(STR, default_fields), + DEF(STR, override_fields), + + DEF(ENUM, skip), + DEF(ENUM, result_success), + DEF(ENUM, result_failure), + DEF(ENUM, result_internalfail), + + DEF(ENUM, auth_verbose), + + SETTING_DEFINE_LIST_END +}; +static const struct auth_userdb_settings auth_userdb_default_settings = { + /* NOTE: when adding fields, update also auth.c:userdb_dummy_set */ + .name = "", + .driver = "", + .args = "", + .default_fields = "", + .override_fields = "", + + .skip = "never:found:notfound", + .result_success = "return-ok:return:return-fail:continue:continue-ok:continue-fail", + .result_failure = "continue:return:return-ok:return-fail:continue-ok:continue-fail", + .result_internalfail = "continue:return:return-ok:return-fail:continue-ok:continue-fail", + + .auth_verbose = "default:yes:no" +}; +const struct setting_parser_info auth_userdb_setting_parser_info = { + .defines = auth_userdb_setting_defines, + .defaults = &auth_userdb_default_settings, + + .type_offset = offsetof(struct auth_userdb_settings, name), + .struct_size = sizeof(struct auth_userdb_settings), + + .parent_offset = SIZE_MAX, + .parent = &auth_setting_parser_info, + + .check_func = auth_userdb_settings_check +}; +#undef DEF +#undef DEF_NOPREFIX +#undef DEFLIST +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type("auth_"#name, name, struct auth_settings) +#define DEF_NOPREFIX(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_settings) +#define DEFLIST(field, name, defines) \ + { .type = SET_DEFLIST, .key = name, \ + .offset = offsetof(struct auth_settings, field), \ + .list_info = defines } +static const struct setting_define auth_setting_defines[] = { + DEF(STR, mechanisms), + DEF(STR, realms), + DEF(STR, default_realm), + DEF(SIZE, cache_size), + DEF(TIME, cache_ttl), + DEF(TIME, cache_negative_ttl), + DEF(BOOL, cache_verify_password_with_worker), + DEF(STR, username_chars), + DEF(STR, username_translation), + DEF(STR, username_format), + DEF(STR, master_user_separator), + DEF(STR, anonymous_username), + DEF(STR, krb5_keytab), + DEF(STR, gssapi_hostname), + DEF(STR, winbind_helper_path), + DEF(STR, proxy_self), + DEF(TIME, failure_delay), + + DEF(STR, policy_server_url), + DEF(STR, policy_server_api_header), + DEF(UINT, policy_server_timeout_msecs), + DEF(STR, policy_hash_mech), + DEF(STR, policy_hash_nonce), + DEF(STR, policy_request_attributes), + DEF(BOOL, policy_reject_on_fail), + DEF(BOOL, policy_check_before_auth), + DEF(BOOL, policy_check_after_auth), + DEF(BOOL, policy_report_after_auth), + DEF(BOOL, policy_log_only), + DEF(UINT, policy_hash_truncate), + + DEF(BOOL, stats), + DEF(BOOL, verbose), + DEF(BOOL, debug), + DEF(BOOL, debug_passwords), + DEF(STR, verbose_passwords), + DEF(BOOL, ssl_require_client_cert), + DEF(BOOL, ssl_username_from_cert), + DEF(BOOL, use_winbind), + + DEF(UINT, worker_max_count), + + DEFLIST(passdbs, "passdb", &auth_passdb_setting_parser_info), + DEFLIST(userdbs, "userdb", &auth_userdb_setting_parser_info), + + DEF_NOPREFIX(STR, base_dir), + DEF_NOPREFIX(BOOL, verbose_proctitle), + DEF_NOPREFIX(UINT, first_valid_uid), + DEF_NOPREFIX(UINT, last_valid_uid), + DEF_NOPREFIX(UINT, first_valid_gid), + DEF_NOPREFIX(UINT, last_valid_gid), + + DEF_NOPREFIX(STR, ssl_client_ca_dir), + DEF_NOPREFIX(STR, ssl_client_ca_file), + + SETTING_DEFINE_LIST_END +}; +static const struct auth_settings auth_default_settings = { + .mechanisms = "plain", + .realms = "", + .default_realm = "", + .cache_size = 0, + .cache_ttl = 60*60, + .cache_negative_ttl = 60*60, + .cache_verify_password_with_worker = FALSE, + .username_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@", + .username_translation = "", + .username_format = "%Lu", + .master_user_separator = "", + .anonymous_username = "anonymous", + .krb5_keytab = "", + .gssapi_hostname = "", + .winbind_helper_path = "/usr/bin/ntlm_auth", + .proxy_self = "", + .failure_delay = 2, + + .policy_server_url = "", + .policy_server_api_header = "", + .policy_server_timeout_msecs = 2000, + .policy_hash_mech = "sha256", + .policy_hash_nonce = "", + .policy_request_attributes = "login=%{requested_username} pwhash=%{hashed_password} remote=%{rip} device_id=%{client_id} protocol=%s session_id=%{session}", + .policy_reject_on_fail = FALSE, + .policy_check_before_auth = TRUE, + .policy_check_after_auth = TRUE, + .policy_report_after_auth = TRUE, + .policy_log_only = FALSE, + .policy_hash_truncate = 12, + + .stats = FALSE, + .verbose = FALSE, + .debug = FALSE, + .debug_passwords = FALSE, + .verbose_passwords = "no", + .ssl_require_client_cert = FALSE, + .ssl_username_from_cert = FALSE, + .ssl_client_ca_dir = "", + .ssl_client_ca_file = "", + + .use_winbind = FALSE, + + .worker_max_count = 30, + + .passdbs = ARRAY_INIT, + .userdbs = ARRAY_INIT, + + .base_dir = PKG_RUNDIR, + .verbose_proctitle = FALSE, + .first_valid_uid = 500, + .last_valid_uid = 0, + .first_valid_gid = 1, + .last_valid_gid = 0, +}; +const struct setting_parser_info auth_setting_parser_info = { + .module_name = "auth", + .defines = auth_setting_defines, + .defaults = &auth_default_settings, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct auth_settings), + + .parent_offset = SIZE_MAX, + + .check_func = auth_settings_check +}; +/* ../../src/anvil/anvil-settings.c */ +/* <settings checks> */ +static struct file_listener_settings anvil_unix_listeners_array[] = { + { "anvil", 0600, "", "" }, + { "anvil-auth-penalty", 0600, "", "" } +}; +static struct file_listener_settings *anvil_unix_listeners[] = { + &anvil_unix_listeners_array[0], + &anvil_unix_listeners_array[1] +}; +static buffer_t anvil_unix_listeners_buf = { + { { anvil_unix_listeners, sizeof(anvil_unix_listeners) } } +}; +/* </settings checks> */ +struct service_settings anvil_service_settings = { + .name = "anvil", + .protocol = "", + .type = "anvil", + .executable = "anvil", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "empty", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 1, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = UINT_MAX, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &anvil_unix_listeners_buf, + sizeof(anvil_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT, + + .process_limit_1 = TRUE +}; +static struct service_settings *config_all_services[] = { +#ifdef HAVE_LIBWRAP + &tcpwrap_service_settings, +#endif + &health_check_service_settings, + &submission_service_settings, + &submission_login_service_settings, + &stats_service_settings, + &replicator_service_settings, + &aggregator_service_settings, + &pop3_service_settings, + &pop3_login_service_settings, + &old_stats_service_settings, + &log_service_settings, + &lmtp_service_settings, + &ipc_service_settings, + &indexer_worker_service_settings, + &indexer_service_settings, + &imap_service_settings, + &imap_urlauth_worker_service_settings, + &imap_urlauth_service_settings, + &imap_urlauth_login_service_settings, + &imap_login_service_settings, + &imap_hibernate_service_settings, + &doveadm_service_settings, + &dns_client_service_settings, + &director_service_settings, + &dict_service_settings, + &dict_async_service_settings, + &config_service_settings, + &auth_service_settings, + &auth_worker_service_settings, + &anvil_service_settings, +}; +buffer_t config_all_services_buf = { + { { config_all_services, sizeof(config_all_services) } } +}; +const struct setting_parser_info *all_default_roots[] = { + &master_service_setting_parser_info, + &master_service_ssl_setting_parser_info, + &master_service_ssl_server_setting_parser_info, + &smtp_submit_setting_parser_info, + &aggregator_setting_parser_info, + &auth_setting_parser_info, + &dict_setting_parser_info, + &director_setting_parser_info, + &doveadm_setting_parser_info, + &fs_crypt_setting_parser_info, + &imap_login_setting_parser_info, + &imap_setting_parser_info, + &imap_urlauth_login_setting_parser_info, + &imap_urlauth_setting_parser_info, + &imap_urlauth_worker_setting_parser_info, + &imapc_setting_parser_info, + &lda_setting_parser_info, + &lmtp_setting_parser_info, + &login_setting_parser_info, + &mail_storage_setting_parser_info, + &mail_user_setting_parser_info, + &maildir_setting_parser_info, + &master_setting_parser_info, + &mbox_setting_parser_info, + &mdbox_setting_parser_info, + &old_stats_setting_parser_info, + &pop3_login_setting_parser_info, + &pop3_setting_parser_info, + &pop3c_setting_parser_info, + "a_status_setting_parser_info, + &replicator_setting_parser_info, + &stats_setting_parser_info, + &submission_login_setting_parser_info, + &submission_setting_parser_info, + NULL +}; +const struct setting_parser_info *const *all_roots = all_default_roots; +ARRAY_TYPE(service_settings) *default_services = &master_default_settings.services; diff --git a/src/config/all-settings.h b/src/config/all-settings.h new file mode 100644 index 0000000..96ac7bd --- /dev/null +++ b/src/config/all-settings.h @@ -0,0 +1,8 @@ +#ifndef ALL_SETTINGS_H +#define ALL_SETTINGS_H + +extern const struct setting_parser_info *const *all_roots; +extern const struct setting_parser_info *all_default_roots[]; +extern ARRAY_TYPE(service_settings) *default_services; + +#endif diff --git a/src/config/config-connection.c b/src/config/config-connection.c new file mode 100644 index 0000000..bd3db86 --- /dev/null +++ b/src/config/config-connection.c @@ -0,0 +1,267 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "llist.h" +#include "istream.h" +#include "ostream.h" +#include "strescape.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "config-request.h" +#include "config-parser.h" +#include "config-connection.h" + +#include <unistd.h> + +#define MAX_INBUF_SIZE 1024 + +#define CONFIG_CLIENT_PROTOCOL_MAJOR_VERSION 2 +#define CONFIG_CLIENT_PROTOCOL_MINOR_VERSION 0 + +struct config_connection { + struct config_connection *prev, *next; + + int fd; + struct istream *input; + struct ostream *output; + struct io *io; + + bool version_received:1; + bool handshaked:1; +}; + +static struct config_connection *config_connections = NULL; + +static const char *const * +config_connection_next_line(struct config_connection *conn) +{ + const char *line; + + line = i_stream_next_line(conn->input); + if (line == NULL) + return NULL; + + return t_strsplit_tabescaped(line); +} + +static void +config_request_output(const char *key, const char *value, + enum config_key_type type ATTR_UNUSED, void *context) +{ + struct ostream *output = context; + const char *p; + + o_stream_nsend_str(output, key); + o_stream_nsend_str(output, "="); + while ((p = strchr(value, '\n')) != NULL) { + o_stream_nsend(output, value, p-value); + o_stream_nsend(output, SETTING_STREAM_LF_CHAR, 1); + value = p+1; + } + o_stream_nsend_str(output, value); + o_stream_nsend_str(output, "\n"); +} + +static int config_connection_request(struct config_connection *conn, + const char *const *args) +{ + struct config_export_context *ctx; + struct master_service_settings_output output; + struct config_filter filter; + const char *path, *error, *module, *const *wanted_modules; + ARRAY(const char *) modules; + ARRAY(const char *) exclude_settings; + bool is_master = FALSE; + + /* [<args>] */ + t_array_init(&modules, 4); + t_array_init(&exclude_settings, 4); + i_zero(&filter); + for (; *args != NULL; args++) { + if (str_begins(*args, "service=")) + filter.service = *args + 8; + else if (str_begins(*args, "module=")) { + module = *args + 7; + if (strcmp(module, "master") == 0) + is_master = TRUE; + array_push_back(&modules, &module); + } else if (str_begins(*args, "exclude=")) { + const char *value = *args + 8; + array_push_back(&exclude_settings, &value); + } else if (str_begins(*args, "lname=")) + filter.local_name = *args + 6; + else if (str_begins(*args, "lip=")) { + if (net_addr2ip(*args + 4, &filter.local_net) == 0) { + filter.local_bits = + IPADDR_IS_V4(&filter.local_net) ? + 32 : 128; + } + } else if (str_begins(*args, "rip=")) { + if (net_addr2ip(*args + 4, &filter.remote_net) == 0) { + filter.remote_bits = + IPADDR_IS_V4(&filter.remote_net) ? + 32 : 128; + } + } + } + array_append_zero(&modules); + wanted_modules = array_count(&modules) == 1 ? NULL : + array_front(&modules); + array_append_zero(&exclude_settings); + + if (is_master) { + /* master reads configuration only when reloading settings */ + path = master_service_get_config_path(master_service); + if (config_parse_file(path, TRUE, NULL, &error) <= 0) { + o_stream_nsend_str(conn->output, + t_strconcat("\nERROR ", error, "\n", NULL)); + config_connection_destroy(conn); + return -1; + } + } + + o_stream_cork(conn->output); + + ctx = config_export_init(wanted_modules, + array_count(&exclude_settings) == 1 ? NULL : + array_front(&exclude_settings), + CONFIG_DUMP_SCOPE_SET, 0, + config_request_output, conn->output); + config_export_by_filter(ctx, &filter); + config_export_get_output(ctx, &output); + + if (output.specific_services != NULL) { + const char *const *s; + + for (s = output.specific_services; *s != NULL; s++) { + o_stream_nsend_str(conn->output, + t_strdup_printf("service=%s\t", *s)); + } + } + if (output.service_uses_local) + o_stream_nsend_str(conn->output, "service-uses-local\t"); + if (output.service_uses_remote) + o_stream_nsend_str(conn->output, "service-uses-remote\t"); + if (output.used_local) + o_stream_nsend_str(conn->output, "used-local\t"); + if (output.used_remote) + o_stream_nsend_str(conn->output, "used-remote\t"); + o_stream_nsend_str(conn->output, "\n"); + + if (config_export_finish(&ctx) < 0) { + config_connection_destroy(conn); + return -1; + } + o_stream_nsend_str(conn->output, "\n"); + o_stream_uncork(conn->output); + return 0; +} + +static int config_filters_request(struct config_connection *conn) +{ + struct config_filter_parser *const *filters = config_filter_get_all(config_filter); + o_stream_cork(conn->output); + while(*filters != NULL) { + const struct config_filter *filter = &(*filters)->filter; + o_stream_nsend_str(conn->output, "FILTER"); + if (filter->service != NULL) + o_stream_nsend_str(conn->output, t_strdup_printf("\tservice=%s", + str_tabescape(filter->service))); + if (filter->local_name != NULL) + o_stream_nsend_str(conn->output, t_strdup_printf("\tlocal-name=%s", + str_tabescape(filter->local_name))); + if (filter->local_bits > 0) + o_stream_nsend_str(conn->output, t_strdup_printf("\tlocal-net=%s/%u", + net_ip2addr(&filter->local_net), + filter->local_bits)); + if (filter->remote_bits > 0) + o_stream_nsend_str(conn->output, t_strdup_printf("\tremote-net=%s/%u", + net_ip2addr(&filter->remote_net), + filter->remote_bits)); + o_stream_nsend_str(conn->output, "\n"); + filters++; + } + o_stream_nsend_str(conn->output, "\n"); + o_stream_uncork(conn->output); + return 0; +} + + +static void config_connection_input(struct config_connection *conn) +{ + const char *const *args, *line; + + switch (i_stream_read(conn->input)) { + case -2: + i_error("BUG: Config client connection sent too much data"); + config_connection_destroy(conn); + return; + case -1: + config_connection_destroy(conn); + return; + } + + if (!conn->version_received) { + line = i_stream_next_line(conn->input); + if (line == NULL) + return; + + if (!version_string_verify(line, "config", + CONFIG_CLIENT_PROTOCOL_MAJOR_VERSION)) { + i_error("Config client not compatible with this server " + "(mixed old and new binaries?)"); + config_connection_destroy(conn); + return; + } + conn->version_received = TRUE; + } + + while ((args = config_connection_next_line(conn)) != NULL) { + if (args[0] == NULL) + continue; + if (strcmp(args[0], "REQ") == 0) { + if (config_connection_request(conn, args + 1) < 0) + break; + } + if (strcmp(args[0], "FILTERS") == 0) { + if (config_filters_request(conn) < 0) + break; + } + } +} + +struct config_connection *config_connection_create(int fd) +{ + struct config_connection *conn; + + conn = i_new(struct config_connection, 1); + conn->fd = fd; + conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); + conn->output = o_stream_create_fd(fd, SIZE_MAX); + o_stream_set_no_error_handling(conn->output, TRUE); + conn->io = io_add(fd, IO_READ, config_connection_input, conn); + DLLIST_PREPEND(&config_connections, conn); + return conn; +} + +void config_connection_destroy(struct config_connection *conn) +{ + DLLIST_REMOVE(&config_connections, conn); + + io_remove(&conn->io); + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + if (close(conn->fd) < 0) + i_error("close(config conn) failed: %m"); + i_free(conn); + + master_service_client_connection_destroyed(master_service); +} + +void config_connections_destroy_all(void) +{ + while (config_connections != NULL) + config_connection_destroy(config_connections); +} diff --git a/src/config/config-connection.h b/src/config/config-connection.h new file mode 100644 index 0000000..7983fff --- /dev/null +++ b/src/config/config-connection.h @@ -0,0 +1,9 @@ +#ifndef CONFIG_CONNECTION_H +#define CONFIG_CONNECTION_H + +struct config_connection *config_connection_create(int fd); +void config_connection_destroy(struct config_connection *conn); + +void config_connections_destroy_all(void); + +#endif diff --git a/src/config/config-filter.c b/src/config/config-filter.c new file mode 100644 index 0000000..336380d --- /dev/null +++ b/src/config/config-filter.c @@ -0,0 +1,401 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "settings-parser.h" +#include "master-service-settings.h" +#include "config-parser.h" +#include "config-filter.h" +#include "dns-util.h" + +struct config_filter_context { + pool_t pool; + struct config_filter_parser *const *parsers; +}; + +static bool config_filter_match_service(const struct config_filter *mask, + const struct config_filter *filter) +{ + if (mask->service != NULL) { + if (filter->service == NULL) + return FALSE; + if (mask->service[0] == '!') { + /* not service */ + if (strcmp(filter->service, mask->service + 1) == 0) + return FALSE; + } else { + if (strcmp(filter->service, mask->service) != 0) + return FALSE; + } + } + return TRUE; +} + +static bool +config_filter_match_local_name(const struct config_filter *mask, + const char *filter_local_name) +{ + /* Handle multiple names separated by spaces in local_name + * Ex: local_name "mail.domain.tld domain.tld mx.domain.tld" { ... } */ + const char *ptr, *local_name = mask->local_name; + while((ptr = strchr(local_name, ' ')) != NULL) { + if (dns_match_wildcard(filter_local_name, + t_strdup_until(local_name, ptr)) == 0) + return TRUE; + local_name = ptr+1; + } + return dns_match_wildcard(filter_local_name, local_name) == 0; +} + +static bool config_filter_match_rest(const struct config_filter *mask, + const struct config_filter *filter) +{ + bool matched; + + if (mask->local_name != NULL) { + if (filter->local_name == NULL) + return FALSE; + T_BEGIN { + matched = config_filter_match_local_name(mask, filter->local_name); + } T_END; + if (!matched) + return FALSE; + } + /* FIXME: it's not comparing full masks */ + if (mask->remote_bits != 0) { + if (filter->remote_bits == 0) + return FALSE; + if (!net_is_in_network(&filter->remote_net, &mask->remote_net, + mask->remote_bits)) + return FALSE; + } + if (mask->local_bits != 0) { + if (filter->local_bits == 0) + return FALSE; + if (!net_is_in_network(&filter->local_net, &mask->local_net, + mask->local_bits)) + return FALSE; + } + return TRUE; +} + +bool config_filter_match(const struct config_filter *mask, + const struct config_filter *filter) +{ + if (!config_filter_match_service(mask, filter)) + return FALSE; + + return config_filter_match_rest(mask, filter); +} + +bool config_filters_equal(const struct config_filter *f1, + const struct config_filter *f2) +{ + if (null_strcmp(f1->service, f2->service) != 0) + return FALSE; + + if (f1->remote_bits != f2->remote_bits) + return FALSE; + if (!net_ip_compare(&f1->remote_net, &f2->remote_net)) + return FALSE; + + if (f1->local_bits != f2->local_bits) + return FALSE; + if (!net_ip_compare(&f1->local_net, &f2->local_net)) + return FALSE; + + if (null_strcasecmp(f1->local_name, f2->local_name) != 0) + return FALSE; + + return TRUE; +} + +struct config_filter_context *config_filter_init(pool_t pool) +{ + struct config_filter_context *ctx; + + ctx = p_new(pool, struct config_filter_context, 1); + ctx->pool = pool; + return ctx; +} + +void config_filter_deinit(struct config_filter_context **_ctx) +{ + struct config_filter_context *ctx = *_ctx; + unsigned int i; + + *_ctx = NULL; + + for (i = 0; ctx->parsers[i] != NULL; i++) + config_filter_parsers_free(ctx->parsers[i]->parsers); + pool_unref(&ctx->pool); +} + +void config_filter_add_all(struct config_filter_context *ctx, + struct config_filter_parser *const *parsers) +{ + ctx->parsers = parsers; +} + +static int +config_filter_parser_cmp(struct config_filter_parser *const *p1, + struct config_filter_parser *const *p2) +{ + const struct config_filter *f1 = &(*p1)->filter, *f2 = &(*p2)->filter; + + /* remote and locals are first, although it doesn't really + matter which one comes first */ + if (f1->local_name != NULL && f2->local_name == NULL) + return -1; + if (f1->local_name == NULL && f2->local_name != NULL) + return 1; + + if (f1->local_bits > f2->local_bits) + return -1; + if (f1->local_bits < f2->local_bits) + return 1; + + if (f1->remote_bits > f2->remote_bits) + return -1; + if (f1->remote_bits < f2->remote_bits) + return 1; + + if (f1->service != NULL && f2->service == NULL) + return -1; + if (f1->service == NULL && f2->service != NULL) + return 1; + return 0; +} + +static int +config_filter_parser_cmp_rev(struct config_filter_parser *const *p1, + struct config_filter_parser *const *p2) +{ + return -config_filter_parser_cmp(p1, p2); +} + +static bool str_array_contains(ARRAY_TYPE(const_string) *arr, const char *str) +{ + const char *p; + + array_foreach_elem(arr, p) { + if (strcmp(p, str) == 0) + return TRUE; + } + return FALSE; +} + +static bool have_changed_settings(const struct config_filter_parser *parser, + const char *const *modules) +{ + const unsigned char *changes; + unsigned int i, j, size; + + for (i = 0; parser->parsers[i].root != NULL; i++) { + if (!config_module_want_parser(config_module_parsers, + modules, parser->parsers[i].root)) + continue; + + changes = settings_parser_get_changes(parser->parsers[i].parser); + size = parser->parsers[i].root->struct_size; + for (j = 0; j < size; j++) { + if (changes[j] != 0) + return TRUE; + } + } + return FALSE; +} + +static struct config_filter_parser *const * +config_filter_find_all(struct config_filter_context *ctx, pool_t pool, + const char *const *modules, + const struct config_filter *filter, + struct master_service_settings_output *output_r) +{ + ARRAY_TYPE(config_filter_parsers) matches; + ARRAY_TYPE(const_string) service_names; + unsigned int i; + + i_zero(output_r); + + p_array_init(&matches, pool, 8); + p_array_init(&service_names, pool, 8); + for (i = 0; ctx->parsers[i] != NULL; i++) { + const struct config_filter *mask = &ctx->parsers[i]->filter; + + if (!config_filter_match_service(mask, filter)) { + if (!str_array_contains(&service_names, mask->service) && + have_changed_settings(ctx->parsers[i], modules)) + array_push_back(&service_names, + &mask->service); + continue; + } + + if (mask->local_bits > 0 || mask->local_name != NULL) + output_r->service_uses_local = TRUE; + if (mask->remote_bits > 0) + output_r->service_uses_remote = TRUE; + if (config_filter_match_rest(mask, filter)) { + if (mask->local_bits > 0 || mask->local_name != NULL) + output_r->used_local = TRUE; + if (mask->remote_bits > 0) + output_r->used_remote = TRUE; + array_push_back(&matches, &ctx->parsers[i]); + } + } + if (filter->service == NULL) { + array_append_zero(&service_names); + output_r->specific_services = array_front(&service_names); + } + + array_sort(&matches, config_filter_parser_cmp); + array_append_zero(&matches); + return array_front(&matches); +} + +struct config_filter_parser *const * +config_filter_get_all(struct config_filter_context *ctx) +{ + ARRAY_TYPE(config_filter_parsers) filters; + unsigned int i; + + t_array_init(&filters, 8); + for (i = 0; ctx->parsers[i] != NULL; i++) { + array_push_back(&filters, &ctx->parsers[i]); + } + array_sort(&filters, config_filter_parser_cmp_rev); + array_append_zero(&filters); + return array_front(&filters); +} + +struct config_filter_parser *const * +config_filter_find_subset(struct config_filter_context *ctx, + const struct config_filter *filter) +{ + ARRAY_TYPE(config_filter_parsers) matches; + struct config_filter tmp_mask; + unsigned int i; + + t_array_init(&matches, 8); + for (i = 0; ctx->parsers[i] != NULL; i++) { + const struct config_filter *mask = &ctx->parsers[i]->filter; + + if (filter->service != NULL) { + if (!config_filter_match_service(mask, filter)) + continue; + } + + tmp_mask = *mask; + if (filter->local_name == NULL) + tmp_mask.local_name = NULL; + if (filter->local_bits == 0) + tmp_mask.local_bits = 0; + if (filter->remote_bits == 0) + tmp_mask.remote_bits = 0; + + if (config_filter_match_rest(&tmp_mask, filter)) + array_push_back(&matches, &ctx->parsers[i]); + } + array_sort(&matches, config_filter_parser_cmp_rev); + array_append_zero(&matches); + return array_front(&matches); +} + +static bool +config_filter_is_superset(const struct config_filter *sup, + const struct config_filter *filter) +{ + /* assume that both of the filters match the same subset, so we don't + need to compare IPs and service name. */ + if (sup->local_bits > filter->local_bits) + return FALSE; + if (sup->remote_bits > filter->remote_bits) + return FALSE; + if (sup->local_name != NULL && filter->local_name == NULL) { + i_warning("%s", sup->local_name); + return FALSE; + } + if (sup->service != NULL && filter->service == NULL) + return FALSE; + return TRUE; +} + +static int +config_module_parser_apply_changes(struct config_module_parser *dest, + const struct config_filter_parser *src, + pool_t pool, const char **error_r) +{ + const char *conflict_key; + unsigned int i; + + for (i = 0; dest[i].root != NULL; i++) { + if (settings_parser_apply_changes(dest[i].parser, + src->parsers[i].parser, pool, + error_r == NULL ? NULL : + &conflict_key) < 0) { + i_assert(error_r != NULL); + *error_r = t_strdup_printf("Conflict in setting %s " + "found from filter at %s", conflict_key, + src->file_and_line); + return -1; + } + } + return 0; +} + +int config_filter_parsers_get(struct config_filter_context *ctx, pool_t pool, + const char *const *modules, + const struct config_filter *filter, + struct config_module_parser **parsers_r, + struct master_service_settings_output *output_r, + const char **error_r) +{ + struct config_filter_parser *const *src; + struct config_module_parser *dest; + const char *error = NULL, **error_p; + unsigned int i, count; + + /* get the matching filters. the most specific ones are handled first, + so that if more generic filters try to override settings we'll fail + with an error. Merging SET_STRLIST types requires + settings_parser_apply_changes() to work a bit unintuitively by + letting the destination settings override the source settings. */ + src = config_filter_find_all(ctx, pool, modules, filter, output_r); + + /* all of them should have the same number of parsers. + duplicate our initial parsers from the first match */ + for (count = 0; src[0]->parsers[count].root != NULL; count++) ; + dest = p_new(pool, struct config_module_parser, count + 1); + for (i = 0; i < count; i++) { + dest[i] = src[0]->parsers[i]; + dest[i].parser = + settings_parser_dup(src[0]->parsers[i].parser, pool); + } + + /* apply the changes from rest of the matches */ + for (i = 1; src[i] != NULL; i++) { + if (config_filter_is_superset(&src[i]->filter, + &src[i-1]->filter)) + error_p = NULL; + else + error_p = &error; + + if (config_module_parser_apply_changes(dest, src[i], pool, + error_p) < 0) { + i_assert(error != NULL); + config_filter_parsers_free(dest); + *error_r = error; + return -1; + } + } + *parsers_r = dest; + return 0; +} + +void config_filter_parsers_free(struct config_module_parser *parsers) +{ + unsigned int i; + + for (i = 0; parsers[i].root != NULL; i++) + settings_parser_deinit(&parsers[i].parser); +} diff --git a/src/config/config-filter.h b/src/config/config-filter.h new file mode 100644 index 0000000..fda3182 --- /dev/null +++ b/src/config/config-filter.h @@ -0,0 +1,58 @@ +#ifndef CONFIG_FILTER_H +#define CONFIG_FILTER_H + +#include "net.h" + +struct master_service_settings_output; + +struct config_filter { + const char *service; + /* local_name is for TLS SNI requests. + both local_name and local_bits can't be set at the same time. */ + const char *local_name; + /* the hosts are used only in doveconf output */ + const char *local_host, *remote_host; + struct ip_addr local_net, remote_net; + unsigned int local_bits, remote_bits; +}; + +struct config_filter_parser { + struct config_filter filter; + const char *file_and_line; + /* NULL-terminated array of parsers */ + struct config_module_parser *parsers; +}; +ARRAY_DEFINE_TYPE(config_filter_parsers, struct config_filter_parser *); + +struct config_filter_context *config_filter_init(pool_t pool); +void config_filter_deinit(struct config_filter_context **ctx); + +/* Replace filter's parsers with given parser list. */ +void config_filter_add_all(struct config_filter_context *ctx, + struct config_filter_parser *const *parsers); + +/* Build new parsers from all existing ones matching the given filter. */ +int config_filter_parsers_get(struct config_filter_context *ctx, pool_t pool, + const char *const *modules, + const struct config_filter *filter, + struct config_module_parser **parsers_r, + struct master_service_settings_output *output_r, + const char **error_r) ATTR_NULL(3); +void config_filter_parsers_free(struct config_module_parser *parsers); + +/* Return a list of filters that are a subset of the given filter. */ +struct config_filter_parser *const * +config_filter_find_subset(struct config_filter_context *ctx, + const struct config_filter *filter); + +struct config_filter_parser *const * +config_filter_get_all(struct config_filter_context *ctx); + +/* Returns TRUE if filter matches mask. */ +bool config_filter_match(const struct config_filter *mask, + const struct config_filter *filter); +/* Returns TRUE if two filters are fully equal. */ +bool config_filters_equal(const struct config_filter *f1, + const struct config_filter *f2); + +#endif diff --git a/src/config/config-parser-private.h b/src/config/config-parser-private.h new file mode 100644 index 0000000..9fe98ce --- /dev/null +++ b/src/config/config-parser-private.h @@ -0,0 +1,75 @@ +#ifndef CONFIG_PARSER_PRIVATE_H +#define CONFIG_PARSER_PRIVATE_H + +#include "config-parser.h" +#include "config-filter.h" + +enum config_line_type { + CONFIG_LINE_TYPE_SKIP, + CONFIG_LINE_TYPE_CONTINUE, + CONFIG_LINE_TYPE_ERROR, + CONFIG_LINE_TYPE_KEYVALUE, + CONFIG_LINE_TYPE_KEYFILE, + CONFIG_LINE_TYPE_KEYVARIABLE, + CONFIG_LINE_TYPE_SECTION_BEGIN, + CONFIG_LINE_TYPE_SECTION_END, + CONFIG_LINE_TYPE_INCLUDE, + CONFIG_LINE_TYPE_INCLUDE_TRY +}; + +struct config_section_stack { + struct config_section_stack *prev; + const char *key; + + struct config_filter filter; + /* root=NULL-terminated list of parsers */ + struct config_module_parser *parsers; + size_t pathlen; + + const char *open_path; + unsigned int open_linenum; + bool is_filter; +}; + +struct input_stack { + struct input_stack *prev; + + struct istream *input; + const char *path; + unsigned int linenum; +}; + +struct config_parser_context { + pool_t pool; + const char *path; + const char *const *modules; + + ARRAY(struct config_filter_parser *) all_parsers; + struct config_module_parser *root_parsers; + struct config_section_stack *cur_section; + struct input_stack *cur_input; + + string_t *str; + size_t pathlen; + unsigned int section_counter; + const char *error; + + struct old_set_parser *old; + + HASH_TABLE(const char *, const char *) seen_settings; + struct config_filter_context *filter; + bool expand_values:1; + bool hide_errors:1; +}; + +extern void (*hook_config_parser_begin)(struct config_parser_context *ctx); +extern int (*hook_config_parser_end)(struct config_parser_context *ctx, + const char **error_r); + +int config_apply_line(struct config_parser_context *ctx, const char *key, + const char *line, const char *section_name) ATTR_NULL(4); +void config_parser_apply_line(struct config_parser_context *ctx, + enum config_line_type type, + const char *key, const char *value); + +#endif diff --git a/src/config/config-parser.c b/src/config/config-parser.c new file mode 100644 index 0000000..9d36796 --- /dev/null +++ b/src/config/config-parser.c @@ -0,0 +1,1212 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hash.h" +#include "strescape.h" +#include "istream.h" +#include "module-dir.h" +#include "settings-parser.h" +#include "service-settings.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "master-service-ssl-settings.h" +#include "all-settings.h" +#include "old-set-parser.h" +#include "config-request.h" +#include "config-parser-private.h" + +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#ifdef HAVE_GLOB_H +# include <glob.h> +#endif + +#ifndef GLOB_BRACE +# define GLOB_BRACE 0 +#endif + +#define DNS_LOOKUP_TIMEOUT_SECS 30 +#define DNS_LOOKUP_WARN_SECS 5 + +ARRAY_DEFINE_TYPE(setting_parser_info_p, const struct setting_parser_info *); + +static const enum settings_parser_flags settings_parser_flags = + SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS | + SETTINGS_PARSER_FLAG_TRACK_CHANGES; + +struct config_module_parser *config_module_parsers; +struct config_filter_context *config_filter; +struct module *modules; +void (*hook_config_parser_begin)(struct config_parser_context *ctx); +int (*hook_config_parser_end)(struct config_parser_context *ctx, + const char **error_r); + +static ARRAY_TYPE(service_settings) services_free_at_deinit = ARRAY_INIT; +static ARRAY_TYPE(setting_parser_info_p) roots_free_at_deinit = ARRAY_INIT; + +static const char *info_type_name_find(const struct setting_parser_info *info) +{ + unsigned int i; + + for (i = 0; info->defines[i].key != NULL; i++) { + if (info->defines[i].offset == info->type_offset) + return info->defines[i].key; + } + i_panic("setting parser: Invalid type_offset value"); + return NULL; +} + +static int config_add_type(struct setting_parser_context *parser, + const char *line, const char *section_name) +{ + const struct setting_parser_info *info; + const char *p; + string_t *str; + int ret; + + info = settings_parse_get_prev_info(parser); + if (info == NULL) { + /* section inside strlist */ + return -1; + } + if (info->type_offset == SIZE_MAX) + return 0; + + str = t_str_new(256); + p = strchr(line, '='); + str_append_data(str, line, p-line); + str_append_c(str, SETTINGS_SEPARATOR); + str_append(str, p+1); + if (info != NULL) { + str_append_c(str, SETTINGS_SEPARATOR); + str_append(str, info_type_name_find(info)); + } + + ret = settings_parse_keyvalue(parser, str_c(str), section_name); + i_assert(ret > 0); + return 0; +} + +static bool +config_parser_is_in_localremote(struct config_section_stack *section) +{ + const struct config_filter *filter = §ion->filter; + + return filter->local_name != NULL || filter->local_bits > 0 || + filter->remote_bits > 0; +} + +static void +section_stack_write(string_t *str, struct config_section_stack *section) +{ + if (section == NULL) + return; + + section_stack_write(str, section->prev); + if (!section->is_filter && section->key != NULL) + str_printfa(str, "%s { ", section->key); +} + +static const char * +get_setting_full_path(struct config_parser_context *ctx, const char *key) +{ + string_t *str = t_str_new(128); + + section_stack_write(str, ctx->cur_section); + str_append(str, key); + return str_c(str); +} + +int config_apply_line(struct config_parser_context *ctx, const char *key, + const char *line, const char *section_name) +{ + struct config_module_parser *l; + bool found = FALSE; + int ret; + + for (l = ctx->cur_section->parsers; l->root != NULL; l++) { + ret = settings_parse_line(l->parser, line); + if (ret > 0) { + found = TRUE; + /* FIXME: remove once auth does support these. */ + if (strcmp(l->root->module_name, "auth") == 0 && + config_parser_is_in_localremote(ctx->cur_section)) { + ctx->error = p_strconcat(ctx->pool, + "Auth settings not supported inside local/remote blocks: ", + key, NULL); + return -1; + } + if (section_name != NULL) { + if (config_add_type(l->parser, line, section_name) < 0) { + ctx->error = "Section not allowed here"; + return -1; + } + } + } else if (ret < 0) { + ctx->error = settings_parser_get_error(l->parser); + return -1; + } + } + if (!found) { + ctx->error = p_strconcat(ctx->pool, "Unknown setting: ", + get_setting_full_path(ctx, key), NULL); + return -1; + } + return 0; +} + +static const char * +fix_relative_path(const char *path, struct input_stack *input) +{ + const char *p; + + if (*path == '/') + return path; + + p = strrchr(input->path, '/'); + if (p == NULL) + return path; + + return t_strconcat(t_strdup_until(input->path, p+1), path, NULL); +} + +static struct config_module_parser * +config_module_parsers_init(pool_t pool) +{ + struct config_module_parser *dest; + unsigned int i, count; + + for (count = 0; all_roots[count] != NULL; count++) ; + + dest = p_new(pool, struct config_module_parser, count + 1); + for (i = 0; i < count; i++) { + dest[i].root = all_roots[i]; + dest[i].parser = settings_parser_init(pool, all_roots[i], + settings_parser_flags); + } + return dest; +} + +static void +config_add_new_parser(struct config_parser_context *ctx) +{ + struct config_section_stack *cur_section = ctx->cur_section; + struct config_filter_parser *parser; + + parser = p_new(ctx->pool, struct config_filter_parser, 1); + parser->filter = cur_section->filter; + if (ctx->cur_input->linenum == 0) { + parser->file_and_line = + p_strdup(ctx->pool, ctx->cur_input->path); + } else { + parser->file_and_line = + p_strdup_printf(ctx->pool, "%s:%d", + ctx->cur_input->path, + ctx->cur_input->linenum); + } + parser->parsers = cur_section->prev == NULL ? ctx->root_parsers : + config_module_parsers_init(ctx->pool); + array_push_back(&ctx->all_parsers, &parser); + + cur_section->parsers = parser->parsers; +} + +static struct config_section_stack * +config_add_new_section(struct config_parser_context *ctx) +{ + struct config_section_stack *section; + + section = p_new(ctx->pool, struct config_section_stack, 1); + section->prev = ctx->cur_section; + section->filter = ctx->cur_section->filter; + section->parsers = ctx->cur_section->parsers; + + section->open_path = p_strdup(ctx->pool, ctx->cur_input->path); + section->open_linenum = ctx->cur_input->linenum; + return section; +} + +static struct config_filter_parser * +config_filter_parser_find(struct config_parser_context *ctx, + const struct config_filter *filter) +{ + struct config_filter_parser *parser; + + array_foreach_elem(&ctx->all_parsers, parser) { + if (config_filters_equal(&parser->filter, filter)) + return parser; + } + return NULL; +} + +int config_parse_net(const char *value, struct ip_addr *ip_r, + unsigned int *bits_r, const char **error_r) +{ + struct ip_addr *ips; + const char *p; + unsigned int ip_count, bits, max_bits; + time_t t1, t2; + int ret; + + if (net_parse_range(value, ip_r, bits_r) == 0) + return 0; + + p = strchr(value, '/'); + if (p != NULL) { + value = t_strdup_until(value, p); + p++; + } + + t1 = time(NULL); + alarm(DNS_LOOKUP_TIMEOUT_SECS); + ret = net_gethostbyname(value, &ips, &ip_count); + alarm(0); + t2 = time(NULL); + if (ret != 0) { + *error_r = t_strdup_printf("gethostbyname(%s) failed: %s", + value, net_gethosterror(ret)); + return -1; + } + *ip_r = ips[0]; + + if (t2 - t1 >= DNS_LOOKUP_WARN_SECS) { + i_warning("gethostbyname(%s) took %d seconds", + value, (int)(t2-t1)); + } + + max_bits = IPADDR_IS_V4(&ips[0]) ? 32 : 128; + if (p == NULL) + *bits_r = max_bits; + else if (str_to_uint(p, &bits) == 0 && bits <= max_bits) + *bits_r = bits; + else { + *error_r = t_strdup_printf("Invalid network mask: %s", p); + return -1; + } + return 0; +} + +static bool +config_filter_add_new_filter(struct config_parser_context *ctx, + const char *key, const char *value) +{ + struct config_filter *filter = &ctx->cur_section->filter; + struct config_filter *parent = &ctx->cur_section->prev->filter; + struct config_filter_parser *parser; + const char *error; + + if (strcmp(key, "protocol") == 0) { + if (parent->service != NULL) + ctx->error = "Nested protocol { protocol { .. } } block not allowed"; + else + filter->service = p_strdup(ctx->pool, value); + } else if (strcmp(key, "local") == 0) { + if (parent->remote_bits > 0) + ctx->error = "remote { local { .. } } not allowed (use local { remote { .. } } instead)"; + else if (parent->service != NULL) + ctx->error = "protocol { local { .. } } not allowed (use local { protocol { .. } } instead)"; + else if (parent->local_name != NULL) + ctx->error = "local_name { local { .. } } not allowed (use local { local_name { .. } } instead)"; + else if (config_parse_net(value, &filter->local_net, + &filter->local_bits, &error) < 0) + ctx->error = p_strdup(ctx->pool, error); + else if (parent->local_bits > filter->local_bits || + (parent->local_bits > 0 && + !net_is_in_network(&filter->local_net, + &parent->local_net, + parent->local_bits))) + ctx->error = "local net1 { local net2 { .. } } requires net2 to be inside net1"; + else + filter->local_host = p_strdup(ctx->pool, value); + } else if (strcmp(key, "local_name") == 0) { + if (parent->remote_bits > 0) + ctx->error = "remote { local_name { .. } } not allowed (use local_name { remote { .. } } instead)"; + else if (parent->service != NULL) + ctx->error = "protocol { local_name { .. } } not allowed (use local_name { protocol { .. } } instead)"; + else + filter->local_name = p_strdup(ctx->pool, value); + } else if (strcmp(key, "remote") == 0) { + if (parent->service != NULL) + ctx->error = "protocol { remote { .. } } not allowed (use remote { protocol { .. } } instead)"; + else if (config_parse_net(value, &filter->remote_net, + &filter->remote_bits, &error) < 0) + ctx->error = p_strdup(ctx->pool, error); + else if (parent->remote_bits > filter->remote_bits || + (parent->remote_bits > 0 && + !net_is_in_network(&filter->remote_net, + &parent->remote_net, + parent->remote_bits))) + ctx->error = "remote net1 { remote net2 { .. } } requires net2 to be inside net1"; + else + filter->remote_host = p_strdup(ctx->pool, value); + } else { + return FALSE; + } + + parser = config_filter_parser_find(ctx, filter); + if (parser != NULL) + ctx->cur_section->parsers = parser->parsers; + else + config_add_new_parser(ctx); + ctx->cur_section->is_filter = TRUE; + return TRUE; +} + +static int +config_filter_parser_check(struct config_parser_context *ctx, + const struct config_module_parser *p, + const char **error_r) +{ + const char *error = NULL; + bool ok; + + for (; p->root != NULL; p++) { + /* skip checking settings we don't care about */ + if (!config_module_want_parser(ctx->root_parsers, + ctx->modules, p->root)) + continue; + + settings_parse_var_skip(p->parser); + T_BEGIN { + ok = settings_parser_check(p->parser, ctx->pool, &error); + } T_END_PASS_STR_IF(!ok, &error); + if (!ok) { + /* be sure to assert-crash early if error is missing */ + i_assert(error != NULL); + *error_r = error; + return -1; + } + } + return 0; +} + +static const char * +get_str_setting(struct config_filter_parser *parser, const char *key, + const char *default_value) +{ + struct config_module_parser *module_parser; + const char *const *set_value; + enum setting_type set_type; + + module_parser = parser->parsers; + for (; module_parser->parser != NULL; module_parser++) { + set_value = settings_parse_get_value(module_parser->parser, + key, &set_type); + if (set_value != NULL && + settings_parse_is_changed(module_parser->parser, key)) { + i_assert(set_type == SET_STR || set_type == SET_ENUM); + return *set_value; + } + } + return default_value; +} + +static int +config_all_parsers_check(struct config_parser_context *ctx, + struct config_filter_context *new_filter, + const char **error_r) +{ + struct config_filter_parser *const *parsers; + struct config_module_parser *tmp_parsers; + struct master_service_settings_output output; + unsigned int i, count; + const char *ssl_set, *global_ssl_set; + pool_t tmp_pool; + bool ssl_warned = FALSE; + int ret = 0; + + if (ctx->cur_section->prev != NULL) { + *error_r = t_strdup_printf( + "Missing '}' (section started at %s:%u)", + ctx->cur_section->open_path, + ctx->cur_section->open_linenum); + return -1; + } + + tmp_pool = pool_alloconly_create(MEMPOOL_GROWING"config parsers check", 1024*64); + parsers = array_get(&ctx->all_parsers, &count); + i_assert(count > 0 && parsers[count-1] == NULL); + count--; + + global_ssl_set = get_str_setting(parsers[0], "ssl", ""); + for (i = 0; i < count && ret == 0; i++) { + if (config_filter_parsers_get(new_filter, tmp_pool, NULL, + &parsers[i]->filter, + &tmp_parsers, &output, + error_r) < 0) { + ret = -1; + break; + } + + ssl_set = get_str_setting(parsers[i], "ssl", global_ssl_set); + if (strcmp(ssl_set, "no") != 0 && + strcmp(global_ssl_set, "no") == 0 && !ssl_warned) { + i_warning("SSL is disabled because global ssl=no, " + "ignoring ssl=%s for subsection", ssl_set); + ssl_warned = TRUE; + } + + ret = config_filter_parser_check(ctx, tmp_parsers, error_r); + config_filter_parsers_free(tmp_parsers); + p_clear(tmp_pool); + } + pool_unref(&tmp_pool); + return ret; +} + +static int +str_append_file(string_t *str, const char *key, const char *path, + const char **error_r) +{ + unsigned char buf[1024]; + int fd; + ssize_t ret; + + *error_r = NULL; + + fd = open(path, O_RDONLY); + if (fd == -1) { + *error_r = t_strdup_printf("%s: Can't open file %s: %m", + key, path); + return -1; + } + while ((ret = read(fd, buf, sizeof(buf))) > 0) + str_append_data(str, buf, ret); + if (ret < 0) { + *error_r = t_strdup_printf("%s: read(%s) failed: %m", + key, path); + } + i_close_fd(&fd); + return ret < 0 ? -1 : 0; +} + +static int settings_add_include(struct config_parser_context *ctx, const char *path, + bool ignore_errors, const char **error_r) +{ + struct input_stack *tmp, *new_input; + int fd; + + for (tmp = ctx->cur_input; tmp != NULL; tmp = tmp->prev) { + if (strcmp(tmp->path, path) == 0) + break; + } + if (tmp != NULL) { + *error_r = t_strdup_printf("Recursive include file: %s", path); + return -1; + } + + if ((fd = open(path, O_RDONLY)) == -1) { + if (ignore_errors) + return 0; + + *error_r = t_strdup_printf("Couldn't open include file %s: %m", + path); + return -1; + } + + new_input = p_new(ctx->pool, struct input_stack, 1); + new_input->prev = ctx->cur_input; + new_input->path = p_strdup(ctx->pool, path); + new_input->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); + i_stream_set_return_partial_line(new_input->input, TRUE); + ctx->cur_input = new_input; + return 0; +} + +static int +settings_include(struct config_parser_context *ctx, const char *pattern, + bool ignore_errors) +{ + const char *error; +#ifdef HAVE_GLOB + glob_t globbers; + unsigned int i; + + switch (glob(pattern, GLOB_BRACE, NULL, &globbers)) { + case 0: + break; + case GLOB_NOSPACE: + ctx->error = "glob() failed: Not enough memory"; + return -1; + case GLOB_ABORTED: + ctx->error = "glob() failed: Read error"; + return -1; + case GLOB_NOMATCH: + if (ignore_errors) + return 0; + ctx->error = "No matches"; + return -1; + default: + ctx->error = "glob() failed: Unknown error"; + return -1; + } + + /* iterate through the different files matching the globbing */ + for (i = globbers.gl_pathc; i > 0; i--) { + if (settings_add_include(ctx, globbers.gl_pathv[i-1], + ignore_errors, &error) < 0) { + ctx->error = p_strdup(ctx->pool, error); + return -1; + } + } + globfree(&globbers); + return 0; +#else + if (settings_add_include(ctx, pattern, ignore_errors, &error) < 0) { + ctx->error = p_strdup(ctx->pool, error); + return -1; + } + return 0; +#endif +} + +static enum config_line_type +config_parse_line(struct config_parser_context *ctx, + char *line, string_t *full_line, + const char **key_r, const char **value_r) +{ + const char *key; + size_t len; + char *p; + + *key_r = NULL; + *value_r = NULL; + + /* @UNSAFE: line is modified */ + + /* skip whitespace */ + while (IS_WHITE(*line)) + line++; + + /* ignore comments or empty lines */ + if (*line == '#' || *line == '\0') + return CONFIG_LINE_TYPE_SKIP; + + /* strip away comments. pretty kludgy way really.. */ + for (p = line; *p != '\0'; p++) { + if (*p == '\'' || *p == '"') { + char quote = *p; + for (p++; *p != quote && *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') + p++; + } + if (*p == '\0') + break; + } else if (*p == '#') { + if (!IS_WHITE(p[-1])) { + i_warning("Configuration file %s line %u: " + "Ambiguous '#' character in line, treating it as comment. " + "Add a space before it to remove this warning.", + ctx->cur_input->path, + ctx->cur_input->linenum); + } + *p = '\0'; + break; + } + } + + /* remove whitespace from end of line */ + len = strlen(line); + while (len >= 1) { + if(!IS_WHITE(line[len-1])) + break; + len--; + } + line[len] = '\0'; + + if (len >= 1 && line[len-1] == '\\') { + /* continues in next line */ + len--; + while (len >= 1) { + if(!IS_WHITE(line[len-1])) + break; + len--; + } + if(len >= 1) { + str_append_data(full_line, line, len); + str_append_c(full_line, ' '); + } + return CONFIG_LINE_TYPE_CONTINUE; + } + if (str_len(full_line) > 0) { + str_append(full_line, line); + line = str_c_modifiable(full_line); + } + + /* a) key = value + b) section_type [section_name] { + c) } */ + key = line; + while (!IS_WHITE(*line) && *line != '\0' && *line != '=') + line++; + if (IS_WHITE(*line)) { + *line++ = '\0'; + while (IS_WHITE(*line)) line++; + } + *key_r = key; + *value_r = line; + + if (strcmp(key, "!include") == 0) + return CONFIG_LINE_TYPE_INCLUDE; + if (strcmp(key, "!include_try") == 0) + return CONFIG_LINE_TYPE_INCLUDE_TRY; + + if (*line == '=') { + /* a) */ + *line++ = '\0'; + while (IS_WHITE(*line)) line++; + + if (*line == '<') { + while (IS_WHITE(line[1])) line++; + *value_r = line + 1; + return CONFIG_LINE_TYPE_KEYFILE; + } + if (*line != '\'' && *line != '"' && strchr(line, '$') != NULL) { + *value_r = line; + return CONFIG_LINE_TYPE_KEYVARIABLE; + } + + len = strlen(line); + if (len > 0 && + ((*line == '"' && line[len-1] == '"') || + (*line == '\'' && line[len-1] == '\''))) { + line[len-1] = '\0'; + line = str_unescape(line+1); + } + *value_r = line; + return CONFIG_LINE_TYPE_KEYVALUE; + } + + if (strcmp(key, "}") == 0 && *line == '\0') + return CONFIG_LINE_TYPE_SECTION_END; + + /* b) + errors */ + line[-1] = '\0'; + + if (*line == '{') + *value_r = ""; + else { + /* get section name */ + if (*line != '"') { + *value_r = line; + while (!IS_WHITE(*line) && *line != '\0') + line++; + if (*line != '\0') { + *line++ = '\0'; + while (IS_WHITE(*line)) + line++; + } + } else { + char *value = ++line; + while (*line != '"' && *line != '\0') + line++; + if (*line == '"') { + *line++ = '\0'; + while (IS_WHITE(*line)) + line++; + *value_r = str_unescape(value); + } + } + if (*line != '{') { + *value_r = "Expecting '{'"; + return CONFIG_LINE_TYPE_ERROR; + } + } + if (line[1] != '\0') { + *value_r = "Garbage after '{'"; + return CONFIG_LINE_TYPE_ERROR; + } + return CONFIG_LINE_TYPE_SECTION_BEGIN; +} + +static int config_parse_finish(struct config_parser_context *ctx, const char **error_r) +{ + struct config_filter_context *new_filter; + const char *error; + int ret = 0; + + if (hook_config_parser_end != NULL) + ret = hook_config_parser_end(ctx, error_r); + + new_filter = config_filter_init(ctx->pool); + array_append_zero(&ctx->all_parsers); + config_filter_add_all(new_filter, array_front(&ctx->all_parsers)); + + if (ret < 0) + ; + else if (ctx->hide_errors) + ret = 0; + else if ((ret = config_all_parsers_check(ctx, new_filter, &error)) < 0) { + *error_r = t_strdup_printf("Error in configuration file %s: %s", + ctx->path, error); + } + + if (config_filter != NULL) + config_filter_deinit(&config_filter); + config_module_parsers = ctx->root_parsers; + config_filter = new_filter; + return ret; +} + +static const void * +config_get_value(struct config_section_stack *section, const char *key, + bool expand_parent, enum setting_type *type_r) +{ + struct config_module_parser *l; + const void *value; + + for (l = section->parsers; l->root != NULL; l++) { + value = settings_parse_get_value(l->parser, key, type_r); + if (value != NULL) { + if (!expand_parent || section->prev == NULL || + settings_parse_is_changed(l->parser, key)) + return value; + + /* not changed by this parser. maybe parent has. */ + return config_get_value(section->prev, + key, TRUE, type_r); + } + } + return NULL; +} + +static bool +config_require_key(struct config_parser_context *ctx, const char *key) +{ + struct config_module_parser *l; + + if (ctx->modules == NULL) + return TRUE; + + for (l = ctx->cur_section->parsers; l->root != NULL; l++) { + if (config_module_want_parser(ctx->root_parsers, + ctx->modules, l->root) && + settings_parse_is_valid_key(l->parser, key)) + return TRUE; + } + return FALSE; +} + +static int config_write_keyvariable(struct config_parser_context *ctx, + const char *key, const char *value, + string_t *str) +{ + const char *var_end, *p_start = value; + bool dump; + while (value != NULL) { + const char *var_name; + bool expand_parent; + var_end = strchr(value, ' '); + + /* expand_parent=TRUE for "key = $key stuff". + we'll always expand it so that doveconf -n can give + usable output */ + if (var_end == NULL) + var_name = value; + else + var_name = t_strdup_until(value, var_end); + expand_parent = strcmp(key, var_name + + (*var_name == '$' ? 1 : 0)) == 0; + + if (!str_begins(var_name, "$") || + (value > p_start && !IS_WHITE(value[-1]))) { + str_append(str, var_name); + } else if (!ctx->expand_values && !expand_parent) { + str_append(str, var_name); + } else if (str_begins(var_name, "$ENV:")) { + /* use environment variable */ + const char *envval = getenv(var_name+5); + if (envval != NULL) + str_append(str, envval); + } else { + const char *var_value; + enum setting_type var_type; + + i_assert(var_name[0] == '$'); + var_name++; + + var_value = config_get_value(ctx->cur_section, var_name, + expand_parent, &var_type); + if (var_value == NULL) { + ctx->error = p_strconcat(ctx->pool, + "Unknown variable: $", + var_name, NULL); + return -1; + } + if (!config_export_type(str, var_value, NULL, + var_type, TRUE, &dump)) { + ctx->error = p_strconcat(ctx->pool, + "Invalid variable: $", + var_name, NULL); + return -1; + } + } + + if (var_end == NULL) + break; + + str_append_c(str, ' '); + + /* find next token */ + while (*var_end != '\0' && IS_WHITE(*var_end)) var_end++; + value = var_end; + while (*var_end != '\0' && !IS_WHITE(*var_end)) var_end++; + } + + return 0; +} + +static int config_write_value(struct config_parser_context *ctx, + enum config_line_type type, + const char *key, const char *value) +{ + string_t *str = ctx->str; + const char *error, *path, *full_key; + + switch (type) { + case CONFIG_LINE_TYPE_KEYVALUE: + str_append(str, value); + break; + case CONFIG_LINE_TYPE_KEYFILE: + full_key = t_strndup(str_data(ctx->str), str_len(str)-1); + if (!ctx->expand_values) { + str_append_c(str, '<'); + str_append(str, value); + } else { + if (!config_require_key(ctx, full_key)) { + /* don't even try to open the file */ + } else { + path = fix_relative_path(value, ctx->cur_input); + if (str_append_file(str, full_key, path, &error) < 0) { + /* file reading failed */ + ctx->error = p_strdup(ctx->pool, error); + return -1; + } + } + } + break; + case CONFIG_LINE_TYPE_KEYVARIABLE: + if (config_write_keyvariable(ctx, key, value, str) < 0) + return -1; + break; + default: + i_unreached(); + } + return 0; +} + +static void +config_parser_check_warnings(struct config_parser_context *ctx, const char *key) +{ + const char *path, *first_pos; + + first_pos = hash_table_lookup(ctx->seen_settings, str_c(ctx->str)); + if (ctx->cur_section->prev == NULL) { + /* changing a root setting. if we've already seen it inside + filters, log a warning. */ + if (first_pos == NULL) + return; + i_warning("%s line %u: Global setting %s won't change the setting inside an earlier filter at %s " + "(if this is intentional, avoid this warning by moving the global setting before %s)", + ctx->cur_input->path, ctx->cur_input->linenum, + key, first_pos, first_pos); + return; + } + if (first_pos != NULL) + return; + first_pos = p_strdup_printf(ctx->pool, "%s line %u", + ctx->cur_input->path, ctx->cur_input->linenum); + path = p_strdup(ctx->pool, str_c(ctx->str)); + hash_table_insert(ctx->seen_settings, path, first_pos); +} + +void config_parser_apply_line(struct config_parser_context *ctx, + enum config_line_type type, + const char *key, const char *value) +{ + const char *section_name; + + str_truncate(ctx->str, ctx->pathlen); + switch (type) { + case CONFIG_LINE_TYPE_SKIP: + break; + case CONFIG_LINE_TYPE_CONTINUE: + i_unreached(); + case CONFIG_LINE_TYPE_ERROR: + ctx->error = p_strdup(ctx->pool, value); + break; + case CONFIG_LINE_TYPE_KEYVALUE: + case CONFIG_LINE_TYPE_KEYFILE: + case CONFIG_LINE_TYPE_KEYVARIABLE: + str_append(ctx->str, key); + config_parser_check_warnings(ctx, key); + str_append_c(ctx->str, '='); + + if (config_write_value(ctx, type, key, value) < 0) + break; + (void)config_apply_line(ctx, key, str_c(ctx->str), NULL); + break; + case CONFIG_LINE_TYPE_SECTION_BEGIN: + ctx->cur_section = config_add_new_section(ctx); + ctx->cur_section->pathlen = ctx->pathlen; + ctx->cur_section->key = p_strdup(ctx->pool, key); + + if (config_filter_add_new_filter(ctx, key, value)) { + /* new filter */ + break; + } + + /* new config section */ + if (*value == '\0') { + /* no section name, use a counter */ + section_name = dec2str(ctx->section_counter++); + } else { + section_name = settings_section_escape(value); + } + str_append(ctx->str, key); + ctx->pathlen = str_len(ctx->str); + + str_append_c(ctx->str, '='); + str_append(ctx->str, section_name); + + if (config_apply_line(ctx, key, str_c(ctx->str), value) < 0) + break; + + str_truncate(ctx->str, ctx->pathlen); + str_append_c(ctx->str, SETTINGS_SEPARATOR); + str_append(ctx->str, section_name); + str_append_c(ctx->str, SETTINGS_SEPARATOR); + ctx->pathlen = str_len(ctx->str); + break; + case CONFIG_LINE_TYPE_SECTION_END: + if (ctx->cur_section->prev == NULL) + ctx->error = "Unexpected '}'"; + else { + ctx->pathlen = ctx->cur_section->pathlen; + ctx->cur_section = ctx->cur_section->prev; + } + break; + case CONFIG_LINE_TYPE_INCLUDE: + case CONFIG_LINE_TYPE_INCLUDE_TRY: + (void)settings_include(ctx, fix_relative_path(value, ctx->cur_input), + type == CONFIG_LINE_TYPE_INCLUDE_TRY); + break; + } +} + +int config_parse_file(const char *path, bool expand_values, + const char *const *modules, const char **error_r) +{ + struct input_stack root; + struct config_parser_context ctx; + unsigned int i, count; + const char *key, *value; + string_t *full_line; + enum config_line_type type; + char *line; + int fd, ret = 0; + bool handled; + + if (path == NULL) { + path = "<defaults>"; + fd = -1; + } else { + fd = open(path, O_RDONLY); + if (fd < 0) { + *error_r = t_strdup_printf("open(%s) failed: %m", path); + return 0; + } + } + + i_zero(&ctx); + ctx.pool = pool_alloconly_create(MEMPOOL_GROWING"config file parser", 1024*256); + ctx.path = path; + ctx.hide_errors = fd == -1; + + for (count = 0; all_roots[count] != NULL; count++) ; + ctx.root_parsers = + p_new(ctx.pool, struct config_module_parser, count+1); + for (i = 0; i < count; i++) { + ctx.root_parsers[i].root = all_roots[i]; + ctx.root_parsers[i].parser = + settings_parser_init(ctx.pool, all_roots[i], + settings_parser_flags); + } + + i_zero(&root); + root.path = path; + ctx.cur_input = &root; + ctx.expand_values = expand_values; + ctx.modules = modules; + hash_table_create(&ctx.seen_settings, ctx.pool, 0, str_hash, strcmp); + + p_array_init(&ctx.all_parsers, ctx.pool, 128); + ctx.cur_section = p_new(ctx.pool, struct config_section_stack, 1); + config_add_new_parser(&ctx); + + ctx.str = str_new(ctx.pool, 256); + full_line = str_new(default_pool, 512); + ctx.cur_input->input = fd != -1 ? + i_stream_create_fd_autoclose(&fd, SIZE_MAX) : + i_stream_create_from_data("", 0); + i_stream_set_return_partial_line(ctx.cur_input->input, TRUE); + old_settings_init(&ctx); + if (hook_config_parser_begin != NULL) T_BEGIN { + hook_config_parser_begin(&ctx); + } T_END; + +prevfile: + while ((line = i_stream_read_next_line(ctx.cur_input->input)) != NULL) { + ctx.cur_input->linenum++; + type = config_parse_line(&ctx, line, full_line, + &key, &value); + str_truncate(ctx.str, ctx.pathlen); + if (type == CONFIG_LINE_TYPE_CONTINUE) + continue; + + T_BEGIN { + handled = old_settings_handle(&ctx, type, key, value); + if (!handled) + config_parser_apply_line(&ctx, type, key, value); + } T_END; + + if (ctx.error != NULL) { + *error_r = t_strdup_printf( + "Error in configuration file %s line %d: %s", + ctx.cur_input->path, ctx.cur_input->linenum, + ctx.error); + ret = -2; + break; + } + str_truncate(full_line, 0); + } + + i_stream_destroy(&ctx.cur_input->input); + ctx.cur_input = ctx.cur_input->prev; + if (line == NULL && ctx.cur_input != NULL) + goto prevfile; + + hash_table_destroy(&ctx.seen_settings); + str_free(&full_line); + if (ret == 0) + ret = config_parse_finish(&ctx, error_r); + return ret < 0 ? ret : 1; +} + +void config_parse_load_modules(void) +{ + struct module_dir_load_settings mod_set; + struct module *m; + const struct setting_parser_info **roots; + ARRAY_TYPE(setting_parser_info_p) new_roots; + ARRAY_TYPE(service_settings) new_services; + struct service_settings *const *services, *service_set; + unsigned int i, count; + + i_zero(&mod_set); + mod_set.abi_version = DOVECOT_ABI_VERSION; + modules = module_dir_load(CONFIG_MODULE_DIR, NULL, &mod_set); + module_dir_init(modules); + + i_array_init(&new_roots, 64); + i_array_init(&new_services, 64); + for (m = modules; m != NULL; m = m->next) { + roots = module_get_symbol_quiet(m, + t_strdup_printf("%s_set_roots", m->name)); + if (roots != NULL) { + for (i = 0; roots[i] != NULL; i++) + array_push_back(&new_roots, &roots[i]); + } + + services = module_get_symbol_quiet(m, + t_strdup_printf("%s_service_settings_array", m->name)); + if (services != NULL) { + for (count = 0; services[count] != NULL; count++) ; + array_append(&new_services, services, count); + } else { + service_set = module_get_symbol_quiet(m, + t_strdup_printf("%s_service_settings", m->name)); + if (service_set != NULL) + array_push_back(&new_services, &service_set); + } + } + if (array_count(&new_roots) > 0) { + /* modules added new settings. add the defaults and start + using the new list. */ + for (i = 0; all_roots[i] != NULL; i++) + array_push_back(&new_roots, &all_roots[i]); + array_append_zero(&new_roots); + all_roots = array_front(&new_roots); + roots_free_at_deinit = new_roots; + } else { + array_free(&new_roots); + } + if (array_count(&new_services) > 0) { + /* module added new services. update the defaults. */ + services = array_get(default_services, &count); + for (i = 0; i < count; i++) + array_push_back(&new_services, &services[i]); + *default_services = new_services; + services_free_at_deinit = new_services; + } else { + array_free(&new_services); + } +} + +static bool parsers_are_connected(const struct setting_parser_info *root, + const struct setting_parser_info *info) +{ + const struct setting_parser_info *p; + const struct setting_parser_info *const *dep; + + /* we're trying to find info or its parents from root's dependencies. */ + + for (p = info; p != NULL; p = p->parent) { + if (p == root) + return TRUE; + } + + if (root->dependencies != NULL) { + for (dep = root->dependencies; *dep != NULL; dep++) { + if (parsers_are_connected(*dep, info)) + return TRUE; + } + } + return FALSE; +} + +bool config_module_want_parser(struct config_module_parser *parsers, + const char *const *modules, + const struct setting_parser_info *root) +{ + struct config_module_parser *l; + + if (modules == NULL) + return TRUE; + if (root == &master_service_setting_parser_info) { + /* everyone wants master service settings */ + return TRUE; + } + + for (l = parsers; l->root != NULL; l++) { + if (!str_array_find(modules, l->root->module_name)) + continue; + + /* see if we can find a way to get from the original parser + to this parser */ + if (parsers_are_connected(l->root, root)) + return TRUE; + } + return FALSE; +} + +void config_parser_deinit(void) +{ + if (array_is_created(&services_free_at_deinit)) + array_free(&services_free_at_deinit); + if (array_is_created(&roots_free_at_deinit)) + array_free(&roots_free_at_deinit); +} diff --git a/src/config/config-parser.h b/src/config/config-parser.h new file mode 100644 index 0000000..e0a0a5b --- /dev/null +++ b/src/config/config-parser.h @@ -0,0 +1,33 @@ +#ifndef CONFIG_PARSER_H +#define CONFIG_PARSER_H + +#define CONFIG_MODULE_DIR MODULEDIR"/settings" + +#define IS_WHITE(c) ((c) == ' ' || (c) == '\t') + +struct config_module_parser { + const struct setting_parser_info *root; + struct setting_parser_context *parser; + void *settings; +}; +ARRAY_DEFINE_TYPE(config_module_parsers, struct config_module_parser *); + +extern struct config_module_parser *config_module_parsers; +extern struct config_filter_context *config_filter; +extern struct module *modules; + +int config_parse_net(const char *value, struct ip_addr *ip_r, + unsigned int *bits_r, const char **error_r); +int config_parse_file(const char *path, bool expand_values, + const char *const *modules, const char **error_r) + ATTR_NULL(3); + +void config_parse_load_modules(void); + +bool config_module_want_parser(struct config_module_parser *parsers, + const char *const *modules, + const struct setting_parser_info *root) + ATTR_NULL(2); +void config_parser_deinit(void); + +#endif diff --git a/src/config/config-request.c b/src/config/config-request.c new file mode 100644 index 0000000..7428367 --- /dev/null +++ b/src/config/config-request.c @@ -0,0 +1,524 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hash.h" +#include "ostream.h" +#include "settings-parser.h" +#include "master-service-settings.h" +#include "all-settings.h" +#include "config-parser.h" +#include "config-request.h" +#include "old-set-parser.h" + +struct config_export_context { + pool_t pool; + string_t *value; + string_t *prefix; + HASH_TABLE(char *, char *) keys; + enum config_dump_scope scope; + + config_request_callback_t *callback; + void *context; + + const char *const *modules; + const char *const *exclude_settings; + enum config_dump_flags flags; + const struct config_module_parser *parsers; + struct config_module_parser *dup_parsers; + struct master_service_settings_output output; + + bool failed; +}; + +static void config_export_size(string_t *str, uoff_t size) +{ + static const char suffixes[] = { 'B', 'k', 'M', 'G', 'T' }; + char suffix = suffixes[0]; + unsigned int i; + + if (size == 0) { + str_append_c(str, '0'); + return; + } + for (i = 1; i < N_ELEMENTS(suffixes) && (size % 1024) == 0; i++) { + suffix = suffixes[i]; + size /= 1024; + } + str_printfa(str, "%"PRIuUOFF_T" %c", size, suffix); +} + +static void config_export_time(string_t *str, unsigned int stamp) +{ + const char *suffix = "secs"; + + if (stamp == 0) { + str_append_c(str, '0'); + return; + } + + if (stamp % 60 == 0) { + stamp /= 60; + suffix = "mins"; + if (stamp % 60 == 0) { + stamp /= 60; + suffix = "hours"; + if (stamp % 24 == 0) { + stamp /= 24; + suffix = "days"; + if (stamp % 7 == 0) { + stamp /= 7; + suffix = "weeks"; + } + } + } + } + + str_printfa(str, "%u %s", stamp, suffix); +} + +static void config_export_time_msecs(string_t *str, unsigned int stamp_msecs) +{ + if ((stamp_msecs % 1000) == 0) + config_export_time(str, stamp_msecs/1000); + else + str_printfa(str, "%u ms", stamp_msecs); +} + +bool config_export_type(string_t *str, const void *value, + const void *default_value, + enum setting_type type, bool dump_default, + bool *dump_r) +{ + switch (type) { + case SET_BOOL: { + const bool *val = value, *dval = default_value; + + if (dump_default || dval == NULL || *val != *dval) + str_append(str, *val ? "yes" : "no"); + break; + } + case SET_SIZE: { + const uoff_t *val = value, *dval = default_value; + + if (dump_default || dval == NULL || *val != *dval) + config_export_size(str, *val); + break; + } + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: { + const unsigned int *val = value, *dval = default_value; + + if (dump_default || dval == NULL || *val != *dval) { + switch (type) { + case SET_UINT_OCT: + str_printfa(str, "0%o", *val); + break; + case SET_TIME: + config_export_time(str, *val); + break; + case SET_TIME_MSECS: + config_export_time_msecs(str, *val); + break; + default: + str_printfa(str, "%u", *val); + break; + } + } + break; + } + case SET_IN_PORT: { + const in_port_t *val = value, *dval = default_value; + + if (dump_default || dval == NULL || *val != *dval) + str_printfa(str, "%u", *val); + break; + } + case SET_STR_VARS: { + const char *const *val = value, *sval; + const char *const *_dval = default_value; + const char *dval = _dval == NULL ? NULL : *_dval; + + i_assert(*val == NULL || + **val == SETTING_STRVAR_UNEXPANDED[0]); + + sval = *val == NULL ? NULL : (*val + 1); + if ((dump_default || null_strcmp(sval, dval) != 0) && + sval != NULL) { + str_append(str, sval); + *dump_r = TRUE; + } + break; + } + case SET_STR: { + const char *const *val = value; + const char *const *_dval = default_value; + const char *dval = _dval == NULL ? NULL : *_dval; + + if ((dump_default || null_strcmp(*val, dval) != 0) && + *val != NULL) { + str_append(str, *val); + *dump_r = TRUE; + } + break; + } + case SET_ENUM: { + const char *const *val = value; + size_t len = strlen(*val); + + if (dump_default) + str_append(str, *val); + else { + const char *const *_dval = default_value; + const char *dval = _dval == NULL ? NULL : *_dval; + + i_assert(dval != NULL); + if (strncmp(*val, dval, len) != 0 || + ((*val)[len] != ':' && (*val)[len] != '\0')) + str_append(str, *val); + } + break; + } + default: + return FALSE; + } + return TRUE; +} + +static void +setting_export_section_name(string_t *str, const struct setting_define *def, + const void *set, unsigned int idx) +{ + const char *const *name; + size_t name_offset; + + if (def->type != SET_DEFLIST_UNIQUE) { + /* not unique, use the index */ + str_printfa(str, "%u", idx); + return; + } + name_offset = def->list_info->type_offset; + i_assert(name_offset != SIZE_MAX); + + name = CONST_PTR_OFFSET(set, name_offset); + if (*name == NULL || **name == '\0') { + /* no name, this one isn't unique. use the index. */ + str_printfa(str, "%u", idx); + } else T_BEGIN { + str_append(str, settings_section_escape(*name)); + } T_END; +} + +static void +settings_export(struct config_export_context *ctx, + const struct setting_parser_info *info, + bool parent_unique_deflist, + const void *set, const void *change_set) +{ + const struct setting_define *def; + const void *value, *default_value, *change_value; + void *const *children, *const *change_children = NULL; + unsigned int i, count, count2; + size_t prefix_len; + const char *str; + char *key; + bool dump, dump_default = FALSE; + + for (def = info->defines; def->key != NULL; def++) { + if (ctx->exclude_settings != NULL && + str_array_find(ctx->exclude_settings, def->key)) + continue; + + value = CONST_PTR_OFFSET(set, def->offset); + default_value = info->defaults == NULL ? NULL : + CONST_PTR_OFFSET(info->defaults, def->offset); + change_value = CONST_PTR_OFFSET(change_set, def->offset); + switch (ctx->scope) { + case CONFIG_DUMP_SCOPE_ALL_WITH_HIDDEN: + dump_default = TRUE; + break; + case CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN: + if ((def->flags & SET_FLAG_HIDDEN) == 0) { + /* not hidden - dump it */ + dump_default = TRUE; + break; + } + /* hidden - dump default only if it's explicitly set */ + /* fall through */ + case CONFIG_DUMP_SCOPE_SET: + dump_default = *((const char *)change_value) != 0; + break; + case CONFIG_DUMP_SCOPE_CHANGED: + dump_default = FALSE; + break; + } + if (!parent_unique_deflist || + (ctx->flags & CONFIG_DUMP_FLAG_HIDE_LIST_DEFAULTS) == 0) { + /* .. */ + } else if (*((const char *)change_value) == 0 && + def->offset != info->type_offset) { + /* this is mainly for service {} blocks. if value + hasn't changed, it's the default. even if + info->defaults has a different value. */ + default_value = value; + } else { + /* value is set explicitly, but we don't know the + default here. assume it's not the default. */ + dump_default = TRUE; + } + + dump = FALSE; + count = 0; children = NULL; + str_truncate(ctx->value, 0); + switch (def->type) { + case SET_BOOL: + case SET_SIZE: + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: + case SET_IN_PORT: + case SET_STR_VARS: + case SET_STR: + case SET_ENUM: + if (!config_export_type(ctx->value, value, + default_value, def->type, + dump_default, &dump)) + i_unreached(); + break; + case SET_DEFLIST: + case SET_DEFLIST_UNIQUE: { + const ARRAY_TYPE(void_array) *val = value; + const ARRAY_TYPE(void_array) *change_val = change_value; + + if (!array_is_created(val)) + break; + + children = array_get(val, &count); + for (i = 0; i < count; i++) { + if (i > 0) + str_append_c(ctx->value, ' '); + setting_export_section_name(ctx->value, def, children[i], i); + } + change_children = array_get(change_val, &count2); + i_assert(count == count2); + break; + } + case SET_STRLIST: { + const ARRAY_TYPE(const_string) *val = value; + const char *const *strings; + + if (!array_is_created(val)) + break; + + key = p_strconcat(ctx->pool, str_c(ctx->prefix), + def->key, NULL); + + if (hash_table_lookup(ctx->keys, key) != NULL) { + /* already added all of these */ + break; + } + hash_table_insert(ctx->keys, key, key); + /* for doveconf -n to see this KEY_LIST */ + ctx->callback(key, "", CONFIG_KEY_LIST, ctx->context); + + strings = array_get(val, &count); + i_assert(count % 2 == 0); + for (i = 0; i < count; i += 2) { + str = p_strdup_printf(ctx->pool, "%s%s%c%s", + str_c(ctx->prefix), + def->key, + SETTINGS_SEPARATOR, + strings[i]); + ctx->callback(str, strings[i+1], + CONFIG_KEY_NORMAL, ctx->context); + } + count = 0; + break; + } + case SET_ALIAS: + break; + } + if (str_len(ctx->value) > 0 || dump) { + key = p_strconcat(ctx->pool, str_c(ctx->prefix), + def->key, NULL); + if (hash_table_lookup(ctx->keys, key) == NULL) { + enum config_key_type type; + + if (def->offset == info->type_offset && + parent_unique_deflist) + type = CONFIG_KEY_UNIQUE_KEY; + else if (SETTING_TYPE_IS_DEFLIST(def->type)) + type = CONFIG_KEY_LIST; + else + type = CONFIG_KEY_NORMAL; + ctx->callback(key, str_c(ctx->value), type, + ctx->context); + hash_table_insert(ctx->keys, key, key); + } + } + + i_assert(count == 0 || children != NULL); + prefix_len = str_len(ctx->prefix); + for (i = 0; i < count; i++) { + str_append(ctx->prefix, def->key); + str_append_c(ctx->prefix, SETTINGS_SEPARATOR); + setting_export_section_name(ctx->prefix, def, children[i], i); + str_append_c(ctx->prefix, SETTINGS_SEPARATOR); + settings_export(ctx, def->list_info, + def->type == SET_DEFLIST_UNIQUE, + children[i], change_children[i]); + + str_truncate(ctx->prefix, prefix_len); + } + } +} + +struct config_export_context * +config_export_init(const char *const *modules, + const char *const *exclude_settings, + enum config_dump_scope scope, + enum config_dump_flags flags, + config_request_callback_t *callback, void *context) +{ + struct config_export_context *ctx; + pool_t pool; + + pool = pool_alloconly_create(MEMPOOL_GROWING"config export", 1024*64); + ctx = p_new(pool, struct config_export_context, 1); + ctx->pool = pool; + + ctx->modules = modules == NULL ? NULL : p_strarray_dup(pool, modules); + ctx->exclude_settings = exclude_settings == NULL ? NULL : + p_strarray_dup(pool, exclude_settings); + ctx->flags = flags; + ctx->callback = callback; + ctx->context = context; + ctx->scope = scope; + ctx->value = str_new(pool, 256); + ctx->prefix = str_new(pool, 64); + hash_table_create(&ctx->keys, ctx->pool, 0, str_hash, strcmp); + return ctx; +} + +void config_export_by_filter(struct config_export_context *ctx, + const struct config_filter *filter) +{ + const char *error; + + if (config_filter_parsers_get(config_filter, ctx->pool, + ctx->modules, filter, + &ctx->dup_parsers, &ctx->output, + &error) < 0) { + i_error("%s", error); + ctx->failed = TRUE; + } + ctx->parsers = ctx->dup_parsers; +} + +void config_export_parsers(struct config_export_context *ctx, + const struct config_module_parser *parsers) +{ + ctx->parsers = parsers; +} + +void config_export_get_output(struct config_export_context *ctx, + struct master_service_settings_output *output_r) +{ + *output_r = ctx->output; +} + +const char * +config_export_get_import_environment(struct config_export_context *ctx) +{ + enum setting_type stype; + unsigned int i; + + for (i = 0; ctx->parsers[i].root != NULL; i++) { + if (ctx->parsers[i].root == &master_service_setting_parser_info) { + const char *const *value = + settings_parse_get_value(ctx->parsers[i].parser, + "import_environment", &stype); + i_assert(value != NULL); + return *value; + } + } + i_unreached(); +} + +static void config_export_free(struct config_export_context *ctx) +{ + if (ctx->dup_parsers != NULL) + config_filter_parsers_free(ctx->dup_parsers); + hash_table_destroy(&ctx->keys); + pool_unref(&ctx->pool); +} + +int config_export_finish(struct config_export_context **_ctx) +{ + struct config_export_context *ctx = *_ctx; + const struct config_module_parser *parser; + const char *error; + unsigned int i; + int ret = 0; + + *_ctx = NULL; + + if (ctx->failed) { + config_export_free(ctx); + return -1; + } + + for (i = 0; ctx->parsers[i].root != NULL; i++) { + parser = &ctx->parsers[i]; + if (!config_module_want_parser(config_module_parsers, + ctx->modules, parser->root)) + continue; + + T_BEGIN { + enum setting_type stype; + const char *const *value = settings_parse_get_value(parser->parser, "ssl", &stype); + + if ((ctx->flags & CONFIG_DUMP_FLAG_IN_SECTION) == 0 && + value != NULL && strcmp(*value, "no") != 0 && + settings_parse_is_valid_key(parser->parser, "ssl_dh")) { + value = settings_parse_get_value(parser->parser, + "ssl_dh", &stype); + + if (value == NULL || **value == '\0') { + const char *newval; + if (old_settings_ssl_dh_load(&newval, &error)) { + if (newval != NULL) + settings_parse_line(parser->parser, t_strdup_printf("%s=%s", "ssl_dh", newval)); + } else { + i_error("%s", error); + ret = -1; + } + } + } + settings_export(ctx, parser->root, FALSE, + settings_parser_get(parser->parser), + settings_parser_get_changes(parser->parser)); + } T_END; + + if ((ctx->flags & CONFIG_DUMP_FLAG_CHECK_SETTINGS) != 0) { + settings_parse_var_skip(parser->parser); + if (!settings_parser_check(parser->parser, ctx->pool, + &error)) { + if ((ctx->flags & CONFIG_DUMP_FLAG_CALLBACK_ERRORS) != 0) { + ctx->callback(NULL, error, CONFIG_KEY_ERROR, + ctx->context); + } else { + i_error("%s", error); + ret = -1; + break; + } + } + } + } + config_export_free(ctx); + return ret; +} diff --git a/src/config/config-request.h b/src/config/config-request.h new file mode 100644 index 0000000..c91e836 --- /dev/null +++ b/src/config/config-request.h @@ -0,0 +1,61 @@ +#ifndef CONFIG_REQUEST_H +#define CONFIG_REQUEST_H + +#include "settings-parser.h" +#include "config-filter.h" + +struct master_service_settings_output; + +enum config_dump_scope { + /* Dump all settings, including hidden settings */ + CONFIG_DUMP_SCOPE_ALL_WITH_HIDDEN, + /* Dump all non-hidden settings */ + CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN, + /* Dump all that have explicitly been set */ + CONFIG_DUMP_SCOPE_SET, + /* Dump only settings that differ from defaults */ + CONFIG_DUMP_SCOPE_CHANGED +}; + +enum config_dump_flags { + CONFIG_DUMP_FLAG_CHECK_SETTINGS = 0x01, + CONFIG_DUMP_FLAG_HIDE_LIST_DEFAULTS = 0x02, + /* Errors are reported using callback and they don't stop handling */ + CONFIG_DUMP_FLAG_CALLBACK_ERRORS = 0x04, + /* Set if dumping a section and not top level config */ + CONFIG_DUMP_FLAG_IN_SECTION = 0x08, +}; + +enum config_key_type { + CONFIG_KEY_NORMAL, + CONFIG_KEY_LIST, + CONFIG_KEY_UNIQUE_KEY, + /* error message is in value */ + CONFIG_KEY_ERROR +}; + +typedef void config_request_callback_t(const char *key, const char *value, + enum config_key_type type, void *context); + +bool config_export_type(string_t *str, const void *value, + const void *default_value, + enum setting_type type, bool dump_default, + bool *dump_r) ATTR_NULL(3); +struct config_export_context * +config_export_init(const char *const *modules, + const char *const *exclude_settings, + enum config_dump_scope scope, + enum config_dump_flags flags, + config_request_callback_t *callback, void *context) + ATTR_NULL(1, 5); +void config_export_by_filter(struct config_export_context *ctx, + const struct config_filter *filter); +void config_export_parsers(struct config_export_context *ctx, + const struct config_module_parser *parsers); +void config_export_get_output(struct config_export_context *ctx, + struct master_service_settings_output *output_r); +const char * +config_export_get_import_environment(struct config_export_context *ctx); +int config_export_finish(struct config_export_context **ctx); + +#endif diff --git a/src/config/config-settings.c b/src/config/config-settings.c new file mode 100644 index 0000000..d19fe4e --- /dev/null +++ b/src/config/config-settings.c @@ -0,0 +1,46 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "settings-parser.h" +#include "service-settings.h" + +#include <stddef.h> + +/* <settings checks> */ +static struct file_listener_settings config_unix_listeners_array[] = { + { "config", 0600, "", "" } +}; +static struct file_listener_settings *config_unix_listeners[] = { + &config_unix_listeners_array[0] +}; +static buffer_t config_unix_listeners_buf = { + { { config_unix_listeners, sizeof(config_unix_listeners) } } +}; +/* </settings checks> */ + +struct service_settings config_service_settings = { + .name = "config", + .protocol = "", + .type = "config", + .executable = "config", + .user = "", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 0, + .client_limit = 0, + .service_count = 0, + .idle_kill = UINT_MAX, + .vsz_limit = UOFF_T_MAX, + + .unix_listeners = { { &config_unix_listeners_buf, + sizeof(config_unix_listeners[0]) } }, + .fifo_listeners = ARRAY_INIT, + .inet_listeners = ARRAY_INIT +}; diff --git a/src/config/doveconf.c b/src/config/doveconf.c new file mode 100644 index 0000000..79ea9e8 --- /dev/null +++ b/src/config/doveconf.c @@ -0,0 +1,1072 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "path-util.h" +#include "module-dir.h" +#include "env-util.h" +#include "guid.h" +#include "hash.h" +#include "hostpid.h" +#include "ostream.h" +#include "str.h" +#include "strescape.h" +#include "settings-parser.h" +#include "master-interface.h" +#include "master-service.h" +#include "all-settings.h" +#include "sysinfo-get.h" +#include "old-set-parser.h" +#include "config-connection.h" +#include "config-parser.h" +#include "config-request.h" +#include "dovecot-version.h" + +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <sysexits.h> + +struct prefix_stack { + unsigned int prefix_idx; + unsigned int str_pos; +}; +ARRAY_DEFINE_TYPE(prefix_stack, struct prefix_stack); + +struct config_dump_human_context { + pool_t pool; + string_t *list_prefix; + ARRAY_TYPE(const_string) strings; + ARRAY_TYPE(const_string) errors; + struct config_export_context *export_ctx; + + bool list_prefix_sent:1; +}; + +#define LIST_KEY_PREFIX "\001" +#define UNIQUE_KEY_SUFFIX "\xff" + +static const char *indent_str = " !!!!"; + +static const char *const secrets[] = { + "key", + "secret", + "pass", + "http://", + "https://", + "ftp://", + NULL +}; + + +static void +config_request_get_strings(const char *key, const char *value, + enum config_key_type type, void *context) +{ + struct config_dump_human_context *ctx = context; + const char *p; + + switch (type) { + case CONFIG_KEY_NORMAL: + value = p_strdup_printf(ctx->pool, "%s=%s", key, value); + break; + case CONFIG_KEY_LIST: + value = p_strdup_printf(ctx->pool, LIST_KEY_PREFIX"%s=%s", + key, value); + break; + case CONFIG_KEY_UNIQUE_KEY: + p = strrchr(key, '/'); + i_assert(p != NULL); + value = p_strdup_printf(ctx->pool, "%.*s/"UNIQUE_KEY_SUFFIX"%s=%s", + (int)(p - key), key, p + 1, value); + break; + case CONFIG_KEY_ERROR: + value = p_strdup(ctx->pool, value); + array_push_back(&ctx->errors, &value); + return; + } + array_push_back(&ctx->strings, &value); +} + +static int config_string_cmp(const char *const *p1, const char *const *p2) +{ + const char *s1 = *p1, *s2 = *p2; + unsigned int i = 0; + + while (s1[i] == s2[i]) { + if (s1[i] == '\0' || s1[i] == '=') + return 0; + i++; + } + + if (s1[i] == '=') + return -1; + if (s2[i] == '=') + return 1; + + return s1[i] - s2[i]; +} + +static struct prefix_stack prefix_stack_pop(ARRAY_TYPE(prefix_stack) *stack) +{ + const struct prefix_stack *s; + struct prefix_stack sc; + unsigned int count; + + s = array_get(stack, &count); + i_assert(count > 0); + if (count == 1) { + sc.prefix_idx = UINT_MAX; + } else { + sc.prefix_idx = s[count-2].prefix_idx; + } + sc.str_pos = s[count-1].str_pos; + array_delete(stack, count-1, 1); + return sc; +} + +static void prefix_stack_reset_str(ARRAY_TYPE(prefix_stack) *stack) +{ + struct prefix_stack *s; + + array_foreach_modifiable(stack, s) + s->str_pos = UINT_MAX; +} + +static struct config_dump_human_context * +config_dump_human_init(const char *const *modules, enum config_dump_scope scope, + bool check_settings, bool in_section) +{ + struct config_dump_human_context *ctx; + enum config_dump_flags flags; + pool_t pool; + + pool = pool_alloconly_create(MEMPOOL_GROWING"config human strings", 1024*32); + ctx = p_new(pool, struct config_dump_human_context, 1); + ctx->pool = pool; + ctx->list_prefix = str_new(ctx->pool, 128); + i_array_init(&ctx->strings, 256); + i_array_init(&ctx->errors, 256); + + flags = CONFIG_DUMP_FLAG_HIDE_LIST_DEFAULTS | + CONFIG_DUMP_FLAG_CALLBACK_ERRORS; + if (check_settings) + flags |= CONFIG_DUMP_FLAG_CHECK_SETTINGS; + if (in_section) + flags |= CONFIG_DUMP_FLAG_IN_SECTION; + ctx->export_ctx = config_export_init(modules, NULL, scope, flags, + config_request_get_strings, ctx); + return ctx; +} + +static void config_dump_human_deinit(struct config_dump_human_context *ctx) +{ + array_free(&ctx->strings); + array_free(&ctx->errors); + pool_unref(&ctx->pool); +} + +static bool value_need_quote(const char *value) +{ + size_t len = strlen(value); + + if (len == 0) + return FALSE; + + if (strchr(value, '#') != NULL) + return TRUE; + if (IS_WHITE(value[0]) || IS_WHITE(value[len-1])) + return TRUE; + return FALSE; +} + +static const char *find_next_secret(const char *input, const char **secret_r) +{ + const char *const *secret; + const char *ptr = NULL; + *secret_r = NULL; + for(secret = secrets; *secret != NULL; secret++) { + const char *cptr; + if ((cptr = strstr(input, *secret)) != NULL) { + if (ptr == NULL || cptr < ptr) { + *secret_r = *secret; + ptr = cptr; + } + } + } + i_assert(*secret_r != NULL || ptr == NULL); + return ptr; +} + +static bool +hide_url_userpart_from_value(struct ostream *output, const char **_ptr, + const char **optr, bool quote) +{ + const char *ptr = *_ptr; + const char *start_of_user = ptr; + const char *start_of_host = NULL; + string_t *quoted = NULL; + + if (quote) + quoted = t_str_new(256); + + /* it's a URL, see if there is a userpart */ + while(*ptr != '\0' && !i_isspace(*ptr) && *ptr != '/') { + if (*ptr == '@') { + start_of_host = ptr; + break; + } + ptr++; + } + + if (quote) { + str_truncate(quoted, 0); + str_append_escaped(quoted, *optr, start_of_user - (*optr)); + o_stream_nsend(output, quoted->data, quoted->used); + } else { + o_stream_nsend(output, *optr, start_of_user - (*optr)); + } + + if (start_of_host != NULL && start_of_host != start_of_user) { + o_stream_nsend_str(output, "#hidden_use-P_to_show#"); + } else if (quote) { + str_truncate(quoted, 0); + str_append_escaped(quoted, start_of_user, ptr - start_of_user); + o_stream_nsend(output, quoted->data, quoted->used); + } else { + o_stream_nsend(output, start_of_user, ptr - start_of_user); + } + + *optr = ptr; + *_ptr = ptr; + return TRUE; +} + +static inline bool key_ends_with(const char *key, const char *eptr, + const char *suffix) +{ + /* take = into account */ + size_t n = strlen(suffix)+1; + return (eptr-key > (ptrdiff_t)n && str_begins(eptr-n, suffix)); +} + +static bool +hide_secrets_from_value(struct ostream *output, const char *key, + const char *value) +{ + bool ret = FALSE, quote = value_need_quote(value); + const char *ptr, *optr, *secret; + if (*value != '\0' && + (key_ends_with(key, value, "_password") || + key_ends_with(key, value, "_key") || + key_ends_with(key, value, "_nonce") || + str_begins(key, "ssl_dh"))) { + o_stream_nsend_str(output, "# hidden, use -P to show it"); + return TRUE; + } + + /* Check if we can find anything that has prefix of any of the + secrets. It should match things like secret_api_key or pass or password, + etc. but not something like nonsecret. */ + optr = ptr = value; + while((ptr = find_next_secret(ptr, &secret)) != NULL) { + if (strstr(secret, "://") != NULL) { + ptr += strlen(secret); + if ((ret = hide_url_userpart_from_value(output, &ptr, &optr, quote))) + continue; + } + /* we have found something that we hide, and will deal with output + here. */ + ret = TRUE; + if (ptr == value || + (ptr > value && !i_isalnum(ptr[-1]))) { + size_t len; + while(*ptr != '\0') { + if (*ptr == '=' || i_isspace(*ptr)) + break; + ptr++; + } + while(i_isspace(*ptr)) + ptr++; + len = (size_t)(ptr-optr); + if (quote) { + string_t *quoted = t_str_new(len*2); + str_append_escaped(quoted, optr, len); + o_stream_nsend(output, + quoted->data, quoted->used); + } else { + o_stream_nsend(output, optr, len); + } + if (*ptr == '=') { + o_stream_nsend(output, ptr, 1); + o_stream_nsend_str(output, "#hidden_use-P_to_show#"); + while(*ptr != '\0' && !i_isspace(*ptr) && + *ptr != ';' && *ptr != ':') + ptr++; + } + optr = ptr; + } else { + /* "secret" is prefixed with alphanumeric character, + e.g. "nopassword". So it's not really a secret. + Skip forward to avoid infinite loop. */ + ptr++; + } + }; + /* if we are dealing with output, send rest here */ + if (ret) { + if (quote) + o_stream_nsend_str(output, str_escape(optr)); + else + o_stream_nsend_str(output, optr); + } + return ret; +} + +static int ATTR_NULL(4) +config_dump_human_output(struct config_dump_human_context *ctx, + struct ostream *output, unsigned int indent, + const char *setting_name_filter, bool hide_passwords) +{ + ARRAY_TYPE(const_string) prefixes_arr; + ARRAY_TYPE(prefix_stack) prefix_stack; + struct prefix_stack prefix; + const char *const *strings, *const *args, *p, *str, *const *prefixes; + const char *key, *key2, *value; + unsigned int i, j, count, prefix_count; + unsigned int prefix_idx = UINT_MAX; + size_t len, skip_len, setting_name_filter_len; + bool unique_key; + int ret = 0; + + setting_name_filter_len = setting_name_filter == NULL ? 0 : + strlen(setting_name_filter); + if (config_export_finish(&ctx->export_ctx) < 0) + return -1; + + array_sort(&ctx->strings, config_string_cmp); + strings = array_get(&ctx->strings, &count); + + /* strings are sorted so that all lists come first */ + p_array_init(&prefixes_arr, ctx->pool, 32); + for (i = 0; i < count && strings[i][0] == LIST_KEY_PREFIX[0]; i++) T_BEGIN { + p = strchr(strings[i], '='); + i_assert(p != NULL); + if (p[1] == '\0') { + /* "strlist=" */ + str = p_strdup_printf(ctx->pool, "%s/", + t_strcut(strings[i]+1, '=')); + array_push_back(&prefixes_arr, &str); + } else { + /* string is in format: "list=0 1 2" */ + for (args = t_strsplit(p + 1, " "); *args != NULL; args++) { + str = p_strdup_printf(ctx->pool, "%s/%s/", + t_strcut(strings[i]+1, '='), + *args); + array_push_back(&prefixes_arr, &str); + } + } + } T_END; + prefixes = array_get(&prefixes_arr, &prefix_count); + + p_array_init(&prefix_stack, ctx->pool, 8); + for (; i < count; i++) T_BEGIN { + value = strchr(strings[i], '='); + i_assert(value != NULL); + + key = t_strdup_until(strings[i], value++); + unique_key = FALSE; + + p = strrchr(key, '/'); + if (p != NULL && p[1] == UNIQUE_KEY_SUFFIX[0]) { + key = t_strconcat(t_strdup_until(key, p + 1), + p + 2, NULL); + unique_key = TRUE; + } + if (setting_name_filter_len > 0) { + /* see if this setting matches the name filter */ + if (!(strncmp(setting_name_filter, key, + setting_name_filter_len) == 0 && + (key[setting_name_filter_len] == '/' || + key[setting_name_filter_len] == '\0'))) + goto end; + } + again: + j = 0; + /* if there are open sections and this key isn't in it, + close the sections */ + while (prefix_idx != UINT_MAX) { + len = strlen(prefixes[prefix_idx]); + if (strncmp(prefixes[prefix_idx], key, len) != 0) { + prefix = prefix_stack_pop(&prefix_stack); + indent--; + if (prefix.str_pos != UINT_MAX) + str_truncate(ctx->list_prefix, prefix.str_pos); + else { + o_stream_nsend(output, indent_str, indent*2); + o_stream_nsend_str(output, "}\n"); + } + prefix_idx = prefix.prefix_idx; + } else { + /* keep the prefix */ + j = prefix_idx + 1; + break; + } + } + /* see if this key is in some section */ + for (; j < prefix_count; j++) { + len = strlen(prefixes[j]); + if (strncmp(prefixes[j], key, len) == 0) { + key2 = key + (prefix_idx == UINT_MAX ? 0 : + strlen(prefixes[prefix_idx])); + prefix.str_pos = !unique_key ? UINT_MAX : + str_len(ctx->list_prefix); + prefix_idx = j; + prefix.prefix_idx = prefix_idx; + array_push_back(&prefix_stack, &prefix); + + str_append_max(ctx->list_prefix, indent_str, indent*2); + p = strchr(key2, '/'); + if (p != NULL) + str_append_data(ctx->list_prefix, key2, p - key2); + else + str_append(ctx->list_prefix, key2); + if (unique_key && *value != '\0') { + if (strchr(value, ' ') == NULL) + str_printfa(ctx->list_prefix, " %s", value); + else + str_printfa(ctx->list_prefix, " \"%s\"", str_escape(value)); + } + str_append(ctx->list_prefix, " {\n"); + indent++; + + if (unique_key) + goto end; + else + goto again; + } + } + o_stream_nsend(output, str_data(ctx->list_prefix), str_len(ctx->list_prefix)); + str_truncate(ctx->list_prefix, 0); + prefix_stack_reset_str(&prefix_stack); + ctx->list_prefix_sent = TRUE; + + skip_len = prefix_idx == UINT_MAX ? 0 : strlen(prefixes[prefix_idx]); + i_assert(skip_len == 0 || + strncmp(prefixes[prefix_idx], strings[i], skip_len) == 0); + o_stream_nsend(output, indent_str, indent*2); + key = strings[i] + skip_len; + if (unique_key) key++; + value = strchr(key, '='); + i_assert(value != NULL); + o_stream_nsend(output, key, value-key); + o_stream_nsend_str(output, " = "); + if (hide_passwords && + hide_secrets_from_value(output, key, value+1)) + /* sent */ + ; + else if (!value_need_quote(value+1)) + o_stream_nsend_str(output, value+1); + else { + o_stream_nsend(output, "\"", 1); + o_stream_nsend_str(output, str_escape(value+1)); + o_stream_nsend(output, "\"", 1); + } + o_stream_nsend(output, "\n", 1); + end: ; + } T_END; + + while (prefix_idx != UINT_MAX) { + prefix = prefix_stack_pop(&prefix_stack); + if (prefix.str_pos != UINT_MAX) + break; + prefix_idx = prefix.prefix_idx; + indent--; + o_stream_nsend(output, indent_str, indent*2); + o_stream_nsend_str(output, "}\n"); + } + + /* flush output before writing errors */ + o_stream_uncork(output); + array_foreach_elem(&ctx->errors, str) { + i_error("%s", str); + ret = -1; + } + return ret; +} + +static unsigned int +config_dump_filter_begin(string_t *str, + const struct config_filter *filter) +{ + unsigned int indent = 0; + + if (filter->local_bits > 0) { + str_printfa(str, "local %s", net_ip2addr(&filter->local_net)); + + if (IPADDR_IS_V4(&filter->local_net)) { + if (filter->local_bits != 32) + str_printfa(str, "/%u", filter->local_bits); + } else { + if (filter->local_bits != 128) + str_printfa(str, "/%u", filter->local_bits); + } + str_append(str, " {\n"); + indent++; + } + + if (filter->local_name != NULL) { + str_append_max(str, indent_str, indent*2); + str_printfa(str, "local_name %s {\n", filter->local_name); + indent++; + } + + if (filter->remote_bits > 0) { + str_append_max(str, indent_str, indent*2); + str_printfa(str, "remote %s", net_ip2addr(&filter->remote_net)); + + if (IPADDR_IS_V4(&filter->remote_net)) { + if (filter->remote_bits != 32) + str_printfa(str, "/%u", filter->remote_bits); + } else { + if (filter->remote_bits != 128) + str_printfa(str, "/%u", filter->remote_bits); + } + str_append(str, " {\n"); + indent++; + } + if (filter->service != NULL) { + str_append_max(str, indent_str, indent*2); + str_printfa(str, "protocol %s {\n", filter->service); + indent++; + } + return indent; +} + +static void +config_dump_filter_end(struct ostream *output, unsigned int indent) +{ + while (indent > 0) { + indent--; + o_stream_nsend(output, indent_str, indent*2); + o_stream_nsend(output, "}\n", 2); + } +} + +static int +config_dump_human_sections(struct ostream *output, + const struct config_filter *filter, + const char *const *modules, bool hide_passwords) +{ + struct config_filter_parser *const *filters; + static struct config_dump_human_context *ctx; + unsigned int indent; + int ret = 0; + + filters = config_filter_find_subset(config_filter, filter); + + /* first filter should be the global one */ + i_assert(filters[0] != NULL && filters[0]->filter.service == NULL); + filters++; + + for (; *filters != NULL; filters++) { + ctx = config_dump_human_init(modules, CONFIG_DUMP_SCOPE_SET, + FALSE, TRUE); + indent = config_dump_filter_begin(ctx->list_prefix, + &(*filters)->filter); + config_export_parsers(ctx->export_ctx, (*filters)->parsers); + if (config_dump_human_output(ctx, output, indent, NULL, hide_passwords) < 0) + ret = -1; + if (ctx->list_prefix_sent) + config_dump_filter_end(output, indent); + config_dump_human_deinit(ctx); + } + return ret; +} + +static int ATTR_NULL(4) +config_dump_human(const struct config_filter *filter, const char *const *modules, + enum config_dump_scope scope, const char *setting_name_filter, + bool hide_passwords) +{ + static struct config_dump_human_context *ctx; + struct ostream *output; + int ret; + + output = o_stream_create_fd(STDOUT_FILENO, 0); + o_stream_set_no_error_handling(output, TRUE); + o_stream_cork(output); + + ctx = config_dump_human_init(modules, scope, TRUE, FALSE); + config_export_by_filter(ctx->export_ctx, filter); + ret = config_dump_human_output(ctx, output, 0, setting_name_filter, hide_passwords); + config_dump_human_deinit(ctx); + + if (setting_name_filter == NULL) + ret = config_dump_human_sections(output, filter, modules, hide_passwords); + + o_stream_uncork(output); + o_stream_destroy(&output); + return ret; +} + +static int +config_dump_one(const struct config_filter *filter, bool hide_key, + enum config_dump_scope scope, const char *setting_name_filter, + bool hide_passwords) +{ + static struct config_dump_human_context *ctx; + const char *str; + size_t len; + bool dump_section = FALSE; + + ctx = config_dump_human_init(NULL, scope, FALSE, FALSE); + config_export_by_filter(ctx->export_ctx, filter); + if (config_export_finish(&ctx->export_ctx) < 0) + return -1; + + len = strlen(setting_name_filter); + array_foreach_elem(&ctx->strings, str) { + if (strncmp(str, setting_name_filter, len) != 0) + continue; + + if (str[len] == '=') { + if (hide_key) + printf("%s\n", str + len+1); + else { + printf("%s = %s\n", setting_name_filter, + str + len+1); + } + dump_section = FALSE; + break; + } else if (str[len] == '/') { + dump_section = TRUE; + } + } + config_dump_human_deinit(ctx); + + if (dump_section) + (void)config_dump_human(filter, NULL, scope, setting_name_filter, hide_passwords); + return 0; +} + +static void config_request_simple_stdout(const char *key, const char *value, + enum config_key_type type ATTR_UNUSED, + void *context) +{ + char **setting_name_filters = context; + unsigned int i; + size_t filter_len; + + if (setting_name_filters == NULL) { + printf("%s=%s\n", key, value); + return; + } + + for (i = 0; setting_name_filters[i] != NULL; i++) { + filter_len = strlen(setting_name_filters[i]); + if (strncmp(setting_name_filters[i], key, filter_len) == 0 && + (key[filter_len] == '\0' || key[filter_len] == '/')) + printf("%s=%s\n", key, value); + } +} + +static void config_request_putenv(const char *key, const char *value, + enum config_key_type type ATTR_UNUSED, + void *context ATTR_UNUSED) +{ + T_BEGIN { + env_put(t_str_ucase(key), value); + } T_END; +} + +static const char *get_setting(const char *module, const char *name) +{ + struct config_module_parser *l; + const struct setting_define *def; + const char *const *value; + const void *set; + + for (l = config_module_parsers; l->root != NULL; l++) { + if (strcmp(l->root->module_name, module) != 0) + continue; + + set = settings_parser_get(l->parser); + for (def = l->root->defines; def->key != NULL; def++) { + if (strcmp(def->key, name) == 0) { + value = CONST_PTR_OFFSET(set, def->offset); + return *value; + } + } + } + return ""; +} + +static void filter_parse_arg(struct config_filter *filter, const char *arg) +{ + const char *key, *value, *error; + + value = strchr(arg, '='); + if (value != NULL) + key = t_strdup_until(arg, value++); + else { + key = arg; + value = ""; + } + + if (strcmp(key, "service") == 0) + filter->service = value; + else if (strcmp(key, "protocol") == 0) + filter->service = value; + else if (strcmp(key, "lname") == 0) + filter->local_name = value; + else if (strcmp(key, "local") == 0) { + if (config_parse_net(value, &filter->local_net, + &filter->local_bits, &error) < 0) + i_fatal("local filter: %s", error); + } else if (strcmp(key, "remote") == 0) { + if (config_parse_net(value, &filter->remote_net, + &filter->remote_bits, &error) < 0) + i_fatal("remote filter: %s", error); + } else { + i_fatal("Unknown filter argument: %s", arg); + } +} + +struct hostname_format { + const char *prefix, *suffix; + unsigned int numcount; + bool zeropadding; +}; + +static void +hostname_format_write(string_t *str, const struct hostname_format *fmt, + unsigned int num) +{ + str_truncate(str, 0); + str_append(str, fmt->prefix); + if (!fmt->zeropadding) + str_printfa(str, "%d", num); + else + str_printfa(str, "%0*d", fmt->numcount, num); + str_append(str, fmt->suffix); +} + +static void hostname_verify_format(const char *arg) +{ + struct hostname_format fmt; + const char *p; + unsigned char hash[GUID_128_HOST_HASH_SIZE]; + unsigned int n, limit; + HASH_TABLE(void *, void *) hosts; + void *key, *value; + string_t *host; + const char *host2; + bool duplicates = FALSE; + + i_zero(&fmt); + if (arg != NULL) { + /* host%d, host%2d, host%02d */ + p = strchr(arg, '%'); + if (p == NULL) + i_fatal("Host parameter missing %%d"); + fmt.prefix = t_strdup_until(arg, p++); + if (*p == '0') { + fmt.zeropadding = TRUE; + p++; + } + if (!i_isdigit(*p)) + fmt.numcount = 1; + else + fmt.numcount = *p++ - '0'; + if (*p++ != 'd') + i_fatal("Host parameter missing %%d"); + fmt.suffix = p; + } else { + /* detect host1[suffix] vs host01[suffix] */ + size_t len = strlen(my_hostname); + while (len > 0 && !i_isdigit(my_hostname[len-1])) + len--; + fmt.suffix = my_hostname + len; + fmt.numcount = 0; + while (len > 0 && i_isdigit(my_hostname[len-1])) { + len--; + fmt.numcount++; + } + if (my_hostname[len] == '0') + fmt.zeropadding = TRUE; + fmt.prefix = t_strndup(my_hostname, len); + if (fmt.numcount == 0) { + i_fatal("Hostname '%s' has no digits, can't verify", + my_hostname); + } + } + for (n = 0, limit = 1; n < fmt.numcount; n++) + limit *= 10; + host = t_str_new(128); + hash_table_create_direct(&hosts, default_pool, limit); + for (n = 0; n < limit; n++) { + hostname_format_write(host, &fmt, n); + + guid_128_host_hash_get(str_c(host), hash); + i_assert(sizeof(key) >= sizeof(hash)); + key = NULL; memcpy(&key, hash, sizeof(hash)); + + value = hash_table_lookup(hosts, key); + if (value != NULL) { + host2 = t_strdup(str_c(host)); + hostname_format_write(host, &fmt, + POINTER_CAST_TO(value, unsigned int)-1); + i_error("Duplicate host hashes: %s and %s", + str_c(host), host2); + duplicates = TRUE; + } else { + hash_table_insert(hosts, key, POINTER_CAST(n+1)); + } + } + hash_table_destroy(&hosts); + + if (duplicates) + lib_exit(EX_CONFIG); + else { + host2 = t_strdup(str_c(host)); + hostname_format_write(host, &fmt, 0); + printf("No duplicate host hashes in %s .. %s\n", + str_c(host), host2); + lib_exit(0); + } +} + +static void check_wrong_config(const char *config_path) +{ + const char *base_dir, *symlink_path, *prev_path, *error; + + base_dir = get_setting("master", "base_dir"); + symlink_path = t_strconcat(base_dir, "/"PACKAGE".conf", NULL); + if (t_readlink(symlink_path, &prev_path, &error) < 0) { + if (errno != ENOENT) + i_error("t_readlink(%s) failed: %s", symlink_path, error); + return; + } + + if (strcmp(prev_path, config_path) != 0) { + i_warning("Dovecot was last started using %s, " + "but this config is %s", prev_path, config_path); + } +} + +static void failure_exit_callback(int *status) +{ + /* don't use EX_CONFIG, because it often causes MTAs to bounce + the mails back. */ + *status = EX_TEMPFAIL; +} + +int main(int argc, char *argv[]) +{ + enum master_service_flags master_service_flags = + MASTER_SERVICE_FLAG_DONT_SEND_STATS | + MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME; + enum config_dump_scope scope = CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN; + const char *orig_config_path, *config_path, *module; + ARRAY(const char *) module_names; + struct config_filter filter; + const char *const *wanted_modules, *error; + char **exec_args = NULL, **setting_name_filters = NULL; + unsigned int i; + int c, ret, ret2; + bool config_path_specified, expand_vars = FALSE, hide_key = FALSE; + bool parse_full_config = FALSE, simple_output = FALSE; + bool dump_defaults = FALSE, host_verify = FALSE; + bool print_plugin_banner = FALSE, hide_passwords = TRUE; + + if (getenv("USE_SYSEXITS") != NULL) { + /* we're coming from (e.g.) LDA */ + i_set_failure_exit_callback(failure_exit_callback); + } + + i_zero(&filter); + master_service = master_service_init("config", master_service_flags, + &argc, &argv, "adf:hHm:nNpPexsS"); + orig_config_path = t_strdup(master_service_get_config_path(master_service)); + + i_set_failure_prefix("doveconf: "); + t_array_init(&module_names, 4); + while ((c = master_getopt(master_service)) > 0) { + if (c == 'e') { + expand_vars = TRUE; + break; + } + switch (c) { + case 'a': + break; + case 'd': + dump_defaults = TRUE; + break; + case 'f': + filter_parse_arg(&filter, optarg); + break; + case 'h': + hide_key = TRUE; + break; + case 'H': + host_verify = TRUE; + break; + case 'm': + module = t_strdup(optarg); + array_push_back(&module_names, &module); + break; + case 'n': + scope = CONFIG_DUMP_SCOPE_CHANGED; + break; + case 'N': + scope = CONFIG_DUMP_SCOPE_SET; + break; + case 'p': + parse_full_config = TRUE; + break; + case 'P': + hide_passwords = FALSE; + break; + case 's': + scope = CONFIG_DUMP_SCOPE_ALL_WITH_HIDDEN; + break; + case 'S': + simple_output = TRUE; + break; + case 'x': + expand_vars = TRUE; + break; + default: + return FATAL_DEFAULT; + } + } + array_append_zero(&module_names); + wanted_modules = array_count(&module_names) == 1 ? NULL : + array_front(&module_names); + + config_path = master_service_get_config_path(master_service); + /* use strcmp() instead of !=, because dovecot -n always gives us + -c parameter */ + config_path_specified = strcmp(config_path, orig_config_path) != 0; + + if (host_verify) + hostname_verify_format(argv[optind]); + + if (c == 'e') { + if (argv[optind] == NULL) + i_fatal("Missing command for -e"); + exec_args = &argv[optind]; + } else if (argv[optind] != NULL) { + /* print only a single config setting */ + setting_name_filters = argv+optind; + if (scope == CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN) + scope = CONFIG_DUMP_SCOPE_ALL_WITH_HIDDEN; + } else if (!simple_output) { + /* print the config file path before parsing it, so in case + of errors it's still shown */ + printf("# "DOVECOT_VERSION_FULL": %s\n", config_path); + print_plugin_banner = TRUE; + fflush(stdout); + } + master_service_init_finish(master_service); + config_parse_load_modules(); + + if (print_plugin_banner) { + struct module *m; + + for (m = modules; m != NULL; m = m->next) { + const char **str = module_get_symbol_quiet(m, + t_strdup_printf("%s_doveconf_banner", m->name)); + if (str != NULL) + printf("# %s\n", *str); + } + } + + if ((ret = config_parse_file(dump_defaults ? NULL : config_path, + expand_vars, + parse_full_config ? NULL : wanted_modules, + &error)) == 0 && + access(EXAMPLE_CONFIG_DIR, X_OK) == 0) { + i_fatal("%s (copy example configs from "EXAMPLE_CONFIG_DIR"/)", + error); + } + + if ((ret == -1 && exec_args != NULL) || ret == 0 || ret == -2) + i_fatal("%s", error); + + if (simple_output) { + struct config_export_context *ctx; + + ctx = config_export_init(wanted_modules, NULL, scope, + CONFIG_DUMP_FLAG_CHECK_SETTINGS, + config_request_simple_stdout, + setting_name_filters); + config_export_by_filter(ctx, &filter); + ret2 = config_export_finish(&ctx); + } else if (setting_name_filters != NULL) { + ret2 = 0; + /* ignore settings-check failures in configuration. this allows + using doveconf to lookup settings for things like install or + uninstall scripts where the configuration might + (temporarily) not be fully usable */ + ret = 0; + for (i = 0; setting_name_filters[i] != NULL; i++) { + if (config_dump_one(&filter, hide_key, scope, + setting_name_filters[i], hide_passwords) < 0) + ret2 = -1; + } + } else if (exec_args == NULL) { + const char *info; + + info = sysinfo_get(get_setting("mail", "mail_location")); + if (*info != '\0') + printf("# %s\n", info); + printf("# Hostname: %s\n", my_hostdomain()); + if (!config_path_specified) + check_wrong_config(config_path); + if (scope == CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN) + printf("# NOTE: Send doveconf -n output instead when asking for help.\n"); + fflush(stdout); + ret2 = config_dump_human(&filter, wanted_modules, scope, NULL, hide_passwords); + } else { + struct config_export_context *ctx; + + ctx = config_export_init(wanted_modules, NULL, CONFIG_DUMP_SCOPE_SET, + CONFIG_DUMP_FLAG_CHECK_SETTINGS, + config_request_putenv, NULL); + config_export_by_filter(ctx, &filter); + + if (getenv(DOVECOT_PRESERVE_ENVS_ENV) != NULL) { + /* Standalone binary is getting its configuration via + doveconf. Clean the environment before calling it. + Do this only if the environment exists, because + lib-master doesn't set it if it doesn't want the + environment to be cleaned (e.g. -k parameter). */ + const char *import_environment = + config_export_get_import_environment(ctx); + master_service_import_environment(import_environment); + master_service_env_clean(); + } + + env_put("DOVECONF_ENV", "1"); + if (config_export_finish(&ctx) < 0) + i_fatal("Invalid configuration"); + execvp(exec_args[0], exec_args); + i_fatal("execvp(%s) failed: %m", exec_args[0]); + } + + if (ret < 0) { + /* delayed error */ + i_fatal("%s", error); + } + if (ret2 < 0) + i_fatal("Errors in configuration"); + + config_filter_deinit(&config_filter); + old_settings_deinit_global(); + module_dir_unload(&modules); + config_parser_deinit(); + master_service_deinit(&master_service); + return 0; +} diff --git a/src/config/main.c b/src/config/main.c new file mode 100644 index 0000000..01a80ba --- /dev/null +++ b/src/config/main.c @@ -0,0 +1,53 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "module-dir.h" +#include "restrict-access.h" +#include "master-service.h" +#include "old-set-parser.h" +#include "config-connection.h" +#include "config-parser.h" +#include "config-request.h" + +static void client_connected(struct master_service_connection *conn) +{ + master_service_client_connection_accept(conn); + (void)config_connection_create(conn->fd); +} + +int main(int argc, char *argv[]) +{ + const enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_DONT_SEND_STATS; + const char *path, *error; + + master_service = master_service_init("config", service_flags, + &argc, &argv, ""); + if (master_getopt(master_service) > 0) + return FATAL_DEFAULT; + master_service_init_log(master_service); + + restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); + restrict_access_allow_coredumps(TRUE); + + config_parse_load_modules(); + + path = master_service_get_config_path(master_service); + if (config_parse_file(path, TRUE, NULL, &error) <= 0) + i_fatal("%s", error); + + /* notify about our success only after successfully parsing the + config file, so if the parsing fails, master won't immediately + just recreate this process (and fail again and so on). */ + master_service_init_finish(master_service); + + master_service_run(master_service, client_connected); + config_connections_destroy_all(); + + config_filter_deinit(&config_filter); + old_settings_deinit_global(); + module_dir_unload(&modules); + config_parser_deinit(); + master_service_deinit(&master_service); + return 0; +} diff --git a/src/config/old-set-parser.c b/src/config/old-set-parser.c new file mode 100644 index 0000000..d5302f8 --- /dev/null +++ b/src/config/old-set-parser.c @@ -0,0 +1,824 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "settings-parser.h" +#include "config-parser-private.h" +#include "old-set-parser.h" +#include "istream.h" +#include "base64.h" + +static bool seen_ssl_parameters_dat; +static const char *ssl_dh_parameters; + +#define config_apply_line (void)config_apply_line + +struct socket_set { + const char *path, *mode, *user, *group; + bool master; +}; + +struct old_set_parser { + const char *base_dir; + /* 1 when in auth {} section, >1 when inside auth { .. { .. } } */ + unsigned int auth_section; + /* 1 when in socket listen {}, >1 when inside more of its sections */ + unsigned int socket_listen_section; + bool seen_auth_section; + struct socket_set socket_set; +}; + +static const struct config_filter any_filter = { + .service = NULL +}; + +static const struct config_filter imap_filter = { + .service = "imap" +}; +static const struct config_filter pop3_filter = { + .service = "pop3" +}; +static const struct config_filter managesieve_filter = { + .service = "sieve" +}; + +static char *ssl_dh_value = NULL; +static bool ssl_dh_loaded = FALSE; + +static void ATTR_FORMAT(2, 3) +obsolete(struct config_parser_context *ctx, const char *str, ...) +{ + static bool seen_obsoletes = FALSE; + va_list args; + + if (!seen_obsoletes) { + i_warning("NOTE: You can get a new clean config file with: " + "doveconf -Pn > dovecot-new.conf"); + seen_obsoletes = TRUE; + } + + va_start(args, str); + i_warning("Obsolete setting in %s:%u: %s", + ctx->cur_input->path, ctx->cur_input->linenum, + t_strdup_vprintf(str, args)); + va_end(args); +} + +static void set_rename(struct config_parser_context *ctx, + const char *old_key, const char *key, const char *value) +{ + obsolete(ctx, "%s has been renamed to %s", old_key, key); + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_KEYVALUE, key, value); +} + +static bool old_settings_ssl_dh_read(const char **value, const char **error_r) +{ + + if (ssl_dh_parameters != NULL) *value = ssl_dh_parameters; + + const char *fn = t_strconcat(PKG_STATEDIR, "/ssl-parameters.dat", NULL); + buffer_t *data = t_buffer_create(300); + string_t *b64_data = t_str_new(500); + size_t siz; + unsigned short keysize; + unsigned int off=0; + + /* try read it */ + struct istream *is = i_stream_create_file(fn, IO_BLOCK_SIZE); + + if (is->stream_errno == ENOENT) { + /* this is given because the ssl-parameters.dat file is no more there + and we don't want to to make go searching for the file + this code is only ever reached if ssl_dh_parameters is empty anyways + */ + /* check moved to correct place from here */ + *value = NULL; + i_stream_unref(&is); + return TRUE; + } else if (is->stream_errno != 0) { + *error_r = t_strdup(i_stream_get_error(is)); + i_stream_unref(&is); + return FALSE; + } + + /* then try to read the rest of the data */ + while(i_stream_read(is) > 0) { + const unsigned char *buf = i_stream_get_data(is, &siz); + if (siz < 88) break; + memcpy(&keysize, buf, 2); + if (keysize == 512) { + memcpy(&off, buf+4, 4); + off += 16; // skip headers + } else { + off = 8; // skip header + } + if (off > siz) break; + buffer_append(data, buf+off, siz); + break; + } + + const void *tmp = buffer_get_data(data, &siz); + + if (siz > 4) { + str_append(b64_data, "-----BEGIN DH PARAMETERS-----\n"); + base64_encode(tmp, siz-4, b64_data); + /* need to wrap the string nicely */ + for(size_t i = 29+65; i < str_len(b64_data); i+=64) /* start at header + first 64 */ + { + str_insert(b64_data, i++, "\n"); + } + str_append_c(b64_data,'\n'); + str_append(b64_data, "-----END DH PARAMETERS-----"); + ssl_dh_parameters = i_strdup(str_c(b64_data)); + *value = ssl_dh_parameters; + + if (!seen_ssl_parameters_dat) { + i_warning("please set ssl_dh=<%s", SYSCONFDIR"/dh.pem"); + i_warning("You can generate it with: dd if=%s bs=1 skip=%u | openssl dhparam -inform der > %s", fn, off, SYSCONFDIR"/dh.pem"); + seen_ssl_parameters_dat = TRUE; + } + } else if (is->stream_errno == ENOENT) { + /* check for empty ssl_dh elsewhere */ + *value = NULL; + i_stream_unref(&is); + return TRUE; + } else { + *error_r = "ssl enabled, but ssl_dh not set"; + i_stream_unref(&is); + return FALSE; + } + i_stream_unref(&is); + + return TRUE; +} + +bool old_settings_ssl_dh_load(const char **value, const char **error_r) +{ + if (ssl_dh_loaded) { + *value = ssl_dh_value; + return TRUE; + } + if (!old_settings_ssl_dh_read(value, error_r)) + return FALSE; + ssl_dh_value = i_strdup(*value); + ssl_dh_loaded = TRUE; + return TRUE; +} + +/* FIXME: Remove ssl_protocols_to_min_protocol() in v2.4 */ +static int ssl_protocols_to_min_protocol(const char *ssl_protocols, + const char **min_protocol_r, + const char **error_r) +{ + static const char *protocol_versions[] = { + "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", + }; + /* Array where -1 = disable, 0 = not found, 1 = enable */ + int protos[N_ELEMENTS(protocol_versions)]; + memset(protos, 0, sizeof(protos)); + bool explicit_enable = FALSE; + + const char *const *tmp = t_strsplit_spaces(ssl_protocols, ", "); + for (; *tmp != NULL; tmp++) { + const char *p = *tmp; + bool enable = TRUE; + if (p[0] == '!') { + enable = FALSE; + ++p; + } + for (unsigned i = 0; i < N_ELEMENTS(protocol_versions); i++) { + if (strcmp(p, protocol_versions[i]) == 0) { + if (enable) { + protos[i] = 1; + explicit_enable = TRUE; + } else { + protos[i] = -1; + } + goto found; + } + } + *error_r = t_strdup_printf("Unrecognized protocol '%s'", p); + return -1; + + found:; + } + + unsigned min = N_ELEMENTS(protocol_versions); + for (unsigned i = 0; i < N_ELEMENTS(protocol_versions); i++) { + if (explicit_enable) { + if (protos[i] > 0) + min = I_MIN(min, i); + } else if (protos[i] == 0) + min = I_MIN(min, i); + } + if (min == N_ELEMENTS(protocol_versions)) { + *error_r = "All protocols disabled"; + return -1; + } + + *min_protocol_r = protocol_versions[min]; + return 0; +} + +static bool +old_settings_handle_root(struct config_parser_context *ctx, + const char *key, const char *value) +{ + const char *p; + size_t len; + + if (strcmp(key, "base_dir") == 0) { + len = strlen(value); + if (len > 0 && value[len-1] == '/') + value = t_strndup(value, len-1); + ctx->old->base_dir = p_strdup(ctx->pool, value); + } + if (strcmp(key, "protocols") == 0) { + char **protos, **s; + bool have_imap = FALSE, have_imaps = FALSE; + bool have_pop3 = FALSE, have_pop3s = FALSE; + + protos = p_strsplit_spaces(pool_datastack_create(), value, " "); + for (s = protos; *s != NULL; s++) { + if (strcmp(*s, "imap") == 0) + have_imap = TRUE; + else if (strcmp(*s, "imaps") == 0) { + *s = ""; + have_imaps = TRUE; + } else if (strcmp(*s, "pop3") == 0) + have_pop3 = TRUE; + else if (strcmp(*s, "pop3s") == 0) { + *s = ""; + have_pop3s = TRUE; + } else if (strcmp(*s, "managesieve") == 0) { + *s = "sieve"; + obsolete(ctx, "protocols=managesieve has been renamed to protocols=sieve"); + } + } + value = t_strarray_join((const char *const *)protos, " "); + /* ugly way to drop extra spaces.. */ + protos = p_strsplit_spaces(pool_datastack_create(), value, " "); + value = t_strarray_join((const char *const *)protos, " "); + + if (have_imaps && !have_imap) { + obsolete(ctx, "'imaps' protocol can no longer be specified (use protocols=imap). to disable non-ssl imap, use service imap-login { inet_listener imap { port=0 } }"); + value = t_strconcat(value, " imap", NULL); + config_apply_line(ctx, "port", + "service/imap-login/inet_listener/imap/port=0", NULL); + } else if (have_imaps) + obsolete(ctx, "'imaps' protocol is no longer necessary, remove it"); + if (have_pop3s && !have_pop3) { + obsolete(ctx, "'pop3s' protocol can no longer be specified (use protocols=pop3). to disable non-ssl pop3, use service pop3-login { inet_listener pop3 { port=0 } }"); + value = t_strconcat(value, " pop3", NULL); + config_apply_line(ctx, "port", + "service/pop3-login/inet_listener/pop3/port=0", NULL); + } else if (have_pop3s) + obsolete(ctx, "'pop3s' protocol is no longer necessary, remove it"); + + if (*value == ' ') value++; + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_KEYVALUE, + key, value); + return TRUE; + } + if (strcmp(key, "ssl_cert_file") == 0 || + strcmp(key, "ssl_key_file") == 0 || + strcmp(key, "ssl_ca_file") == 0) { + if (*value == '\0') + return TRUE; + p = t_strdup_until(key, strrchr(key, '_')); + obsolete(ctx, "%s has been replaced by %s = <file", key, p); + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_KEYFILE, + p, value); + return TRUE; + } + if (strcmp(key, "ssl_disable") == 0) { + if (strcasecmp(value, "yes") == 0) + value = "no"; + else if (strcasecmp(value, "no") == 0) + value = "yes"; + set_rename(ctx, key, "ssl", value); + return TRUE; + } + if (strcmp(key, "ssl_parameters_regenerate") == 0 || + strcmp(key, "ssl_dh_parameters_length") == 0) { + obsolete(ctx, "%s is no longer needed", key); + return TRUE; + } + if (strcmp(key, "ssl_protocols") == 0) { + obsolete(ctx, "%s has been replaced by ssl_min_protocol", key); + const char *min_protocol, *error; + if (ssl_protocols_to_min_protocol(value, &min_protocol, &error) < 0) { + i_error("Could not find a minimum ssl_min_protocol " + "setting from ssl_protocols = %s: %s", + value, error); + return TRUE; + } + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_KEYVALUE, + "ssl_min_protocol", min_protocol); + return TRUE; + } + if (strcmp(key, "sieve") == 0 || + strcmp(key, "sieve_storage") == 0) { + if (strcmp(key, "sieve_storage") == 0) + obsolete(ctx, "sieve_storage has been moved into plugin { sieve_dir }"); + else + obsolete(ctx, "%s has been moved into plugin {} section", key); + + config_apply_line(ctx, "", "plugin=", NULL); + config_apply_line(ctx, key, + t_strdup_printf("plugin/%s=%s", key, value), NULL); + return TRUE; + } + if (strcmp(key, "fsync_disable") == 0) { + if (strcasecmp(value, "yes") == 0) + value = "never"; + else if (strcasecmp(value, "no") == 0) + value = "optimized"; + set_rename(ctx, key, "mail_fsync", value); + return TRUE; + } + if (strcmp(key, "dbox_rotate_size") == 0) { + set_rename(ctx, key, "mdbox_rotate_size", value); + return TRUE; + } + if (str_begins(key, "mail_cache_compress_")) { + const char *new_key = t_strconcat("mail_cache_purge_", key+20, NULL); + set_rename(ctx, key, new_key, value); + return TRUE; + } + if (strcmp(key, "imap_client_workarounds") == 0) { + char **args, **arg; + + args = p_strsplit_spaces(pool_datastack_create(), value, " ,"); + for (arg = args; *arg != NULL; arg++) { + if (strcmp(*arg, "outlook-idle") == 0) { + *arg = ""; + obsolete(ctx, "imap_client_workarounds=outlook-idle is no longer necessary"); + } else if (strcmp(*arg, "netscape-eoh") == 0) { + *arg = ""; + obsolete(ctx, "imap_client_workarounds=netscape-eoh is no longer supported"); + } + } + value = t_strarray_join((void *)args, " "); + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_KEYVALUE, + key, value); + return TRUE; + } + + if (strcmp(key, "login_dir") == 0 || + strcmp(key, "dbox_rotate_min_size") == 0 || + strcmp(key, "dbox_rotate_days") == 0 || + strcmp(key, "director_consistent_hashing") == 0 || + strcmp(key, "mail_log_max_lines_per_sec") == 0 || + strcmp(key, "maildir_copy_preserve_filename") == 0) { + obsolete(ctx, "%s has been removed", key); + return TRUE; + } + if (ctx->old->auth_section == 1) { + if (!str_begins(key, "auth_")) + key = t_strconcat("auth_", key, NULL); + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_KEYVALUE, + key, value); + return TRUE; + } + return FALSE; +} + +static void +config_apply_login_set(struct config_parser_context *ctx, + struct config_section_stack *old_section, + const char *old_key, const char *key, const char *value) +{ + obsolete(ctx, "%s has been replaced by service { %s }", old_key, key); + + if (config_filter_match(&old_section->filter, &imap_filter)) { + config_apply_line(ctx, key, + t_strdup_printf("service/imap-login/%s=%s", key, value), NULL); + } + if (config_filter_match(&old_section->filter, &pop3_filter)) { + config_apply_line(ctx, key, + t_strdup_printf("service/pop3-login/%s=%s", key, value), NULL); + } + if (config_filter_match(&old_section->filter, &managesieve_filter)) { + /* if pigeonhole isn't installed, this fails. + just ignore it then.. */ + config_apply_line(ctx, key, + t_strdup_printf("service/managesieve-login/%s=%s", key, value), NULL); + ctx->error = NULL; + } +} + +static void +config_apply_mail_set(struct config_parser_context *ctx, + struct config_section_stack *old_section, + const char *old_key, const char *key, const char *value) +{ + obsolete(ctx, "%s has been replaced by service { %s }", old_key, key); + + if (config_filter_match(&old_section->filter, &imap_filter)) { + config_apply_line(ctx, key, + t_strdup_printf("service/imap/%s=%s", key,value), NULL); + } + if (config_filter_match(&old_section->filter, &pop3_filter)) { + config_apply_line(ctx, key, + t_strdup_printf("service/pop3/%s=%s", key,value), NULL); + } + if (config_filter_match(&old_section->filter, &managesieve_filter)) { + config_apply_line(ctx, key, + t_strdup_printf("service/managesieve/%s=%s", key,value), NULL); + ctx->error = NULL; + } +} + +static void +config_apply_auth_set(struct config_parser_context *ctx, + const char *old_key, const char *key, const char *value) +{ + obsolete(ctx, "%s has been replaced by service auth { %s }", old_key, key); + config_apply_line(ctx, key, + t_strdup_printf("service/auth/%s=%s", key,value), NULL); +} + +static bool listen_has_port(const char *str) +{ + const char *const *addrs; + + if (strchr(str, ':') == NULL) + return FALSE; + + addrs = t_strsplit_spaces(str, ", "); + for (; *addrs != NULL; addrs++) { + if (strcmp(*addrs, "*") != 0 && + strcmp(*addrs, "::") != 0 && + strcmp(*addrs, "[::]") != 0 && + !is_ipv4_address(*addrs) && + !is_ipv6_address(*addrs)) + return TRUE; + } + return FALSE; +} + +static bool +old_settings_handle_proto(struct config_parser_context *ctx, + const char *key, const char *value) +{ + struct config_section_stack *old_section = ctx->cur_section; + const char *p; + uoff_t size; + bool root; + + while (ctx->cur_section->prev != NULL) + ctx->cur_section = ctx->cur_section->prev; + + root = config_filter_match(&old_section->filter, &any_filter); + + if (strcmp(key, "ssl_listen") == 0 || + (strcmp(key, "listen") == 0 && + (listen_has_port(value) || !root))) { + const char *ssl = strcmp(key, "ssl_listen") == 0 ? "s" : ""; + + if (*value == '\0') { + /* default */ + return TRUE; + } + p = strrchr(value, ':'); + if (p != NULL && listen_has_port(value)) { + obsolete(ctx, "%s=..:port has been replaced by service { inet_listener { port } }", key); + value = t_strdup_until(value, p++); + if (config_filter_match(&old_section->filter, &imap_filter)) { + config_apply_line(ctx, "port", + t_strdup_printf("service/imap-login/inet_listener/imap%s/port=%s", ssl, p), NULL); + } + if (config_filter_match(&old_section->filter, &pop3_filter)) { + config_apply_line(ctx, "port", + t_strdup_printf("service/pop3-login/inet_listener/pop3%s/port=%s", ssl, p), NULL); + } + if (*ssl == '\0' && + config_filter_match(&old_section->filter, &managesieve_filter)) { + config_apply_line(ctx, "port", + t_strdup_printf("service/managesieve-login/inet_listener/managesieve/port=%s", p), NULL); + ctx->error = NULL; + } + } + if (root && *ssl == '\0') { + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_KEYVALUE, + key, value); + } else { + obsolete(ctx, "protocol { %s } has been replaced by service { inet_listener { address } }", key); + if (config_filter_match(&old_section->filter, &imap_filter)) { + config_apply_line(ctx, "address", + t_strdup_printf("service/imap-login/inet_listener/imap%s/address=%s", ssl, value), NULL); + } + if (config_filter_match(&old_section->filter, &pop3_filter)) { + config_apply_line(ctx, "address", + t_strdup_printf("service/pop3-login/inet_listener/pop3%s/address=%s", ssl, value), NULL); + } + if (*ssl == '\0' && + config_filter_match(&old_section->filter, &managesieve_filter)) { + config_apply_line(ctx, "address", + t_strdup_printf("service/managesieve-login/inet_listener/managesieve/address=%s", value), NULL); + ctx->error = NULL; + } + } + return TRUE; + } + if (strcmp(key, "login_chroot") == 0) { + if (strcmp(value, "no") == 0) + value = ""; + else + value = "login"; + + config_apply_login_set(ctx, old_section, key, "chroot", value); + return TRUE; + } + if (strcmp(key, "login_user") == 0) { + config_apply_login_set(ctx, old_section, key, "user", value); + return TRUE; + } + if (strcmp(key, "login_executable") == 0) { + config_apply_login_set(ctx, old_section, key, "executable", value); + return TRUE; + } + if (strcmp(key, "login_process_size") == 0) { + config_apply_login_set(ctx, old_section, key, "vsz_limit", + t_strconcat(value, " M", NULL)); + return TRUE; + } + if (strcmp(key, "login_process_per_connection") == 0) { + config_apply_login_set(ctx, old_section, key, "service_count", + strcmp(value, "no") == 0 ? "0" : "1"); + return TRUE; + } + if (strcmp(key, "login_processes_count") == 0) { + config_apply_login_set(ctx, old_section, key, "process_min_avail", value); + return TRUE; + } + if (strcmp(key, "login_max_processes_count") == 0) { + config_apply_login_set(ctx, old_section, key, "process_limit", value); + return TRUE; + } + if (strcmp(key, "login_max_connections") == 0) { + config_apply_login_set(ctx, old_section, key, "client_limit", value); + return TRUE; + } + if (strcmp(key, "login_process_size") == 0) { + config_apply_login_set(ctx, old_section, key, "vsz_limit", + t_strconcat(value, " M", NULL)); + return TRUE; + } + + if (strcmp(key, "max_mail_processes") == 0) { + config_apply_mail_set(ctx, old_section, key, "process_limit", value); + return TRUE; + } + if (strcmp(key, "mail_executable") == 0) { + config_apply_mail_set(ctx, old_section, key, "executable", value); + return TRUE; + } + if (strcmp(key, "mail_process_size") == 0) { + config_apply_mail_set(ctx, old_section, key, "vsz_limit", + t_strconcat(value, " M", NULL)); + return TRUE; + } + if (strcmp(key, "mail_drop_priv_before_exec") == 0) { + config_apply_mail_set(ctx, old_section, key, "drop_priv_before_exec", value); + return TRUE; + } + + if (ctx->old->auth_section == 1) { + if (!str_begins(key, "auth_")) + key = t_strconcat("auth_", key, NULL); + } + + if (strcmp(key, "auth_executable") == 0) { + config_apply_auth_set(ctx, key, "executable", value); + return TRUE; + } + if (strcmp(key, "auth_process_size") == 0) { + config_apply_auth_set(ctx, key, "vsz_limit", + t_strconcat(value, " M", NULL)); + return TRUE; + } + if (strcmp(key, "auth_user") == 0) { + config_apply_auth_set(ctx, key, "user", value); + return TRUE; + } + if (strcmp(key, "auth_chroot") == 0) { + config_apply_auth_set(ctx, key, "chroot", value); + return TRUE; + } + if (strcmp(key, "auth_cache_size") == 0 && + str_to_uoff(value, &size) == 0 && size > 0 && size < 1024) { + obsolete(ctx, "auth_cache_size value no longer defaults to " + "megabytes. Use %sM", value); + config_apply_line(ctx, key, + t_strdup_printf("%s=%sM", key, value), NULL); + return TRUE; + } + if (strcmp(key, "auth_count") == 0) { + if (strcmp(value, "count") == 0) + obsolete(ctx, "auth_count has been removed"); + else + obsolete(ctx, "auth_count has been removed, and its value must be 1"); + return TRUE; + } + if (ctx->old->socket_listen_section == 2) { + const char **p = NULL; + + if (strcmp(key, "path") == 0) + p = &ctx->old->socket_set.path; + else if (strcmp(key, "mode") == 0) + p = &ctx->old->socket_set.mode; + else if (strcmp(key, "user") == 0) + p = &ctx->old->socket_set.user; + else if (strcmp(key, "group") == 0) + p = &ctx->old->socket_set.group; + + if (p != NULL) { + *p = p_strdup(ctx->pool, value); + return TRUE; + } + } + return FALSE; +} + +static bool old_auth_section(struct config_parser_context *ctx, + const char *key, const char *value) +{ + if (ctx->old->auth_section == 0 && ctx->old->seen_auth_section) { + obsolete(ctx, "Multiple auth sections are no longer supported"); + return FALSE; + } + ctx->old->seen_auth_section = TRUE; + i_zero(&ctx->old->socket_set); + + ctx->old->auth_section++; + if ((strcmp(key, "passdb") == 0 || strcmp(key, "userdb") == 0) && + ctx->old->auth_section == 2) { + obsolete(ctx, "%s %s {} has been replaced by %s { driver=%s }", + key, value, key, value); + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_SECTION_BEGIN, key, ""); + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_KEYVALUE, + "driver", value); + return TRUE; + } + if (strcmp(key, "socket") == 0 && ctx->old->auth_section == 2) { + if (strcmp(value, "connect") == 0) { + obsolete(ctx, "socket connect {} is no longer supported, configure external auth server separately"); + return FALSE; + } + if (strcmp(value, "listen") != 0) + return FALSE; + + /* socket listen { .. } */ + ctx->old->socket_listen_section++; + return TRUE; + } + + if (ctx->old->socket_listen_section > 0) + ctx->old->socket_listen_section++; + if ((strcmp(key, "master") == 0 || strcmp(key, "client") == 0) && + ctx->old->socket_listen_section == 2) { + ctx->old->socket_set.master = strcmp(key, "master") == 0; + return TRUE; + } + return FALSE; +} + +static void socket_apply(struct config_parser_context *ctx) +{ + const struct socket_set *set = &ctx->old->socket_set; + const char *path, *prefix; + size_t len; + bool master_suffix; + + if (set->path == NULL) { + ctx->error = "socket listen {} is missing path"; + return; + } + path = set->path; + len = strlen(ctx->old->base_dir); + if (str_begins(path, ctx->old->base_dir) && + path[len] == '/') + path += len + 1; + + len = strlen(path); + master_suffix = len >= 7 && + (strcmp(path + len - 7, "-master") == 0 || + strcmp(path + len - 7, "-userdb") == 0); + + if (set->master && !master_suffix) { + ctx->error = "socket listen { master { path=.. } } must end with -master (or -userdb) suffix"; + return; + } else if (!set->master && master_suffix) { + ctx->error = "socket listen { client { path=.. } } must end not with -master or -userdb suffix"; + return; + } + + config_apply_line(ctx, "unix_listener", + t_strdup_printf("service/auth/unix_listener=%s", settings_section_escape(path)), path); + prefix = t_strdup_printf("service/auth/unix_listener/%s", settings_section_escape(path)); + if (set->mode != NULL) { + config_apply_line(ctx, "mode", + t_strdup_printf("%s/mode=%s", prefix, set->mode), NULL); + } + if (set->user != NULL) { + config_apply_line(ctx, "user", + t_strdup_printf("%s/user=%s", prefix, set->user), NULL); + } + if (set->group != NULL) { + config_apply_line(ctx, "group", + t_strdup_printf("%s/group=%s", prefix, set->group), NULL); + } + i_zero(&ctx->old->socket_set); +} + +static bool +old_settings_handle_path(struct config_parser_context *ctx, + const char *key, const char *value) +{ + if (strcmp(str_c(ctx->str), "plugin/0/") == 0) { + if (strcmp(key, "imap_zlib_compress_level") == 0) { + obsolete(ctx, "%s has been replaced by imap_compress_deflate_level", key); + config_apply_line(ctx, key, + t_strdup_printf("plugin/0/imap_compress_deflate_level=%s", value), NULL); + return TRUE; + } + } + return FALSE; +} + +bool old_settings_handle(struct config_parser_context *ctx, + enum config_line_type type, + const char *key, const char *value) +{ + switch (type) { + case CONFIG_LINE_TYPE_SKIP: + case CONFIG_LINE_TYPE_CONTINUE: + case CONFIG_LINE_TYPE_ERROR: + case CONFIG_LINE_TYPE_INCLUDE: + case CONFIG_LINE_TYPE_INCLUDE_TRY: + case CONFIG_LINE_TYPE_KEYFILE: + case CONFIG_LINE_TYPE_KEYVARIABLE: + break; + case CONFIG_LINE_TYPE_KEYVALUE: + if (ctx->pathlen == 0) { + struct config_section_stack *old_section = + ctx->cur_section; + bool ret; + + ret = old_settings_handle_proto(ctx, key, value); + ctx->cur_section = old_section; + if (ret) + return TRUE; + + return old_settings_handle_root(ctx, key, value); + } + return old_settings_handle_path(ctx, key, value); + case CONFIG_LINE_TYPE_SECTION_BEGIN: + if (ctx->old->auth_section > 0) + return old_auth_section(ctx, key, value); + else if (ctx->pathlen == 0 && strcmp(key, "auth") == 0) { + obsolete(ctx, "add auth_ prefix to all settings inside auth {} and remove the auth {} section completely"); + ctx->old->auth_section = 1; + return TRUE; + } else if (ctx->pathlen == 0 && strcmp(key, "protocol") == 0 && + strcmp(value, "managesieve") == 0) { + obsolete(ctx, "protocol managesieve {} has been replaced by protocol sieve { }"); + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_SECTION_BEGIN, + "protocol", "sieve"); + return TRUE; + } else if (ctx->pathlen == 0 && strcmp(key, "service") == 0 && + strcmp(value, "dns_client") == 0) { + obsolete(ctx, "service dns_client {} has been replaced by service dns-client { }"); + config_parser_apply_line(ctx, CONFIG_LINE_TYPE_SECTION_BEGIN, + "service", "dns-client"); + return TRUE; + } + break; + case CONFIG_LINE_TYPE_SECTION_END: + if (ctx->old->auth_section > 0) { + if (--ctx->old->auth_section == 0) + return TRUE; + } + if (ctx->old->socket_listen_section > 0) { + if (ctx->old->socket_listen_section == 2) + socket_apply(ctx); + ctx->old->socket_listen_section--; + return TRUE; + } + break; + } + return FALSE; +} + +void old_settings_init(struct config_parser_context *ctx) +{ + ctx->old = p_new(ctx->pool, struct old_set_parser, 1); + ctx->old->base_dir = PKG_RUNDIR; +} + +void old_settings_deinit_global(void) +{ + i_free(ssl_dh_value); +} diff --git a/src/config/old-set-parser.h b/src/config/old-set-parser.h new file mode 100644 index 0000000..e95b3e1 --- /dev/null +++ b/src/config/old-set-parser.h @@ -0,0 +1,16 @@ +#ifndef OLD_SET_PARSER_H +#define OLD_SET_PARSER_H + +#include "config-parser-private.h" + +struct config_parser_context; + +bool old_settings_ssl_dh_load(const char **value, const char **error_r); + +bool old_settings_handle(struct config_parser_context *ctx, + enum config_line_type type, + const char *key, const char *value); +void old_settings_init(struct config_parser_context *ctx); +void old_settings_deinit_global(void); + +#endif diff --git a/src/config/settings-get.pl b/src/config/settings-get.pl new file mode 100755 index 0000000..ca124eb --- /dev/null +++ b/src/config/settings-get.pl @@ -0,0 +1,162 @@ +#!/usr/bin/env perl +use strict; + +print "/* WARNING: THIS FILE IS GENERATED - DO NOT PATCH!\n"; +print " It's not enough alone in any case, because the defaults may be\n"; +print " coming from the individual *-settings.c in some situations. If you\n"; +print " wish to modify defaults, change the other *-settings.c files and\n"; +print " just delete this file. This file will be automatically regenerated\n"; +print " by make. (This file is distributed in the tarball only because some\n"; +print " systems might not have Perl installed.) */\n"; +print '#include "lib.h"'."\n"; +print '#include "array.h"'."\n"; +print '#include "str.h"'."\n"; +print '#include "ipwd.h"'."\n"; +print '#include "var-expand.h"'."\n"; +print '#include "file-lock.h"'."\n"; +print '#include "fsync-mode.h"'."\n"; +print '#include "hash-format.h"'."\n"; +print '#include "net.h"'."\n"; +print '#include "unichar.h"'."\n"; +print '#include "hash-method.h"'."\n"; +print '#include "settings-parser.h"'."\n"; +print '#include "message-header-parser.h"'."\n"; +print '#include "all-settings.h"'."\n"; +print '#include <stddef.h>'."\n"; +print '#include <unistd.h>'."\n"; +print '#define CONFIG_BINARY'."\n"; +print 'extern buffer_t config_all_services_buf;'; + +my @services = (); +my @service_ifdefs = (); +my %parsers = {}; + +foreach my $file (@ARGV) { + my $f; + open($f, $file) || die "Can't open $file: $@"; + + my $state = 0; + my $file_contents = ""; + my $externs = ""; + my $code = ""; + my %funcs; + my $cur_name = ""; + my $ifdef = ""; + my $state_ifdef = 0; + + while (<$f>) { + my $write = 0; + if ($state == 0) { + if (/struct .*_settings \{/ || + /struct setting_define.*\{/ || + /struct .*_default_settings = \{/) { + $state++; + } elsif (/^struct service_settings (.*) = \{/) { + $state++; + if ($ifdef eq "") { + $state_ifdef = 0; + } else { + $_ = $ifdef."\n".$_; + $state_ifdef = 1; + } + push @services, $1; + push @service_ifdefs, $ifdef; + } elsif (/^(static )?const struct setting_parser_info (.*) = \{/) { + $cur_name = $2; + $state++ if ($cur_name !~ /^\*default_/); + } elsif (/^extern const struct setting_parser_info (.*);/) { + $externs .= "extern const struct setting_parser_info $1;\n"; + } elsif (/\/\* <settings checks> \*\//) { + $state = 4; + $code .= $_; + } + + if (/(^#ifdef .*)$/ || /^(#if .*)$/) { + $ifdef = $1; + } else { + $ifdef = ""; + } + + if (/#define.*DEF/ || /^#undef.*DEF/ || /ARRAY_DEFINE_TYPE.*_settings/) { + $write = 1; + $state = 2 if (/\\$/); + } + } elsif ($state == 2) { + $write = 1; + $state = 0 if (!/\\$/); + } elsif ($state == 4) { + $code .= $_; + $state = 0 if (/\/\* <\/settings checks> \*\//); + } + + if ($state == 1 || $state == 3) { + if ($state == 1) { + if (/\.module_name = "(.*)"/) { + $parsers{$cur_name} = $1; + } + if (/DEFLIST.*".*",(.*)$/) { + my $value = $1; + if ($value =~ /.*&(.*)\)/) { + $parsers{$1} = 0; + $externs .= "extern const struct setting_parser_info $1;\n"; + } else { + $state = 3; + } + } + } elsif ($state == 3) { + if (/.*&(.*)\)/) { + $parsers{$1} = 0; + } + } + + s/^static const (struct master_settings master_default_settings)/$1/; + + $write = 1; + if (/};/) { + $state = 0; + if ($state_ifdef) { + $_ .= "#endif\n"; + $state_ifdef = 0; + } + } + } + + $file_contents .= $_ if ($write); + } + + print "/* $file */\n"; + print $externs; + print $code; + print $file_contents; + + close $f; +} + +print "static struct service_settings *config_all_services[] = {\n"; + +for (my $i = 0; $i < scalar(@services); $i++) { + my $ifdef = $service_ifdefs[$i]; + print "$ifdef\n" if ($ifdef ne ""); + print "\t&".$services[$i].",\n"; + print "#endif\n" if ($ifdef ne ""); +} +print "};\n"; +print "buffer_t config_all_services_buf = {\n"; +print "\t{ { config_all_services, sizeof(config_all_services) } }\n"; +print "};\n"; + +print "const struct setting_parser_info *all_default_roots[] = {\n"; +print "\t&master_service_setting_parser_info,\n"; +print "\t&master_service_ssl_setting_parser_info,\n"; +print "\t&master_service_ssl_server_setting_parser_info,\n"; +print "\t&smtp_submit_setting_parser_info,\n"; +foreach my $name (sort(keys %parsers)) { + my $module = $parsers{$name}; + next if (!$module); + + print "\t&".$name.", \n"; +} +print "\tNULL\n"; +print "};\n"; +print "const struct setting_parser_info *const *all_roots = all_default_roots;\n"; +print "ARRAY_TYPE(service_settings) *default_services = &master_default_settings.services;\n"; diff --git a/src/config/sysinfo-get.c b/src/config/sysinfo-get.c new file mode 100644 index 0000000..dffebe6 --- /dev/null +++ b/src/config/sysinfo-get.c @@ -0,0 +1,127 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mountpoint.h" +#include "strescape.h" +#include "sysinfo-get.h" + +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_UTSNAME_H +# include <sys/utsname.h> +#endif + +static bool readfile(const char *path, const char **data_r) +{ + char buf[1024]; + int fd, ret; + + fd = open(path, O_RDONLY); + if (fd == -1) + return FALSE; + ret = read(fd, buf, sizeof(buf)); + i_close_fd(&fd); + if (ret <= 0) + return FALSE; + + *data_r = t_strndup(buf, ret); + return TRUE; +} + +static bool lsb_distro_get(const char *path, const char **name_r) +{ + const char *data, *const *p, *str, *end; + + if (!readfile(path, &data)) + return FALSE; + + for (p = t_strsplit(data, "\n"); *p != NULL; p++) { + if (str_begins(*p, "DISTRIB_DESCRIPTION=")) + break; + } + if (*p == NULL) + return FALSE; + + str = t_strcut(*p + 20, '\n'); + if (*str != '"') + *name_r = str; + else { + end = strrchr(++str, '"'); + *name_r = str_unescape(p_strdup_until(unsafe_data_stack_pool, + str, end)); + } + return TRUE; +} + +static const char *distro_get(void) +{ + static const char *files[] = { + "", "/etc/redhat-release", + "", "/etc/SuSE-release", + "", "/etc/mandriva-release", + "", "/etc/fedora-release", + "", "/etc/sourcemage-release", + "", "/etc/slackware-version", + "", "/etc/gentoo-release", + "Debian ", "/etc/debian_version", + NULL + }; + const char *name; + unsigned int i; + + if (lsb_distro_get("/etc/lsb-release", &name)) + return name; + for (i = 0; files[i] != NULL; i += 2) { + if (readfile(files[i+1], &name)) { + return t_strconcat(files[i], t_strcut(name, '\n'), + NULL); + } + } + return ""; +} + +static const char *filesystem_get(const char *mail_location) +{ + struct mountpoint mp; + const char *path; + + path = strchr(mail_location, ':'); + if (path == NULL) + path = mail_location; + else + path = t_strcut(path + 1, ':'); + if (*path == '~') { + /* we don't know where users' home dirs are */ + return ""; + } + path = t_strcut(path, '%'); + if (strlen(path) <= 1) + return ""; + + /* all in all it seems we can support only /<path>/%u style location */ + if (mountpoint_get(path, pool_datastack_create(), &mp) < 0) + return ""; + return mp.type == NULL ? "" : mp.type; +} + +const char *sysinfo_get(const char *mail_location) +{ + const char *distro = "", *fs, *uname_info = ""; +#ifdef HAVE_SYS_UTSNAME_H + struct utsname u; + + if (uname(&u) < 0) + i_error("uname() failed: %m"); + else { + uname_info = t_strdup_printf("%s %s %s", + u.sysname, u.release, u.machine); + } + if (strcmp(u.sysname, "Linux") == 0) + distro = distro_get(); +#endif + fs = filesystem_get(mail_location); + if (*uname_info == '\0' && *distro == '\0' && *fs == '\0') + return ""; + return t_strdup_printf("OS: %s %s %s %s %s", u.sysname, u.release, u.machine, distro, fs); +} diff --git a/src/config/sysinfo-get.h b/src/config/sysinfo-get.h new file mode 100644 index 0000000..0f319cb --- /dev/null +++ b/src/config/sysinfo-get.h @@ -0,0 +1,6 @@ +#ifndef SYSINFO_GET_H +#define SYSINFO_GET_H + +const char *sysinfo_get(const char *mail_location); + +#endif diff --git a/src/config/test-config-parser.c b/src/config/test-config-parser.c new file mode 100644 index 0000000..baf91a1 --- /dev/null +++ b/src/config/test-config-parser.c @@ -0,0 +1,170 @@ +/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ostream.h" +#include "service-settings.h" +#include "settings-parser.h" +#include "config-filter.h" +#include "test-common.h" +#include "all-settings.h" +#include "config-parser.h" + +#define TEST_CONFIG_FILE ".test-config" + +static ARRAY_TYPE(service_settings) services = ARRAY_INIT; +ARRAY_TYPE(service_settings) *default_services = &services; + +struct test_settings { + const char *key; + const char *key2; + const char *key3; + const char *key4; + const char *key5; + const char *pop3_deleted_flag; + const char *env_key; + const char *env_key2; + const char *env_key3; + const char *env_key4; + const char *env_key5; + const char *protocols; +}; + +static const struct setting_define test_settings_defs[] = { + SETTING_DEFINE_STRUCT_STR("key", key, struct test_settings), + SETTING_DEFINE_STRUCT_STR("key2", key2, struct test_settings), + SETTING_DEFINE_STRUCT_STR("key3", key3, struct test_settings), + SETTING_DEFINE_STRUCT_STR("key4", key4, struct test_settings), + SETTING_DEFINE_STRUCT_STR("key5", key5, struct test_settings), + SETTING_DEFINE_STRUCT_STR("pop3_deleted_flag", pop3_deleted_flag, struct test_settings), + SETTING_DEFINE_STRUCT_STR("env_key", env_key, struct test_settings), + SETTING_DEFINE_STRUCT_STR("env_key2", env_key2, struct test_settings), + SETTING_DEFINE_STRUCT_STR("env_key3", env_key3, struct test_settings), + SETTING_DEFINE_STRUCT_STR("env_key4", env_key4, struct test_settings), + SETTING_DEFINE_STRUCT_STR("env_key5", env_key5, struct test_settings), + SETTING_DEFINE_STRUCT_STR("protocols", protocols, struct test_settings), + SETTING_DEFINE_LIST_END +}; + +static const struct test_settings test_settings_defaults = { + .key = "", + .key2 = "", + .key3 = "", + .key4 = "", + .key5 = "", + .pop3_deleted_flag = "", + .env_key = "", + .env_key2 = "", + .env_key3 = "", + .env_key4 = "", + .env_key5 = "", + .protocols = "pop3", +}; + +const struct setting_parser_info test_settings_root = { + .module_name = "test", + .defines = test_settings_defs, + .defaults = &test_settings_defaults, + + .type_offset = SIZE_MAX, + .struct_size = sizeof(struct test_settings), + + .parent_offset = SIZE_MAX, + .parent = NULL, +}; + +static const struct setting_parser_info *const roots[] = { + &test_settings_root, + NULL +}; + +const struct setting_parser_info *const *all_roots = roots; + +static void write_config_file(const char *contents) +{ + struct ostream *os = o_stream_create_file(TEST_CONFIG_FILE, 0, 0600, 0); + o_stream_nsend_str(os, contents); + test_assert(o_stream_finish(os) == 1); + o_stream_unref(&os); +} + +static void test_config_parser(void) +{ + const char *error = NULL; + + test_begin("config_parse_file"); + + write_config_file( +"# comment\n" +"key=value\n" +"key2 = \\$escape \\escape \\\"escape\\\"\n" +"key3 = value\n" +"key3 = $key3 nothervalue\n" +"key3 = yetanother value $key3 right here\n" +"key4 = \" $key3 \"\n" +"key5 = ' $key4 '\n" +"pop3_deleted_flag = \"$Deleted\"\n" +"env_key=$ENV:foo\n" +"env_key=$env_key $ENV:bar\n" +"env_key=$env_key \"$env_key\"\n" +"env_key2 = foo$ENV:FOO bar\n" +"env_key3 = $ENV:FOO$ENV:FOO bar\n" +"env_key4 = $ENV:foo $ENV:bar $key\n" +"env_key5 = $ENV:foo $ENV:foo\n" +"protocols = $protocols imap\n" + ); + + putenv("foo=test1"); + putenv("bar=test2"); + putenv("FOO$ENV:FOO=works"); + + test_assert(config_parse_file(TEST_CONFIG_FILE, TRUE, NULL, &error) == 1); + if (error != NULL) + i_error("config_parse_file(): %s", error); + + /* get the parsed output */ + const struct test_settings *set = + settings_parser_get(config_module_parsers[0].parser); + test_assert_strcmp(set->key, "value"); + test_assert_strcmp(set->key2, "\\$escape \\escape \\\"escape\\\""); + test_assert_strcmp(set->key3, "yetanother value value nothervalue right here"); + test_assert_strcmp(set->key4, " $key3 "); + test_assert_strcmp(set->key5, " $key4 "); + test_assert_strcmp(set->pop3_deleted_flag, "$Deleted"); + test_assert_strcmp(set->env_key, "test1 test2 \"$env_key\""); + test_assert_strcmp(set->env_key2, "foo$ENV:FOO bar"); + test_assert_strcmp(set->env_key3, "works bar"); + test_assert_strcmp(set->env_key4, "test1 test2 value"); + test_assert_strcmp(set->env_key5, "test1 test1"); + test_assert_strcmp(set->protocols, "pop3 imap"); + + /* try again unexpanded */ + test_assert(config_parse_file(TEST_CONFIG_FILE, FALSE, NULL, &error) == 1); + set = settings_parser_get(config_module_parsers[0].parser); + + test_assert_strcmp(set->key, "value"); + test_assert_strcmp(set->key2, "\\$escape \\escape \\\"escape\\\""); + test_assert_strcmp(set->key3, "yetanother value value nothervalue right here"); + test_assert_strcmp(set->key4, " $key3 "); + test_assert_strcmp(set->key5, " $key4 "); + test_assert_strcmp(set->pop3_deleted_flag, "$Deleted"); + test_assert_strcmp(set->env_key, "$ENV:foo $ENV:bar \"$env_key\""); + test_assert_strcmp(set->env_key2, "foo$ENV:FOO bar"); + test_assert_strcmp(set->env_key3, "$ENV:FOO$ENV:FOO bar"); + test_assert_strcmp(set->env_key4, "$ENV:foo $ENV:bar $key"); + test_assert_strcmp(set->env_key5, "$ENV:foo $ENV:foo"); + test_assert_strcmp(set->protocols, "pop3 imap"); + + config_filter_deinit(&config_filter); + config_parser_deinit(); + i_unlink_if_exists(TEST_CONFIG_FILE); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_config_parser, + NULL + }; + return test_run(test_functions); +} |